diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..68a8e97 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Main environment example for Docker Compose and n8n +POSTGRES_DB=slotifydb +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres + +# n8n credentials and config +N8N_BASIC_AUTH_ACTIVE=true +N8N_BASIC_AUTH_USER=admin +N8N_BASIC_AUTH_PASSWORD=changeme +N8N_HOST=localhost +N8N_PORT=5678 +N8N_PROTOCOL=http diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..67dc470 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,90 @@ +name: CI Pipeline + +on: + push: + branches: + - main + - dev + - feature/** + - slo-* + pull_request: + branches: + - main + - dev + - slo-* + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + backend: ${{ steps.filter.outputs.backend || 'true' }} + frontend: ${{ steps.filter.outputs.frontend || 'true' }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Filter changes + id: filter + uses: dorny/paths-filter@v3 + with: + filters: | + backend: + - 'server/**' + frontend: + - 'client/**' + + backend: + name: Backend + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.backend == 'true' + defaults: + run: + working-directory: server + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.2" + extensions: mbstring, bcmath, intl, pdo_mysql + coverage: none + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Copy env + run: cp .env.example .env + - name: Generate app key + run: php artisan key:generate + - name: Run migrations + run: php artisan migrate --env=testing --force + env: + DB_CONNECTION: sqlite + DB_DATABASE: ":memory:" + - name: Run tests + run: php artisan test + + frontend: + name: Frontend + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.frontend == 'true' + defaults: + run: + working-directory: client + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Install dependencies + run: npm install + - name: Run lint + run: npm run lint --if-present + - name: Clear Jest cache + run: npx jest --clearCache + - name: Run tests + run: npm test -- --watchAll=false + - name: Build project + run: npm run build diff --git a/.gitignore b/.gitignore index 6109102..018ce2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +businessmanagementapp1/ .history node_modules/ .env diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f3d5cdb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Hussein-Alayan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index cdceff7..2dbc4b3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -
-
-This diagram illustrates the main components of Slotify and their interactions.
-
-+ Welcome back! Please sign in to your account +
++ Manage appointments, bookings, and time slots with our intuitive + platform designed for modern businesses. +
++ Start managing your appointments more efficiently and grow your + business with our powerful scheduling tools. +
++ Welcome back! Here is what is happening with your business today. +
+{stat.change}
+Please check the URL and try again.
++ {service.description} +
++ Manage your business services and offerings +
+Total Services
+Active Services
+Please check the URL and try again.
++ {staff.name} has been marked absent from{" "} + {absenceData.start_date} to {absenceData.end_date} +
++ {result.affected_bookings_count} total bookings +
++ {result.successfully_reassigned.length} bookings +
++ {result.conflicts.length} bookings could not be reassigned + automatically +
++ Manage staff absences and handle automatic booking reassignments. +
+| + Staff Name + | ++ Status + | ++ Absence Reason + | ++ Absence Period + | ++ Services + | ++ Actions + | +
|---|---|---|---|---|---|
|
+ {staff.name}
+
+ {staff.role || "Staff Member"}
+
+ |
+
+ {staff.is_absent ? (
+ |
+ + {staff.absence_reason || "-"} + | ++ {formatAbsencePeriod(staff)} + | +
+ {staff.services && staff.services.length > 0 ? (
+
+ {staff.services.slice(0, 2).map((service) => (
+
+ ) : (
+ No services
+ )}
+ |
+
+
+ {staff.is_absent ? (
+
+ ) : (
+
+ )}
+
+ |
+
+ Keep your staff schedules and resources organized in one place. +
++ Are you sure you want to delete{" "} + {staffName}? This action cannot + be undone. +
+| + Name + | ++ Role + | ++ Status + | ++ Availability + | ++ Services + | ++ Actions + | +
|---|---|---|---|---|---|
|
+ {staff.name}
+ |
+ {staff.role || "-"} | +
+ {staff.is_absent ? (
+ |
+ + {/* Compact availability: group consecutive days with same hours */} + {(() => { + const daysOrder = [ + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + "sun", + ]; + const dayLabels = { + mon: "Mon", + tue: "Tue", + wed: "Wed", + thu: "Thu", + fri: "Fri", + sat: "Sat", + sun: "Sun", + }; + + // Normalize availability keys to support both "mon" and "Monday" formats + const normalizedAvailability: Record< + string, + { start: string; end: string; closed: boolean } + > = {}; + Object.entries(staff.availability || {}).forEach( + ([key, value]) => { + const normalizedKey = key.toLowerCase().substring(0, 3); + normalizedAvailability[normalizedKey] = value; + } + ); + + // Filter open days and map to {day, start, end} + const openDays = daysOrder + .filter( + (d) => + normalizedAvailability[d] && + !normalizedAvailability[d].closed + ) + .map((d) => ({ + day: d, + start: normalizedAvailability[d].start, + end: normalizedAvailability[d].end, + })); + if (openDays.length === 0) + return Unavailable; + + // Group consecutive days with same hours + const groups = []; + let group = [openDays[0]]; + for (let i = 1; i < openDays.length; i++) { + const prev = group[group.length - 1]; + const curr = openDays[i]; + if (prev.start === curr.start && prev.end === curr.end) { + group.push(curr); + } else { + groups.push(group); + group = [curr]; + } + } + groups.push(group); + + // Format each group + return groups + .map((g) => { + if (g.length === 1) { + return `${ + dayLabels[g[0].day as keyof typeof dayLabels] + } ${g[0].start}-${g[0].end}`; + } else { + return `${ + dayLabels[g[0].day as keyof typeof dayLabels] + }-${ + dayLabels[ + g[g.length - 1].day as keyof typeof dayLabels + ] + } ${g[0].start}-${g[0].end}`; + } + }) + .join(", "); + })()} + | +
+ {staff.services && staff.services.length > 0 ? (
+
+ {staff.services.map((service) => (
+
+ ) : (
+ No services
+ )}
+ |
+
+
+
+
+
+ |
+
{category}
++ Set up a new business profile and start managing +
++ Ready to manage your businesses? Here is your dashboard overview. +
+ + {/* Removed Active Businesses badge */} ++ Set up your booking policies and restrictions +
++ Minimum time before a booking can be made +
++ Time limit for cancellations +
++ Time between appointments +
++ Maximum active bookings per client +
++ Tell us about your business and setup basic information +
++ Configure automated messaging and notifications +
++ Credentials for AI to send messages +
++ Enable automatic responses to client messages +
++ Define the services you offer to your clients +
++ Add your team members and their availability +
+Call Status
++ {callId ? `Connected - ID: ${callId}` : "Not connected"} +
+Connection
+
+ WS:{" "}
+
+ ws://localhost:8001/ws/call/{callId || "{id}"}
+
+
Format: PCM LINEAR16 @ 16kHz
++ Waiting for AI to speak... +
++ Join thousands of businesses using AI for seamless appointment + booking +
++ Advanced technology meets intuitive design to deliver the ultimate + booking experience +
++ {feature.description} +
++ Streamline your business with AI-powered Socials booking. + Clients book instantly through chat while you manage everything + from one dashboard. +
+