diff --git a/frontend/playwright-report/data/0a42ec1ac282237211ff77fe71fbc66cd540581c.png b/frontend/playwright-report/data/0a42ec1ac282237211ff77fe71fbc66cd540581c.png deleted file mode 100644 index c91771e..0000000 Binary files a/frontend/playwright-report/data/0a42ec1ac282237211ff77fe71fbc66cd540581c.png and /dev/null differ diff --git a/frontend/playwright-report/data/20396fe6d45dcaee26cb0f172bf45db95a8229f4.png b/frontend/playwright-report/data/20396fe6d45dcaee26cb0f172bf45db95a8229f4.png deleted file mode 100644 index 04698e7..0000000 Binary files a/frontend/playwright-report/data/20396fe6d45dcaee26cb0f172bf45db95a8229f4.png and /dev/null differ diff --git a/frontend/playwright-report/data/2275fc381ec608a5eca61a5215f598c49e053a2d.md b/frontend/playwright-report/data/2275fc381ec608a5eca61a5215f598c49e053a2d.md deleted file mode 100644 index 2244db4..0000000 --- a/frontend/playwright-report/data/2275fc381ec608a5eca61a5215f598c49e053a2d.md +++ /dev/null @@ -1,343 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - generic [ref=e3]: - - navigation [ref=e4]: - - generic [ref=e6]: - - link "Smooth Schedule" [ref=e7] [cursor=pointer]: - - /url: "#/" - - img [ref=e8] - - generic [ref=e14]: Smooth Schedule - - generic [ref=e15]: - - link "Features" [ref=e16] [cursor=pointer]: - - /url: "#/features" - - link "Pricing" [ref=e17] [cursor=pointer]: - - /url: "#/pricing" - - link "About" [ref=e18] [cursor=pointer]: - - /url: "#/about" - - link "Contact" [ref=e19] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e20]: - - button "πŸ‡ΊπŸ‡Έ English" [ref=e23]: - - img [ref=e24] - - generic [ref=e27]: πŸ‡ΊπŸ‡Έ - - generic [ref=e28]: English - - img [ref=e29] - - button "Switch to dark mode" [ref=e31]: - - img [ref=e32] - - link "Login" [ref=e34] [cursor=pointer]: - - /url: "#/login" - - link "Get Started" [ref=e35] [cursor=pointer]: - - /url: "#/signup" - - main [ref=e36]: - - generic [ref=e37]: - - generic [ref=e42]: - - generic [ref=e43]: - - generic [ref=e44]: - - generic [ref=e47]: Get started today - - heading "Scheduling Made Simple" [level=1] [ref=e48] - - paragraph [ref=e49]: The all-in-one platform for managing appointments, resources, and customers. Start free, scale as you grow. - - generic [ref=e50]: - - link "Get Started Free" [ref=e51] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e52] - - button "Watch Demo" [ref=e54]: - - img [ref=e55] - - text: Watch Demo - - generic [ref=e57]: - - generic [ref=e58]: - - img [ref=e59] - - generic [ref=e62]: No credit card required - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Get started today - - generic [ref=e69]: - - generic [ref=e71]: - - generic [ref=e78]: dashboard.smoothschedule.com - - generic [ref=e79]: - - generic [ref=e80]: - - generic [ref=e81]: - - generic [ref=e82]: Today - - generic [ref=e83]: "12" - - generic [ref=e84]: - - generic [ref=e85]: This Week - - generic [ref=e86]: "48" - - generic [ref=e87]: - - generic [ref=e88]: Revenue - - generic [ref=e89]: $2.4k - - generic [ref=e90]: - - generic [ref=e91]: Today's Schedule - - generic [ref=e92]: - - generic [ref=e95]: - - generic [ref=e96]: 9:00 AM - - generic [ref=e97]: Sarah J. - Haircut - - generic [ref=e100]: - - generic [ref=e101]: 10:30 AM - - generic [ref=e102]: Mike T. - Consultation - - generic [ref=e105]: - - generic [ref=e106]: 2:00 PM - - generic [ref=e107]: Emma W. - Color - - generic [ref=e109]: - - img [ref=e111] - - generic [ref=e114]: - - generic [ref=e115]: New Booking! - - generic [ref=e116]: Just now - - generic [ref=e117]: - - paragraph [ref=e118]: Trusted by 1,000+ businesses worldwide - - generic [ref=e119]: - - generic [ref=e120]: TechCorp - - generic [ref=e121]: Innovate - - generic [ref=e122]: StartupX - - generic [ref=e123]: GrowthCo - - generic [ref=e124]: ScaleUp - - generic [ref=e126]: - - generic [ref=e127]: - - heading "Everything You Need" [level=2] [ref=e128] - - paragraph [ref=e129]: Powerful features to run your service business - - generic [ref=e130]: - - generic [ref=e131]: - - img [ref=e133] - - heading "Smart Scheduling" [level=3] [ref=e135] - - paragraph [ref=e136]: Drag-and-drop calendar with real-time availability, automated reminders, and conflict detection. - - generic [ref=e137]: - - img [ref=e139] - - heading "Resource Management" [level=3] [ref=e144] - - paragraph [ref=e145]: Manage staff, rooms, and equipment. Set availability, skills, and booking rules. - - generic [ref=e146]: - - img [ref=e148] - - heading "Customer Portal" [level=3] [ref=e152] - - paragraph [ref=e153]: Self-service booking portal for customers. View history, manage appointments, and save payment methods. - - generic [ref=e154]: - - img [ref=e156] - - heading "Integrated Payments" [level=3] [ref=e158] - - paragraph [ref=e159]: Accept payments online with Stripe. Deposits, full payments, and automatic invoicing. - - generic [ref=e160]: - - img [ref=e162] - - heading "Multi-Location Support" [level=3] [ref=e166] - - paragraph [ref=e167]: Manage multiple locations or brands from a single dashboard with isolated data. - - generic [ref=e168]: - - img [ref=e170] - - heading "White-Label Ready" [level=3] [ref=e176] - - paragraph [ref=e177]: Custom domain, branding, and remove SmoothSchedule branding for a seamless experience. - - link "View All features" [ref=e179] [cursor=pointer]: - - /url: "#/features" - - text: View All features - - img [ref=e180] - - generic [ref=e183]: - - generic [ref=e184]: - - heading "Get Started in Minutes" [level=2] [ref=e185] - - paragraph [ref=e186]: Three simple steps to transform your scheduling - - generic [ref=e187]: - - generic [ref=e190]: - - generic [ref=e191]: "01" - - img [ref=e193] - - heading "Create Your Account" [level=3] [ref=e196] - - paragraph [ref=e197]: Sign up for free and set up your business profile in minutes. - - generic [ref=e200]: - - generic [ref=e201]: "02" - - img [ref=e203] - - heading "Add Your Services" [level=3] [ref=e206] - - paragraph [ref=e207]: Configure your services, pricing, and available resources. - - generic [ref=e209]: - - generic [ref=e210]: "03" - - img [ref=e212] - - heading "Start Booking" [level=3] [ref=e217] - - paragraph [ref=e218]: Share your booking link and let customers schedule instantly. - - generic [ref=e221]: - - generic [ref=e222]: - - img [ref=e224] - - generic [ref=e226]: 1M+ - - generic [ref=e227]: Appointments Scheduled - - generic [ref=e228]: - - img [ref=e230] - - generic [ref=e234]: 5,000+ - - generic [ref=e235]: Businesses - - generic [ref=e236]: - - img [ref=e238] - - generic [ref=e241]: 50+ - - generic [ref=e242]: Countries - - generic [ref=e243]: - - img [ref=e245] - - generic [ref=e248]: 99.9% - - generic [ref=e249]: Uptime - - generic [ref=e251]: - - generic [ref=e252]: - - heading "Loved by Businesses Everywhere" [level=2] [ref=e253] - - paragraph [ref=e254]: See what our customers have to say - - generic [ref=e255]: - - generic [ref=e256]: - - generic [ref=e257]: - - img [ref=e258] - - img [ref=e260] - - img [ref=e262] - - img [ref=e264] - - img [ref=e266] - - blockquote [ref=e268]: "\"SmoothSchedule transformed how we manage appointments. Our no-show rate dropped by 40% with automated reminders.\"" - - generic [ref=e269]: - - generic [ref=e271]: S - - generic [ref=e272]: - - generic [ref=e273]: Sarah Johnson - - generic [ref=e274]: Owner at Luxe Salon - - generic [ref=e275]: - - generic [ref=e276]: - - img [ref=e277] - - img [ref=e279] - - img [ref=e281] - - img [ref=e283] - - img [ref=e285] - - blockquote [ref=e287]: "\"The white-label feature is perfect for our multi-location business. Each location has its own branded booking experience.\"" - - generic [ref=e288]: - - generic [ref=e290]: M - - generic [ref=e291]: - - generic [ref=e292]: Michael Chen - - generic [ref=e293]: CEO at FitLife Studios - - generic [ref=e294]: - - generic [ref=e295]: - - img [ref=e296] - - img [ref=e298] - - img [ref=e300] - - img [ref=e302] - - img [ref=e304] - - blockquote [ref=e306]: "\"Setup was incredibly easy. We were up and running in under an hour, and our clients love the self-service booking.\"" - - generic [ref=e307]: - - generic [ref=e309]: E - - generic [ref=e310]: - - generic [ref=e311]: Emily Rodriguez - - generic [ref=e312]: Manager at Peak Performance Therapy - - generic [ref=e314]: - - generic [ref=e315]: - - heading "Simple, Transparent Pricing" [level=2] [ref=e316] - - paragraph [ref=e317]: Start free, upgrade as you grow. No hidden fees. - - generic [ref=e318]: - - generic [ref=e319]: - - heading "Free" [level=3] [ref=e320] - - paragraph [ref=e321]: Perfect for getting started - - generic [ref=e322]: $0/month - - link "Get Started" [ref=e323] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e324]: - - generic [ref=e325]: Most Popular - - heading "Professional" [level=3] [ref=e326] - - paragraph [ref=e327]: For growing businesses - - generic [ref=e328]: $29/month - - link "Get Started" [ref=e329] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e330]: - - heading "Business" [level=3] [ref=e331] - - paragraph [ref=e332]: For established teams - - generic [ref=e333]: $79/month - - link "Get Started" [ref=e334] [cursor=pointer]: - - /url: "#/signup" - - link "View full pricing details" [ref=e336] [cursor=pointer]: - - /url: "#/pricing" - - text: View full pricing details - - img [ref=e337] - - generic [ref=e343]: - - heading "Ready to get started?" [level=2] [ref=e344] - - paragraph [ref=e345]: Join thousands of businesses already using SmoothSchedule. - - generic [ref=e346]: - - link "Get Started Free" [ref=e347] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e348] - - link "Talk to Sales" [ref=e350] [cursor=pointer]: - - /url: "#/contact" - - paragraph [ref=e351]: No credit card required - - contentinfo [ref=e352]: - - generic [ref=e353]: - - generic [ref=e354]: - - generic [ref=e355]: - - link "Smooth Schedule" [ref=e356] [cursor=pointer]: - - /url: "#/" - - img [ref=e357] - - generic [ref=e363]: Smooth Schedule - - paragraph [ref=e364]: The all-in-one scheduling platform for businesses of all sizes. Manage resources, staff, and bookings effortlessly. - - generic [ref=e365]: - - link "Twitter" [ref=e366] [cursor=pointer]: - - /url: https://twitter.com/smoothschedule - - img [ref=e367] - - link "LinkedIn" [ref=e369] [cursor=pointer]: - - /url: https://linkedin.com/company/smoothschedule - - img [ref=e370] - - link "GitHub" [ref=e374] [cursor=pointer]: - - /url: https://github.com/smoothschedule - - img [ref=e375] - - link "YouTube" [ref=e378] [cursor=pointer]: - - /url: https://youtube.com/@smoothschedule - - img [ref=e379] - - generic [ref=e382]: - - heading "Product" [level=3] [ref=e383] - - list [ref=e384]: - - listitem [ref=e385]: - - link "Features" [ref=e386] [cursor=pointer]: - - /url: "#/features" - - listitem [ref=e387]: - - link "Pricing" [ref=e388] [cursor=pointer]: - - /url: "#/pricing" - - listitem [ref=e389]: - - link "Get Started" [ref=e390] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e391]: - - heading "Company" [level=3] [ref=e392] - - list [ref=e393]: - - listitem [ref=e394]: - - link "About" [ref=e395] [cursor=pointer]: - - /url: "#/about" - - listitem [ref=e396]: - - link "Contact" [ref=e397] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e398]: - - heading "Legal" [level=3] [ref=e399] - - list [ref=e400]: - - listitem [ref=e401]: - - link "Privacy Policy" [ref=e402] [cursor=pointer]: - - /url: "#/privacy" - - listitem [ref=e403]: - - link "Terms of Service" [ref=e404] [cursor=pointer]: - - /url: "#/terms" - - paragraph [ref=e406]: Β© 2025 Smooth Schedule Inc. All rights reserved. - - generic [ref=e407]: - - generic [ref=e408]: - - heading "πŸ”“ Quick Login (Dev Only)" [level=3] [ref=e409]: - - generic [ref=e410]: πŸ”“ - - generic [ref=e411]: Quick Login (Dev Only) - - button "Γ—" [ref=e412] - - generic [ref=e413]: - - button "Logging in..." [disabled] [ref=e414]: - - generic [ref=e415]: - - img [ref=e416] - - text: Logging in... - - button "Platform Manager PLATFORM_MANAGER" [disabled] [ref=e419]: - - generic [ref=e420]: - - generic [ref=e421]: Platform Manager - - generic [ref=e422]: PLATFORM_MANAGER - - button "Platform Sales PLATFORM_SALES" [disabled] [ref=e423]: - - generic [ref=e424]: - - generic [ref=e425]: Platform Sales - - generic [ref=e426]: PLATFORM_SALES - - button "Platform Support PLATFORM_SUPPORT" [disabled] [ref=e427]: - - generic [ref=e428]: - - generic [ref=e429]: Platform Support - - generic [ref=e430]: PLATFORM_SUPPORT - - button "Business Owner TENANT_OWNER" [disabled] [ref=e431]: - - generic [ref=e432]: - - generic [ref=e433]: Business Owner - - generic [ref=e434]: TENANT_OWNER - - button "Business Manager TENANT_MANAGER" [disabled] [ref=e435]: - - generic [ref=e436]: - - generic [ref=e437]: Business Manager - - generic [ref=e438]: TENANT_MANAGER - - button "Staff Member TENANT_STAFF" [disabled] [ref=e439]: - - generic [ref=e440]: - - generic [ref=e441]: Staff Member - - generic [ref=e442]: TENANT_STAFF - - button "Customer CUSTOMER" [disabled] [ref=e443]: - - generic [ref=e444]: - - generic [ref=e445]: Customer - - generic [ref=e446]: CUSTOMER - - generic [ref=e447]: - - text: "Password for all:" - - code [ref=e448]: test123 -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/256f7121825a63fa8016c3156fd2a439250704d4.md b/frontend/playwright-report/data/256f7121825a63fa8016c3156fd2a439250704d4.md deleted file mode 100644 index 84cb091..0000000 --- a/frontend/playwright-report/data/256f7121825a63fa8016c3156fd2a439250704d4.md +++ /dev/null @@ -1,343 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - generic [ref=e3]: - - navigation [ref=e4]: - - generic [ref=e6]: - - link "Smooth Schedule" [ref=e7]: - - /url: "#/" - - img [ref=e8] - - generic [ref=e14]: Smooth Schedule - - generic [ref=e15]: - - link "Features" [ref=e16]: - - /url: "#/features" - - link "Pricing" [ref=e17]: - - /url: "#/pricing" - - link "About" [ref=e18]: - - /url: "#/about" - - link "Contact" [ref=e19]: - - /url: "#/contact" - - generic [ref=e20]: - - button "πŸ‡ΊπŸ‡Έ English" [ref=e23]: - - img [ref=e24] - - generic [ref=e27]: πŸ‡ΊπŸ‡Έ - - generic [ref=e28]: English - - img [ref=e29] - - button "Switch to dark mode" [ref=e31]: - - img [ref=e32] - - link "Login" [ref=e34]: - - /url: "#/login" - - link "Get Started" [ref=e35]: - - /url: "#/signup" - - main [ref=e36]: - - generic [ref=e37]: - - generic [ref=e42]: - - generic [ref=e43]: - - generic [ref=e44]: - - generic [ref=e47]: Get started today - - heading "Scheduling Made Simple" [level=1] [ref=e48] - - paragraph [ref=e49]: The all-in-one platform for managing appointments, resources, and customers. Start free, scale as you grow. - - generic [ref=e50]: - - link "Get Started Free" [ref=e51]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e52] - - button "Watch Demo" [ref=e54]: - - img [ref=e55] - - text: Watch Demo - - generic [ref=e57]: - - generic [ref=e58]: - - img [ref=e59] - - generic [ref=e62]: No credit card required - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Get started today - - generic [ref=e69]: - - generic [ref=e71]: - - generic [ref=e78]: dashboard.smoothschedule.com - - generic [ref=e79]: - - generic [ref=e80]: - - generic [ref=e81]: - - generic [ref=e82]: Today - - generic [ref=e83]: "12" - - generic [ref=e84]: - - generic [ref=e85]: This Week - - generic [ref=e86]: "48" - - generic [ref=e87]: - - generic [ref=e88]: Revenue - - generic [ref=e89]: $2.4k - - generic [ref=e90]: - - generic [ref=e91]: Today's Schedule - - generic [ref=e92]: - - generic [ref=e95]: - - generic [ref=e96]: 9:00 AM - - generic [ref=e97]: Sarah J. - Haircut - - generic [ref=e100]: - - generic [ref=e101]: 10:30 AM - - generic [ref=e102]: Mike T. - Consultation - - generic [ref=e105]: - - generic [ref=e106]: 2:00 PM - - generic [ref=e107]: Emma W. - Color - - generic [ref=e109]: - - img [ref=e111] - - generic [ref=e114]: - - generic [ref=e115]: New Booking! - - generic [ref=e116]: Just now - - generic [ref=e117]: - - paragraph [ref=e118]: Trusted by 1,000+ businesses worldwide - - generic [ref=e119]: - - generic [ref=e120]: TechCorp - - generic [ref=e121]: Innovate - - generic [ref=e122]: StartupX - - generic [ref=e123]: GrowthCo - - generic [ref=e124]: ScaleUp - - generic [ref=e126]: - - generic [ref=e127]: - - heading "Everything You Need" [level=2] [ref=e128] - - paragraph [ref=e129]: Powerful features to run your service business - - generic [ref=e130]: - - generic [ref=e131]: - - img [ref=e133] - - heading "Smart Scheduling" [level=3] [ref=e135] - - paragraph [ref=e136]: Drag-and-drop calendar with real-time availability, automated reminders, and conflict detection. - - generic [ref=e137]: - - img [ref=e139] - - heading "Resource Management" [level=3] [ref=e144] - - paragraph [ref=e145]: Manage staff, rooms, and equipment. Set availability, skills, and booking rules. - - generic [ref=e146]: - - img [ref=e148] - - heading "Customer Portal" [level=3] [ref=e152] - - paragraph [ref=e153]: Self-service booking portal for customers. View history, manage appointments, and save payment methods. - - generic [ref=e154]: - - img [ref=e156] - - heading "Integrated Payments" [level=3] [ref=e158] - - paragraph [ref=e159]: Accept payments online with Stripe. Deposits, full payments, and automatic invoicing. - - generic [ref=e160]: - - img [ref=e162] - - heading "Multi-Location Support" [level=3] [ref=e166] - - paragraph [ref=e167]: Manage multiple locations or brands from a single dashboard with isolated data. - - generic [ref=e168]: - - img [ref=e170] - - heading "White-Label Ready" [level=3] [ref=e176] - - paragraph [ref=e177]: Custom domain, branding, and remove SmoothSchedule branding for a seamless experience. - - link "View All features" [ref=e179]: - - /url: "#/features" - - text: View All features - - img [ref=e180] - - generic [ref=e183]: - - generic [ref=e184]: - - heading "Get Started in Minutes" [level=2] [ref=e185] - - paragraph [ref=e186]: Three simple steps to transform your scheduling - - generic [ref=e187]: - - generic [ref=e190]: - - generic [ref=e191]: "01" - - img [ref=e193] - - heading "Create Your Account" [level=3] [ref=e196] - - paragraph [ref=e197]: Sign up for free and set up your business profile in minutes. - - generic [ref=e200]: - - generic [ref=e201]: "02" - - img [ref=e203] - - heading "Add Your Services" [level=3] [ref=e206] - - paragraph [ref=e207]: Configure your services, pricing, and available resources. - - generic [ref=e209]: - - generic [ref=e210]: "03" - - img [ref=e212] - - heading "Start Booking" [level=3] [ref=e217] - - paragraph [ref=e218]: Share your booking link and let customers schedule instantly. - - generic [ref=e221]: - - generic [ref=e222]: - - img [ref=e224] - - generic [ref=e226]: 1M+ - - generic [ref=e227]: Appointments Scheduled - - generic [ref=e228]: - - img [ref=e230] - - generic [ref=e234]: 5,000+ - - generic [ref=e235]: Businesses - - generic [ref=e236]: - - img [ref=e238] - - generic [ref=e241]: 50+ - - generic [ref=e242]: Countries - - generic [ref=e243]: - - img [ref=e245] - - generic [ref=e248]: 99.9% - - generic [ref=e249]: Uptime - - generic [ref=e251]: - - generic [ref=e252]: - - heading "Loved by Businesses Everywhere" [level=2] [ref=e253] - - paragraph [ref=e254]: See what our customers have to say - - generic [ref=e255]: - - generic [ref=e256]: - - generic [ref=e257]: - - img [ref=e258] - - img [ref=e260] - - img [ref=e262] - - img [ref=e264] - - img [ref=e266] - - blockquote [ref=e268]: "\"SmoothSchedule transformed how we manage appointments. Our no-show rate dropped by 40% with automated reminders.\"" - - generic [ref=e269]: - - generic [ref=e271]: S - - generic [ref=e272]: - - generic [ref=e273]: Sarah Johnson - - generic [ref=e274]: Owner at Luxe Salon - - generic [ref=e275]: - - generic [ref=e276]: - - img [ref=e277] - - img [ref=e279] - - img [ref=e281] - - img [ref=e283] - - img [ref=e285] - - blockquote [ref=e287]: "\"The white-label feature is perfect for our multi-location business. Each location has its own branded booking experience.\"" - - generic [ref=e288]: - - generic [ref=e290]: M - - generic [ref=e291]: - - generic [ref=e292]: Michael Chen - - generic [ref=e293]: CEO at FitLife Studios - - generic [ref=e294]: - - generic [ref=e295]: - - img [ref=e296] - - img [ref=e298] - - img [ref=e300] - - img [ref=e302] - - img [ref=e304] - - blockquote [ref=e306]: "\"Setup was incredibly easy. We were up and running in under an hour, and our clients love the self-service booking.\"" - - generic [ref=e307]: - - generic [ref=e309]: E - - generic [ref=e310]: - - generic [ref=e311]: Emily Rodriguez - - generic [ref=e312]: Manager at Peak Performance Therapy - - generic [ref=e314]: - - generic [ref=e315]: - - heading "Simple, Transparent Pricing" [level=2] [ref=e316] - - paragraph [ref=e317]: Start free, upgrade as you grow. No hidden fees. - - generic [ref=e318]: - - generic [ref=e319]: - - heading "Free" [level=3] [ref=e320] - - paragraph [ref=e321]: Perfect for getting started - - generic [ref=e322]: $0/month - - link "Get Started" [ref=e323]: - - /url: "#/signup" - - generic [ref=e324]: - - generic [ref=e325]: Most Popular - - heading "Professional" [level=3] [ref=e326] - - paragraph [ref=e327]: For growing businesses - - generic [ref=e328]: $29/month - - link "Get Started" [ref=e329]: - - /url: "#/signup" - - generic [ref=e330]: - - heading "Business" [level=3] [ref=e331] - - paragraph [ref=e332]: For established teams - - generic [ref=e333]: $79/month - - link "Get Started" [ref=e334]: - - /url: "#/signup" - - link "View full pricing details" [ref=e336]: - - /url: "#/pricing" - - text: View full pricing details - - img [ref=e337] - - generic [ref=e343]: - - heading "Ready to get started?" [level=2] [ref=e344] - - paragraph [ref=e345]: Join thousands of businesses already using SmoothSchedule. - - generic [ref=e346]: - - link "Get Started Free" [ref=e347]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e348] - - link "Talk to Sales" [ref=e350]: - - /url: "#/contact" - - paragraph [ref=e351]: No credit card required - - contentinfo [ref=e352]: - - generic [ref=e353]: - - generic [ref=e354]: - - generic [ref=e355]: - - link "Smooth Schedule" [ref=e356]: - - /url: "#/" - - img [ref=e357] - - generic [ref=e363]: Smooth Schedule - - paragraph [ref=e364]: The all-in-one scheduling platform for businesses of all sizes. Manage resources, staff, and bookings effortlessly. - - generic [ref=e365]: - - link "Twitter" [ref=e366]: - - /url: https://twitter.com/smoothschedule - - img [ref=e367] - - link "LinkedIn" [ref=e369]: - - /url: https://linkedin.com/company/smoothschedule - - img [ref=e370] - - link "GitHub" [ref=e374]: - - /url: https://github.com/smoothschedule - - img [ref=e375] - - link "YouTube" [ref=e378]: - - /url: https://youtube.com/@smoothschedule - - img [ref=e379] - - generic [ref=e382]: - - heading "Product" [level=3] [ref=e383] - - list [ref=e384]: - - listitem [ref=e385]: - - link "Features" [ref=e386]: - - /url: "#/features" - - listitem [ref=e387]: - - link "Pricing" [ref=e388]: - - /url: "#/pricing" - - listitem [ref=e389]: - - link "Get Started" [ref=e390]: - - /url: "#/signup" - - generic [ref=e391]: - - heading "Company" [level=3] [ref=e392] - - list [ref=e393]: - - listitem [ref=e394]: - - link "About" [ref=e395]: - - /url: "#/about" - - listitem [ref=e396]: - - link "Contact" [ref=e397]: - - /url: "#/contact" - - generic [ref=e398]: - - heading "Legal" [level=3] [ref=e399] - - list [ref=e400]: - - listitem [ref=e401]: - - link "Privacy Policy" [ref=e402]: - - /url: "#/privacy" - - listitem [ref=e403]: - - link "Terms of Service" [ref=e404]: - - /url: "#/terms" - - paragraph [ref=e406]: Β© 2025 Smooth Schedule Inc. All rights reserved. - - generic [ref=e407]: - - generic [ref=e408]: - - heading "πŸ”“ Quick Login (Dev Only)" [level=3] [ref=e409]: - - generic [ref=e410]: πŸ”“ - - generic [ref=e411]: Quick Login (Dev Only) - - button "Γ—" [ref=e412] - - generic [ref=e413]: - - button "Logging in..." [disabled] [ref=e414]: - - generic [ref=e415]: - - img [ref=e416] - - text: Logging in... - - button "Platform Manager PLATFORM_MANAGER" [disabled] [ref=e419]: - - generic [ref=e420]: - - generic [ref=e421]: Platform Manager - - generic [ref=e422]: PLATFORM_MANAGER - - button "Platform Sales PLATFORM_SALES" [disabled] [ref=e423]: - - generic [ref=e424]: - - generic [ref=e425]: Platform Sales - - generic [ref=e426]: PLATFORM_SALES - - button "Platform Support PLATFORM_SUPPORT" [disabled] [ref=e427]: - - generic [ref=e428]: - - generic [ref=e429]: Platform Support - - generic [ref=e430]: PLATFORM_SUPPORT - - button "Business Owner TENANT_OWNER" [disabled] [ref=e431]: - - generic [ref=e432]: - - generic [ref=e433]: Business Owner - - generic [ref=e434]: TENANT_OWNER - - button "Business Manager TENANT_MANAGER" [disabled] [ref=e435]: - - generic [ref=e436]: - - generic [ref=e437]: Business Manager - - generic [ref=e438]: TENANT_MANAGER - - button "Staff Member TENANT_STAFF" [disabled] [ref=e439]: - - generic [ref=e440]: - - generic [ref=e441]: Staff Member - - generic [ref=e442]: TENANT_STAFF - - button "Customer CUSTOMER" [disabled] [ref=e443]: - - generic [ref=e444]: - - generic [ref=e445]: Customer - - generic [ref=e446]: CUSTOMER - - generic [ref=e447]: - - text: "Password for all:" - - code [ref=e448]: test123 -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/5767637b599b7485b07783391dbaf50e83f9e3cf.md b/frontend/playwright-report/data/5767637b599b7485b07783391dbaf50e83f9e3cf.md deleted file mode 100644 index 58e710c..0000000 --- a/frontend/playwright-report/data/5767637b599b7485b07783391dbaf50e83f9e3cf.md +++ /dev/null @@ -1,343 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - generic [ref=e3]: - - navigation [ref=e4]: - - generic [ref=e6]: - - link "Smooth Schedule" [ref=e7] [cursor=pointer]: - - /url: "#/" - - img [ref=e8] - - generic [ref=e14]: Smooth Schedule - - generic [ref=e15]: - - link "Features" [ref=e16] [cursor=pointer]: - - /url: "#/features" - - link "Pricing" [ref=e17] [cursor=pointer]: - - /url: "#/pricing" - - link "About" [ref=e18] [cursor=pointer]: - - /url: "#/about" - - link "Contact" [ref=e19] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e20]: - - button "πŸ‡ΊπŸ‡Έ English" [ref=e23]: - - img [ref=e24] - - generic [ref=e28]: πŸ‡ΊπŸ‡Έ - - generic [ref=e29]: English - - img [ref=e30] - - button "Switch to dark mode" [ref=e32]: - - img [ref=e33] - - link "Login" [ref=e35] [cursor=pointer]: - - /url: "#/login" - - link "Get Started" [ref=e36] [cursor=pointer]: - - /url: "#/signup" - - main [ref=e37]: - - generic [ref=e38]: - - generic [ref=e43]: - - generic [ref=e44]: - - generic [ref=e45]: - - generic [ref=e48]: Get started today - - heading "Scheduling Made Simple" [level=1] [ref=e49] - - paragraph [ref=e50]: The all-in-one platform for managing appointments, resources, and customers. Start free, scale as you grow. - - generic [ref=e51]: - - link "Get Started Free" [ref=e52] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e53] - - button "Watch Demo" [ref=e56]: - - img [ref=e57] - - text: Watch Demo - - generic [ref=e59]: - - generic [ref=e60]: - - img [ref=e61] - - generic [ref=e64]: No credit card required - - generic [ref=e66]: - - img [ref=e67] - - generic [ref=e70]: Get started today - - generic [ref=e71]: - - generic [ref=e73]: - - generic [ref=e80]: dashboard.smoothschedule.com - - generic [ref=e81]: - - generic [ref=e82]: - - generic [ref=e83]: - - generic [ref=e84]: Today - - generic [ref=e85]: "12" - - generic [ref=e86]: - - generic [ref=e87]: This Week - - generic [ref=e88]: "48" - - generic [ref=e89]: - - generic [ref=e90]: Revenue - - generic [ref=e91]: $2.4k - - generic [ref=e92]: - - generic [ref=e93]: Today's Schedule - - generic [ref=e94]: - - generic [ref=e97]: - - generic [ref=e98]: 9:00 AM - - generic [ref=e99]: Sarah J. - Haircut - - generic [ref=e102]: - - generic [ref=e103]: 10:30 AM - - generic [ref=e104]: Mike T. - Consultation - - generic [ref=e107]: - - generic [ref=e108]: 2:00 PM - - generic [ref=e109]: Emma W. - Color - - generic [ref=e111]: - - img [ref=e113] - - generic [ref=e116]: - - generic [ref=e117]: New Booking! - - generic [ref=e118]: Just now - - generic [ref=e119]: - - paragraph [ref=e120]: Trusted by 1,000+ businesses worldwide - - generic [ref=e121]: - - generic [ref=e122]: TechCorp - - generic [ref=e123]: Innovate - - generic [ref=e124]: StartupX - - generic [ref=e125]: GrowthCo - - generic [ref=e126]: ScaleUp - - generic [ref=e128]: - - generic [ref=e129]: - - heading "Everything You Need" [level=2] [ref=e130] - - paragraph [ref=e131]: Powerful features to run your service business - - generic [ref=e132]: - - generic [ref=e133]: - - img [ref=e135] - - heading "Smart Scheduling" [level=3] [ref=e140] - - paragraph [ref=e141]: Drag-and-drop calendar with real-time availability, automated reminders, and conflict detection. - - generic [ref=e142]: - - img [ref=e144] - - heading "Resource Management" [level=3] [ref=e149] - - paragraph [ref=e150]: Manage staff, rooms, and equipment. Set availability, skills, and booking rules. - - generic [ref=e151]: - - img [ref=e153] - - heading "Customer Portal" [level=3] [ref=e157] - - paragraph [ref=e158]: Self-service booking portal for customers. View history, manage appointments, and save payment methods. - - generic [ref=e159]: - - img [ref=e161] - - heading "Integrated Payments" [level=3] [ref=e164] - - paragraph [ref=e165]: Accept payments online with Stripe. Deposits, full payments, and automatic invoicing. - - generic [ref=e166]: - - img [ref=e168] - - heading "Multi-Location Support" [level=3] [ref=e174] - - paragraph [ref=e175]: Manage multiple locations or brands from a single dashboard with isolated data. - - generic [ref=e176]: - - img [ref=e178] - - heading "White-Label Ready" [level=3] [ref=e184] - - paragraph [ref=e185]: Custom domain, branding, and remove SmoothSchedule branding for a seamless experience. - - link "View All features" [ref=e187] [cursor=pointer]: - - /url: "#/features" - - text: View All features - - img [ref=e188] - - generic [ref=e192]: - - generic [ref=e193]: - - heading "Get Started in Minutes" [level=2] [ref=e194] - - paragraph [ref=e195]: Three simple steps to transform your scheduling - - generic [ref=e196]: - - generic [ref=e199]: - - generic [ref=e200]: "01" - - img [ref=e202] - - heading "Create Your Account" [level=3] [ref=e207] - - paragraph [ref=e208]: Sign up for free and set up your business profile in minutes. - - generic [ref=e211]: - - generic [ref=e212]: "02" - - img [ref=e214] - - heading "Add Your Services" [level=3] [ref=e217] - - paragraph [ref=e218]: Configure your services, pricing, and available resources. - - generic [ref=e220]: - - generic [ref=e221]: "03" - - img [ref=e223] - - heading "Start Booking" [level=3] [ref=e228] - - paragraph [ref=e229]: Share your booking link and let customers schedule instantly. - - generic [ref=e232]: - - generic [ref=e233]: - - img [ref=e235] - - generic [ref=e240]: 1M+ - - generic [ref=e241]: Appointments Scheduled - - generic [ref=e242]: - - img [ref=e244] - - generic [ref=e250]: 5,000+ - - generic [ref=e251]: Businesses - - generic [ref=e252]: - - img [ref=e254] - - generic [ref=e258]: 50+ - - generic [ref=e259]: Countries - - generic [ref=e260]: - - img [ref=e262] - - generic [ref=e265]: 99.9% - - generic [ref=e266]: Uptime - - generic [ref=e268]: - - generic [ref=e269]: - - heading "Loved by Businesses Everywhere" [level=2] [ref=e270] - - paragraph [ref=e271]: See what our customers have to say - - generic [ref=e272]: - - generic [ref=e273]: - - generic [ref=e274]: - - img [ref=e275] - - img [ref=e277] - - img [ref=e279] - - img [ref=e281] - - img [ref=e283] - - blockquote [ref=e285]: "\"SmoothSchedule transformed how we manage appointments. Our no-show rate dropped by 40% with automated reminders.\"" - - generic [ref=e286]: - - generic [ref=e288]: S - - generic [ref=e289]: - - generic [ref=e290]: Sarah Johnson - - generic [ref=e291]: Owner at Luxe Salon - - generic [ref=e292]: - - generic [ref=e293]: - - img [ref=e294] - - img [ref=e296] - - img [ref=e298] - - img [ref=e300] - - img [ref=e302] - - blockquote [ref=e304]: "\"The white-label feature is perfect for our multi-location business. Each location has its own branded booking experience.\"" - - generic [ref=e305]: - - generic [ref=e307]: M - - generic [ref=e308]: - - generic [ref=e309]: Michael Chen - - generic [ref=e310]: CEO at FitLife Studios - - generic [ref=e311]: - - generic [ref=e312]: - - img [ref=e313] - - img [ref=e315] - - img [ref=e317] - - img [ref=e319] - - img [ref=e321] - - blockquote [ref=e323]: "\"Setup was incredibly easy. We were up and running in under an hour, and our clients love the self-service booking.\"" - - generic [ref=e324]: - - generic [ref=e326]: E - - generic [ref=e327]: - - generic [ref=e328]: Emily Rodriguez - - generic [ref=e329]: Manager at Peak Performance Therapy - - generic [ref=e331]: - - generic [ref=e332]: - - heading "Simple, Transparent Pricing" [level=2] [ref=e333] - - paragraph [ref=e334]: Start free, upgrade as you grow. No hidden fees. - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Free" [level=3] [ref=e337] - - paragraph [ref=e338]: Perfect for getting started - - generic [ref=e339]: $0/month - - link "Get Started" [ref=e340] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e341]: - - generic [ref=e342]: Most Popular - - heading "Professional" [level=3] [ref=e343] - - paragraph [ref=e344]: For growing businesses - - generic [ref=e345]: $29/month - - link "Get Started" [ref=e346] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e347]: - - heading "Business" [level=3] [ref=e348] - - paragraph [ref=e349]: For established teams - - generic [ref=e350]: $79/month - - link "Get Started" [ref=e351] [cursor=pointer]: - - /url: "#/signup" - - link "View full pricing details" [ref=e353] [cursor=pointer]: - - /url: "#/pricing" - - text: View full pricing details - - img [ref=e354] - - generic [ref=e361]: - - heading "Ready to get started?" [level=2] [ref=e362] - - paragraph [ref=e363]: Join thousands of businesses already using SmoothSchedule. - - generic [ref=e364]: - - link "Get Started Free" [ref=e365] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e366] - - link "Talk to Sales" [ref=e369] [cursor=pointer]: - - /url: "#/contact" - - paragraph [ref=e370]: No credit card required - - contentinfo [ref=e371]: - - generic [ref=e372]: - - generic [ref=e373]: - - generic [ref=e374]: - - link "Smooth Schedule" [ref=e375] [cursor=pointer]: - - /url: "#/" - - img [ref=e376] - - generic [ref=e382]: Smooth Schedule - - paragraph [ref=e383]: The all-in-one scheduling platform for businesses of all sizes. Manage resources, staff, and bookings effortlessly. - - generic [ref=e384]: - - link "Twitter" [ref=e385] [cursor=pointer]: - - /url: https://twitter.com/smoothschedule - - img [ref=e386] - - link "LinkedIn" [ref=e388] [cursor=pointer]: - - /url: https://linkedin.com/company/smoothschedule - - img [ref=e389] - - link "GitHub" [ref=e393] [cursor=pointer]: - - /url: https://github.com/smoothschedule - - img [ref=e394] - - link "YouTube" [ref=e397] [cursor=pointer]: - - /url: https://youtube.com/@smoothschedule - - img [ref=e398] - - generic [ref=e401]: - - heading "Product" [level=3] [ref=e402] - - list [ref=e403]: - - listitem [ref=e404]: - - link "Features" [ref=e405] [cursor=pointer]: - - /url: "#/features" - - listitem [ref=e406]: - - link "Pricing" [ref=e407] [cursor=pointer]: - - /url: "#/pricing" - - listitem [ref=e408]: - - link "Get Started" [ref=e409] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e410]: - - heading "Company" [level=3] [ref=e411] - - list [ref=e412]: - - listitem [ref=e413]: - - link "About" [ref=e414] [cursor=pointer]: - - /url: "#/about" - - listitem [ref=e415]: - - link "Contact" [ref=e416] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e417]: - - heading "Legal" [level=3] [ref=e418] - - list [ref=e419]: - - listitem [ref=e420]: - - link "Privacy Policy" [ref=e421] [cursor=pointer]: - - /url: "#/privacy" - - listitem [ref=e422]: - - link "Terms of Service" [ref=e423] [cursor=pointer]: - - /url: "#/terms" - - paragraph [ref=e425]: Β© 2025 Smooth Schedule Inc. All rights reserved. - - generic [ref=e426]: - - generic [ref=e427]: - - heading "πŸ”“ Quick Login (Dev Only)" [level=3] [ref=e428]: - - generic [ref=e429]: πŸ”“ - - generic [ref=e430]: Quick Login (Dev Only) - - button "Γ—" [ref=e431] - - generic [ref=e432]: - - button "Logging in..." [disabled] [ref=e433]: - - generic [ref=e434]: - - img [ref=e435] - - text: Logging in... - - button "Platform Manager PLATFORM_MANAGER" [disabled] [ref=e438]: - - generic [ref=e439]: - - generic [ref=e440]: Platform Manager - - generic [ref=e441]: PLATFORM_MANAGER - - button "Platform Sales PLATFORM_SALES" [disabled] [ref=e442]: - - generic [ref=e443]: - - generic [ref=e444]: Platform Sales - - generic [ref=e445]: PLATFORM_SALES - - button "Platform Support PLATFORM_SUPPORT" [disabled] [ref=e446]: - - generic [ref=e447]: - - generic [ref=e448]: Platform Support - - generic [ref=e449]: PLATFORM_SUPPORT - - button "Business Owner TENANT_OWNER" [disabled] [ref=e450]: - - generic [ref=e451]: - - generic [ref=e452]: Business Owner - - generic [ref=e453]: TENANT_OWNER - - button "Business Manager TENANT_MANAGER" [disabled] [ref=e454]: - - generic [ref=e455]: - - generic [ref=e456]: Business Manager - - generic [ref=e457]: TENANT_MANAGER - - button "Staff Member TENANT_STAFF" [disabled] [ref=e458]: - - generic [ref=e459]: - - generic [ref=e460]: Staff Member - - generic [ref=e461]: TENANT_STAFF - - button "Customer CUSTOMER" [disabled] [ref=e462]: - - generic [ref=e463]: - - generic [ref=e464]: Customer - - generic [ref=e465]: CUSTOMER - - generic [ref=e466]: - - text: "Password for all:" - - code [ref=e467]: test123 -``` \ No newline at end of file diff --git a/frontend/playwright-report/data/918709084366e8b4220550f4f81e43c7237bef69.png b/frontend/playwright-report/data/918709084366e8b4220550f4f81e43c7237bef69.png deleted file mode 100644 index 1fc3763..0000000 Binary files a/frontend/playwright-report/data/918709084366e8b4220550f4f81e43c7237bef69.png and /dev/null differ diff --git a/frontend/playwright-report/index.html b/frontend/playwright-report/index.html index d5287f3..cd8f8ba 100644 --- a/frontend/playwright-report/index.html +++ b/frontend/playwright-report/index.html @@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
- \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/components/DevQuickLogin.tsx b/frontend/src/components/DevQuickLogin.tsx index 4ef4131..8f928dd 100644 --- a/frontend/src/components/DevQuickLogin.tsx +++ b/frontend/src/components/DevQuickLogin.tsx @@ -96,11 +96,36 @@ export function DevQuickLogin({ embedded = false }: DevQuickLoginProps) { // Store token in cookie (use 'access_token' to match what client.ts expects) setCookie('access_token', response.data.token, 7); - // Invalidate queries to refetch user data - await queryClient.invalidateQueries({ queryKey: ['currentUser'] }); - await queryClient.invalidateQueries({ queryKey: ['currentBusiness'] }); + // Fetch user data to determine redirect + const userResponse = await apiClient.get('/api/auth/me/'); + const userData = userResponse.data; - // Reload page to trigger auth flow + // Determine the correct subdomain based on user role + const currentHostname = window.location.hostname; + const currentPort = window.location.port; + let targetSubdomain: string | null = null; + + // Platform users (superuser, platform_manager, platform_support) + if (['superuser', 'platform_manager', 'platform_support'].includes(userData.role)) { + targetSubdomain = 'platform'; + } + // Business users - redirect to their business subdomain + else if (userData.business_subdomain) { + targetSubdomain = userData.business_subdomain; + } + + // Check if we need to redirect to a different subdomain + const isOnTargetSubdomain = currentHostname === `${targetSubdomain}.lvh.me`; + const needsRedirect = targetSubdomain && !isOnTargetSubdomain; + + if (needsRedirect) { + // Redirect to the correct subdomain + const portStr = currentPort ? `:${currentPort}` : ''; + window.location.href = `http://${targetSubdomain}.lvh.me${portStr}/`; + return; + } + + // Already on correct subdomain - just reload to update auth state window.location.reload(); } catch (error: any) { console.error('Quick login failed:', error); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 366e60d..852645d 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -36,7 +36,7 @@ const Sidebar: React.FC = ({ business, user, isCollapsed, toggleCo const baseClasses = `flex items-center gap-3 py-3 text-sm font-medium rounded-lg transition-colors`; const collapsedClasses = isCollapsed ? 'px-3 justify-center' : 'px-4'; - const activeClasses = 'bg-opacity-10 text-white bg-white'; + const activeClasses = 'bg-white/10 text-white'; const inactiveClasses = 'text-white/70 hover:text-white hover:bg-white/5'; const disabledClasses = 'text-white/30 cursor-not-allowed'; diff --git a/frontend/src/hooks/useAppointmentWebSocket.ts b/frontend/src/hooks/useAppointmentWebSocket.ts index f059905..408ad9c 100644 --- a/frontend/src/hooks/useAppointmentWebSocket.ts +++ b/frontend/src/hooks/useAppointmentWebSocket.ts @@ -35,6 +35,9 @@ interface UseAppointmentWebSocketOptions { onError?: (error: Event) => void; } +// WebSocket is not yet implemented in the backend - disable for now +const WEBSOCKET_ENABLED = false; + /** * Transform backend appointment format to frontend format */ @@ -60,6 +63,9 @@ function transformAppointment(data: WebSocketMessage['appointment']): Appointmen */ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions = {}) { const { enabled = true, onConnected, onDisconnected, onError } = options; + + // Early return if WebSocket is globally disabled + const effectivelyEnabled = enabled && WEBSOCKET_ENABLED; const queryClient = useQueryClient(); const wsRef = useRef(null); const reconnectTimeoutRef = useRef(null); @@ -138,7 +144,7 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions // Main effect to manage WebSocket connection // Only depends on `enabled` - other values are read from refs or called as functions useEffect(() => { - if (!enabled) { + if (!effectivelyEnabled) { return; } @@ -285,7 +291,7 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions setIsConnected(false); }; - }, [enabled]); // Only re-run when enabled changes + }, [effectivelyEnabled]); // Only re-run when enabled changes const reconnect = useCallback(() => { isCleaningUpRef.current = false; diff --git a/frontend/src/hooks/useBusiness.ts b/frontend/src/hooks/useBusiness.ts index 4385e4b..bb0e26c 100644 --- a/frontend/src/hooks/useBusiness.ts +++ b/frontend/src/hooks/useBusiness.ts @@ -30,8 +30,8 @@ export const useCurrentBusiness = () => { id: String(data.id), name: data.name, subdomain: data.subdomain, - primaryColor: data.primary_color, - secondaryColor: data.secondary_color, + primaryColor: data.primary_color || '#3B82F6', // Blue-500 default + secondaryColor: data.secondary_color || '#1E40AF', // Blue-800 default logoUrl: data.logo_url, whitelabelEnabled: data.whitelabel_enabled, plan: data.tier, // Map tier to plan diff --git a/frontend/src/hooks/useCustomers.ts b/frontend/src/hooks/useCustomers.ts index 7f9c48a..06f5bf6 100644 --- a/frontend/src/hooks/useCustomers.ts +++ b/frontend/src/hooks/useCustomers.ts @@ -43,6 +43,7 @@ export const useCustomers = (filters?: CustomerFilters) => { user_data: c.user_data, // Include user_data for masquerading })); }, + retry: false, // Don't retry on 404 - endpoint may not exist yet }); }; diff --git a/frontend/src/hooks/useServices.ts b/frontend/src/hooks/useServices.ts index bb06163..dfc1f2a 100644 --- a/frontend/src/hooks/useServices.ts +++ b/frontend/src/hooks/useServices.ts @@ -24,6 +24,7 @@ export const useServices = () => { description: s.description || '', })); }, + retry: false, // Don't retry on 404 - endpoint may not exist yet }); }; @@ -45,6 +46,7 @@ export const useService = (id: string) => { }; }, enabled: !!id, + retry: false, }); }; diff --git a/frontend/src/pages/OwnerScheduler.tsx b/frontend/src/pages/OwnerScheduler.tsx index be08631..0744c28 100644 --- a/frontend/src/pages/OwnerScheduler.tsx +++ b/frontend/src/pages/OwnerScheduler.tsx @@ -224,6 +224,56 @@ const OwnerScheduler: React.FC = ({ user, business }) => { return new Date(date.getFullYear(), date.getMonth() + 1, 0); }; + // Generate calendar grid data for month view + const getMonthCalendarData = () => { + const firstDay = getStartOfMonth(viewDate); + const lastDay = getEndOfMonth(viewDate); + const startDayOfWeek = firstDay.getDay(); // 0 = Sunday + const daysInMonth = lastDay.getDate(); + + // Create array of week rows + const weeks: (Date | null)[][] = []; + let currentWeek: (Date | null)[] = []; + + // Add empty cells for days before the first of the month + for (let i = 0; i < startDayOfWeek; i++) { + currentWeek.push(null); + } + + // Add all days of the month + for (let day = 1; day <= daysInMonth; day++) { + currentWeek.push(new Date(viewDate.getFullYear(), viewDate.getMonth(), day)); + if (currentWeek.length === 7) { + weeks.push(currentWeek); + currentWeek = []; + } + } + + // Add empty cells for remaining days after the last of the month + if (currentWeek.length > 0) { + while (currentWeek.length < 7) { + currentWeek.push(null); + } + weeks.push(currentWeek); + } + + return weeks; + }; + + // Get appointments for a specific day (for month view) + const getAppointmentsForDay = (date: Date) => { + const dayStart = new Date(date); + dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(date); + dayEnd.setHours(23, 59, 59, 999); + + return filteredAppointments.filter(apt => { + if (!apt.resourceId) return false; // Exclude pending + const aptDate = new Date(apt.startTime); + return aptDate >= dayStart && aptDate <= dayEnd; + }).sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()); + }; + const navigateDate = (direction: 'prev' | 'next') => { const newDate = new Date(viewDate); @@ -685,11 +735,13 @@ const OwnerScheduler: React.FC = ({ user, business }) => { Month -
- - Zoom - -
+ {viewMode !== 'month' && ( +
+ + Zoom + +
+ )}
+ {/* Month View - Calendar Grid */} + {viewMode === 'month' && ( +
+ {/* Pending Sidebar for Month View */} +
+
+

Pending Requests ({pendingAppointments.length})

+
+ {pendingAppointments.length === 0 && (
No pending requests
)} + {pendingAppointments.map(apt => { + const service = services.find(s => s.id === apt.serviceId); + return ( +
handleAppointmentClick(apt)} + > +

{apt.customerName}

+

{service?.name}

+
+ {formatDuration(apt.durationMinutes)} +
+
+ ); + })} +
+
+
+ + {/* Calendar Grid */} +
+
+ {/* Day headers */} +
+ {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => ( +
+ {day} +
+ ))} +
+ + {/* Calendar weeks */} +
+ {getMonthCalendarData().flat().map((date, index) => { + const isToday = date && new Date().toDateString() === date.toDateString(); + const dayAppointments = date ? getAppointmentsForDay(date) : []; + const displayedAppointments = dayAppointments.slice(0, 3); + const remainingCount = dayAppointments.length - 3; + + return ( +
{ if (date) { setViewDate(date); setViewMode('day'); } }} + > + {date && ( + <> +
+ {date.getDate()} +
+
+ {displayedAppointments.map(apt => { + const service = services.find(s => s.id === apt.serviceId); + const resource = resources.find(r => r.id === apt.resourceId); + const startTime = new Date(apt.startTime); + return ( +
{ e.stopPropagation(); handleAppointmentClick(apt); }} + title={`${apt.customerName} - ${service?.name} with ${resource?.name}`} + > + {startTime.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })} + {' '}{apt.customerName} +
+ ); + })} + {remainingCount > 0 && ( +
+ +{remainingCount} more +
+ )} +
+ + )} +
+ ); + })} +
+
+
+
+ )} + + {/* Day/Week View - Timeline */} + {viewMode !== 'month' && (
Resources
@@ -849,6 +1003,7 @@ const OwnerScheduler: React.FC = ({ user, business }) => {
+ )} {/* Appointment Detail/Edit Modal */} {selectedAppointment && ( diff --git a/frontend/src/pages/VerifyEmail.tsx b/frontend/src/pages/VerifyEmail.tsx index 5aee14b..a011f55 100644 --- a/frontend/src/pages/VerifyEmail.tsx +++ b/frontend/src/pages/VerifyEmail.tsx @@ -24,20 +24,20 @@ const VerifyEmail: React.FC = () => { setStatus('loading'); try { - const response = await apiClient.get(`/api/auth/emails/verify/${token}/`); + const response = await apiClient.post('/api/auth/email/verify/', { token }); // Immediately clear auth cookies to log out deleteCookie('access_token'); deleteCookie('refresh_token'); - if (response.data.message === 'Email is already verified') { + if (response.data.detail === 'Email already verified.') { setStatus('already_verified'); } else { setStatus('success'); } } catch (err: any) { setStatus('error'); - setErrorMessage(err.response?.data?.detail || 'Failed to verify email'); + setErrorMessage(err.response?.data?.error || 'Failed to verify email'); } }; diff --git a/frontend/test-results/.last-run.json b/frontend/test-results/.last-run.json index 0d6405c..92bc0b0 100644 --- a/frontend/test-results/.last-run.json +++ b/frontend/test-results/.last-run.json @@ -1,8 +1,6 @@ { "status": "failed", "failedTests": [ - "5f1889fdd7b10a4db9e9-2b1e81c51a733cc89956", - "5f1889fdd7b10a4db9e9-66724cc37c12aaf9dc66", - "5f1889fdd7b10a4db9e9-b93c630b7987c0eb4adc" + "7662eeffef95b745c0c7-05f7d22eaed6ca80a04d" ] } \ No newline at end of file diff --git a/frontend/test-results/debug-login-debug-login-request-chromium/error-context.md b/frontend/test-results/debug-login-debug-login-request-chromium/error-context.md deleted file mode 100644 index 2244db4..0000000 --- a/frontend/test-results/debug-login-debug-login-request-chromium/error-context.md +++ /dev/null @@ -1,343 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - generic [ref=e3]: - - navigation [ref=e4]: - - generic [ref=e6]: - - link "Smooth Schedule" [ref=e7] [cursor=pointer]: - - /url: "#/" - - img [ref=e8] - - generic [ref=e14]: Smooth Schedule - - generic [ref=e15]: - - link "Features" [ref=e16] [cursor=pointer]: - - /url: "#/features" - - link "Pricing" [ref=e17] [cursor=pointer]: - - /url: "#/pricing" - - link "About" [ref=e18] [cursor=pointer]: - - /url: "#/about" - - link "Contact" [ref=e19] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e20]: - - button "πŸ‡ΊπŸ‡Έ English" [ref=e23]: - - img [ref=e24] - - generic [ref=e27]: πŸ‡ΊπŸ‡Έ - - generic [ref=e28]: English - - img [ref=e29] - - button "Switch to dark mode" [ref=e31]: - - img [ref=e32] - - link "Login" [ref=e34] [cursor=pointer]: - - /url: "#/login" - - link "Get Started" [ref=e35] [cursor=pointer]: - - /url: "#/signup" - - main [ref=e36]: - - generic [ref=e37]: - - generic [ref=e42]: - - generic [ref=e43]: - - generic [ref=e44]: - - generic [ref=e47]: Get started today - - heading "Scheduling Made Simple" [level=1] [ref=e48] - - paragraph [ref=e49]: The all-in-one platform for managing appointments, resources, and customers. Start free, scale as you grow. - - generic [ref=e50]: - - link "Get Started Free" [ref=e51] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e52] - - button "Watch Demo" [ref=e54]: - - img [ref=e55] - - text: Watch Demo - - generic [ref=e57]: - - generic [ref=e58]: - - img [ref=e59] - - generic [ref=e62]: No credit card required - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Get started today - - generic [ref=e69]: - - generic [ref=e71]: - - generic [ref=e78]: dashboard.smoothschedule.com - - generic [ref=e79]: - - generic [ref=e80]: - - generic [ref=e81]: - - generic [ref=e82]: Today - - generic [ref=e83]: "12" - - generic [ref=e84]: - - generic [ref=e85]: This Week - - generic [ref=e86]: "48" - - generic [ref=e87]: - - generic [ref=e88]: Revenue - - generic [ref=e89]: $2.4k - - generic [ref=e90]: - - generic [ref=e91]: Today's Schedule - - generic [ref=e92]: - - generic [ref=e95]: - - generic [ref=e96]: 9:00 AM - - generic [ref=e97]: Sarah J. - Haircut - - generic [ref=e100]: - - generic [ref=e101]: 10:30 AM - - generic [ref=e102]: Mike T. - Consultation - - generic [ref=e105]: - - generic [ref=e106]: 2:00 PM - - generic [ref=e107]: Emma W. - Color - - generic [ref=e109]: - - img [ref=e111] - - generic [ref=e114]: - - generic [ref=e115]: New Booking! - - generic [ref=e116]: Just now - - generic [ref=e117]: - - paragraph [ref=e118]: Trusted by 1,000+ businesses worldwide - - generic [ref=e119]: - - generic [ref=e120]: TechCorp - - generic [ref=e121]: Innovate - - generic [ref=e122]: StartupX - - generic [ref=e123]: GrowthCo - - generic [ref=e124]: ScaleUp - - generic [ref=e126]: - - generic [ref=e127]: - - heading "Everything You Need" [level=2] [ref=e128] - - paragraph [ref=e129]: Powerful features to run your service business - - generic [ref=e130]: - - generic [ref=e131]: - - img [ref=e133] - - heading "Smart Scheduling" [level=3] [ref=e135] - - paragraph [ref=e136]: Drag-and-drop calendar with real-time availability, automated reminders, and conflict detection. - - generic [ref=e137]: - - img [ref=e139] - - heading "Resource Management" [level=3] [ref=e144] - - paragraph [ref=e145]: Manage staff, rooms, and equipment. Set availability, skills, and booking rules. - - generic [ref=e146]: - - img [ref=e148] - - heading "Customer Portal" [level=3] [ref=e152] - - paragraph [ref=e153]: Self-service booking portal for customers. View history, manage appointments, and save payment methods. - - generic [ref=e154]: - - img [ref=e156] - - heading "Integrated Payments" [level=3] [ref=e158] - - paragraph [ref=e159]: Accept payments online with Stripe. Deposits, full payments, and automatic invoicing. - - generic [ref=e160]: - - img [ref=e162] - - heading "Multi-Location Support" [level=3] [ref=e166] - - paragraph [ref=e167]: Manage multiple locations or brands from a single dashboard with isolated data. - - generic [ref=e168]: - - img [ref=e170] - - heading "White-Label Ready" [level=3] [ref=e176] - - paragraph [ref=e177]: Custom domain, branding, and remove SmoothSchedule branding for a seamless experience. - - link "View All features" [ref=e179] [cursor=pointer]: - - /url: "#/features" - - text: View All features - - img [ref=e180] - - generic [ref=e183]: - - generic [ref=e184]: - - heading "Get Started in Minutes" [level=2] [ref=e185] - - paragraph [ref=e186]: Three simple steps to transform your scheduling - - generic [ref=e187]: - - generic [ref=e190]: - - generic [ref=e191]: "01" - - img [ref=e193] - - heading "Create Your Account" [level=3] [ref=e196] - - paragraph [ref=e197]: Sign up for free and set up your business profile in minutes. - - generic [ref=e200]: - - generic [ref=e201]: "02" - - img [ref=e203] - - heading "Add Your Services" [level=3] [ref=e206] - - paragraph [ref=e207]: Configure your services, pricing, and available resources. - - generic [ref=e209]: - - generic [ref=e210]: "03" - - img [ref=e212] - - heading "Start Booking" [level=3] [ref=e217] - - paragraph [ref=e218]: Share your booking link and let customers schedule instantly. - - generic [ref=e221]: - - generic [ref=e222]: - - img [ref=e224] - - generic [ref=e226]: 1M+ - - generic [ref=e227]: Appointments Scheduled - - generic [ref=e228]: - - img [ref=e230] - - generic [ref=e234]: 5,000+ - - generic [ref=e235]: Businesses - - generic [ref=e236]: - - img [ref=e238] - - generic [ref=e241]: 50+ - - generic [ref=e242]: Countries - - generic [ref=e243]: - - img [ref=e245] - - generic [ref=e248]: 99.9% - - generic [ref=e249]: Uptime - - generic [ref=e251]: - - generic [ref=e252]: - - heading "Loved by Businesses Everywhere" [level=2] [ref=e253] - - paragraph [ref=e254]: See what our customers have to say - - generic [ref=e255]: - - generic [ref=e256]: - - generic [ref=e257]: - - img [ref=e258] - - img [ref=e260] - - img [ref=e262] - - img [ref=e264] - - img [ref=e266] - - blockquote [ref=e268]: "\"SmoothSchedule transformed how we manage appointments. Our no-show rate dropped by 40% with automated reminders.\"" - - generic [ref=e269]: - - generic [ref=e271]: S - - generic [ref=e272]: - - generic [ref=e273]: Sarah Johnson - - generic [ref=e274]: Owner at Luxe Salon - - generic [ref=e275]: - - generic [ref=e276]: - - img [ref=e277] - - img [ref=e279] - - img [ref=e281] - - img [ref=e283] - - img [ref=e285] - - blockquote [ref=e287]: "\"The white-label feature is perfect for our multi-location business. Each location has its own branded booking experience.\"" - - generic [ref=e288]: - - generic [ref=e290]: M - - generic [ref=e291]: - - generic [ref=e292]: Michael Chen - - generic [ref=e293]: CEO at FitLife Studios - - generic [ref=e294]: - - generic [ref=e295]: - - img [ref=e296] - - img [ref=e298] - - img [ref=e300] - - img [ref=e302] - - img [ref=e304] - - blockquote [ref=e306]: "\"Setup was incredibly easy. We were up and running in under an hour, and our clients love the self-service booking.\"" - - generic [ref=e307]: - - generic [ref=e309]: E - - generic [ref=e310]: - - generic [ref=e311]: Emily Rodriguez - - generic [ref=e312]: Manager at Peak Performance Therapy - - generic [ref=e314]: - - generic [ref=e315]: - - heading "Simple, Transparent Pricing" [level=2] [ref=e316] - - paragraph [ref=e317]: Start free, upgrade as you grow. No hidden fees. - - generic [ref=e318]: - - generic [ref=e319]: - - heading "Free" [level=3] [ref=e320] - - paragraph [ref=e321]: Perfect for getting started - - generic [ref=e322]: $0/month - - link "Get Started" [ref=e323] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e324]: - - generic [ref=e325]: Most Popular - - heading "Professional" [level=3] [ref=e326] - - paragraph [ref=e327]: For growing businesses - - generic [ref=e328]: $29/month - - link "Get Started" [ref=e329] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e330]: - - heading "Business" [level=3] [ref=e331] - - paragraph [ref=e332]: For established teams - - generic [ref=e333]: $79/month - - link "Get Started" [ref=e334] [cursor=pointer]: - - /url: "#/signup" - - link "View full pricing details" [ref=e336] [cursor=pointer]: - - /url: "#/pricing" - - text: View full pricing details - - img [ref=e337] - - generic [ref=e343]: - - heading "Ready to get started?" [level=2] [ref=e344] - - paragraph [ref=e345]: Join thousands of businesses already using SmoothSchedule. - - generic [ref=e346]: - - link "Get Started Free" [ref=e347] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e348] - - link "Talk to Sales" [ref=e350] [cursor=pointer]: - - /url: "#/contact" - - paragraph [ref=e351]: No credit card required - - contentinfo [ref=e352]: - - generic [ref=e353]: - - generic [ref=e354]: - - generic [ref=e355]: - - link "Smooth Schedule" [ref=e356] [cursor=pointer]: - - /url: "#/" - - img [ref=e357] - - generic [ref=e363]: Smooth Schedule - - paragraph [ref=e364]: The all-in-one scheduling platform for businesses of all sizes. Manage resources, staff, and bookings effortlessly. - - generic [ref=e365]: - - link "Twitter" [ref=e366] [cursor=pointer]: - - /url: https://twitter.com/smoothschedule - - img [ref=e367] - - link "LinkedIn" [ref=e369] [cursor=pointer]: - - /url: https://linkedin.com/company/smoothschedule - - img [ref=e370] - - link "GitHub" [ref=e374] [cursor=pointer]: - - /url: https://github.com/smoothschedule - - img [ref=e375] - - link "YouTube" [ref=e378] [cursor=pointer]: - - /url: https://youtube.com/@smoothschedule - - img [ref=e379] - - generic [ref=e382]: - - heading "Product" [level=3] [ref=e383] - - list [ref=e384]: - - listitem [ref=e385]: - - link "Features" [ref=e386] [cursor=pointer]: - - /url: "#/features" - - listitem [ref=e387]: - - link "Pricing" [ref=e388] [cursor=pointer]: - - /url: "#/pricing" - - listitem [ref=e389]: - - link "Get Started" [ref=e390] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e391]: - - heading "Company" [level=3] [ref=e392] - - list [ref=e393]: - - listitem [ref=e394]: - - link "About" [ref=e395] [cursor=pointer]: - - /url: "#/about" - - listitem [ref=e396]: - - link "Contact" [ref=e397] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e398]: - - heading "Legal" [level=3] [ref=e399] - - list [ref=e400]: - - listitem [ref=e401]: - - link "Privacy Policy" [ref=e402] [cursor=pointer]: - - /url: "#/privacy" - - listitem [ref=e403]: - - link "Terms of Service" [ref=e404] [cursor=pointer]: - - /url: "#/terms" - - paragraph [ref=e406]: Β© 2025 Smooth Schedule Inc. All rights reserved. - - generic [ref=e407]: - - generic [ref=e408]: - - heading "πŸ”“ Quick Login (Dev Only)" [level=3] [ref=e409]: - - generic [ref=e410]: πŸ”“ - - generic [ref=e411]: Quick Login (Dev Only) - - button "Γ—" [ref=e412] - - generic [ref=e413]: - - button "Logging in..." [disabled] [ref=e414]: - - generic [ref=e415]: - - img [ref=e416] - - text: Logging in... - - button "Platform Manager PLATFORM_MANAGER" [disabled] [ref=e419]: - - generic [ref=e420]: - - generic [ref=e421]: Platform Manager - - generic [ref=e422]: PLATFORM_MANAGER - - button "Platform Sales PLATFORM_SALES" [disabled] [ref=e423]: - - generic [ref=e424]: - - generic [ref=e425]: Platform Sales - - generic [ref=e426]: PLATFORM_SALES - - button "Platform Support PLATFORM_SUPPORT" [disabled] [ref=e427]: - - generic [ref=e428]: - - generic [ref=e429]: Platform Support - - generic [ref=e430]: PLATFORM_SUPPORT - - button "Business Owner TENANT_OWNER" [disabled] [ref=e431]: - - generic [ref=e432]: - - generic [ref=e433]: Business Owner - - generic [ref=e434]: TENANT_OWNER - - button "Business Manager TENANT_MANAGER" [disabled] [ref=e435]: - - generic [ref=e436]: - - generic [ref=e437]: Business Manager - - generic [ref=e438]: TENANT_MANAGER - - button "Staff Member TENANT_STAFF" [disabled] [ref=e439]: - - generic [ref=e440]: - - generic [ref=e441]: Staff Member - - generic [ref=e442]: TENANT_STAFF - - button "Customer CUSTOMER" [disabled] [ref=e443]: - - generic [ref=e444]: - - generic [ref=e445]: Customer - - generic [ref=e446]: CUSTOMER - - generic [ref=e447]: - - text: "Password for all:" - - code [ref=e448]: test123 -``` \ No newline at end of file diff --git a/frontend/test-results/debug-login-debug-login-request-chromium/test-failed-1.png b/frontend/test-results/debug-login-debug-login-request-chromium/test-failed-1.png deleted file mode 100644 index 04698e7..0000000 Binary files a/frontend/test-results/debug-login-debug-login-request-chromium/test-failed-1.png and /dev/null differ diff --git a/frontend/test-results/debug-login-debug-login-request-firefox/error-context.md b/frontend/test-results/debug-login-debug-login-request-firefox/error-context.md deleted file mode 100644 index 58e710c..0000000 --- a/frontend/test-results/debug-login-debug-login-request-firefox/error-context.md +++ /dev/null @@ -1,343 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - generic [ref=e3]: - - navigation [ref=e4]: - - generic [ref=e6]: - - link "Smooth Schedule" [ref=e7] [cursor=pointer]: - - /url: "#/" - - img [ref=e8] - - generic [ref=e14]: Smooth Schedule - - generic [ref=e15]: - - link "Features" [ref=e16] [cursor=pointer]: - - /url: "#/features" - - link "Pricing" [ref=e17] [cursor=pointer]: - - /url: "#/pricing" - - link "About" [ref=e18] [cursor=pointer]: - - /url: "#/about" - - link "Contact" [ref=e19] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e20]: - - button "πŸ‡ΊπŸ‡Έ English" [ref=e23]: - - img [ref=e24] - - generic [ref=e28]: πŸ‡ΊπŸ‡Έ - - generic [ref=e29]: English - - img [ref=e30] - - button "Switch to dark mode" [ref=e32]: - - img [ref=e33] - - link "Login" [ref=e35] [cursor=pointer]: - - /url: "#/login" - - link "Get Started" [ref=e36] [cursor=pointer]: - - /url: "#/signup" - - main [ref=e37]: - - generic [ref=e38]: - - generic [ref=e43]: - - generic [ref=e44]: - - generic [ref=e45]: - - generic [ref=e48]: Get started today - - heading "Scheduling Made Simple" [level=1] [ref=e49] - - paragraph [ref=e50]: The all-in-one platform for managing appointments, resources, and customers. Start free, scale as you grow. - - generic [ref=e51]: - - link "Get Started Free" [ref=e52] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e53] - - button "Watch Demo" [ref=e56]: - - img [ref=e57] - - text: Watch Demo - - generic [ref=e59]: - - generic [ref=e60]: - - img [ref=e61] - - generic [ref=e64]: No credit card required - - generic [ref=e66]: - - img [ref=e67] - - generic [ref=e70]: Get started today - - generic [ref=e71]: - - generic [ref=e73]: - - generic [ref=e80]: dashboard.smoothschedule.com - - generic [ref=e81]: - - generic [ref=e82]: - - generic [ref=e83]: - - generic [ref=e84]: Today - - generic [ref=e85]: "12" - - generic [ref=e86]: - - generic [ref=e87]: This Week - - generic [ref=e88]: "48" - - generic [ref=e89]: - - generic [ref=e90]: Revenue - - generic [ref=e91]: $2.4k - - generic [ref=e92]: - - generic [ref=e93]: Today's Schedule - - generic [ref=e94]: - - generic [ref=e97]: - - generic [ref=e98]: 9:00 AM - - generic [ref=e99]: Sarah J. - Haircut - - generic [ref=e102]: - - generic [ref=e103]: 10:30 AM - - generic [ref=e104]: Mike T. - Consultation - - generic [ref=e107]: - - generic [ref=e108]: 2:00 PM - - generic [ref=e109]: Emma W. - Color - - generic [ref=e111]: - - img [ref=e113] - - generic [ref=e116]: - - generic [ref=e117]: New Booking! - - generic [ref=e118]: Just now - - generic [ref=e119]: - - paragraph [ref=e120]: Trusted by 1,000+ businesses worldwide - - generic [ref=e121]: - - generic [ref=e122]: TechCorp - - generic [ref=e123]: Innovate - - generic [ref=e124]: StartupX - - generic [ref=e125]: GrowthCo - - generic [ref=e126]: ScaleUp - - generic [ref=e128]: - - generic [ref=e129]: - - heading "Everything You Need" [level=2] [ref=e130] - - paragraph [ref=e131]: Powerful features to run your service business - - generic [ref=e132]: - - generic [ref=e133]: - - img [ref=e135] - - heading "Smart Scheduling" [level=3] [ref=e140] - - paragraph [ref=e141]: Drag-and-drop calendar with real-time availability, automated reminders, and conflict detection. - - generic [ref=e142]: - - img [ref=e144] - - heading "Resource Management" [level=3] [ref=e149] - - paragraph [ref=e150]: Manage staff, rooms, and equipment. Set availability, skills, and booking rules. - - generic [ref=e151]: - - img [ref=e153] - - heading "Customer Portal" [level=3] [ref=e157] - - paragraph [ref=e158]: Self-service booking portal for customers. View history, manage appointments, and save payment methods. - - generic [ref=e159]: - - img [ref=e161] - - heading "Integrated Payments" [level=3] [ref=e164] - - paragraph [ref=e165]: Accept payments online with Stripe. Deposits, full payments, and automatic invoicing. - - generic [ref=e166]: - - img [ref=e168] - - heading "Multi-Location Support" [level=3] [ref=e174] - - paragraph [ref=e175]: Manage multiple locations or brands from a single dashboard with isolated data. - - generic [ref=e176]: - - img [ref=e178] - - heading "White-Label Ready" [level=3] [ref=e184] - - paragraph [ref=e185]: Custom domain, branding, and remove SmoothSchedule branding for a seamless experience. - - link "View All features" [ref=e187] [cursor=pointer]: - - /url: "#/features" - - text: View All features - - img [ref=e188] - - generic [ref=e192]: - - generic [ref=e193]: - - heading "Get Started in Minutes" [level=2] [ref=e194] - - paragraph [ref=e195]: Three simple steps to transform your scheduling - - generic [ref=e196]: - - generic [ref=e199]: - - generic [ref=e200]: "01" - - img [ref=e202] - - heading "Create Your Account" [level=3] [ref=e207] - - paragraph [ref=e208]: Sign up for free and set up your business profile in minutes. - - generic [ref=e211]: - - generic [ref=e212]: "02" - - img [ref=e214] - - heading "Add Your Services" [level=3] [ref=e217] - - paragraph [ref=e218]: Configure your services, pricing, and available resources. - - generic [ref=e220]: - - generic [ref=e221]: "03" - - img [ref=e223] - - heading "Start Booking" [level=3] [ref=e228] - - paragraph [ref=e229]: Share your booking link and let customers schedule instantly. - - generic [ref=e232]: - - generic [ref=e233]: - - img [ref=e235] - - generic [ref=e240]: 1M+ - - generic [ref=e241]: Appointments Scheduled - - generic [ref=e242]: - - img [ref=e244] - - generic [ref=e250]: 5,000+ - - generic [ref=e251]: Businesses - - generic [ref=e252]: - - img [ref=e254] - - generic [ref=e258]: 50+ - - generic [ref=e259]: Countries - - generic [ref=e260]: - - img [ref=e262] - - generic [ref=e265]: 99.9% - - generic [ref=e266]: Uptime - - generic [ref=e268]: - - generic [ref=e269]: - - heading "Loved by Businesses Everywhere" [level=2] [ref=e270] - - paragraph [ref=e271]: See what our customers have to say - - generic [ref=e272]: - - generic [ref=e273]: - - generic [ref=e274]: - - img [ref=e275] - - img [ref=e277] - - img [ref=e279] - - img [ref=e281] - - img [ref=e283] - - blockquote [ref=e285]: "\"SmoothSchedule transformed how we manage appointments. Our no-show rate dropped by 40% with automated reminders.\"" - - generic [ref=e286]: - - generic [ref=e288]: S - - generic [ref=e289]: - - generic [ref=e290]: Sarah Johnson - - generic [ref=e291]: Owner at Luxe Salon - - generic [ref=e292]: - - generic [ref=e293]: - - img [ref=e294] - - img [ref=e296] - - img [ref=e298] - - img [ref=e300] - - img [ref=e302] - - blockquote [ref=e304]: "\"The white-label feature is perfect for our multi-location business. Each location has its own branded booking experience.\"" - - generic [ref=e305]: - - generic [ref=e307]: M - - generic [ref=e308]: - - generic [ref=e309]: Michael Chen - - generic [ref=e310]: CEO at FitLife Studios - - generic [ref=e311]: - - generic [ref=e312]: - - img [ref=e313] - - img [ref=e315] - - img [ref=e317] - - img [ref=e319] - - img [ref=e321] - - blockquote [ref=e323]: "\"Setup was incredibly easy. We were up and running in under an hour, and our clients love the self-service booking.\"" - - generic [ref=e324]: - - generic [ref=e326]: E - - generic [ref=e327]: - - generic [ref=e328]: Emily Rodriguez - - generic [ref=e329]: Manager at Peak Performance Therapy - - generic [ref=e331]: - - generic [ref=e332]: - - heading "Simple, Transparent Pricing" [level=2] [ref=e333] - - paragraph [ref=e334]: Start free, upgrade as you grow. No hidden fees. - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Free" [level=3] [ref=e337] - - paragraph [ref=e338]: Perfect for getting started - - generic [ref=e339]: $0/month - - link "Get Started" [ref=e340] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e341]: - - generic [ref=e342]: Most Popular - - heading "Professional" [level=3] [ref=e343] - - paragraph [ref=e344]: For growing businesses - - generic [ref=e345]: $29/month - - link "Get Started" [ref=e346] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e347]: - - heading "Business" [level=3] [ref=e348] - - paragraph [ref=e349]: For established teams - - generic [ref=e350]: $79/month - - link "Get Started" [ref=e351] [cursor=pointer]: - - /url: "#/signup" - - link "View full pricing details" [ref=e353] [cursor=pointer]: - - /url: "#/pricing" - - text: View full pricing details - - img [ref=e354] - - generic [ref=e361]: - - heading "Ready to get started?" [level=2] [ref=e362] - - paragraph [ref=e363]: Join thousands of businesses already using SmoothSchedule. - - generic [ref=e364]: - - link "Get Started Free" [ref=e365] [cursor=pointer]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e366] - - link "Talk to Sales" [ref=e369] [cursor=pointer]: - - /url: "#/contact" - - paragraph [ref=e370]: No credit card required - - contentinfo [ref=e371]: - - generic [ref=e372]: - - generic [ref=e373]: - - generic [ref=e374]: - - link "Smooth Schedule" [ref=e375] [cursor=pointer]: - - /url: "#/" - - img [ref=e376] - - generic [ref=e382]: Smooth Schedule - - paragraph [ref=e383]: The all-in-one scheduling platform for businesses of all sizes. Manage resources, staff, and bookings effortlessly. - - generic [ref=e384]: - - link "Twitter" [ref=e385] [cursor=pointer]: - - /url: https://twitter.com/smoothschedule - - img [ref=e386] - - link "LinkedIn" [ref=e388] [cursor=pointer]: - - /url: https://linkedin.com/company/smoothschedule - - img [ref=e389] - - link "GitHub" [ref=e393] [cursor=pointer]: - - /url: https://github.com/smoothschedule - - img [ref=e394] - - link "YouTube" [ref=e397] [cursor=pointer]: - - /url: https://youtube.com/@smoothschedule - - img [ref=e398] - - generic [ref=e401]: - - heading "Product" [level=3] [ref=e402] - - list [ref=e403]: - - listitem [ref=e404]: - - link "Features" [ref=e405] [cursor=pointer]: - - /url: "#/features" - - listitem [ref=e406]: - - link "Pricing" [ref=e407] [cursor=pointer]: - - /url: "#/pricing" - - listitem [ref=e408]: - - link "Get Started" [ref=e409] [cursor=pointer]: - - /url: "#/signup" - - generic [ref=e410]: - - heading "Company" [level=3] [ref=e411] - - list [ref=e412]: - - listitem [ref=e413]: - - link "About" [ref=e414] [cursor=pointer]: - - /url: "#/about" - - listitem [ref=e415]: - - link "Contact" [ref=e416] [cursor=pointer]: - - /url: "#/contact" - - generic [ref=e417]: - - heading "Legal" [level=3] [ref=e418] - - list [ref=e419]: - - listitem [ref=e420]: - - link "Privacy Policy" [ref=e421] [cursor=pointer]: - - /url: "#/privacy" - - listitem [ref=e422]: - - link "Terms of Service" [ref=e423] [cursor=pointer]: - - /url: "#/terms" - - paragraph [ref=e425]: Β© 2025 Smooth Schedule Inc. All rights reserved. - - generic [ref=e426]: - - generic [ref=e427]: - - heading "πŸ”“ Quick Login (Dev Only)" [level=3] [ref=e428]: - - generic [ref=e429]: πŸ”“ - - generic [ref=e430]: Quick Login (Dev Only) - - button "Γ—" [ref=e431] - - generic [ref=e432]: - - button "Logging in..." [disabled] [ref=e433]: - - generic [ref=e434]: - - img [ref=e435] - - text: Logging in... - - button "Platform Manager PLATFORM_MANAGER" [disabled] [ref=e438]: - - generic [ref=e439]: - - generic [ref=e440]: Platform Manager - - generic [ref=e441]: PLATFORM_MANAGER - - button "Platform Sales PLATFORM_SALES" [disabled] [ref=e442]: - - generic [ref=e443]: - - generic [ref=e444]: Platform Sales - - generic [ref=e445]: PLATFORM_SALES - - button "Platform Support PLATFORM_SUPPORT" [disabled] [ref=e446]: - - generic [ref=e447]: - - generic [ref=e448]: Platform Support - - generic [ref=e449]: PLATFORM_SUPPORT - - button "Business Owner TENANT_OWNER" [disabled] [ref=e450]: - - generic [ref=e451]: - - generic [ref=e452]: Business Owner - - generic [ref=e453]: TENANT_OWNER - - button "Business Manager TENANT_MANAGER" [disabled] [ref=e454]: - - generic [ref=e455]: - - generic [ref=e456]: Business Manager - - generic [ref=e457]: TENANT_MANAGER - - button "Staff Member TENANT_STAFF" [disabled] [ref=e458]: - - generic [ref=e459]: - - generic [ref=e460]: Staff Member - - generic [ref=e461]: TENANT_STAFF - - button "Customer CUSTOMER" [disabled] [ref=e462]: - - generic [ref=e463]: - - generic [ref=e464]: Customer - - generic [ref=e465]: CUSTOMER - - generic [ref=e466]: - - text: "Password for all:" - - code [ref=e467]: test123 -``` \ No newline at end of file diff --git a/frontend/test-results/debug-login-debug-login-request-firefox/test-failed-1.png b/frontend/test-results/debug-login-debug-login-request-firefox/test-failed-1.png deleted file mode 100644 index c91771e..0000000 Binary files a/frontend/test-results/debug-login-debug-login-request-firefox/test-failed-1.png and /dev/null differ diff --git a/frontend/test-results/debug-login-debug-login-request-webkit/error-context.md b/frontend/test-results/debug-login-debug-login-request-webkit/error-context.md deleted file mode 100644 index 84cb091..0000000 --- a/frontend/test-results/debug-login-debug-login-request-webkit/error-context.md +++ /dev/null @@ -1,343 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - generic [ref=e3]: - - navigation [ref=e4]: - - generic [ref=e6]: - - link "Smooth Schedule" [ref=e7]: - - /url: "#/" - - img [ref=e8] - - generic [ref=e14]: Smooth Schedule - - generic [ref=e15]: - - link "Features" [ref=e16]: - - /url: "#/features" - - link "Pricing" [ref=e17]: - - /url: "#/pricing" - - link "About" [ref=e18]: - - /url: "#/about" - - link "Contact" [ref=e19]: - - /url: "#/contact" - - generic [ref=e20]: - - button "πŸ‡ΊπŸ‡Έ English" [ref=e23]: - - img [ref=e24] - - generic [ref=e27]: πŸ‡ΊπŸ‡Έ - - generic [ref=e28]: English - - img [ref=e29] - - button "Switch to dark mode" [ref=e31]: - - img [ref=e32] - - link "Login" [ref=e34]: - - /url: "#/login" - - link "Get Started" [ref=e35]: - - /url: "#/signup" - - main [ref=e36]: - - generic [ref=e37]: - - generic [ref=e42]: - - generic [ref=e43]: - - generic [ref=e44]: - - generic [ref=e47]: Get started today - - heading "Scheduling Made Simple" [level=1] [ref=e48] - - paragraph [ref=e49]: The all-in-one platform for managing appointments, resources, and customers. Start free, scale as you grow. - - generic [ref=e50]: - - link "Get Started Free" [ref=e51]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e52] - - button "Watch Demo" [ref=e54]: - - img [ref=e55] - - text: Watch Demo - - generic [ref=e57]: - - generic [ref=e58]: - - img [ref=e59] - - generic [ref=e62]: No credit card required - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Get started today - - generic [ref=e69]: - - generic [ref=e71]: - - generic [ref=e78]: dashboard.smoothschedule.com - - generic [ref=e79]: - - generic [ref=e80]: - - generic [ref=e81]: - - generic [ref=e82]: Today - - generic [ref=e83]: "12" - - generic [ref=e84]: - - generic [ref=e85]: This Week - - generic [ref=e86]: "48" - - generic [ref=e87]: - - generic [ref=e88]: Revenue - - generic [ref=e89]: $2.4k - - generic [ref=e90]: - - generic [ref=e91]: Today's Schedule - - generic [ref=e92]: - - generic [ref=e95]: - - generic [ref=e96]: 9:00 AM - - generic [ref=e97]: Sarah J. - Haircut - - generic [ref=e100]: - - generic [ref=e101]: 10:30 AM - - generic [ref=e102]: Mike T. - Consultation - - generic [ref=e105]: - - generic [ref=e106]: 2:00 PM - - generic [ref=e107]: Emma W. - Color - - generic [ref=e109]: - - img [ref=e111] - - generic [ref=e114]: - - generic [ref=e115]: New Booking! - - generic [ref=e116]: Just now - - generic [ref=e117]: - - paragraph [ref=e118]: Trusted by 1,000+ businesses worldwide - - generic [ref=e119]: - - generic [ref=e120]: TechCorp - - generic [ref=e121]: Innovate - - generic [ref=e122]: StartupX - - generic [ref=e123]: GrowthCo - - generic [ref=e124]: ScaleUp - - generic [ref=e126]: - - generic [ref=e127]: - - heading "Everything You Need" [level=2] [ref=e128] - - paragraph [ref=e129]: Powerful features to run your service business - - generic [ref=e130]: - - generic [ref=e131]: - - img [ref=e133] - - heading "Smart Scheduling" [level=3] [ref=e135] - - paragraph [ref=e136]: Drag-and-drop calendar with real-time availability, automated reminders, and conflict detection. - - generic [ref=e137]: - - img [ref=e139] - - heading "Resource Management" [level=3] [ref=e144] - - paragraph [ref=e145]: Manage staff, rooms, and equipment. Set availability, skills, and booking rules. - - generic [ref=e146]: - - img [ref=e148] - - heading "Customer Portal" [level=3] [ref=e152] - - paragraph [ref=e153]: Self-service booking portal for customers. View history, manage appointments, and save payment methods. - - generic [ref=e154]: - - img [ref=e156] - - heading "Integrated Payments" [level=3] [ref=e158] - - paragraph [ref=e159]: Accept payments online with Stripe. Deposits, full payments, and automatic invoicing. - - generic [ref=e160]: - - img [ref=e162] - - heading "Multi-Location Support" [level=3] [ref=e166] - - paragraph [ref=e167]: Manage multiple locations or brands from a single dashboard with isolated data. - - generic [ref=e168]: - - img [ref=e170] - - heading "White-Label Ready" [level=3] [ref=e176] - - paragraph [ref=e177]: Custom domain, branding, and remove SmoothSchedule branding for a seamless experience. - - link "View All features" [ref=e179]: - - /url: "#/features" - - text: View All features - - img [ref=e180] - - generic [ref=e183]: - - generic [ref=e184]: - - heading "Get Started in Minutes" [level=2] [ref=e185] - - paragraph [ref=e186]: Three simple steps to transform your scheduling - - generic [ref=e187]: - - generic [ref=e190]: - - generic [ref=e191]: "01" - - img [ref=e193] - - heading "Create Your Account" [level=3] [ref=e196] - - paragraph [ref=e197]: Sign up for free and set up your business profile in minutes. - - generic [ref=e200]: - - generic [ref=e201]: "02" - - img [ref=e203] - - heading "Add Your Services" [level=3] [ref=e206] - - paragraph [ref=e207]: Configure your services, pricing, and available resources. - - generic [ref=e209]: - - generic [ref=e210]: "03" - - img [ref=e212] - - heading "Start Booking" [level=3] [ref=e217] - - paragraph [ref=e218]: Share your booking link and let customers schedule instantly. - - generic [ref=e221]: - - generic [ref=e222]: - - img [ref=e224] - - generic [ref=e226]: 1M+ - - generic [ref=e227]: Appointments Scheduled - - generic [ref=e228]: - - img [ref=e230] - - generic [ref=e234]: 5,000+ - - generic [ref=e235]: Businesses - - generic [ref=e236]: - - img [ref=e238] - - generic [ref=e241]: 50+ - - generic [ref=e242]: Countries - - generic [ref=e243]: - - img [ref=e245] - - generic [ref=e248]: 99.9% - - generic [ref=e249]: Uptime - - generic [ref=e251]: - - generic [ref=e252]: - - heading "Loved by Businesses Everywhere" [level=2] [ref=e253] - - paragraph [ref=e254]: See what our customers have to say - - generic [ref=e255]: - - generic [ref=e256]: - - generic [ref=e257]: - - img [ref=e258] - - img [ref=e260] - - img [ref=e262] - - img [ref=e264] - - img [ref=e266] - - blockquote [ref=e268]: "\"SmoothSchedule transformed how we manage appointments. Our no-show rate dropped by 40% with automated reminders.\"" - - generic [ref=e269]: - - generic [ref=e271]: S - - generic [ref=e272]: - - generic [ref=e273]: Sarah Johnson - - generic [ref=e274]: Owner at Luxe Salon - - generic [ref=e275]: - - generic [ref=e276]: - - img [ref=e277] - - img [ref=e279] - - img [ref=e281] - - img [ref=e283] - - img [ref=e285] - - blockquote [ref=e287]: "\"The white-label feature is perfect for our multi-location business. Each location has its own branded booking experience.\"" - - generic [ref=e288]: - - generic [ref=e290]: M - - generic [ref=e291]: - - generic [ref=e292]: Michael Chen - - generic [ref=e293]: CEO at FitLife Studios - - generic [ref=e294]: - - generic [ref=e295]: - - img [ref=e296] - - img [ref=e298] - - img [ref=e300] - - img [ref=e302] - - img [ref=e304] - - blockquote [ref=e306]: "\"Setup was incredibly easy. We were up and running in under an hour, and our clients love the self-service booking.\"" - - generic [ref=e307]: - - generic [ref=e309]: E - - generic [ref=e310]: - - generic [ref=e311]: Emily Rodriguez - - generic [ref=e312]: Manager at Peak Performance Therapy - - generic [ref=e314]: - - generic [ref=e315]: - - heading "Simple, Transparent Pricing" [level=2] [ref=e316] - - paragraph [ref=e317]: Start free, upgrade as you grow. No hidden fees. - - generic [ref=e318]: - - generic [ref=e319]: - - heading "Free" [level=3] [ref=e320] - - paragraph [ref=e321]: Perfect for getting started - - generic [ref=e322]: $0/month - - link "Get Started" [ref=e323]: - - /url: "#/signup" - - generic [ref=e324]: - - generic [ref=e325]: Most Popular - - heading "Professional" [level=3] [ref=e326] - - paragraph [ref=e327]: For growing businesses - - generic [ref=e328]: $29/month - - link "Get Started" [ref=e329]: - - /url: "#/signup" - - generic [ref=e330]: - - heading "Business" [level=3] [ref=e331] - - paragraph [ref=e332]: For established teams - - generic [ref=e333]: $79/month - - link "Get Started" [ref=e334]: - - /url: "#/signup" - - link "View full pricing details" [ref=e336]: - - /url: "#/pricing" - - text: View full pricing details - - img [ref=e337] - - generic [ref=e343]: - - heading "Ready to get started?" [level=2] [ref=e344] - - paragraph [ref=e345]: Join thousands of businesses already using SmoothSchedule. - - generic [ref=e346]: - - link "Get Started Free" [ref=e347]: - - /url: "#/signup" - - text: Get Started Free - - img [ref=e348] - - link "Talk to Sales" [ref=e350]: - - /url: "#/contact" - - paragraph [ref=e351]: No credit card required - - contentinfo [ref=e352]: - - generic [ref=e353]: - - generic [ref=e354]: - - generic [ref=e355]: - - link "Smooth Schedule" [ref=e356]: - - /url: "#/" - - img [ref=e357] - - generic [ref=e363]: Smooth Schedule - - paragraph [ref=e364]: The all-in-one scheduling platform for businesses of all sizes. Manage resources, staff, and bookings effortlessly. - - generic [ref=e365]: - - link "Twitter" [ref=e366]: - - /url: https://twitter.com/smoothschedule - - img [ref=e367] - - link "LinkedIn" [ref=e369]: - - /url: https://linkedin.com/company/smoothschedule - - img [ref=e370] - - link "GitHub" [ref=e374]: - - /url: https://github.com/smoothschedule - - img [ref=e375] - - link "YouTube" [ref=e378]: - - /url: https://youtube.com/@smoothschedule - - img [ref=e379] - - generic [ref=e382]: - - heading "Product" [level=3] [ref=e383] - - list [ref=e384]: - - listitem [ref=e385]: - - link "Features" [ref=e386]: - - /url: "#/features" - - listitem [ref=e387]: - - link "Pricing" [ref=e388]: - - /url: "#/pricing" - - listitem [ref=e389]: - - link "Get Started" [ref=e390]: - - /url: "#/signup" - - generic [ref=e391]: - - heading "Company" [level=3] [ref=e392] - - list [ref=e393]: - - listitem [ref=e394]: - - link "About" [ref=e395]: - - /url: "#/about" - - listitem [ref=e396]: - - link "Contact" [ref=e397]: - - /url: "#/contact" - - generic [ref=e398]: - - heading "Legal" [level=3] [ref=e399] - - list [ref=e400]: - - listitem [ref=e401]: - - link "Privacy Policy" [ref=e402]: - - /url: "#/privacy" - - listitem [ref=e403]: - - link "Terms of Service" [ref=e404]: - - /url: "#/terms" - - paragraph [ref=e406]: Β© 2025 Smooth Schedule Inc. All rights reserved. - - generic [ref=e407]: - - generic [ref=e408]: - - heading "πŸ”“ Quick Login (Dev Only)" [level=3] [ref=e409]: - - generic [ref=e410]: πŸ”“ - - generic [ref=e411]: Quick Login (Dev Only) - - button "Γ—" [ref=e412] - - generic [ref=e413]: - - button "Logging in..." [disabled] [ref=e414]: - - generic [ref=e415]: - - img [ref=e416] - - text: Logging in... - - button "Platform Manager PLATFORM_MANAGER" [disabled] [ref=e419]: - - generic [ref=e420]: - - generic [ref=e421]: Platform Manager - - generic [ref=e422]: PLATFORM_MANAGER - - button "Platform Sales PLATFORM_SALES" [disabled] [ref=e423]: - - generic [ref=e424]: - - generic [ref=e425]: Platform Sales - - generic [ref=e426]: PLATFORM_SALES - - button "Platform Support PLATFORM_SUPPORT" [disabled] [ref=e427]: - - generic [ref=e428]: - - generic [ref=e429]: Platform Support - - generic [ref=e430]: PLATFORM_SUPPORT - - button "Business Owner TENANT_OWNER" [disabled] [ref=e431]: - - generic [ref=e432]: - - generic [ref=e433]: Business Owner - - generic [ref=e434]: TENANT_OWNER - - button "Business Manager TENANT_MANAGER" [disabled] [ref=e435]: - - generic [ref=e436]: - - generic [ref=e437]: Business Manager - - generic [ref=e438]: TENANT_MANAGER - - button "Staff Member TENANT_STAFF" [disabled] [ref=e439]: - - generic [ref=e440]: - - generic [ref=e441]: Staff Member - - generic [ref=e442]: TENANT_STAFF - - button "Customer CUSTOMER" [disabled] [ref=e443]: - - generic [ref=e444]: - - generic [ref=e445]: Customer - - generic [ref=e446]: CUSTOMER - - generic [ref=e447]: - - text: "Password for all:" - - code [ref=e448]: test123 -``` \ No newline at end of file diff --git a/frontend/test-results/debug-login-debug-login-request-webkit/test-failed-1.png b/frontend/test-results/debug-login-debug-login-request-webkit/test-failed-1.png deleted file mode 100644 index 1fc3763..0000000 Binary files a/frontend/test-results/debug-login-debug-login-request-webkit/test-failed-1.png and /dev/null differ diff --git a/frontend/tests/e2e/business-owner-login.spec.ts b/frontend/tests/e2e/business-owner-login.spec.ts new file mode 100644 index 0000000..e628262 --- /dev/null +++ b/frontend/tests/e2e/business-owner-login.spec.ts @@ -0,0 +1,97 @@ +import { test, expect } from '@playwright/test'; + +test('business owner login flow', async ({ page }) => { + // Enable console logging + page.on('console', msg => { + const type = msg.type(); + const text = msg.text(); + // Only log errors, warnings, and our custom debug logs + if (type === 'error' || type === 'warning' || text.includes('Failed to')) { + console.log(`BROWSER ${type.toUpperCase()}:`, text); + } + }); + + // Enable error logging + page.on('pageerror', error => { + console.error('PAGE ERROR:', error.message); + console.error('STACK:', error.stack); + }); + + // Track network errors + page.on('requestfailed', request => { + console.error('REQUEST FAILED:', request.url(), request.failure()?.errorText); + }); + + // Go to the login page + console.log('Navigating to login page...'); + await page.goto('http://lvh.me:5173/#/login'); + + // Wait for the page to load + await page.waitForLoadState('networkidle'); + + // Take screenshot of initial state + await page.screenshot({ path: 'test-results/01-initial-page.png', fullPage: true }); + + // Check if DevQuickLogin component loaded + const quickLoginVisible = await page.locator('text=Quick Login (Dev Only)').isVisible().catch(() => false); + console.log('Quick Login visible:', quickLoginVisible); + + if (quickLoginVisible) { + // Click the Business Owner button + console.log('Clicking Business Owner button...'); + await page.click('button:has-text("Business Owner")'); + + // Wait for navigation or changes + await page.waitForTimeout(2000); + + // Take screenshot after clicking + await page.screenshot({ path: 'test-results/02-after-click.png', fullPage: true }); + + // Check the current URL + const currentUrl = page.url(); + console.log('Current URL:', currentUrl); + + // Check if #root has content + const rootContent = await page.locator('#root').innerHTML(); + console.log('Root content length:', rootContent.length); + console.log('Root content:', rootContent.substring(0, 500)); + + // Check full page HTML if root is empty + if (rootContent.length === 0) { + const bodyContent = await page.locator('body').innerHTML(); + console.log('\nFull body HTML (first 1000 chars):'); + console.log(bodyContent.substring(0, 1000)); + + // Check for script tags + const scripts = await page.locator('script').count(); + console.log('\nNumber of script tags:', scripts); + + // Evaluate JavaScript in the page context to check for errors + const jsErrors = await page.evaluate(() => { + // Check if React root exists + const root = document.getElementById('root'); + return { + rootExists: !!root, + rootHasChildren: root ? root.childNodes.length : 0, + documentReady: document.readyState, + }; + }); + console.log('\nJS Context:', JSON.stringify(jsErrors, null, 2)); + } + + // Check for React errors + const hasReactError = await page.locator('text=/error|failed/i').count(); + console.log('\nError count on page:', hasReactError); + + } else { + console.log('Quick Login component not found. Page content:'); + const bodyText = await page.locator('body').textContent(); + console.log(bodyText?.substring(0, 1000)); + } + + // Wait a bit more to see what happens + await page.waitForTimeout(3000); + + // Final screenshot + await page.screenshot({ path: 'test-results/03-final-state.png', fullPage: true }); +}); diff --git a/smoothschedule/config/urls.py b/smoothschedule/config/urls.py index 82953ab..a0f970b 100644 --- a/smoothschedule/config/urls.py +++ b/smoothschedule/config/urls.py @@ -10,7 +10,7 @@ from drf_spectacular.views import SpectacularAPIView from drf_spectacular.views import SpectacularSwaggerView from rest_framework.authtoken.views import obtain_auth_token -from smoothschedule.users.api_views import current_user_view, logout_view +from smoothschedule.users.api_views import current_user_view, logout_view, send_verification_email, verify_email from schedule.api_views import current_business_view urlpatterns = [ @@ -37,6 +37,8 @@ urlpatterns += [ path("api/auth-token/", csrf_exempt(obtain_auth_token), name="obtain_auth_token"), path("api/auth/me/", current_user_view, name="current_user"), path("api/auth/logout/", logout_view, name="logout"), + path("api/auth/email/verify/send/", send_verification_email, name="send_verification_email"), + path("api/auth/email/verify/", verify_email, name="verify_email"), # Business API path("api/business/current/", current_business_view, name="current_business"), # API Docs diff --git a/smoothschedule/schedule/api_views.py b/smoothschedule/schedule/api_views.py index 58377bb..7568089 100644 --- a/smoothschedule/schedule/api_views.py +++ b/smoothschedule/schedule/api_views.py @@ -40,8 +40,8 @@ def current_business_view(request): 'status': 'active' if tenant.is_active else 'inactive', 'created_at': tenant.created_on.isoformat() if tenant.created_on else None, # Optional fields with defaults - 'primary_color': None, - 'secondary_color': None, + 'primary_color': '#3B82F6', # Blue-500 default + 'secondary_color': '#1E40AF', # Blue-800 default 'logo_url': None, 'whitelabel_enabled': False, 'resources_can_reschedule': False, diff --git a/smoothschedule/schedule/management/__init__.py b/smoothschedule/schedule/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/smoothschedule/schedule/management/commands/__init__.py b/smoothschedule/schedule/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/smoothschedule/schedule/management/commands/create_demo_appointments.py b/smoothschedule/schedule/management/commands/create_demo_appointments.py new file mode 100644 index 0000000..5381979 --- /dev/null +++ b/smoothschedule/schedule/management/commands/create_demo_appointments.py @@ -0,0 +1,167 @@ +""" +Management command to create demo appointments for the current month. +""" +import random +from datetime import datetime, timedelta +from decimal import Decimal + +from django.contrib.contenttypes.models import ContentType +from django.core.management.base import BaseCommand +from django.utils import timezone + +from schedule.models import Event, Resource, Service, Participant +from smoothschedule.users.models import User + + +class Command(BaseCommand): + help = 'Create demo appointments spanning the current month' + + def add_arguments(self, parser): + parser.add_argument( + '--clear', + action='store_true', + help='Clear existing events before creating new ones', + ) + parser.add_argument( + '--count', + type=int, + default=50, + help='Number of appointments to create (default: 50)', + ) + + def handle(self, *args, **options): + if options['clear']: + deleted_count = Event.objects.all().delete()[0] + self.stdout.write(f'Deleted {deleted_count} existing events') + + # Ensure we have some resources + resources = list(Resource.objects.filter(is_active=True)) + if not resources: + self.stdout.write('Creating demo resources...') + resources = [ + Resource.objects.create(name='Sarah Johnson', type='STAFF', description='Senior Stylist'), + Resource.objects.create(name='Mike Chen', type='STAFF', description='Barber'), + Resource.objects.create(name='Room A', type='ROOM', description='Main treatment room'), + Resource.objects.create(name='Room B', type='ROOM', description='Private consultation'), + ] + self.stdout.write(f'Created {len(resources)} resources') + + # Ensure we have some services + services = list(Service.objects.filter(is_active=True)) + if not services: + self.stdout.write('Creating demo services...') + services = [ + Service.objects.create(name='Haircut', duration=30, price=Decimal('35.00')), + Service.objects.create(name='Hair Coloring', duration=90, price=Decimal('120.00')), + Service.objects.create(name='Beard Trim', duration=15, price=Decimal('15.00')), + Service.objects.create(name='Full Styling', duration=60, price=Decimal('75.00')), + Service.objects.create(name='Consultation', duration=30, price=Decimal('0.00')), + ] + self.stdout.write(f'Created {len(services)} services') + + # Ensure we have customer users + customers = list(User.objects.filter(role=User.Role.CUSTOMER)) + if not customers: + self.stdout.write('Creating demo customers...') + customer_data = [ + ('alice', 'Alice Williams', 'alice@example.com'), + ('bob', 'Bob Martinez', 'bob@example.com'), + ('carol', 'Carol Davis', 'carol@example.com'), + ('david', 'David Lee', 'david@example.com'), + ('emma', 'Emma Thompson', 'emma@example.com'), + ('frank', 'Frank Wilson', 'frank@example.com'), + ('grace', 'Grace Kim', 'grace@example.com'), + ('henry', 'Henry Brown', 'henry@example.com'), + ('ivy', 'Ivy Chen', 'ivy@example.com'), + ('jack', 'Jack Taylor', 'jack@example.com'), + ] + for username, full_name, email in customer_data: + first_name, last_name = full_name.split(' ', 1) + user = User.objects.create_user( + username=username, + email=email, + password='test123', + first_name=first_name, + last_name=last_name, + role=User.Role.CUSTOMER, + ) + customers.append(user) + self.stdout.write(f'Created {len(customer_data)} customer users') + + statuses = ['SCHEDULED', 'SCHEDULED', 'SCHEDULED', 'CONFIRMED', 'CONFIRMED', 'COMPLETED'] + + # Get the current month range + now = timezone.now() + start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + if now.month == 12: + end_of_month = start_of_month.replace(year=now.year + 1, month=1) + else: + end_of_month = start_of_month.replace(month=now.month + 1) + + count = options['count'] + created = 0 + + # Get content types for participants + resource_ct = ContentType.objects.get_for_model(Resource) + user_ct = ContentType.objects.get_for_model(User) + + self.stdout.write(f'Creating {count} appointments for {start_of_month.strftime("%B %Y")}...') + + for _ in range(count): + # Random day in the month + days_in_month = (end_of_month - start_of_month).days + random_day = random.randint(0, days_in_month - 1) + appointment_date = start_of_month + timedelta(days=random_day) + + # Random time between 8am and 6pm + hour = random.randint(8, 17) + minute = random.choice([0, 15, 30, 45]) + start_time = appointment_date.replace(hour=hour, minute=minute) + + # Skip if in the past and marked as scheduled + status = random.choice(statuses) + if start_time < now and status == 'SCHEDULED': + status = 'COMPLETED' + + # Random service and duration + service = random.choice(services) + duration = service.duration + + # Random resource + resource = random.choice(resources) + + # Random customer + customer = random.choice(customers) + + # Create the event + end_time = start_time + timedelta(minutes=duration) + + event = Event.objects.create( + title=f'{customer.full_name} - {service.name}', + start_time=start_time, + end_time=end_time, + status=status, + notes=f'Service: {service.name}', + ) + + # Create participant for the resource + Participant.objects.create( + event=event, + role=Participant.Role.RESOURCE, + content_type=resource_ct, + object_id=resource.id, + ) + + # Create participant for the customer + Participant.objects.create( + event=event, + role=Participant.Role.CUSTOMER, + content_type=user_ct, + object_id=customer.id, + ) + + created += 1 + + self.stdout.write( + self.style.SUCCESS(f'Successfully created {created} demo appointments with resource and customer links') + ) diff --git a/smoothschedule/schedule/migrations/0002_add_service_model.py b/smoothschedule/schedule/migrations/0002_add_service_model.py new file mode 100644 index 0000000..ba25443 --- /dev/null +++ b/smoothschedule/schedule/migrations/0002_add_service_model.py @@ -0,0 +1,34 @@ +# Generated manually + +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('schedule', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Service', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(blank=True)), + ('duration', models.PositiveIntegerField(default=60, help_text='Duration in minutes')), + ('price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.AddIndex( + model_name='service', + index=models.Index(fields=['is_active', 'name'], name='schedule_se_is_acti_idx'), + ), + ] diff --git a/smoothschedule/schedule/migrations/0003_add_resource_type.py b/smoothschedule/schedule/migrations/0003_add_resource_type.py new file mode 100644 index 0000000..ec79623 --- /dev/null +++ b/smoothschedule/schedule/migrations/0003_add_resource_type.py @@ -0,0 +1,22 @@ +# Generated manually + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('schedule', '0002_add_service_model'), + ] + + operations = [ + migrations.AddField( + model_name='resource', + name='type', + field=models.CharField( + choices=[('STAFF', 'Staff Member'), ('ROOM', 'Room'), ('EQUIPMENT', 'Equipment')], + default='STAFF', + max_length=20 + ), + ), + ] diff --git a/smoothschedule/schedule/migrations/0004_rename_schedule_se_is_acti_idx_schedule_se_is_acti_8c055e_idx.py b/smoothschedule/schedule/migrations/0004_rename_schedule_se_is_acti_idx_schedule_se_is_acti_8c055e_idx.py new file mode 100644 index 0000000..5de0062 --- /dev/null +++ b/smoothschedule/schedule/migrations/0004_rename_schedule_se_is_acti_idx_schedule_se_is_acti_8c055e_idx.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-11-28 01:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('schedule', '0003_add_resource_type'), + ] + + operations = [ + migrations.RenameIndex( + model_name='service', + new_name='schedule_se_is_acti_8c055e_idx', + old_name='schedule_se_is_acti_idx', + ), + ] diff --git a/smoothschedule/schedule/models.py b/smoothschedule/schedule/models.py index 8d08f0e..29d3a8e 100644 --- a/smoothschedule/schedule/models.py +++ b/smoothschedule/schedule/models.py @@ -3,18 +3,56 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.validators import MinValueValidator from django.utils import timezone +from decimal import Decimal + + +class Service(models.Model): + """ + A service offered by the business (e.g., Haircut, Massage, Consultation). + """ + name = models.CharField(max_length=200) + description = models.TextField(blank=True) + duration = models.PositiveIntegerField( + help_text="Duration in minutes", + default=60 + ) + price = models.DecimalField( + max_digits=10, + decimal_places=2, + default=Decimal('0.00') + ) + is_active = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ['name'] + indexes = [models.Index(fields=['is_active', 'name'])] + + def __str__(self): + return f"{self.name} ({self.duration} min - ${self.price})" class Resource(models.Model): """ A bookable resource with configurable concurrency. - + Concurrency Modes: - max_concurrent_events = 1: Strict blocking (Dentist Chair, Private Room) - max_concurrent_events > 1: Limited overlap (Waiting Room with N seats) - max_concurrent_events = 0: Infinite capacity (Virtual Resource/Category) """ + class Type(models.TextChoices): + STAFF = 'STAFF', 'Staff Member' + ROOM = 'ROOM', 'Room' + EQUIPMENT = 'EQUIPMENT', 'Equipment' + name = models.CharField(max_length=200) + type = models.CharField( + max_length=20, + choices=Type.choices, + default=Type.STAFF + ) description = models.TextField(blank=True) max_concurrent_events = models.PositiveIntegerField( default=1, diff --git a/smoothschedule/schedule/serializers.py b/smoothschedule/schedule/serializers.py index 0fd4bf0..db92ada 100644 --- a/smoothschedule/schedule/serializers.py +++ b/smoothschedule/schedule/serializers.py @@ -3,18 +3,84 @@ DRF Serializers for Schedule App with Availability Validation """ from rest_framework import serializers from django.contrib.contenttypes.models import ContentType -from .models import Resource, Event, Participant +from .models import Resource, Event, Participant, Service from .services import AvailabilityService +from smoothschedule.users.models import User + + +class CustomerSerializer(serializers.ModelSerializer): + """Serializer for Customer (User with role=CUSTOMER)""" + name = serializers.SerializerMethodField() + total_spend = serializers.SerializerMethodField() + last_visit = serializers.SerializerMethodField() + status = serializers.SerializerMethodField() + avatar_url = serializers.SerializerMethodField() + tags = serializers.SerializerMethodField() + user_id = serializers.IntegerField(source='id', read_only=True) + city = serializers.SerializerMethodField() + state = serializers.SerializerMethodField() + zip = serializers.SerializerMethodField() + + class Meta: + model = User + fields = [ + 'id', 'name', 'email', 'phone', 'city', 'state', 'zip', + 'total_spend', 'last_visit', 'status', 'avatar_url', 'tags', + 'user_id', + ] + read_only_fields = ['id', 'email'] + + def get_name(self, obj): + return obj.full_name + + def get_total_spend(self, obj): + # TODO: Calculate from payments when implemented + return 0 + + def get_last_visit(self, obj): + # TODO: Get from last appointment when implemented + return None + + def get_status(self, obj): + return 'Active' if obj.is_active else 'Inactive' + + def get_avatar_url(self, obj): + return None # TODO: Implement avatar + + def get_tags(self, obj): + return [] # TODO: Implement customer tags + + def get_city(self, obj): + return '' # TODO: Add address fields to User model + + def get_state(self, obj): + return '' + + def get_zip(self, obj): + return '' + + +class ServiceSerializer(serializers.ModelSerializer): + """Serializer for Service model""" + duration_minutes = serializers.IntegerField(source='duration', read_only=True) + + class Meta: + model = Service + fields = [ + 'id', 'name', 'description', 'duration', 'duration_minutes', + 'price', 'is_active', 'created_at', 'updated_at', + ] + read_only_fields = ['created_at', 'updated_at'] class ResourceSerializer(serializers.ModelSerializer): """Serializer for Resource model""" capacity_description = serializers.SerializerMethodField() - + class Meta: model = Resource fields = [ - 'id', 'name', 'description', 'max_concurrent_events', + 'id', 'name', 'type', 'description', 'max_concurrent_events', 'buffer_duration', 'is_active', 'capacity_description', 'created_at', 'updated_at', ] @@ -58,9 +124,9 @@ class EventSerializer(serializers.ModelSerializer): duration_minutes = serializers.SerializerMethodField() # Simplified fields for frontend compatibility - resource = serializers.SerializerMethodField() - customer = serializers.SerializerMethodField() - service = serializers.SerializerMethodField() + resource_id = serializers.SerializerMethodField() + customer_id = serializers.SerializerMethodField() + service_id = serializers.SerializerMethodField() customer_name = serializers.SerializerMethodField() service_name = serializers.SerializerMethodField() is_paid = serializers.SerializerMethodField() @@ -84,7 +150,7 @@ class EventSerializer(serializers.ModelSerializer): fields = [ 'id', 'title', 'start_time', 'end_time', 'status', 'notes', 'duration_minutes', 'participants', 'resource_ids', 'staff_ids', - 'resource', 'customer', 'service', 'customer_name', 'service_name', 'is_paid', + 'resource_id', 'customer_id', 'service_id', 'customer_name', 'service_name', 'is_paid', 'created_at', 'updated_at', 'created_by', ] read_only_fields = ['created_at', 'updated_at', 'created_by'] @@ -92,25 +158,35 @@ class EventSerializer(serializers.ModelSerializer): def get_duration_minutes(self, obj): return int(obj.duration.total_seconds() / 60) - def get_resource(self, obj): + def get_resource_id(self, obj): """Get first resource ID from participants""" resource_participant = obj.participants.filter(role='RESOURCE').first() return resource_participant.object_id if resource_participant else None - def get_customer(self, obj): - """Get customer ID - placeholder for now""" - return 1 # TODO: Implement actual customer logic + def get_customer_id(self, obj): + """Get customer ID from participants""" + customer_participant = obj.participants.filter(role='CUSTOMER').first() + return customer_participant.object_id if customer_participant else None - def get_service(self, obj): + def get_service_id(self, obj): """Get service ID - placeholder for now""" - return 1 # TODO: Implement actual service logic + # TODO: Add service link to Event model or participants + return 1 def get_customer_name(self, obj): - """Get customer name from title for now""" - return obj.title + """Get customer name from participant""" + customer_participant = obj.participants.filter(role='CUSTOMER').first() + if customer_participant and customer_participant.content_object: + user = customer_participant.content_object + return user.full_name if hasattr(user, 'full_name') else str(user) + # Fallback to title + return obj.title.split(' - ')[0] if ' - ' in obj.title else obj.title def get_service_name(self, obj): - """Get service name - placeholder""" + """Get service name from title""" + # Extract from title format "Customer Name - Service Name" + if ' - ' in obj.title: + return obj.title.split(' - ')[-1] return "Service" def get_is_paid(self, obj): diff --git a/smoothschedule/schedule/urls.py b/smoothschedule/schedule/urls.py index e65ceda..add4d24 100644 --- a/smoothschedule/schedule/urls.py +++ b/smoothschedule/schedule/urls.py @@ -3,7 +3,7 @@ Schedule App URLs """ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .views import ResourceViewSet, EventViewSet, ParticipantViewSet +from .views import ResourceViewSet, EventViewSet, ParticipantViewSet, CustomerViewSet, ServiceViewSet # Create router and register viewsets router = DefaultRouter() @@ -11,6 +11,8 @@ router.register(r'resources', ResourceViewSet, basename='resource') router.register(r'appointments', EventViewSet, basename='appointment') # Alias for frontend router.register(r'events', EventViewSet, basename='event') router.register(r'participants', ParticipantViewSet, basename='participant') +router.register(r'customers', CustomerViewSet, basename='customer') +router.register(r'services', ServiceViewSet, basename='service') # URL patterns urlpatterns = [ diff --git a/smoothschedule/schedule/views.py b/smoothschedule/schedule/views.py index 63737c4..fb3eef7 100644 --- a/smoothschedule/schedule/views.py +++ b/smoothschedule/schedule/views.py @@ -7,8 +7,10 @@ from rest_framework import viewsets, status from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response from .models import Resource, Event, Participant -from .serializers import ResourceSerializer, EventSerializer, ParticipantSerializer +from .serializers import ResourceSerializer, EventSerializer, ParticipantSerializer, CustomerSerializer, ServiceSerializer +from .models import Service from core.permissions import HasQuota +from smoothschedule.users.models import User class ResourceViewSet(viewsets.ModelViewSet): @@ -52,17 +54,46 @@ class EventViewSet(viewsets.ModelViewSet): - EventSerializer.validate() automatically checks resource availability - If resource capacity exceeded, returns 400 Bad Request - See schedule/services.py AvailabilityService for logic + + Query Parameters: + - start_date: Filter events starting on or after this date (ISO format) + - end_date: Filter events starting before this date (ISO format) + - status: Filter by event status """ queryset = Event.objects.all() serializer_class = EventSerializer # TODO: Re-enable authentication for production permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development - - filterset_fields = ['status', 'start_time', 'end_time'] + + filterset_fields = ['status'] search_fields = ['title', 'notes'] ordering_fields = ['start_time', 'end_time', 'created_at'] ordering = ['start_time'] - + + def get_queryset(self): + """ + Filter events by date range if start_date and end_date are provided. + """ + queryset = Event.objects.all() + + # Filter by date range + start_date = self.request.query_params.get('start_date') + end_date = self.request.query_params.get('end_date') + + if start_date: + from django.utils.dateparse import parse_datetime + start_dt = parse_datetime(start_date) + if start_dt: + queryset = queryset.filter(start_time__gte=start_dt) + + if end_date: + from django.utils.dateparse import parse_datetime + end_dt = parse_datetime(end_date) + if end_dt: + queryset = queryset.filter(start_time__lt=end_dt) + + return queryset + def perform_create(self, serializer): """ Create event with automatic availability validation. @@ -90,14 +121,93 @@ class EventViewSet(viewsets.ModelViewSet): class ParticipantViewSet(viewsets.ModelViewSet): """ API endpoint for managing Event Participants. - + Allows adding/removing participants (Resources, Staff, Customers) to/from events via the GenericForeignKey pattern. """ queryset = Participant.objects.all() serializer_class = ParticipantSerializer permission_classes = [IsAuthenticated] - + filterset_fields = ['event', 'role', 'content_type'] ordering_fields = ['created_at'] ordering = ['-created_at'] + + +class CustomerViewSet(viewsets.ModelViewSet): + """ + API endpoint for managing Customers. + + Customers are Users with role=CUSTOMER belonging to the current tenant. + """ + serializer_class = CustomerSerializer + # TODO: Re-enable authentication for production + permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development + + filterset_fields = ['is_active'] + search_fields = ['email', 'first_name', 'last_name'] + ordering_fields = ['email', 'created_at'] + ordering = ['email'] + + def get_queryset(self): + """ + Return customers for the current tenant. + + Customers are Users with role=CUSTOMER. + For now, return all customers. When authentication is enabled, + filter by the user's tenant. + """ + queryset = User.objects.filter(role=User.Role.CUSTOMER) + + # Filter by tenant if user is authenticated and has a tenant + # TODO: Re-enable this when authentication is enabled + # if self.request.user.is_authenticated and self.request.user.tenant: + # queryset = queryset.filter(tenant=self.request.user.tenant) + + # Apply status filter if provided + status_filter = self.request.query_params.get('status') + if status_filter: + if status_filter == 'Active': + queryset = queryset.filter(is_active=True) + elif status_filter == 'Inactive': + queryset = queryset.filter(is_active=False) + + # Apply search filter if provided + search = self.request.query_params.get('search') + if search: + from django.db.models import Q + queryset = queryset.filter( + Q(email__icontains=search) | + Q(first_name__icontains=search) | + Q(last_name__icontains=search) + ) + + return queryset + + +class ServiceViewSet(viewsets.ModelViewSet): + """ + API endpoint for managing Services. + + Services are the offerings a business provides (e.g., Haircut, Massage). + """ + queryset = Service.objects.filter(is_active=True) + serializer_class = ServiceSerializer + # TODO: Re-enable authentication for production + permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development + + filterset_fields = ['is_active'] + search_fields = ['name', 'description'] + ordering_fields = ['name', 'price', 'duration', 'created_at'] + ordering = ['name'] + + def get_queryset(self): + """Return services, optionally including inactive ones.""" + queryset = Service.objects.all() + + # By default only show active services + show_inactive = self.request.query_params.get('show_inactive', 'false') + if show_inactive.lower() != 'true': + queryset = queryset.filter(is_active=True) + + return queryset diff --git a/smoothschedule/smoothschedule/users/api_views.py b/smoothschedule/smoothschedule/users/api_views.py index aace65e..c1147ff 100644 --- a/smoothschedule/smoothschedule/users/api_views.py +++ b/smoothschedule/smoothschedule/users/api_views.py @@ -1,12 +1,16 @@ """ API views for user authentication """ +import secrets +from django.core.mail import send_mail +from django.conf import settings +from django.utils import timezone from rest_framework import status from rest_framework.decorators import api_view, permission_classes -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response -from .models import User +from .models import User, EmailVerificationToken @api_view(['GET']) @@ -31,14 +35,27 @@ def current_user_view(request): else: business_subdomain = user.tenant.schema_name + # Map database roles to frontend roles + role_mapping = { + 'superuser': 'superuser', + 'platform_manager': 'platform_manager', + 'platform_sales': 'platform_sales', + 'platform_support': 'platform_support', + 'tenant_owner': 'owner', + 'tenant_manager': 'manager', + 'tenant_staff': 'staff', + 'customer': 'customer', + } + frontend_role = role_mapping.get(user.role.lower(), user.role.lower()) + user_data = { 'id': user.id, 'username': user.username, 'email': user.email, 'name': user.full_name, - 'role': user.role.lower(), + 'role': frontend_role, 'avatar_url': None, # TODO: Implement avatar - 'email_verified': False, # TODO: Implement email verification + 'email_verified': user.email_verified, 'is_staff': user.is_staff, 'is_superuser': user.is_superuser, 'business': user.tenant_id, @@ -59,3 +76,87 @@ def logout_view(request): from django.contrib.auth import logout logout(request) return Response({"detail": "Successfully logged out."}, status=status.HTTP_200_OK) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def send_verification_email(request): + """ + Send email verification link + POST /api/auth/email/verify/send/ + """ + user = request.user + + if user.email_verified: + return Response({"detail": "Email already verified."}, status=status.HTTP_400_BAD_REQUEST) + + # Create token + token = EmailVerificationToken.create_for_user(user) + + # Build verification URL + # Use the frontend URL for verification + port = ':5173' if settings.DEBUG else '' + subdomain = '' + if user.tenant: + primary_domain = user.tenant.domains.filter(is_primary=True).first() + if primary_domain: + subdomain = primary_domain.domain.split('.')[0] + '.' + + verify_url = f"http://{subdomain}lvh.me{port}/#/verify-email?token={token.token}" + + # Send email (goes to console in development) + subject = "Verify your email - Smooth Schedule" + message = f"""Hi {user.full_name}, + +Please click the link below to verify your email address: + +{verify_url} + +This link will expire in 24 hours. + +If you did not request this, please ignore this email. + +Thanks, +The Smooth Schedule Team +""" + + send_mail( + subject, + message, + settings.DEFAULT_FROM_EMAIL if hasattr(settings, 'DEFAULT_FROM_EMAIL') else 'noreply@smoothschedule.com', + [user.email], + fail_silently=False, + ) + + return Response({"detail": "Verification email sent."}, status=status.HTTP_200_OK) + + +@api_view(['POST']) +@permission_classes([AllowAny]) +def verify_email(request): + """ + Verify email with token + POST /api/auth/email/verify/ + """ + token_str = request.data.get('token') + + if not token_str: + return Response({"error": "Token is required."}, status=status.HTTP_400_BAD_REQUEST) + + try: + token = EmailVerificationToken.objects.get(token=token_str) + except EmailVerificationToken.DoesNotExist: + return Response({"error": "Invalid token."}, status=status.HTTP_400_BAD_REQUEST) + + if not token.is_valid(): + return Response({"error": "Token has expired or already been used."}, status=status.HTTP_400_BAD_REQUEST) + + # Mark token as used + token.used = True + token.save() + + # Mark user email as verified + token.user.email_verified = True + token.user.save(update_fields=['email_verified']) + + return Response({"detail": "Email verified successfully."}, status=status.HTTP_200_OK) diff --git a/smoothschedule/smoothschedule/users/migrations/0003_add_email_verification.py b/smoothschedule/smoothschedule/users/migrations/0003_add_email_verification.py new file mode 100644 index 0000000..baaf0ea --- /dev/null +++ b/smoothschedule/smoothschedule/users/migrations/0003_add_email_verification.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.8 on 2025-11-28 00:18 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_alter_user_options_remove_user_name_user_created_at_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='email_verified', + field=models.BooleanField(default=False, help_text='Whether user has verified their email address'), + ), + migrations.CreateModel( + name='EmailVerificationToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.CharField(max_length=64, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('expires_at', models.DateTimeField()), + ('used', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='email_tokens', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/smoothschedule/smoothschedule/users/models.py b/smoothschedule/smoothschedule/users/models.py index 1af7e8d..e0cff5e 100644 --- a/smoothschedule/smoothschedule/users/models.py +++ b/smoothschedule/smoothschedule/users/models.py @@ -2,8 +2,11 @@ Smooth Schedule Custom User Model Implements strict role hierarchy and multi-tenant user management """ +import secrets +from datetime import timedelta from django.contrib.auth.models import AbstractUser from django.db import models +from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -53,6 +56,10 @@ class User(AbstractUser): default=False, help_text="True for sales demo accounts - can be masqueraded by Platform Sales" ) + email_verified = models.BooleanField( + default=False, + help_text="Whether user has verified their email address" + ) # Additional profile fields phone = models.CharField(max_length=20, blank=True) @@ -157,5 +164,34 @@ class User(AbstractUser): # Tenant users must have a tenant if self.is_tenant_user() and not self.tenant: raise ValueError(f"Users with role {self.role} must be assigned to a tenant") - + super().save(*args, **kwargs) + + +class EmailVerificationToken(models.Model): + """Token for email verification""" + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='email_tokens') + token = models.CharField(max_length=64, unique=True) + created_at = models.DateTimeField(auto_now_add=True) + expires_at = models.DateTimeField() + used = models.BooleanField(default=False) + + class Meta: + ordering = ['-created_at'] + + def save(self, *args, **kwargs): + if not self.token: + self.token = secrets.token_urlsafe(32) + if not self.expires_at: + self.expires_at = timezone.now() + timedelta(hours=24) + super().save(*args, **kwargs) + + def is_valid(self): + return not self.used and timezone.now() < self.expires_at + + @classmethod + def create_for_user(cls, user): + # Invalidate old tokens + cls.objects.filter(user=user, used=False).update(used=True) + # Create new token + return cls.objects.create(user=user)