From f8af59b005c7be6f58cc03edbf146cb93f7a9cb1 Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 14:50:28 -0400 Subject: [PATCH 01/23] feat: auth lo fi screens --- clients/shared/package-lock.json | 52 ++--- clients/shared/src/api/orval-mutator.ts | 1 + .../onboarding/EmployeeRoleStep.tsx | 105 ++++++++++ .../components/onboarding/InviteTeamStep.tsx | 103 ++++++++++ .../src/components/onboarding/LeftPanel.tsx | 31 +++ .../components/onboarding/OnboardingPage.tsx | 82 ++++++++ .../onboarding/PropertyDetailsStep.tsx | 118 +++++++++++ .../src/components/onboarding/RoleCard.tsx | 22 ++ .../onboarding/RoleSelectionStep.tsx | 109 ++++++++++ .../src/components/onboarding/WelcomeStep.tsx | 64 ++++++ .../components/onboarding/onboardingMocks.ts | 8 + .../web/src/components/onboarding/types.ts | 8 + clients/web/src/routeTree.gen.ts | 42 ++++ clients/web/src/routes/onboarding.tsx | 7 + clients/web/src/routes/requests.list.tsx | 192 ++++++++++++++++++ package-lock.json | 6 + 16 files changed, 924 insertions(+), 26 deletions(-) create mode 100644 clients/web/src/components/onboarding/EmployeeRoleStep.tsx create mode 100644 clients/web/src/components/onboarding/InviteTeamStep.tsx create mode 100644 clients/web/src/components/onboarding/LeftPanel.tsx create mode 100644 clients/web/src/components/onboarding/OnboardingPage.tsx create mode 100644 clients/web/src/components/onboarding/PropertyDetailsStep.tsx create mode 100644 clients/web/src/components/onboarding/RoleCard.tsx create mode 100644 clients/web/src/components/onboarding/RoleSelectionStep.tsx create mode 100644 clients/web/src/components/onboarding/WelcomeStep.tsx create mode 100644 clients/web/src/components/onboarding/onboardingMocks.ts create mode 100644 clients/web/src/components/onboarding/types.ts create mode 100644 clients/web/src/routes/onboarding.tsx create mode 100644 clients/web/src/routes/requests.list.tsx create mode 100644 package-lock.json diff --git a/clients/shared/package-lock.json b/clients/shared/package-lock.json index 5b09f9ed0..d6f36a258 100644 --- a/clients/shared/package-lock.json +++ b/clients/shared/package-lock.json @@ -1023,6 +1023,16 @@ "dev": true, "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1680,6 +1690,22 @@ "node": ">=8.6" } }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2141,32 +2167,6 @@ "typedoc": "0.28.x" } }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", diff --git a/clients/shared/src/api/orval-mutator.ts b/clients/shared/src/api/orval-mutator.ts index 927ca8e5e..11315c7ba 100755 --- a/clients/shared/src/api/orval-mutator.ts +++ b/clients/shared/src/api/orval-mutator.ts @@ -20,3 +20,4 @@ export const useCustomInstance = (): (( }; export default useCustomInstance; +export const customInstance = useCustomInstance \ No newline at end of file diff --git a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx new file mode 100644 index 000000000..12baf7889 --- /dev/null +++ b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx @@ -0,0 +1,105 @@ +import LeftPanel from './LeftPanel' +import { OnboardingFormData } from './types' + +interface EmployeeRoleStepProps { + formData: OnboardingFormData + updateForm: (updates: Partial) => void + onNext: () => void + onBack: () => void +} + +const EMPLOYEE_ROLES = [ + { id: 'front_desk', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, + { id: 'housekeeping', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, + { id: 'maintenance', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, + { id: 'concierge', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, +] + +export default function EmployeeRoleStep({ formData, updateForm, onNext }: EmployeeRoleStepProps) { + return ( +
+ + + {/* Right panel */} +
+ {/* Card */} +
+ {/* Header */} +
+

+ Employee Role +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

+
+ + {/* 2x2 Role cards grid */} +
+ {EMPLOYEE_ROLES.map(role => ( + + ))} +
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/InviteTeamStep.tsx b/clients/web/src/components/onboarding/InviteTeamStep.tsx new file mode 100644 index 000000000..906cf403e --- /dev/null +++ b/clients/web/src/components/onboarding/InviteTeamStep.tsx @@ -0,0 +1,103 @@ +import { useState } from 'react' +import LeftPanel from './LeftPanel' +import { OnboardingFormData } from './types' + +interface InviteTeamStepProps { + formData: OnboardingFormData + updateForm: (updates: Partial) => void +} + +export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepProps) { + const [invited, setInvited] = useState(false) + + return ( +
+ + + {/* Right panel */} +
+ {/* Card */} +
+ {/* Header */} +
+ {/* Logo placeholder */} +
+
+

+ Invite your team +

+

+ SelfServe is better when the whole staff is connected. +

+
+
+ + {/* Email invite */} +
+
+
+ updateForm({ inviteEmail: e.target.value })} + placeholder="Enter email address..." + className="flex-1 bg-transparent text-sm outline-none" + /> + +
+ {invited &&

Invite sent!

} +

+ You can also do this later from your settings. +

+
+ + {/* Actions */} +
+ + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/LeftPanel.tsx b/clients/web/src/components/onboarding/LeftPanel.tsx new file mode 100644 index 000000000..570504cfe --- /dev/null +++ b/clients/web/src/components/onboarding/LeftPanel.tsx @@ -0,0 +1,31 @@ +export default function LeftPanel() { + return ( +
+ {/* SelfServe wordmark */} +
+ + SelfServe + +
+ {/* Lorem ipsum text */} +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec risus nunc, ullamcorper vitae risus vel, tristique vehicula lectus. +

+
+
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/OnboardingPage.tsx b/clients/web/src/components/onboarding/OnboardingPage.tsx new file mode 100644 index 000000000..a9bd461d7 --- /dev/null +++ b/clients/web/src/components/onboarding/OnboardingPage.tsx @@ -0,0 +1,82 @@ +import { useState } from 'react' +import WelcomeStep from './WelcomeStep' +import RoleSelectionStep from './RoleSelectionStep' +import EmployeeRoleStep from './EmployeeRoleStep' +import PropertyDetailsStep from './PropertyDetailsStep' +import InviteTeamStep from './InviteTeamStep' +import { OnboardingFormData } from './types' + +const INITIAL_FORM_DATA: OnboardingFormData = { + role: null, + employeeRole: null, + hotelName: '', + numberOfRooms: '', + propertyType: '', + inviteEmail: '', +} + +type Step = 'welcome' | 'role' | 'employeeRole' | 'propertyDetails' | 'inviteTeam' + +export default function OnboardingPage() { + const [currentStep, setCurrentStep] = useState('welcome') + const [formData, setFormData] = useState(INITIAL_FORM_DATA) + + const updateForm = (updates: Partial) => { + setFormData(prev => ({ ...prev, ...updates })) + } + + const handleRoleSelected = (role: string) => { + updateForm({ role }) + if (role === 'employee') { + setCurrentStep('employeeRole') + } else { + setCurrentStep('propertyDetails') + } + } + + const renderStep = () => { + switch (currentStep) { + case 'welcome': + return setCurrentStep('role')} /> + case 'role': + return ( + + ) + case 'employeeRole': + return ( + setCurrentStep('propertyDetails')} + onBack={() => setCurrentStep('role')} + /> + ) + case 'propertyDetails': + return ( + setCurrentStep('inviteTeam')} + onBack={() => setCurrentStep(formData.role === 'employee' ? 'employeeRole' : 'role')} + /> + ) + case 'inviteTeam': + return ( + + ) + } + } + + return ( +
+ {renderStep()} +
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx new file mode 100644 index 000000000..703a18268 --- /dev/null +++ b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx @@ -0,0 +1,118 @@ +import LeftPanel from './LeftPanel' +import { OnboardingFormData } from './types' + +interface PropertyDetailsStepProps { + formData: OnboardingFormData + updateForm: (updates: Partial) => void + onNext: () => void + onBack: () => void +} + +const PROPERTY_TYPES = ['Hotel', 'Motel', 'Resort', 'Bed & Breakfast', 'Hostel'] + +export default function PropertyDetailsStep({ formData, updateForm, onNext, onBack }: PropertyDetailsStepProps) { + const isValid = formData.hotelName && formData.numberOfRooms && formData.propertyType + + return ( +
+ + + {/* Right panel */} +
+ {/* Card */} +
+ {/* Header */} +
+

+ Property Details +

+

+ Lorem ipsum dolor sit amet. +

+
+ + {/* Form fields */} +
+
+ + updateForm({ hotelName: e.target.value })} + placeholder="Lorem ipsum dolor sit amet" + className="border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-green-900" + /> +
+
+ + updateForm({ numberOfRooms: e.target.value })} + placeholder="e.g. 150" + className="border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-green-900" + /> +
+
+ + +
+
+ + {/* Actions */} +
+ + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/RoleCard.tsx b/clients/web/src/components/onboarding/RoleCard.tsx new file mode 100644 index 000000000..7b656cdcb --- /dev/null +++ b/clients/web/src/components/onboarding/RoleCard.tsx @@ -0,0 +1,22 @@ +interface RoleCardProps { + label: string + description: string + selected: boolean + onSelect: () => void +} + +export default function RoleCard({ label, description, selected, onSelect }: RoleCardProps) { + return ( + + ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/RoleSelectionStep.tsx b/clients/web/src/components/onboarding/RoleSelectionStep.tsx new file mode 100644 index 000000000..70f780727 --- /dev/null +++ b/clients/web/src/components/onboarding/RoleSelectionStep.tsx @@ -0,0 +1,109 @@ +import LeftPanel from './LeftPanel' +import { OnboardingFormData } from './types' + +interface RoleSelectionStepProps { + formData: OnboardingFormData + updateForm: (updates: Partial) => void + onNext: (role: string) => void +} + +const ROLES = [ + { id: 'manager', label: 'Manager', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, + { id: 'employee', label: 'Employee', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, +] + +export default function RoleSelectionStep({ formData, onNext }: RoleSelectionStepProps) { + return ( +
+ + + {/* Right panel */} +
+ {/* Outer card */} +
+ {/* Logo placeholder */} +
+ + {/* Role title */} + + Role + + + {/* Subtitle */} + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + + {/* Role cards grid */} +
+ {ROLES.map(role => ( + + ))} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/WelcomeStep.tsx b/clients/web/src/components/onboarding/WelcomeStep.tsx new file mode 100644 index 000000000..0c1b5db58 --- /dev/null +++ b/clients/web/src/components/onboarding/WelcomeStep.tsx @@ -0,0 +1,64 @@ +import LeftPanel from './LeftPanel' + +interface WelcomeStepProps { + onNext: () => void +} + +export default function WelcomeStep({ onNext }: WelcomeStepProps) { + return ( +
+ + + {/* Right panel */} +
+ {/* Welcome card */} +
+ {/* Logo placeholder */} +
+ + {/* Inner container: Welcome + Start */} +
+ {/* Welcome text */} +

+ Welcome +

+ {/* Start button */} + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/onboardingMocks.ts b/clients/web/src/components/onboarding/onboardingMocks.ts new file mode 100644 index 000000000..2bd61742a --- /dev/null +++ b/clients/web/src/components/onboarding/onboardingMocks.ts @@ -0,0 +1,8 @@ +export const ROLES = [ + { id: 'manager', label: 'Manager', description: 'Oversees operations and staff.' }, + { id: 'front_desk', label: 'Front Desk', description: 'Handles guest check-ins and requests.' }, + { id: 'housekeeping', label: 'Housekeeping', description: 'Manages room cleaning and maintenance.' }, + { id: 'maintenance', label: 'Maintenance', description: 'Handles repairs and facilities.' }, +] + +export const PROPERTY_TYPES = ['Hotel', 'Motel', 'Resort', 'Bed & Breakfast', 'Hostel'] \ No newline at end of file diff --git a/clients/web/src/components/onboarding/types.ts b/clients/web/src/components/onboarding/types.ts new file mode 100644 index 000000000..f0b22fe8f --- /dev/null +++ b/clients/web/src/components/onboarding/types.ts @@ -0,0 +1,8 @@ +export interface OnboardingFormData { + role: string | null + employeeRole: string | null + hotelName: string + numberOfRooms: string + propertyType: string + inviteEmail: string +} \ No newline at end of file diff --git a/clients/web/src/routeTree.gen.ts b/clients/web/src/routeTree.gen.ts index 385331118..cea500927 100644 --- a/clients/web/src/routeTree.gen.ts +++ b/clients/web/src/routeTree.gen.ts @@ -11,8 +11,10 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as SignUpRouteImport } from './routes/sign-up' import { Route as SignInRouteImport } from './routes/sign-in' +import { Route as OnboardingRouteImport } from './routes/onboarding' import { Route as ProtectedRouteImport } from './routes/_protected' import { Route as IndexRouteImport } from './routes/index' +import { Route as RequestsListRouteImport } from './routes/requests.list' import { Route as ProtectedTestApiRouteImport } from './routes/_protected/test-api' import { Route as ProtectedSettingsRouteImport } from './routes/_protected/settings' import { Route as ProtectedRoomsRouteImport } from './routes/_protected/rooms' @@ -32,6 +34,11 @@ const SignInRoute = SignInRouteImport.update({ path: '/sign-in', getParentRoute: () => rootRouteImport, } as any) +const OnboardingRoute = OnboardingRouteImport.update({ + id: '/onboarding', + path: '/onboarding', + getParentRoute: () => rootRouteImport, +} as any) const ProtectedRoute = ProtectedRouteImport.update({ id: '/_protected', getParentRoute: () => rootRouteImport, @@ -41,6 +48,11 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) +const RequestsListRoute = RequestsListRouteImport.update({ + id: '/requests/list', + path: '/requests/list', + getParentRoute: () => rootRouteImport, +} as any) const ProtectedTestApiRoute = ProtectedTestApiRouteImport.update({ id: '/test-api', path: '/test-api', @@ -84,6 +96,7 @@ const ProtectedGuestsGuestIdRoute = ProtectedGuestsGuestIdRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/onboarding': typeof OnboardingRoute '/sign-in': typeof SignInRoute '/sign-up': typeof SignUpRoute '/home': typeof ProtectedHomeRoute @@ -91,18 +104,21 @@ export interface FileRoutesByFullPath { '/rooms': typeof ProtectedRoomsRouteWithChildren '/settings': typeof ProtectedSettingsRoute '/test-api': typeof ProtectedTestApiRoute + '/requests/list': typeof RequestsListRoute '/guests/$guestId': typeof ProtectedGuestsGuestIdRoute '/guests/': typeof ProtectedGuestsIndexRoute '/rooms/': typeof ProtectedRoomsIndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/onboarding': typeof OnboardingRoute '/sign-in': typeof SignInRoute '/sign-up': typeof SignUpRoute '/home': typeof ProtectedHomeRoute '/profile': typeof ProtectedProfileRoute '/settings': typeof ProtectedSettingsRoute '/test-api': typeof ProtectedTestApiRoute + '/requests/list': typeof RequestsListRoute '/guests/$guestId': typeof ProtectedGuestsGuestIdRoute '/guests': typeof ProtectedGuestsIndexRoute '/rooms': typeof ProtectedRoomsIndexRoute @@ -111,6 +127,7 @@ export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/_protected': typeof ProtectedRouteWithChildren + '/onboarding': typeof OnboardingRoute '/sign-in': typeof SignInRoute '/sign-up': typeof SignUpRoute '/_protected/home': typeof ProtectedHomeRoute @@ -118,6 +135,7 @@ export interface FileRoutesById { '/_protected/rooms': typeof ProtectedRoomsRouteWithChildren '/_protected/settings': typeof ProtectedSettingsRoute '/_protected/test-api': typeof ProtectedTestApiRoute + '/requests/list': typeof RequestsListRoute '/_protected/guests/$guestId': typeof ProtectedGuestsGuestIdRoute '/_protected/guests/': typeof ProtectedGuestsIndexRoute '/_protected/rooms/': typeof ProtectedRoomsIndexRoute @@ -126,6 +144,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/onboarding' | '/sign-in' | '/sign-up' | '/home' @@ -133,18 +152,21 @@ export interface FileRouteTypes { | '/rooms' | '/settings' | '/test-api' + | '/requests/list' | '/guests/$guestId' | '/guests/' | '/rooms/' fileRoutesByTo: FileRoutesByTo to: | '/' + | '/onboarding' | '/sign-in' | '/sign-up' | '/home' | '/profile' | '/settings' | '/test-api' + | '/requests/list' | '/guests/$guestId' | '/guests' | '/rooms' @@ -152,6 +174,7 @@ export interface FileRouteTypes { | '__root__' | '/' | '/_protected' + | '/onboarding' | '/sign-in' | '/sign-up' | '/_protected/home' @@ -159,6 +182,7 @@ export interface FileRouteTypes { | '/_protected/rooms' | '/_protected/settings' | '/_protected/test-api' + | '/requests/list' | '/_protected/guests/$guestId' | '/_protected/guests/' | '/_protected/rooms/' @@ -167,8 +191,10 @@ export interface FileRouteTypes { export interface RootRouteChildren { IndexRoute: typeof IndexRoute ProtectedRoute: typeof ProtectedRouteWithChildren + OnboardingRoute: typeof OnboardingRoute SignInRoute: typeof SignInRoute SignUpRoute: typeof SignUpRoute + RequestsListRoute: typeof RequestsListRoute } declare module '@tanstack/react-router' { @@ -187,6 +213,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SignInRouteImport parentRoute: typeof rootRouteImport } + '/onboarding': { + id: '/onboarding' + path: '/onboarding' + fullPath: '/onboarding' + preLoaderRoute: typeof OnboardingRouteImport + parentRoute: typeof rootRouteImport + } '/_protected': { id: '/_protected' path: '' @@ -201,6 +234,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/requests/list': { + id: '/requests/list' + path: '/requests/list' + fullPath: '/requests/list' + preLoaderRoute: typeof RequestsListRouteImport + parentRoute: typeof rootRouteImport + } '/_protected/test-api': { id: '/_protected/test-api' path: '/test-api' @@ -299,8 +339,10 @@ const ProtectedRouteWithChildren = ProtectedRoute._addFileChildren( const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, ProtectedRoute: ProtectedRouteWithChildren, + OnboardingRoute: OnboardingRoute, SignInRoute: SignInRoute, SignUpRoute: SignUpRoute, + RequestsListRoute: RequestsListRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/clients/web/src/routes/onboarding.tsx b/clients/web/src/routes/onboarding.tsx new file mode 100644 index 000000000..6f9f9b474 --- /dev/null +++ b/clients/web/src/routes/onboarding.tsx @@ -0,0 +1,7 @@ +import { createFileRoute } from '@tanstack/react-router' +import OnboardingPage from '../components/onboarding/OnboardingPage' + +export const Route = createFileRoute('/onboarding')({ + component: OnboardingPage, +}) + diff --git a/clients/web/src/routes/requests.list.tsx b/clients/web/src/routes/requests.list.tsx new file mode 100644 index 000000000..01593b48e --- /dev/null +++ b/clients/web/src/routes/requests.list.tsx @@ -0,0 +1,192 @@ +import { createFileRoute } from '@tanstack/react-router' +import { useState } from 'react' +import { useCreateRequest, useGetAllRequests } from '@shared/hooks/use-requests' +import type { MakeRequest, Request } from '@shared/types/request.types' + +export const Route = createFileRoute('/requests/list')({ + component: RequestsListPage, +}) + +function RequestsListPage() { + const [isModalOpen, setIsModalOpen] = useState(false) + const { data: requests, isLoading, error, refetch } = useGetAllRequests() + const createRequestMutation = useCreateRequest() + + const handleCreateRequest = async () => { + // TODO: Replace hardcoded values with form inputs + const newRequest: MakeRequest = { + hotel_id: '521e8400-e458-41d4-a716-446655440000', + name: 'Room Cleaning', + description: 'Please clean room 504', + request_type: 'one-time', + status: 'pending', + priority: 'medium', + request_category: 'Housekeeping', + department: 'Housekeeping', + } + + try { + await createRequestMutation.mutateAsync(newRequest) + setIsModalOpen(false) + } catch (err) { + console.error('Failed to create request:', err) + } + } + + return ( +
+
+ {/* Create Button */} + + + {/* Request List */} +
+ {isLoading && ( +
+

Loading requests...

+
+ )} + + {error && ( +
+

+ Failed to load requests: {error.message} +

+ +
+ )} + + {!isLoading && !error && requests && requests.length === 0 && ( +
+

+ No requests yet. Create your first request! +

+
+ )} + + {requests && requests.length > 0 && ( + <> + {requests.map((request: Request) => ( +
+
+

+ {request.name} +

+ + + {new Date(request.created_at).toLocaleDateString()} + +
+ + {request.description && ( +

{request.description}

+ )} + +
+ + Type:{' '} + + {request.request_type} + + + + Status:{' '} + + {request.status} + + + + Priority:{' '} + + {request.priority} + + +
+
+ ))} + + )} +
+ + {/* Modal */} + {isModalOpen && ( +
+
+

+ Create Request +

+

+ Creating a new request with default values +

+ +
+
+ Name: + + Room Cleaning + +
+
+ Description: + + Clean room 504 + +
+
+ Type: + One-time +
+
+ Status: + Pending +
+
+ Priority: + Medium +
+
+ +
+ + +
+ + {createRequestMutation.isError && ( +
+

+ {createRequestMutation.error.message} +

+
+ )} +
+
+ )} +
+
+ ) +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..9af034f7a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "selfserve", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 6b7c90cd6fcf850944bb2e7d615c782c3e62e2ec Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 15:01:21 -0400 Subject: [PATCH 02/23] fix: use import type for OnboardingFormData --- clients/web/src/components/onboarding/EmployeeRoleStep.tsx | 2 +- clients/web/src/components/onboarding/InviteTeamStep.tsx | 2 +- clients/web/src/components/onboarding/OnboardingPage.tsx | 2 +- clients/web/src/components/onboarding/PropertyDetailsStep.tsx | 2 +- clients/web/src/components/onboarding/RoleSelectionStep.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx index 12baf7889..4360fc2dd 100644 --- a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx +++ b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx @@ -1,5 +1,5 @@ import LeftPanel from './LeftPanel' -import { OnboardingFormData } from './types' +import type { OnboardingFormData } from './types' interface EmployeeRoleStepProps { formData: OnboardingFormData diff --git a/clients/web/src/components/onboarding/InviteTeamStep.tsx b/clients/web/src/components/onboarding/InviteTeamStep.tsx index 906cf403e..1f5274893 100644 --- a/clients/web/src/components/onboarding/InviteTeamStep.tsx +++ b/clients/web/src/components/onboarding/InviteTeamStep.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import LeftPanel from './LeftPanel' -import { OnboardingFormData } from './types' +import type { OnboardingFormData } from './types' interface InviteTeamStepProps { formData: OnboardingFormData diff --git a/clients/web/src/components/onboarding/OnboardingPage.tsx b/clients/web/src/components/onboarding/OnboardingPage.tsx index a9bd461d7..8d36c1112 100644 --- a/clients/web/src/components/onboarding/OnboardingPage.tsx +++ b/clients/web/src/components/onboarding/OnboardingPage.tsx @@ -4,7 +4,7 @@ import RoleSelectionStep from './RoleSelectionStep' import EmployeeRoleStep from './EmployeeRoleStep' import PropertyDetailsStep from './PropertyDetailsStep' import InviteTeamStep from './InviteTeamStep' -import { OnboardingFormData } from './types' +import type { OnboardingFormData } from './types' const INITIAL_FORM_DATA: OnboardingFormData = { role: null, diff --git a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx index 703a18268..28aad1458 100644 --- a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx +++ b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx @@ -1,5 +1,5 @@ import LeftPanel from './LeftPanel' -import { OnboardingFormData } from './types' +import type { OnboardingFormData } from './types' interface PropertyDetailsStepProps { formData: OnboardingFormData diff --git a/clients/web/src/components/onboarding/RoleSelectionStep.tsx b/clients/web/src/components/onboarding/RoleSelectionStep.tsx index 70f780727..8bd38b1f5 100644 --- a/clients/web/src/components/onboarding/RoleSelectionStep.tsx +++ b/clients/web/src/components/onboarding/RoleSelectionStep.tsx @@ -1,5 +1,5 @@ import LeftPanel from './LeftPanel' -import { OnboardingFormData } from './types' +import type { OnboardingFormData } from './types' interface RoleSelectionStepProps { formData: OnboardingFormData From 2615a56e691242c84285349fea43b869b94fbc9e Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 15:07:03 -0400 Subject: [PATCH 03/23] fix: run prettier on onboarding components --- .../onboarding/EmployeeRoleStep.tsx | 129 +++++++++++------- .../components/onboarding/InviteTeamStep.tsx | 76 +++++++---- .../src/components/onboarding/LeftPanel.tsx | 49 ++++--- .../components/onboarding/OnboardingPage.tsx | 27 ++-- .../onboarding/PropertyDetailsStep.tsx | 113 ++++++++++----- .../src/components/onboarding/RoleCard.tsx | 16 ++- .../onboarding/RoleSelectionStep.tsx | 111 +++++++++------ .../src/components/onboarding/WelcomeStep.tsx | 25 +++- .../components/onboarding/onboardingMocks.ts | 32 ++++- .../web/src/components/onboarding/types.ts | 2 +- clients/web/src/routes/onboarding.tsx | 1 - 11 files changed, 387 insertions(+), 194 deletions(-) diff --git a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx index 4360fc2dd..d0c12b2d3 100644 --- a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx +++ b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx @@ -9,56 +9,87 @@ interface EmployeeRoleStepProps { } const EMPLOYEE_ROLES = [ - { id: 'front_desk', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, - { id: 'housekeeping', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, - { id: 'maintenance', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, - { id: 'concierge', label: 'Role', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, + { + id: 'front_desk', + label: 'Role', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + id: 'housekeeping', + label: 'Role', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + id: 'maintenance', + label: 'Role', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + id: 'concierge', + label: 'Role', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, ] -export default function EmployeeRoleStep({ formData, updateForm, onNext }: EmployeeRoleStepProps) { +export default function EmployeeRoleStep({ + formData, + updateForm, + onNext, +}: EmployeeRoleStepProps) { return (
{/* Right panel */} -
+
{/* Card */}
{/* Header */} -
-

+

Employee Role

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

{/* 2x2 Role cards grid */} -
- {EMPLOYEE_ROLES.map(role => ( +
+ {EMPLOYEE_ROLES.map((role) => ( ))}
- -
) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/InviteTeamStep.tsx b/clients/web/src/components/onboarding/InviteTeamStep.tsx index 1f5274893..397e00171 100644 --- a/clients/web/src/components/onboarding/InviteTeamStep.tsx +++ b/clients/web/src/components/onboarding/InviteTeamStep.tsx @@ -7,7 +7,10 @@ interface InviteTeamStepProps { updateForm: (updates: Partial) => void } -export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepProps) { +export default function InviteTeamStep({ + formData, + updateForm, +}: InviteTeamStepProps) { const [invited, setInvited] = useState(false) return ( @@ -22,7 +25,13 @@ export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepP {/* Card */}
{/* Header */}
@@ -32,37 +41,44 @@ export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepP style={{ width: '80px', height: '80px', borderRadius: '8px' }} />
-

+

Invite your team

-

+

SelfServe is better when the whole staff is connected.

{/* Email invite */} -
+
updateForm({ inviteEmail: e.target.value })} + onChange={(e) => updateForm({ inviteEmail: e.target.value })} placeholder="Enter email address..." className="flex-1 bg-transparent text-sm outline-none" /> @@ -73,14 +89,26 @@ export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepP Invite
- {invited &&

Invite sent!

} -

+ {invited && ( +

Invite sent!

+ )} +

You can also do this later from your settings.

{/* Actions */} -
+
) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/LeftPanel.tsx b/clients/web/src/components/onboarding/LeftPanel.tsx index 570504cfe..cee2c5467 100644 --- a/clients/web/src/components/onboarding/LeftPanel.tsx +++ b/clients/web/src/components/onboarding/LeftPanel.tsx @@ -2,30 +2,41 @@ export default function LeftPanel() { return (
{/* SelfServe wordmark */} -
- +
+ SelfServe
{/* Lorem ipsum text */} -
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec risus nunc, ullamcorper vitae risus vel, tristique vehicula lectus. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec risus + nunc, ullamcorper vitae risus vel, tristique vehicula lectus.

) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/OnboardingPage.tsx b/clients/web/src/components/onboarding/OnboardingPage.tsx index 8d36c1112..b800066b8 100644 --- a/clients/web/src/components/onboarding/OnboardingPage.tsx +++ b/clients/web/src/components/onboarding/OnboardingPage.tsx @@ -15,14 +15,20 @@ const INITIAL_FORM_DATA: OnboardingFormData = { inviteEmail: '', } -type Step = 'welcome' | 'role' | 'employeeRole' | 'propertyDetails' | 'inviteTeam' +type Step = + | 'welcome' + | 'role' + | 'employeeRole' + | 'propertyDetails' + | 'inviteTeam' export default function OnboardingPage() { const [currentStep, setCurrentStep] = useState('welcome') - const [formData, setFormData] = useState(INITIAL_FORM_DATA) + const [formData, setFormData] = + useState(INITIAL_FORM_DATA) const updateForm = (updates: Partial) => { - setFormData(prev => ({ ...prev, ...updates })) + setFormData((prev) => ({ ...prev, ...updates })) } const handleRoleSelected = (role: string) => { @@ -61,16 +67,15 @@ export default function OnboardingPage() { formData={formData} updateForm={updateForm} onNext={() => setCurrentStep('inviteTeam')} - onBack={() => setCurrentStep(formData.role === 'employee' ? 'employeeRole' : 'role')} + onBack={() => + setCurrentStep( + formData.role === 'employee' ? 'employeeRole' : 'role', + ) + } /> ) case 'inviteTeam': - return ( - - ) + return } } @@ -79,4 +84,4 @@ export default function OnboardingPage() { {renderStep()}
) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx index 28aad1458..7f3581fc8 100644 --- a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx +++ b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx @@ -10,8 +10,14 @@ interface PropertyDetailsStepProps { const PROPERTY_TYPES = ['Hotel', 'Motel', 'Resort', 'Bed & Breakfast', 'Hostel'] -export default function PropertyDetailsStep({ formData, updateForm, onNext, onBack }: PropertyDetailsStepProps) { - const isValid = formData.hotelName && formData.numberOfRooms && formData.propertyType +export default function PropertyDetailsStep({ + formData, + updateForm, + onNext, + onBack, +}: PropertyDetailsStepProps) { + const isValid = + formData.hotelName && formData.numberOfRooms && formData.propertyType return (
@@ -22,72 +28,110 @@ export default function PropertyDetailsStep({ formData, updateForm, onNext, onBa {/* Card */}
{/* Header */}
-

+

Property Details

-

+

Lorem ipsum dolor sit amet.

{/* Form fields */} -
+
-
-
-
@@ -99,7 +143,12 @@ export default function PropertyDetailsStep({ formData, updateForm, onNext, onBa onClick={onNext} disabled={!isValid} className="w-full bg-green-900 hover:bg-green-800 disabled:opacity-50 text-white flex items-center justify-center transition-colors" - style={{ height: '56px', width: '256.6796875px', borderRadius: '14px', alignSelf: 'center' }} + style={{ + height: '56px', + width: '256.6796875px', + borderRadius: '14px', + alignSelf: 'center', + }} > Continue @@ -115,4 +164,4 @@ export default function PropertyDetailsStep({ formData, updateForm, onNext, onBa
) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/RoleCard.tsx b/clients/web/src/components/onboarding/RoleCard.tsx index 7b656cdcb..a43ea1d73 100644 --- a/clients/web/src/components/onboarding/RoleCard.tsx +++ b/clients/web/src/components/onboarding/RoleCard.tsx @@ -5,18 +5,24 @@ interface RoleCardProps { onSelect: () => void } -export default function RoleCard({ label, description, selected, onSelect }: RoleCardProps) { +export default function RoleCard({ + label, + description, + selected, + onSelect, +}: RoleCardProps) { return ( ) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/RoleSelectionStep.tsx b/clients/web/src/components/onboarding/RoleSelectionStep.tsx index 8bd38b1f5..b34c041f0 100644 --- a/clients/web/src/components/onboarding/RoleSelectionStep.tsx +++ b/clients/web/src/components/onboarding/RoleSelectionStep.tsx @@ -8,23 +8,38 @@ interface RoleSelectionStepProps { } const ROLES = [ - { id: 'manager', label: 'Manager', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, - { id: 'employee', label: 'Employee', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }, + { + id: 'manager', + label: 'Manager', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + { + id: 'employee', + label: 'Employee', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, ] -export default function RoleSelectionStep({ formData, onNext }: RoleSelectionStepProps) { +export default function RoleSelectionStep({ + formData, + onNext, +}: RoleSelectionStepProps) { return (
{/* Right panel */} -
+
{/* Outer card */}
{/* Logo placeholder */}
{/* Role title */} - + Role {/* Subtitle */} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -66,7 +85,7 @@ export default function RoleSelectionStep({ formData, onNext }: RoleSelectionSte gap: '16px', }} > - {ROLES.map(role => ( + {ROLES.map((role) => ( @@ -106,4 +129,4 @@ export default function RoleSelectionStep({ formData, onNext }: RoleSelectionSte
) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/WelcomeStep.tsx b/clients/web/src/components/onboarding/WelcomeStep.tsx index 0c1b5db58..22549bb12 100644 --- a/clients/web/src/components/onboarding/WelcomeStep.tsx +++ b/clients/web/src/components/onboarding/WelcomeStep.tsx @@ -22,13 +22,24 @@ export default function WelcomeStep({ onNext }: WelcomeStepProps) { {/* Logo placeholder */}
{/* Inner container: Welcome + Start */}
{/* Welcome text */}

Start › @@ -61,4 +78,4 @@ export default function WelcomeStep({ onNext }: WelcomeStepProps) {

) -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/onboardingMocks.ts b/clients/web/src/components/onboarding/onboardingMocks.ts index 2bd61742a..d4e5317d9 100644 --- a/clients/web/src/components/onboarding/onboardingMocks.ts +++ b/clients/web/src/components/onboarding/onboardingMocks.ts @@ -1,8 +1,30 @@ export const ROLES = [ - { id: 'manager', label: 'Manager', description: 'Oversees operations and staff.' }, - { id: 'front_desk', label: 'Front Desk', description: 'Handles guest check-ins and requests.' }, - { id: 'housekeeping', label: 'Housekeeping', description: 'Manages room cleaning and maintenance.' }, - { id: 'maintenance', label: 'Maintenance', description: 'Handles repairs and facilities.' }, + { + id: 'manager', + label: 'Manager', + description: 'Oversees operations and staff.', + }, + { + id: 'front_desk', + label: 'Front Desk', + description: 'Handles guest check-ins and requests.', + }, + { + id: 'housekeeping', + label: 'Housekeeping', + description: 'Manages room cleaning and maintenance.', + }, + { + id: 'maintenance', + label: 'Maintenance', + description: 'Handles repairs and facilities.', + }, ] -export const PROPERTY_TYPES = ['Hotel', 'Motel', 'Resort', 'Bed & Breakfast', 'Hostel'] \ No newline at end of file +export const PROPERTY_TYPES = [ + 'Hotel', + 'Motel', + 'Resort', + 'Bed & Breakfast', + 'Hostel', +] diff --git a/clients/web/src/components/onboarding/types.ts b/clients/web/src/components/onboarding/types.ts index f0b22fe8f..310904109 100644 --- a/clients/web/src/components/onboarding/types.ts +++ b/clients/web/src/components/onboarding/types.ts @@ -5,4 +5,4 @@ export interface OnboardingFormData { numberOfRooms: string propertyType: string inviteEmail: string -} \ No newline at end of file +} diff --git a/clients/web/src/routes/onboarding.tsx b/clients/web/src/routes/onboarding.tsx index 6f9f9b474..d314025cc 100644 --- a/clients/web/src/routes/onboarding.tsx +++ b/clients/web/src/routes/onboarding.tsx @@ -4,4 +4,3 @@ import OnboardingPage from '../components/onboarding/OnboardingPage' export const Route = createFileRoute('/onboarding')({ component: OnboardingPage, }) - From afeb94c89eb84a8ad4224c5214680cd6ee9400d6 Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 15:11:13 -0400 Subject: [PATCH 04/23] fix: remove broken requests.list route blocking CI --- clients/web/src/routes/requests.list.tsx | 192 ----------------------- 1 file changed, 192 deletions(-) delete mode 100644 clients/web/src/routes/requests.list.tsx diff --git a/clients/web/src/routes/requests.list.tsx b/clients/web/src/routes/requests.list.tsx deleted file mode 100644 index 01593b48e..000000000 --- a/clients/web/src/routes/requests.list.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' -import { useState } from 'react' -import { useCreateRequest, useGetAllRequests } from '@shared/hooks/use-requests' -import type { MakeRequest, Request } from '@shared/types/request.types' - -export const Route = createFileRoute('/requests/list')({ - component: RequestsListPage, -}) - -function RequestsListPage() { - const [isModalOpen, setIsModalOpen] = useState(false) - const { data: requests, isLoading, error, refetch } = useGetAllRequests() - const createRequestMutation = useCreateRequest() - - const handleCreateRequest = async () => { - // TODO: Replace hardcoded values with form inputs - const newRequest: MakeRequest = { - hotel_id: '521e8400-e458-41d4-a716-446655440000', - name: 'Room Cleaning', - description: 'Please clean room 504', - request_type: 'one-time', - status: 'pending', - priority: 'medium', - request_category: 'Housekeeping', - department: 'Housekeeping', - } - - try { - await createRequestMutation.mutateAsync(newRequest) - setIsModalOpen(false) - } catch (err) { - console.error('Failed to create request:', err) - } - } - - return ( -
-
- {/* Create Button */} - - - {/* Request List */} -
- {isLoading && ( -
-

Loading requests...

-
- )} - - {error && ( -
-

- Failed to load requests: {error.message} -

- -
- )} - - {!isLoading && !error && requests && requests.length === 0 && ( -
-

- No requests yet. Create your first request! -

-
- )} - - {requests && requests.length > 0 && ( - <> - {requests.map((request: Request) => ( -
-
-

- {request.name} -

- - - {new Date(request.created_at).toLocaleDateString()} - -
- - {request.description && ( -

{request.description}

- )} - -
- - Type:{' '} - - {request.request_type} - - - - Status:{' '} - - {request.status} - - - - Priority:{' '} - - {request.priority} - - -
-
- ))} - - )} -
- - {/* Modal */} - {isModalOpen && ( -
-
-

- Create Request -

-

- Creating a new request with default values -

- -
-
- Name: - - Room Cleaning - -
-
- Description: - - Clean room 504 - -
-
- Type: - One-time -
-
- Status: - Pending -
-
- Priority: - Medium -
-
- -
- - -
- - {createRequestMutation.isError && ( -
-

- {createRequestMutation.error.message} -

-
- )} -
-
- )} -
-
- ) -} From 8fe64cd28c920ab0c43fb7261ef49f943c4aab14 Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 15:18:35 -0400 Subject: [PATCH 05/23] fix: prettier formatting --- FETCH_HEAD | 0 clients/mobile/app/(tabs)/_layout.tsx | 6 + clients/mobile/app/(tabs)/guests/[id].tsx | 18 + clients/mobile/app/(tabs)/guests/index.tsx | 77 ++++ clients/mobile/app/_layout.tsx | 83 ++-- clients/mobile/app/index.tsx | 30 +- clients/mobile/components/ui/filters.tsx | 28 +- clients/mobile/components/ui/guest-card.tsx | 107 ++--- .../mobile/components/ui/guest-profile.tsx | 376 +++++++----------- clients/mobile/components/ui/search-bar.tsx | 7 +- clients/shared/src/api/config.ts | 19 +- clients/shared/src/api/orval-mutator.ts | 2 +- clients/shared/src/utils/index.ts | 3 - clients/shared/tsconfig.json | 2 +- clients/web/src/routeTree.gen.ts | 21 - 15 files changed, 350 insertions(+), 429 deletions(-) create mode 100644 FETCH_HEAD diff --git a/FETCH_HEAD b/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/clients/mobile/app/(tabs)/_layout.tsx b/clients/mobile/app/(tabs)/_layout.tsx index ce6a6287d..764e8d9b0 100644 --- a/clients/mobile/app/(tabs)/_layout.tsx +++ b/clients/mobile/app/(tabs)/_layout.tsx @@ -98,6 +98,7 @@ export default function TabLayout() { ( +======= + title: "Guests", + tabBarIcon: ({ color }) => ( + +>>>>>>> 9282a6e (fix: prettier formatting) ), }} /> diff --git a/clients/mobile/app/(tabs)/guests/[id].tsx b/clients/mobile/app/(tabs)/guests/[id].tsx index f2857c5f1..3ed478d22 100644 --- a/clients/mobile/app/(tabs)/guests/[id].tsx +++ b/clients/mobile/app/(tabs)/guests/[id].tsx @@ -1,3 +1,4 @@ +<<<<<<< HEAD import { useState, useMemo } from "react"; import { View, @@ -14,9 +15,16 @@ import { GuestHeader, Tab } from "@/components/ui/guest-header"; import { GuestProfileTab } from "@/components/ui/guest-profile"; import { GuestRequestsTab } from "@/components/ui/guest-activity"; import { Colors } from "@/constants/theme"; +======= +import { useLocalSearchParams } from "expo-router"; +import { Text } from "react-native"; +import GuestProfile from "@/components/ui/guest-profile"; +import { guestData } from "@/test-data/guests"; +>>>>>>> 9282a6e (fix: prettier formatting) export default function GuestProfileScreen() { const { id } = useLocalSearchParams(); +<<<<<<< HEAD const guestId = id as string; const { data, isLoading } = useGetGuestsStaysId(guestId); @@ -102,4 +110,14 @@ export default function GuestProfileScreen() { )} ); +======= + + const guest = guestData.find((g) => g.id === Number(id)); + + if (!guest) { + return Guest not found; + } + + return ; +>>>>>>> 9282a6e (fix: prettier formatting) } diff --git a/clients/mobile/app/(tabs)/guests/index.tsx b/clients/mobile/app/(tabs)/guests/index.tsx index a51fd62a9..ef1771e70 100644 --- a/clients/mobile/app/(tabs)/guests/index.tsx +++ b/clients/mobile/app/(tabs)/guests/index.tsx @@ -1,3 +1,4 @@ +<<<<<<< HEAD import { useState } from "react"; import { View, FlatList, ActivityIndicator, Text } from "react-native"; import { GuestCard } from "@/components/ui/guest-card"; @@ -21,6 +22,59 @@ export default function GuestsList() { floorSort: null, assistance: [], floors: [], +======= +import React, { useState } from "react"; +import { View, ScrollView, Alert } from "react-native"; +import { Header } from "@/components/ui/header"; +import { SearchBar } from "@/components/ui/search-bar"; +import { Filters } from "@/components/ui/filters"; +import { GuestCard } from "@/components/ui/guest-card"; +import { guestData } from "@/test-data/guests"; +import { router } from "expo-router"; + +export default function GuestsList() { + const [search, setSearch] = useState(""); + const [group, setGroup] = useState(null); + const [floor, setFloor] = useState(null); + + const handleGuestPress = (guestId: number) => { + router.push(`/guests/${guestId}`); + }; + + const filterConfig = [ + { + value: group, + onChange: setGroup, + placeholder: "Group", + emptyValue: null, + options: [ + { label: "Group 1", value: 1 }, + { label: "Group 2", value: 2 }, + { label: "Group 3", value: 3 }, + ], + }, + { + value: floor, + onChange: setFloor, + placeholder: "Floor", + emptyValue: null, + options: [ + { label: "Floor 1", value: 1 }, + { label: "Floor 2", value: 2 }, + { label: "Floor 3", value: 3 }, + ], + }, + ]; + + const filteredGuests = guestData.filter((guest) => { + const matchesSearch = guest.name + .toLowerCase() + .includes(search.toLowerCase()); + const matchesGroup = group === null || guest.group === group; + const matchesFloor = floor === null || guest.floor === floor; + + return matchesSearch && matchesGroup && matchesFloor; +>>>>>>> 9282a6e (fix: prettier formatting) }); const { data: floorOptions } = useGetRoomsFloors({ @@ -69,6 +123,7 @@ export default function GuestsList() { return ( +<<<<<<< HEAD g.id ?? `guest-${index}`} @@ -120,6 +175,28 @@ export default function GuestsList() { onShowResults={() => setFilterSheetOpen(false)} onClearAll={clearAll} /> +======= +
+ + + + + + + + {filteredGuests.map((guest) => ( + handleGuestPress(guest.id)} + /> + ))} + + +>>>>>>> 9282a6e (fix: prettier formatting) ); } diff --git a/clients/mobile/app/_layout.tsx b/clients/mobile/app/_layout.tsx index d45635bf4..4c63df2b7 100644 --- a/clients/mobile/app/_layout.tsx +++ b/clients/mobile/app/_layout.tsx @@ -9,18 +9,18 @@ import { DefaultTheme, ThemeProvider, } from "@react-navigation/native"; + import { tokenCache } from "@clerk/clerk-expo/token-cache"; -import { ClerkProvider } from "@clerk/clerk-expo"; +import { ClerkProvider, ClerkLoaded, useAuth } from "@clerk/clerk-expo"; import { useColorScheme } from "@/hooks/use-color-scheme"; -import { StartupProvider, StartupStatus, useStartup } from "@/context/startup"; -import NoUserInfo from "@/components/ui/NoUserInfo"; -import { ActivityIndicator, StyleSheet, View } from "react-native"; -import { usePushNotifications } from "@/hooks/use-push-notifications"; +import { setConfig } from "@shared"; +import { useEffect } from "react"; +// Client explicity created outside component to avoid recreation const queryClient = new QueryClient({ defaultOptions: { queries: { - staleTime: 60 * 1000, + staleTime: 60 * 1000, // 1 min retry: 1, refetchOnWindowFocus: false, }, @@ -31,61 +31,46 @@ export const unstable_settings = { anchor: "(tabs)", }; -function AppLayout() { - const colorScheme = useColorScheme(); - const status = useStartup(); - - if (status === StartupStatus.NoUserInfo) return ; +// Component to configure auth provider and the api base url +function AppConfigurator() { + const { getToken } = useAuth(); + useEffect(() => { + setConfig({ + API_BASE_URL: process.env.EXPO_PUBLIC_API_BASE_URL ?? "", + getToken, + }); + }, [getToken]); - return ( - - - - - - - - - {status === StartupStatus.Loading && ( - - - - )} - - - ); -} - -// Registers the Expo push token with the backend and wires up tap-to-navigate. -// Must render inside QueryClientProvider so useMutation is available. -function PushNotificationRegistrar() { - usePushNotifications(); return null; } export default function RootLayout() { + const colorScheme = useColorScheme(); + return ( - - - + + + - + + + + + + + - - + + ); } diff --git a/clients/mobile/app/index.tsx b/clients/mobile/app/index.tsx index 2ac21ffd1..39a4f6989 100644 --- a/clients/mobile/app/index.tsx +++ b/clients/mobile/app/index.tsx @@ -1,20 +1,12 @@ -import { StartupStatus, useStartup } from "@/context/startup"; -import { useRouter } from "expo-router"; -import { useEffect } from "react"; - -export default function Index() { - const status = useStartup(); - const router = useRouter(); - - useEffect(() => { - if (status === StartupStatus.Loading) return; - if (status === StartupStatus.Unauthenticated) { - router.replace("/sign-in"); - return; - } - - router.replace("/(tabs)"); - }, [status, router]); - - return null; +import { StyleSheet, Text, View } from "react-native"; + +export default function Page() { + return ( + + + Hello + World + + + ); } diff --git a/clients/mobile/components/ui/filters.tsx b/clients/mobile/components/ui/filters.tsx index 61b44b383..4d3693001 100644 --- a/clients/mobile/components/ui/filters.tsx +++ b/clients/mobile/components/ui/filters.tsx @@ -1,34 +1,34 @@ +// components/ui/filters.tsx import React from "react"; import { View } from "react-native"; -import { MultiSelectFilter } from "./mutli-select-filter"; +import { Dropdown } from "./dropdown"; -export interface FilterOption { +interface FilterOption { label: string; value: T; } -export interface Filter { - value: T[]; +export interface Filter { + value: T; onChange: (value: T) => void; options: FilterOption[]; placeholder: string; + emptyValue: T; } -interface FiltersProps { - filters: Filter[]; +interface FiltersProps { + // must use type any here because each filter's values can have a different type + // however, any is not a problem here because the only logic that interacts with the data + // is performed inside the filter component which has a type T and is type safe + filters: Filter[]; className?: string; } -export function Filters({ - filters, - className, -}: FiltersProps) { +export function Filters({ filters, className }: FiltersProps) { return ( - + {filters.map((filter, index) => ( - - - + ))} ); diff --git a/clients/mobile/components/ui/guest-card.tsx b/clients/mobile/components/ui/guest-card.tsx index 5e4f70f90..0148acf15 100644 --- a/clients/mobile/components/ui/guest-card.tsx +++ b/clients/mobile/components/ui/guest-card.tsx @@ -1,104 +1,43 @@ +import React from "react"; import { Pressable, View, Text } from "react-native"; -import { - Accessibility, - BriefcaseMedical, - Utensils, - Flag, -} from "lucide-react-native"; -import type { ActiveBooking, Assistance } from "@shared"; -import { Colors } from "@/constants/theme"; - -const MAX_VISIBLE_BOOKINGS = 3; +import { User } from "lucide-react-native"; +import { cn } from "@shared/utils"; interface GuestCardProps { - firstName: string; - lastName: string; - activeBookings: ActiveBooking[]; - requestCount: number; - hasUrgent: boolean; - assistance?: Assistance; + name: string; + floor: number; + room: number; + group: number; onPress: () => void; + className?: string; } export function GuestCard({ - firstName, - lastName, - activeBookings, - requestCount, - hasUrgent, - assistance, + name, + floor, + room, + group, onPress, + className, }: GuestCardProps) { - const needsAccessibility = !!assistance?.accessibility?.length; - const needsMedical = !!assistance?.medical?.length; - const needsDietary = !!assistance?.dietary?.length; - - const visibleBookings = activeBookings.slice(0, MAX_VISIBLE_BOOKINGS); - const overflow = Math.max(0, activeBookings.length - MAX_VISIBLE_BOOKINGS); - return ( - {/* Row 1: name + assistance icons */} - - - {firstName} {lastName.charAt(0)}. - - - - - - + + - {/* Row 2: priority badge + request count */} - - {hasUrgent && ( - - - - High Priority - - - )} - - Requests: {requestCount} + + {name} + + Floor: {floor} Room: {room} Group: {group} - - {/* Row 3: active bookings */} - - Active Bookings: - {activeBookings.length === 0 ? ( - None - ) : ( - <> - {visibleBookings.map((b, i) => ( - - - Floor {b.floor}, Suite {b.room_number} - - - ))} - {overflow > 0 && ( - - + {overflow} more - - )} - - )} - ); } diff --git a/clients/mobile/components/ui/guest-profile.tsx b/clients/mobile/components/ui/guest-profile.tsx index e15333d0f..8c6538077 100755 --- a/clients/mobile/components/ui/guest-profile.tsx +++ b/clients/mobile/components/ui/guest-profile.tsx @@ -1,250 +1,180 @@ -import { useState, useEffect } from "react"; -import { View, Text, TextInput, Pressable } from "react-native"; -import { Calendar, Clock, Users } from "lucide-react-native"; -import { useQueryClient } from "@tanstack/react-query"; -import { Colors } from "@/constants/theme"; -import { usePutGuestsId, getGetGuestsStaysIdQueryKey } from "@shared"; -import type { Stay, Assistance } from "@shared"; -import { formatDate, formatTime } from "@/utils/time"; - -interface GuestProfileTabProps { - guestId: string; - firstName: string; - lastName: string; - pronouns?: string | null; - doNotDisturbStart?: string | null; - doNotDisturbEnd?: string | null; - housekeepingCadence?: string | null; - notes?: string | null; - assistance?: Assistance; - currentStays: Stay[]; - onViewAllBookings: () => void; +import { View, Text, Pressable } from "react-native"; +import { Box } from "./box"; +import { ChevronLeft, User } from "lucide-react-native"; +import { Collapsible } from "./collapsible"; +import { router } from "expo-router"; + +interface GuestProfileProps { + name: string; + pronouns: string; + dateOfBirth: Date; + room: number; + groupSize: number; + arrival: Date; + departure: Date; + notes: string; + preferences: string; + needs: string; + previousStays: Stay[]; } -export function GuestProfileTab(props: GuestProfileTabProps) { +type Stay = { + notes: string; + arrival: Date; + departure: Date; +}; + +export default function GuestProfile(props: GuestProfileProps) { return ( - - - - - - + + + + + + + ); } -function InfoSection({ - firstName, - lastName, - pronouns, - doNotDisturbStart, - doNotDisturbEnd, - housekeepingCadence, -}: GuestProfileTabProps) { - const dnd = - doNotDisturbStart && doNotDisturbEnd - ? `${formatTime(doNotDisturbStart)} - ${formatTime(doNotDisturbEnd)}` - : null; - - const capitalize = (s?: string | null) => - s ? s.charAt(0).toUpperCase() + s.slice(1) : null; - - const fields = [ - { label: "Government Name", value: `${firstName} ${lastName}` }, - { label: "Pronouns", value: pronouns }, - { label: "Do Not Disturb", value: dnd }, - { label: "Housekeeping", value: capitalize(housekeepingCadence) }, - ].filter((f) => f.value); - +function HeaderWithBackArrow() { return ( - - {fields.map((field) => ( - - - {field.label} - - - {String(field.value)} - - - ))} - + + router.back()}> + + + + Guest Profile + + + + ); } -function ActiveBookingsSection({ - currentStays, - onViewAll, -}: { - currentStays: Stay[]; - onViewAll: () => void; -}) { +function GuestDescription(props: GuestProfileProps) { return ( - - - Active Bookings ({currentStays.length}) - - - {currentStays.map((stay, i) => ( + + - - - Suite {stay.room_number} - - - - - {stay.group_size ?? 1} - - - - - - Arrival: - - - {formatDate(stay.arrival_date)} - - - - {formatTime(stay.arrival_date)} - - - - - - Departure: - - - - {formatDate(stay.departure_date)} - - - - {formatTime(stay.departure_date)} - - + - ))} + + + {props.name} + + {props.pronouns} + + - - - View All Bookings - - - + + {GUEST_PROFILE_CONFIG.infoFields.map((field, index) => ( + + ))} + + ); } -function SpecificAssistanceSection({ - assistance, -}: { - assistance?: Assistance; -}) { - const categories = [ - { label: "Accessibility", items: assistance?.accessibility ?? [] }, - { label: "Dietary Restrictions", items: assistance?.dietary ?? [] }, - { label: "Medical Needs", items: assistance?.medical ?? [] }, - ]; - +function InfoRow({ label, value }: { label: string; value: unknown }) { return ( - - - Specific Assistance - - - {categories.map((cat, i) => ( - - - {cat.label} - - {cat.items.length === 0 ? ( - None - ) : ( - - {cat.items.map((item, j) => ( - - {item} - - ))} - - )} - {i < categories.length - 1 && ( - - )} - - ))} - + + {label} + {String(value)} ); } -function NotesSection({ - guestId, - notes, -}: { - guestId: string; - notes?: string | null; -}) { - const [value, setValue] = useState(notes ?? ""); - const queryClient = useQueryClient(); - - useEffect(() => { - setValue(notes ?? ""); - }, [notes]); - - const isDirty = value !== (notes ?? ""); - const { mutate: updateGuest, isPending } = usePutGuestsId(); - - const save = () => { - updateGuest( - { id: guestId, data: { notes: value } }, - { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: getGetGuestsStaysIdQueryKey(guestId), - }); - }, - }, - ); - }; - +function GuestInfoCollapsibles(props: GuestProfileProps) { return ( - - - Notes - {isDirty && ( - - - {isPending ? "Saving..." : "Save"} - - + + {GUEST_PROFILE_CONFIG.collapsibles.map((item, index) => ( + + + {item.format(props)} + + + ))} + + + {props.previousStays.length === 0 ? ( + No previous stays + ) : ( + + {props.previousStays.map((stay, index) => ( + + {stay.notes} + + {stay.arrival.toLocaleDateString()}-{" "} + {stay.departure.toLocaleDateString()} + + + ))} + )} - - - + + ); } + +// to provide a label and formatting of the data for each piece of data concerning the guest +const GUEST_PROFILE_CONFIG = { + infoFields: [ + { + key: "governmentName", + label: "Government Name", + format: (props: GuestProfileProps) => props.name, + }, + { + key: "dateOfBirth", + label: "Date of Birth", + format: (props: GuestProfileProps) => + props.dateOfBirth.toLocaleDateString(), + }, + { + key: "room", + label: "Room", + format: (props: GuestProfileProps) => props.room, + }, + { + key: "groupSize", + label: "Group Size", + format: (props: GuestProfileProps) => props.groupSize.toString(), + }, + { + key: "arrival", + label: "Arrival", + format: (props: GuestProfileProps) => + `${props.arrival.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} ${props.arrival.toLocaleDateString()}`, + }, + { + key: "departure", + label: "Departure", + format: (props: GuestProfileProps) => + `${props.departure.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} ${props.departure.toLocaleDateString()}`, + }, + ], + collapsibles: [ + { + key: "notes", + title: "Notes", + format: (props: GuestProfileProps) => props.notes, + }, + { + key: "preferences", + title: "Housekeeping Preferences", + format: (props: GuestProfileProps) => props.preferences, + }, + { + key: "needs", + title: "Special Needs", + format: (props: GuestProfileProps) => props.needs, + }, + ], +}; diff --git a/clients/mobile/components/ui/search-bar.tsx b/clients/mobile/components/ui/search-bar.tsx index dd5521b95..bf3555a69 100644 --- a/clients/mobile/components/ui/search-bar.tsx +++ b/clients/mobile/components/ui/search-bar.tsx @@ -1,7 +1,6 @@ import React from "react"; import { View, TextInput } from "react-native"; import { Search } from "lucide-react-native"; -import { Colors } from "@/constants/theme"; interface SearchBarProps { value: string; @@ -20,11 +19,11 @@ export function SearchBar({ value={value} onChangeText={onChangeText} placeholder={placeholder} - placeholderTextColor={Colors.light.text} - className="w-full h-[8vh] px-[4vw] pr-[12vw] rounded-xl border border-stroke-subtle text-[4.5vw]" + placeholderTextColor="#9CA3AF" + className="w-full h-[6vh] px-[4vw] pr-[12vw] border border-gray-300 rounded-md" /> - + ); diff --git a/clients/shared/src/api/config.ts b/clients/shared/src/api/config.ts index 12cb60820..0c874238c 100644 --- a/clients/shared/src/api/config.ts +++ b/clients/shared/src/api/config.ts @@ -6,20 +6,19 @@ */ export type Config = { - API_BASE_URL: string - getToken: () => Promise - hotelId: string -} + API_BASE_URL: string; + getToken: () => Promise; +}; -let config: Config | null = null +let config: Config | null = null; export const setConfig = (c: Config) => { - config = c -} + config = c; +}; export const getConfig = (): Config => { if (!config) { - throw new Error('Config not initialized. Call setConfig() at app startup.') + throw new Error("Config not initialized. Call setConfig() at app startup."); } - return config -} \ No newline at end of file + return config; +}; diff --git a/clients/shared/src/api/orval-mutator.ts b/clients/shared/src/api/orval-mutator.ts index 11315c7ba..d843869eb 100755 --- a/clients/shared/src/api/orval-mutator.ts +++ b/clients/shared/src/api/orval-mutator.ts @@ -20,4 +20,4 @@ export const useCustomInstance = (): (( }; export default useCustomInstance; -export const customInstance = useCustomInstance \ No newline at end of file +export const customInstance = useCustomInstance; diff --git a/clients/shared/src/utils/index.ts b/clients/shared/src/utils/index.ts index 6ea1da47a..2c20000fd 100644 --- a/clients/shared/src/utils/index.ts +++ b/clients/shared/src/utils/index.ts @@ -5,7 +5,4 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); - } - -export { getExtFromMime } from "./mime"; \ No newline at end of file diff --git a/clients/shared/tsconfig.json b/clients/shared/tsconfig.json index f31e1f167..8d92de00b 100644 --- a/clients/shared/tsconfig.json +++ b/clients/shared/tsconfig.json @@ -17,7 +17,7 @@ "resolveJsonModule": true, "isolatedModules": true, "paths": { - "@/*": ["./src/*"], + "@/*": ["./src/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx"], diff --git a/clients/web/src/routeTree.gen.ts b/clients/web/src/routeTree.gen.ts index cea500927..63b8cd75b 100644 --- a/clients/web/src/routeTree.gen.ts +++ b/clients/web/src/routeTree.gen.ts @@ -14,7 +14,6 @@ import { Route as SignInRouteImport } from './routes/sign-in' import { Route as OnboardingRouteImport } from './routes/onboarding' import { Route as ProtectedRouteImport } from './routes/_protected' import { Route as IndexRouteImport } from './routes/index' -import { Route as RequestsListRouteImport } from './routes/requests.list' import { Route as ProtectedTestApiRouteImport } from './routes/_protected/test-api' import { Route as ProtectedSettingsRouteImport } from './routes/_protected/settings' import { Route as ProtectedRoomsRouteImport } from './routes/_protected/rooms' @@ -48,11 +47,6 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const RequestsListRoute = RequestsListRouteImport.update({ - id: '/requests/list', - path: '/requests/list', - getParentRoute: () => rootRouteImport, -} as any) const ProtectedTestApiRoute = ProtectedTestApiRouteImport.update({ id: '/test-api', path: '/test-api', @@ -104,7 +98,6 @@ export interface FileRoutesByFullPath { '/rooms': typeof ProtectedRoomsRouteWithChildren '/settings': typeof ProtectedSettingsRoute '/test-api': typeof ProtectedTestApiRoute - '/requests/list': typeof RequestsListRoute '/guests/$guestId': typeof ProtectedGuestsGuestIdRoute '/guests/': typeof ProtectedGuestsIndexRoute '/rooms/': typeof ProtectedRoomsIndexRoute @@ -118,7 +111,6 @@ export interface FileRoutesByTo { '/profile': typeof ProtectedProfileRoute '/settings': typeof ProtectedSettingsRoute '/test-api': typeof ProtectedTestApiRoute - '/requests/list': typeof RequestsListRoute '/guests/$guestId': typeof ProtectedGuestsGuestIdRoute '/guests': typeof ProtectedGuestsIndexRoute '/rooms': typeof ProtectedRoomsIndexRoute @@ -135,7 +127,6 @@ export interface FileRoutesById { '/_protected/rooms': typeof ProtectedRoomsRouteWithChildren '/_protected/settings': typeof ProtectedSettingsRoute '/_protected/test-api': typeof ProtectedTestApiRoute - '/requests/list': typeof RequestsListRoute '/_protected/guests/$guestId': typeof ProtectedGuestsGuestIdRoute '/_protected/guests/': typeof ProtectedGuestsIndexRoute '/_protected/rooms/': typeof ProtectedRoomsIndexRoute @@ -152,7 +143,6 @@ export interface FileRouteTypes { | '/rooms' | '/settings' | '/test-api' - | '/requests/list' | '/guests/$guestId' | '/guests/' | '/rooms/' @@ -166,7 +156,6 @@ export interface FileRouteTypes { | '/profile' | '/settings' | '/test-api' - | '/requests/list' | '/guests/$guestId' | '/guests' | '/rooms' @@ -182,7 +171,6 @@ export interface FileRouteTypes { | '/_protected/rooms' | '/_protected/settings' | '/_protected/test-api' - | '/requests/list' | '/_protected/guests/$guestId' | '/_protected/guests/' | '/_protected/rooms/' @@ -194,7 +182,6 @@ export interface RootRouteChildren { OnboardingRoute: typeof OnboardingRoute SignInRoute: typeof SignInRoute SignUpRoute: typeof SignUpRoute - RequestsListRoute: typeof RequestsListRoute } declare module '@tanstack/react-router' { @@ -234,13 +221,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/requests/list': { - id: '/requests/list' - path: '/requests/list' - fullPath: '/requests/list' - preLoaderRoute: typeof RequestsListRouteImport - parentRoute: typeof rootRouteImport - } '/_protected/test-api': { id: '/_protected/test-api' path: '/test-api' @@ -342,7 +322,6 @@ const rootRouteChildren: RootRouteChildren = { OnboardingRoute: OnboardingRoute, SignInRoute: SignInRoute, SignUpRoute: SignUpRoute, - RequestsListRoute: RequestsListRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) From 1fc2e93a174a9092b51bd879941e1674075f6f56 Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 15:21:14 -0400 Subject: [PATCH 06/23] fix: prettier formatting --- .github/workflows/BackendCI.yaml | 14 +- backend/README.md | 70 +- backend/docs/swagger.yaml | 1893 +++--------------------------- 3 files changed, 210 insertions(+), 1767 deletions(-) diff --git a/.github/workflows/BackendCI.yaml b/.github/workflows/BackendCI.yaml index 6b5dbd4a9..60d6f4bb2 100644 --- a/.github/workflows/BackendCI.yaml +++ b/.github/workflows/BackendCI.yaml @@ -38,9 +38,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version-file: './backend/go.mod' + go-version-file: "./backend/go.mod" cache: true - cache-dependency-path: './backend/go.sum' + cache-dependency-path: "./backend/go.sum" - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest - name: Generate swagger docs @@ -71,9 +71,9 @@ jobs: fetch-depth: 2 - uses: actions/setup-go@v5 with: - go-version-file: './backend/go.mod' + go-version-file: "./backend/go.mod" cache: true - cache-dependency-path: './backend/go.sum' + cache-dependency-path: "./backend/go.sum" - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest - name: Generate swagger docs @@ -96,9 +96,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version-file: './backend/go.mod' + go-version-file: "./backend/go.mod" cache: true - cache-dependency-path: './backend/go.sum' + cache-dependency-path: "./backend/go.sum" - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest - name: Generate swagger docs @@ -114,4 +114,4 @@ jobs: with: name: selfserve-binary path: backend/bin/selfserve - retention-days: 7 \ No newline at end of file + retention-days: 7 diff --git a/backend/README.md b/backend/README.md index 29d6315e1..95a862098 100644 --- a/backend/README.md +++ b/backend/README.md @@ -5,7 +5,6 @@ ### Prerequisites - **Go 1.24.0 or later** - [Install Go](https://go.dev/doc/install) -- **Doppler CLI** - [Install Doppler](https://docs.doppler.com/docs/install-cli) or `brew install dopplerhq/cli/doppler` ### Installation Steps @@ -25,45 +24,52 @@ make download ``` -4. **Set up Doppler secrets management**: +4. **Set up environment variables**: - a. Authenticate with Doppler (first time only): + (Slack us for these) + Create a `config/.env` file with the following variables: - ```bash - doppler login - ``` - - b. Set up the backend project: + ```env + # Application Configuration + APP_PORT=8080 + APP_LOG_LEVEL=info - ```bash - cd backend - doppler setup + # Database Configuration (required) + DB_HOST=your-database-host + DB_PORT=5432 + DB_USER=your-database-user + DB_PASSWORD=your-database-password + DB_NAME=your-database-name + DB_MAX_CONNS=8 + DB_MAX_CONN_LIFETIME=30s ``` - This will configure your local environment to use the `selfserve-backend` project with the `dev` config + > **Note**: All database variables (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME) are required. The application will fail to start if they are missing. -5. **Start local Supabase**: + **LLM Configuration** – used for parsing request text (e.g. `/request/parse`). Add this to your existing .env: - ```bash - make db-start + ```env + LLM_SERVER_ADDRESS=http://127.0.0.1:11434 + LLM_MODEL=qwen2.5:3b + LLM_TIMEOUT=60 ``` -6. **Run with hot reload** (development): +5. **Run with hot reload** (development): ```bash - make air + air ``` - Or run directly without hot reload: + Or run directly: ```bash - make run + make dev ``` - Or build and run: + Or run with GenKit UI: ```bash - make dev + make genkit-run ``` ## Directory Structure @@ -155,14 +161,14 @@ Logic traversal: make build # Run -make air # Hot reload - -# Or run directly make run -# Or build and run +# Build and run make dev +# OR air to build and run if you installed air above +air + # Run tests make test @@ -175,6 +181,16 @@ make clean ## Configuration -The application reads configuration from environment variables injected by Doppler: +The application reads configuration from environment variables (loaded from `config/.env`): + +- `APP_PORT`: Server port (default: 8080) +- `APP_LOG_LEVEL`: Log level (default: info) +- `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`: Database connection details +- `DB_MAX_CONNS`: Maximum database connections (default: 8) +- `DB_MAX_CONN_LIFETIME`: Connection lifetime (default: 30s) -See config/.env.sample +- `LLM_SERVER_ADDRESS`: LLM server URL (default: http://127.0.0.1:11434) +- `LLM_MODEL`: Model name, e.g. qwen2.5:7b-instruct, llama3.2, gemma2 +- `LLM_TIMEOUT`: Response timeout in seconds (default: 60) +- `LLM_MAX_OUTPUT_TOKENS`: Max tokens for generation; lower values reduce latency (default: 1024) +- `LLM_TEMPERATURE`: Sampling temperature 0–1; lower is more deterministic and often faster for extraction diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index b0f70916f..7cfb0c1b1 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,31 +1,9 @@ basePath: /api/v1 definitions: - ActiveBooking: - properties: - floor: - type: integer - room_number: - type: integer - type: object - Assistance: - properties: - accessibility: - items: - type: string - type: array - dietary: - items: - type: string - type: array - medical: - items: - type: string - type: array - type: object CreateGuest: properties: first_name: - example: Jane + example: John type: string last_name: example: Doe @@ -39,6 +17,9 @@ definitions: type: object CreateUser: properties: + clerk_id: + example: user_123 + type: string department: example: Housekeeping type: string @@ -48,21 +29,9 @@ definitions: first_name: example: John type: string - hotel_id: - example: org_550e8400-e29b-41d4-a716-446655440000 - type: string - id: - example: user_123 - type: string last_name: example: Doe type: string - phone_number: - example: "+11234567890" - type: string - primary_email: - example: john@example.com - type: string profile_picture: example: https://example.com/john.jpg type: string @@ -73,22 +42,6 @@ definitions: example: America/New_York type: string type: object - Department: - properties: - created_at: - type: string - hotel_id: - example: org_2abc123 - type: string - id: - example: 550e8400-e29b-41d4-a716-446655440000 - type: string - name: - example: Housekeeping - type: string - updated_at: - type: string - type: object Dev: properties: created_at: @@ -101,64 +54,22 @@ definitions: example: Dao Ho type: string type: object - FilterRoomsRequest: - properties: - cursor: - type: string - floors: - items: - type: integer - type: array - limit: - minimum: 0 - type: integer - type: object GenerateRequestInput: properties: hotel_id: - example: org_521e8400-e458-41d4-a716-446655440000 + example: 521e8400-e458-41d4-a716-446655440000 type: string raw_text: example: Guest in room 504 needs extra towels urgently type: string type: object - GenerateRequestResponse: - properties: - request: - $ref: '#/definitions/Request' - warning: - $ref: '#/definitions/GenerateRequestWarning' - type: object - GenerateRequestWarning: - properties: - code: - example: room_not_found - type: string - message: - example: Room 301 could not be resolved for this hotel. - type: string - type: object - GetRequestsByStatusInput: - properties: - cursor_id: - type: string - cursor_time: - type: integer - status: - enum: - - pending - - assigned - - in progress - - completed - type: string - type: object Guest: properties: created_at: example: "2024-01-02T00:00:00Z" type: string first_name: - example: Jane + example: John type: string id: example: 530e8400-e458-41d4-a716-446655440000 @@ -166,9 +77,6 @@ definitions: last_name: example: Doe type: string - notes: - example: VIP guest - type: string profile_picture: example: https://example.com/john.jpg type: string @@ -179,153 +87,6 @@ definitions: example: "2024-01-02T00:00:00Z" type: string type: object - GuestFilters: - properties: - assistance: - items: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.AssistanceFilter' - type: array - cursor: - type: string - floor_sort: - allOf: - - $ref: '#/definitions/github_com_generate_selfserve_internal_models.FloorSortOrder' - enum: - - ascending - - descending - floors: - items: - type: integer - type: array - group_size: - items: - type: integer - type: array - limit: - maximum: 100 - minimum: 1 - type: integer - request_sort: - allOf: - - $ref: '#/definitions/github_com_generate_selfserve_internal_models.RequestSortOrder' - enum: - - high_to_low - - low_to_high - - urgent - search: - type: string - status: - items: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.BookingStatus' - type: array - type: object - GuestPage: - properties: - data: - items: - $ref: '#/definitions/GuestWithBooking' - type: array - next_cursor: - type: string - type: object - GuestRequest: - properties: - created_at: - type: string - description: - type: string - id: - type: string - name: - type: string - notes: - type: string - priority: - type: string - request_category: - type: string - request_type: - type: string - request_version: - type: string - room_number: - type: integer - status: - type: string - type: object - GuestWithBooking: - properties: - active_bookings: - items: - $ref: '#/definitions/ActiveBooking' - type: array - assistance: - $ref: '#/definitions/Assistance' - first_name: - type: string - has_urgent: - type: boolean - id: - type: string - last_name: - type: string - preferred_name: - type: string - request_count: - type: integer - type: object - GuestWithStays: - properties: - assistance: - $ref: '#/definitions/Assistance' - current_stays: - items: - $ref: '#/definitions/Stay' - type: array - do_not_disturb_end: - example: "07:00:00" - type: string - do_not_disturb_start: - example: "17:00:00" - type: string - email: - example: jane.doe@example.com - type: string - first_name: - example: Jane - type: string - housekeeping_cadence: - example: daily - type: string - id: - example: 530e8400-e458-41d4-a716-446655440000 - type: string - last_name: - example: Doe - type: string - notes: - example: VIP - type: string - past_stays: - items: - $ref: '#/definitions/Stay' - type: array - phone: - example: +1 (617) 012-3456 - type: string - preferences: - example: extra pillows - type: string - pronouns: - example: she/her - type: string - required: - - current_stays - - first_name - - id - - last_name - - past_stays - type: object Hotel: properties: created_at: @@ -333,10 +94,9 @@ definitions: type: string floors: example: 10 - minimum: 1 type: integer id: - example: org_2abc123 + example: "550e8400-e29b-41d4-a\t716-446655440000" type: string name: example: Hotel California @@ -363,7 +123,7 @@ definitions: example: 521e8417-e458-41d4-a716-446655440990 type: string hotel_id: - example: org_521e8400-e458-41d4-a716-446655440000 + example: 521e8400-e458-41d4-a716-446655440000 type: string name: example: room cleaning @@ -372,11 +132,7 @@ definitions: example: No special requests type: string priority: - enum: - - low - - medium - - high - example: high + example: urgent type: string request_category: example: Cleaning @@ -394,60 +150,12 @@ definitions: example: "2024-01-01T00:00:00Z" type: string status: - enum: - - pending - - assigned - - in progress - - completed example: assigned type: string user_id: example: 521ee400-e458-41d4-a716-446655440000 type: string type: object - Notification: - properties: - body: - type: string - created_at: - type: string - data: - items: - type: integer - type: array - id: - type: string - read_at: - type: string - title: - type: string - type: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.NotificationType' - user_id: - type: string - type: object - OnboardUser: - properties: - department: - example: front_desk - type: string - hotel_name: - example: Hotel California - type: string - role: - example: manager - type: string - type: object - RegisterDeviceTokenInput: - properties: - platform: - enum: - - ios - - android - type: string - token: - type: string - type: object Request: properties: completed_at: @@ -469,7 +177,7 @@ definitions: example: 521e8417-e458-41d4-a716-446655440990 type: string hotel_id: - example: org_521e8400-e458-41d4-a716-446655440000 + example: 521e8400-e458-41d4-a716-446655440000 type: string id: example: 530e8400-e458-41d4-a716-446655440000 @@ -481,11 +189,7 @@ definitions: example: No special requests type: string priority: - enum: - - low - - medium - - high - example: high + example: urgent type: string request_category: example: Cleaning @@ -493,9 +197,6 @@ definitions: request_type: example: recurring type: string - request_version: - example: "2024-01-02T00:00:00Z" - type: string reservation_id: example: 521e8400-e458-41d4-a716-498655440000 type: string @@ -506,80 +207,23 @@ definitions: example: "2024-01-01T00:00:00Z" type: string status: - enum: - - pending - - assigned - - in progress - - completed example: assigned type: string + updated_at: + example: "2024-01-02T00:00:00Z" + type: string user_id: example: 521ee400-e458-41d4-a716-446655440000 type: string type: object - RoomRequestsResponse: - properties: - assigned: - items: - $ref: '#/definitions/GuestRequest' - type: array - unassigned: - items: - $ref: '#/definitions/GuestRequest' - type: array - type: object - RoomWithOptionalGuestBooking: - properties: - booking_status: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.BookingStatus' - floor: - type: integer - guests: - items: - $ref: '#/definitions/Guest' - type: array - id: - type: string - room_number: - type: integer - room_status: - type: string - suite_type: - type: string - type: object - Stay: - properties: - arrival_date: - example: "2024-01-02" - type: string - departure_date: - example: "2024-01-05" - type: string - group_size: - type: integer - room_number: - example: 101 - type: integer - status: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.BookingStatus' - required: - - arrival_date - - departure_date - - room_number - - status - type: object UpdateGuest: properties: first_name: - example: Jane + example: John type: string last_name: example: Doe type: string - notes: - example: VIP guest - maxLength: 1000 - type: string profile_picture: example: https://example.com/john.jpg type: string @@ -587,48 +231,29 @@ definitions: example: America/New_York type: string type: object - UpdateUser: - properties: - phone_number: - example: "+11234567890" - type: string - type: object User: properties: + clerk_id: + example: user_123 + type: string created_at: example: "2024-01-01T00:00:00Z" type: string department: example: Housekeeping type: string - departments: - items: - type: string - type: array employee_id: example: EMP-1234 type: string first_name: example: John type: string - hotel_id: - example: org_550e8400-e29b-41d4-a716-446655440000 - type: string id: - example: user_123 + example: 550e8400-e29b-41d4-a716-446655440000 type: string - is_onboarded: - example: false - type: boolean last_name: example: Doe type: string - phone_number: - example: "+11234567890" - type: string - primary_email: - example: john@example.com - type: string profile_picture: example: https://example.com/john.jpg type: string @@ -642,120 +267,12 @@ definitions: example: "2024-01-01T00:00:00Z" type: string type: object - UserPage: - properties: - next_cursor: - type: string - users: - items: - $ref: '#/definitions/User' - type: array - type: object github_com_generate_selfserve_internal_errs.HTTPError: properties: code: type: integer message: {} type: object - github_com_generate_selfserve_internal_models.AssistanceFilter: - enum: - - accessibility - - dietary - - medical - type: string - x-enum-varnames: - - AssistanceAccessibility - - AssistanceDietary - - AssistanceMedical - github_com_generate_selfserve_internal_models.BookingStatus: - enum: - - active - - inactive - type: string - x-enum-varnames: - - BookingStatusActive - - BookingStatusInactive - github_com_generate_selfserve_internal_models.CreateDepartment: - properties: - name: - type: string - type: object - github_com_generate_selfserve_internal_models.FloorSortOrder: - enum: - - ascending - - descending - type: string - x-enum-varnames: - - FloorSortAscending - - FloorSortDescending - github_com_generate_selfserve_internal_models.NotificationType: - enum: - - task_assigned - - high_priority_task - type: string - x-enum-varnames: - - TypeTaskAssigned - - TypeHighPriorityTask - github_com_generate_selfserve_internal_models.RequestSortOrder: - enum: - - high_to_low - - low_to_high - - urgent - type: string - x-enum-varnames: - - RequestSortHighToLow - - RequestSortLowToHigh - - RequestSortUrgent - github_com_generate_selfserve_internal_models.UpdateDepartment: - properties: - name: - type: string - type: object - github_com_generate_selfserve_internal_utils.CursorPage-GuestRequest: - properties: - has_more: - type: boolean - items: - items: - $ref: '#/definitions/GuestRequest' - type: array - next_cursor: - description: nil when no more pages - type: string - type: object - github_com_generate_selfserve_internal_utils.CursorPage-RoomWithOptionalGuestBooking: - properties: - has_more: - type: boolean - items: - items: - $ref: '#/definitions/RoomWithOptionalGuestBooking' - type: array - next_cursor: - description: nil when no more pages - type: string - type: object - internal_handler.AddEmployeeDepartmentBody: - properties: - department_id: - type: string - type: object - internal_handler.SearchUsersBody: - properties: - cursor: - type: string - hotel_id: - type: string - q: - type: string - type: object - internal_handler.UpdateProfilePictureRequest: - description: Request body containing the S3 key after uploading - properties: - key: - example: profile-pictures/user123/1706540000.jpg - type: string - type: object host: localhost:8080 info: contact: @@ -769,109 +286,25 @@ info: title: SelfServe API version: "1.0" paths: - /device-tokens: - post: - consumes: - - application/json - description: Registers an Expo push token so the user receives mobile push notifications - parameters: - - description: Device token - in: body - name: request - required: true - schema: - $ref: '#/definitions/RegisterDeviceTokenInput' - responses: - "204": - description: No Content - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Register device token - tags: - - notifications - /devs/{name}: - get: - consumes: - - application/json - description: Retrieves a developer member by name - parameters: - - description: Developer name - in: path - name: name - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/Dev' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Get developer member - tags: - - devs - /guest_bookings/group_sizes: - get: - description: Retrieves all distinct group sizes across guest bookings - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - type: integer - type: array - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Get available group size options - tags: - - guest-bookings - /guests: + /api/v1/guests: post: consumes: - - application/json + - application/json description: Creates a guest with the given data parameters: - - description: Guest data - in: body - name: request - required: true - schema: - $ref: '#/definitions/CreateGuest' + - description: Guest data + in: body + name: request + required: true + schema: + $ref: "#/definitions/CreateGuest" produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/Guest' + $ref: "#/definitions/Guest" "400": description: Invalid guest body format schema: @@ -884,29 +317,27 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] summary: Creates a guest tags: - - guests - /guests/{id}: + - guests + /api/v1/guests/{id}: get: consumes: - - application/json + - application/json description: Retrieves a single guest given an id parameters: - - description: Guest ID (UUID) - in: path - name: id - required: true - type: string + - description: Guest ID (UUID) + in: path + name: id + required: true + type: string produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/Guest' + $ref: "#/definitions/Guest" "400": description: Invalid guest ID format schema: @@ -916,41 +347,39 @@ paths: "404": description: Guest not found schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + $ref: "#/definitions/github_com_generate_selfserve_internal_errs.HTTPError" "500": description: Internal server error schema: additionalProperties: type: string type: object - security: - - BearerAuth: [] summary: Gets a guest tags: - - guests + - guests put: consumes: - - application/json + - application/json description: Updates fields on a guest parameters: - - description: Guest ID (UUID) - in: path - name: id - required: true - type: string - - description: Guest update data - in: body - name: request - required: true - schema: - $ref: '#/definitions/UpdateGuest' + - description: Guest ID (UUID) + in: path + name: id + required: true + type: string + - description: Guest update data + in: body + name: request + required: true + schema: + $ref: "#/definitions/UpdateGuest" produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/Guest' + $ref: "#/definitions/Guest" "400": description: Bad Request schema: @@ -969,151 +398,126 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] summary: Updates a guest tags: - - guests - /guests/search: - post: - consumes: - - application/json - description: Retrieves guests optionally filtered by floor - parameters: - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string - - description: Guest filters - in: body - name: body - required: true - schema: - $ref: '#/definitions/GuestFilters' - produces: - - application/json + - guests + /api/v1/hotels/{id}: + get: + description: Retrieve a hotel's details using its UUID + parameters: + - description: Hotel ID (UUID) + in: path + name: id + required: true + type: string responses: "200": description: OK schema: - $ref: '#/definitions/GuestPage' + $ref: "#/definitions/Hotel" "400": - description: Bad Request + description: Invalid hotel ID format schema: - additionalProperties: - type: string - type: object + $ref: "#/definitions/github_com_generate_selfserve_internal_errs.HTTPError" + "404": + description: Hotel not found + schema: + $ref: "#/definitions/github_com_generate_selfserve_internal_errs.HTTPError" "500": - description: Internal Server Error + description: Internal server error schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Get Guests + $ref: "#/definitions/github_com_generate_selfserve_internal_errs.HTTPError" + summary: Get hotel by ID tags: - - guests - /guests/stays/{id}: + - hotels + /devs/{name}: get: consumes: - - application/json - description: Retrieves a single guest with previous stays given an id - operationId: getGuestsStaysId + - application/json + description: Retrieves a developer member by name parameters: - - description: Guest ID (UUID) - in: path - name: id - required: true - type: string + - description: Developer name + in: path + name: name + required: true + type: string produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/GuestWithStays' + $ref: "#/definitions/Dev" "400": - description: Invalid guest ID format + description: Bad Request schema: additionalProperties: type: string type: object - "404": - description: Guest not found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' "500": - description: Internal server error + description: Internal Server Error schema: additionalProperties: type: string type: object - security: - - BearerAuth: [] - summary: Gets a guest with previous stays + summary: Get developer member tags: - - guests + - devs /hello: get: consumes: - - application/json + - application/json description: Returns a simple hello message produces: - - text/plain + - text/plain responses: "200": - description: 'Yogurt. Gurt: Yo!' + description: "Yogurt. Gurt: Yo!" schema: type: string - security: - - BearerAuth: [] summary: Get hello message tags: - - hello + - hello /hello/{name}: get: consumes: - - application/json + - application/json description: Returns a hello message with the provided name parameters: - - description: Name to greet - in: path - name: name - required: true - type: string + - description: Name to greet + in: path + name: name + required: true + type: string produces: - - text/plain + - text/plain responses: "200": description: Yo, {name}! schema: type: string - security: - - BearerAuth: [] summary: Get personalized hello message tags: - - hello - /hotels: + - hello + /hotel: post: consumes: - - application/json + - application/json description: Create a new hotel with the given data parameters: - - description: Hotel data - in: body - name: hotel - required: true - schema: - $ref: '#/definitions/Hotel' + - description: Hotel data + in: body + name: hotel + required: true + schema: + $ref: "#/definitions/Hotel" produces: - - application/json + - application/json responses: "201": description: Created schema: - $ref: '#/definitions/Hotel' + $ref: "#/definitions/Hotel" "400": description: Bad Request schema: @@ -1126,304 +530,28 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] summary: Create hotel tags: - - hotels - /hotels/{id}: - get: - description: Retrieve a hotel's details using its UUID - parameters: - - description: Hotel ID (UUID) - in: path - name: id - required: true - type: string - responses: - "200": - description: OK - schema: - $ref: '#/definitions/Hotel' - "400": - description: Invalid hotel ID format - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "404": - description: Hotel not found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal server error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Get hotel by ID - tags: - - hotels - /hotels/{id}/departments: - get: - description: Returns all departments for a hotel - parameters: - - description: Hotel ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/Department' - type: array - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Get departments by hotel - tags: - - hotels - post: - consumes: - - application/json - description: Adds a new department to a hotel - parameters: - - description: Hotel ID - in: path - name: id - required: true - type: string - - description: Department data - in: body - name: request - required: true - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.CreateDepartment' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/Department' - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Create department - tags: - - hotels - /hotels/{id}/departments/{deptId}: - delete: - description: Removes a department from a hotel - parameters: - - description: Hotel ID - in: path - name: id - required: true - type: string - - description: Department ID - in: path - name: deptId - required: true - type: string - produces: - - application/json - responses: - "204": - description: No Content - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "404": - description: Not Found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Delete department - tags: - - hotels - put: - consumes: - - application/json - description: Renames a department - parameters: - - description: Hotel ID - in: path - name: id - required: true - type: string - - description: Department ID - in: path - name: deptId - required: true - type: string - - description: Department data - in: body - name: request - required: true - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_models.UpdateDepartment' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/Department' - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "404": - description: Not Found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Update department - tags: - - hotels - /hotels/{id}/users: - get: - description: Returns a paginated list of all users for a hotel - parameters: - - description: Hotel ID - in: path - name: id - required: true - type: string - - description: Pagination cursor (last seen user ID) - in: query - name: cursor - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - additionalProperties: true - type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Get users by hotel - tags: - - hotels - /notifications: - get: - description: Returns the most recent notifications for the authenticated user - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/Notification' - type: array - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: List notifications - tags: - - notifications - /notifications/{id}/read: - put: - description: Marks a single notification as read for the authenticated user - parameters: - - description: Notification ID - in: path - name: id - required: true - type: string - responses: - "204": - description: No Content - "404": - description: Not Found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Mark notification as read - tags: - - notifications - /notifications/read-all: - put: - description: Marks all unread notifications as read for the authenticated user - responses: - "204": - description: No Content - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Mark all notifications as read - tags: - - notifications + - hotels /request: post: consumes: - - application/json + - application/json description: Creates a request with the given data parameters: - - description: Request data - in: body - name: request - required: true - schema: - $ref: '#/definitions/MakeRequest' + - description: Request data + in: body + name: request + required: true + schema: + $ref: "#/definitions/MakeRequest" produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/Request' + $ref: "#/definitions/Request" "400": description: Bad Request schema: @@ -1436,72 +564,28 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] summary: creates a request tags: - - requests - /request/cursor: - post: - consumes: - - application/json - description: Gets 20 requests starting after the cursor position, filtered by - status - parameters: - - description: Hotel UUID - in: header - name: X-Hotel-ID - required: true - type: string - - description: Cursor position and status filter - in: body - name: body - schema: - $ref: '#/definitions/GetRequestsByStatusInput' - produces: - - application/json - responses: - "200": - description: Returns requests array, next_cursor_time, and next_cursor_id - schema: - additionalProperties: true - type: object - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Get requests by cursor - tags: - - requests + - requests /request/generate: post: consumes: - - application/json + - application/json description: Generates a request using AI parameters: - - description: Request data with raw text - in: body - name: request - required: true - schema: - $ref: '#/definitions/GenerateRequestInput' + - description: Request data with raw text + in: body + name: request + required: true + schema: + $ref: "#/definitions/GenerateRequestInput" produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/GenerateRequestResponse' + $ref: "#/definitions/Request" "400": description: Bad Request schema: @@ -1514,66 +598,28 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] summary: generates a request tags: - - requests - /request/guest/{id}: - get: - description: Retrieves all requests for a given guest + - requests + /users: + post: + consumes: + - application/json + description: Creates a user with the given data parameters: - - description: Guest ID (UUID) - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_utils.CursorPage-GuestRequest' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error + - description: User data + in: body + name: request + required: true schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Get requests by guest - tags: - - requests - /request/room/{id}: - get: - description: 'Returns two lists for the given room and hotel: requests assigned - to the caller and unassigned requests' - parameters: - - description: Room ID (UUID) - in: path - name: id - required: true - type: string - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string + $ref: "#/definitions/CreateUser" produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/RoomRequestsResponse' + $ref: "#/definitions/User" "400": description: Bad Request schema: @@ -1586,81 +632,27 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] - summary: Get requests by room + summary: Creates a user tags: - - requests - /requests: + - users + /users/{id}: get: - description: Returns a paginated list of requests for the hotel, optionally - filtered by assigned user - parameters: - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string - - description: Pagination cursor - in: query - name: cursor - type: string - - description: Page size (default 20, max 100) - in: query - name: limit - type: integer - - description: Filter by assigned user ID - in: query - name: user_id - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_utils.CursorPage-GuestRequest' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Get requests feed - tags: - - requests - /rooms: - post: consumes: - - application/json - description: Retrieves rooms with optional floor filters and cursor pagination, - including any active guest bookings + - application/json + description: Retrieves a user by their unique app ID parameters: - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string - - description: Filters and pagination - in: body - name: body - schema: - $ref: '#/definitions/FilterRoomsRequest' + - description: User ID + in: path + name: id + required: true + type: string produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_utils.CursorPage-RoomWithOptionalGuestBooking' + $ref: "#/definitions/User" "400": description: Bad Request schema: @@ -1673,575 +665,10 @@ paths: additionalProperties: type: string type: object - security: - - BearerAuth: [] - summary: List rooms with filters - tags: - - rooms - /rooms/{id}: - get: - description: Retrieves a single room by its UUID - parameters: - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string - - description: Room ID (UUID) - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/RoomWithOptionalGuestBooking' - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "404": - description: Not Found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Get room by ID - tags: - - rooms - /rooms/floors: - get: - description: Retrieves all distinct floor numbers - parameters: - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - type: integer - type: array - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Get Floors - tags: - - rooms - /s3/presigned-get-url/{key}: - get: - consumes: - - application/json - description: Generates a presigned URL for a file. The key is the full S3 path - (e.g., profile-pictures/user123/image.jpg) - parameters: - - description: File key (full path after /presigned-url/) - in: path - name: key - required: true - type: string - produces: - - application/json - responses: - "200": - description: Presigned URL - schema: - type: string - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Generate a presigned URL for a file - tags: - - s3 - /s3/presigned-url/{key}: - get: - consumes: - - application/json - description: Generates a presigned URL for a file. The key is the full S3 path - (e.g., profile-pictures/user123/image.jpg) - parameters: - - description: File key (full path after /presigned-url/) - in: path - name: key - required: true - type: string - produces: - - application/json - responses: - "200": - description: Presigned URL - schema: - type: string - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Generate a presigned URL for a file - tags: - - s3 - /s3/upload-url/{userId}: - get: - description: Generates a presigned S3 URL and unique key for uploading a profile - picture. After uploading to S3, use PUT /users/{userId}/profile-picture to - save the key. - parameters: - - description: User ID - in: path - name: userId - required: true - type: string - - default: jpg - description: File extension (jpg, jpeg, png, webp) - in: query - name: ext - type: string - produces: - - application/json - responses: - "200": - description: Returns presigned_url and key - schema: - additionalProperties: - type: string - type: object - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Get presigned URL for profile picture upload - tags: - - s3 - /users: - post: - consumes: - - application/json - description: Creates a user with the given data - parameters: - - description: User data - in: body - name: request - required: true - schema: - $ref: '#/definitions/CreateUser' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/User' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Creates a user - tags: - - users - /users/{id}: - get: - consumes: - - application/json - description: Retrieves a user by their unique app ID - parameters: - - description: User ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/User' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] summary: Get user by ID tags: - - users - put: - consumes: - - application/json - description: Updates allowed fields on a user - parameters: - - description: User ID - in: path - name: id - required: true - type: string - - description: Fields to update - in: body - name: request - required: true - schema: - $ref: '#/definitions/UpdateUser' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/User' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "404": - description: Not Found - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Update user - tags: - - users - /users/{id}/departments: - post: - consumes: - - application/json - description: Creates a row in employee_departments linking the user to a department - parameters: - - description: User ID - in: path - name: id - required: true - type: string - - description: Department to add - in: body - name: request - required: true - schema: - $ref: '#/definitions/internal_handler.AddEmployeeDepartmentBody' - produces: - - application/json - responses: - "204": - description: No Content - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Add department to employee - tags: - - users - /users/{id}/departments/{deptId}: - delete: - consumes: - - application/json - description: Deletes a row from employee_departments unlinking the user from - a department - parameters: - - description: User ID - in: path - name: id - required: true - type: string - - description: Department ID - in: path - name: deptId - required: true - type: string - produces: - - application/json - responses: - "204": - description: No Content - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Remove department from employee - tags: - - users - /users/{id}/onboard: - put: - consumes: - - application/json - description: Saves onboarding data and marks a user as onboarded - parameters: - - description: User ID - in: path - name: id - required: true - type: string - - description: Onboarding data - in: body - name: request - required: true - schema: - $ref: '#/definitions/OnboardUser' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/User' - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "404": - description: Not Found - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Complete user onboarding - tags: - - users - /users/{userId}/profile-picture: - delete: - consumes: - - application/json - description: Deletes the user's profile picture from the database - parameters: - - description: User ID - in: path - name: userId - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - additionalProperties: - type: string - type: object - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Delete user's profile picture - tags: - - users - get: - consumes: - - application/json - description: Retrieves the user's profile picture key and returns a presigned - URL for display - parameters: - - description: User ID - in: path - name: userId - required: true - type: string - produces: - - application/json - responses: - "200": - description: Returns key and presigned_url if profile picture exists - schema: - additionalProperties: - type: string - type: object - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "404": - description: No profile picture found - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Get user's profile picture - tags: - - users - put: - consumes: - - application/json - description: Saves the S3 key to the user's profile after the image has been - uploaded to S3 - parameters: - - description: User ID - in: path - name: userId - required: true - type: string - - description: S3 key from upload - in: body - name: request - required: true - schema: - $ref: '#/definitions/internal_handler.UpdateProfilePictureRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - additionalProperties: - type: string - type: object - "400": - description: Bad Request - schema: - additionalProperties: - type: string - type: object - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Update user's profile picture - tags: - - users - /users/search: - post: - consumes: - - application/json - description: Returns a paginated list of users for a hotel, optionally filtered - by name. Cursor is the last seen user ID. - parameters: - - description: Search params - in: body - name: request - required: true - schema: - $ref: '#/definitions/internal_handler.SearchUsersBody' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/UserPage' - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Search users by hotel - tags: - - users + - users schemes: -- http -- https -securityDefinitions: - BearerAuth: - description: Your Clerk JWT token (prefixed with "Bearer ") - in: header - name: Authorization - type: apiKey + - http + - https swagger: "2.0" From c683e6e3877a1403484edf0fc538be6670a2ccd3 Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 20:30:40 -0400 Subject: [PATCH 07/23] fix: prettier formatting --- .../onboarding/EmployeeRoleStep.tsx | 106 +++++++++--------- .../components/onboarding/InviteTeamStep.tsx | 68 +++++------ .../src/components/onboarding/LeftPanel.tsx | 24 ++-- .../components/onboarding/OnboardingPage.tsx | 84 +++++++------- .../onboarding/PropertyDetailsStep.tsx | 94 ++++++++-------- .../src/components/onboarding/RoleCard.tsx | 14 +-- .../onboarding/RoleSelectionStep.tsx | 96 ++++++++-------- .../src/components/onboarding/WelcomeStep.tsx | 56 ++++----- .../components/onboarding/onboardingMocks.ts | 38 +++---- .../web/src/components/onboarding/types.ts | 12 +- clients/web/src/routes/onboarding.tsx | 8 +- 11 files changed, 303 insertions(+), 297 deletions(-) diff --git a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx index d0c12b2d3..00893c80a 100644 --- a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx +++ b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx @@ -1,35 +1,35 @@ -import LeftPanel from './LeftPanel' -import type { OnboardingFormData } from './types' +import LeftPanel from "./LeftPanel"; +import type { OnboardingFormData } from "./types"; interface EmployeeRoleStepProps { - formData: OnboardingFormData - updateForm: (updates: Partial) => void - onNext: () => void - onBack: () => void + formData: OnboardingFormData; + updateForm: (updates: Partial) => void; + onNext: () => void; + onBack: () => void; } const EMPLOYEE_ROLES = [ { - id: 'front_desk', - label: 'Role', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + id: "front_desk", + label: "Role", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, { - id: 'housekeeping', - label: 'Role', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + id: "housekeeping", + label: "Role", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, { - id: 'maintenance', - label: 'Role', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + id: "maintenance", + label: "Role", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, { - id: 'concierge', - label: 'Role', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + id: "concierge", + label: "Role", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, -] +]; export default function EmployeeRoleStep({ formData, @@ -46,38 +46,38 @@ export default function EmployeeRoleStep({
{/* Header */}

Employee Role

Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -87,43 +87,43 @@ export default function EmployeeRoleStep({ {/* 2x2 Role cards grid */}

{EMPLOYEE_ROLES.map((role) => (
- ) + ); } diff --git a/clients/web/src/components/onboarding/InviteTeamStep.tsx b/clients/web/src/components/onboarding/InviteTeamStep.tsx index 397e00171..01337de9b 100644 --- a/clients/web/src/components/onboarding/InviteTeamStep.tsx +++ b/clients/web/src/components/onboarding/InviteTeamStep.tsx @@ -1,17 +1,17 @@ -import { useState } from 'react' -import LeftPanel from './LeftPanel' -import type { OnboardingFormData } from './types' +import { useState } from "react"; +import LeftPanel from "./LeftPanel"; +import type { OnboardingFormData } from "./types"; interface InviteTeamStepProps { - formData: OnboardingFormData - updateForm: (updates: Partial) => void + formData: OnboardingFormData; + updateForm: (updates: Partial) => void; } export default function InviteTeamStep({ formData, updateForm, }: InviteTeamStepProps) { - const [invited, setInvited] = useState(false) + const [invited, setInvited] = useState(false); return (
@@ -20,17 +20,17 @@ export default function InviteTeamStep({ {/* Right panel */}
{/* Card */}
{/* Header */} @@ -38,29 +38,29 @@ export default function InviteTeamStep({ {/* Logo placeholder */}

Invite your team

SelfServe is better when the whole staff is connected. @@ -71,7 +71,7 @@ export default function InviteTeamStep({ {/* Email invite */}

@@ -94,10 +94,10 @@ export default function InviteTeamStep({ )}

You can also do this later from your settings. @@ -107,19 +107,19 @@ export default function InviteTeamStep({ {/* Actions */}

@@ -127,5 +127,5 @@ export default function InviteTeamStep({
- ) + ); } diff --git a/clients/web/src/components/onboarding/LeftPanel.tsx b/clients/web/src/components/onboarding/LeftPanel.tsx index cee2c5467..b86f5200c 100644 --- a/clients/web/src/components/onboarding/LeftPanel.tsx +++ b/clients/web/src/components/onboarding/LeftPanel.tsx @@ -4,16 +4,16 @@ export default function LeftPanel() { {/* SelfServe wordmark */}
SelfServe @@ -22,15 +22,15 @@ export default function LeftPanel() { {/* Lorem ipsum text */}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec risus @@ -38,5 +38,5 @@ export default function LeftPanel() {

- ) + ); } diff --git a/clients/web/src/components/onboarding/OnboardingPage.tsx b/clients/web/src/components/onboarding/OnboardingPage.tsx index b800066b8..c89f5a85c 100644 --- a/clients/web/src/components/onboarding/OnboardingPage.tsx +++ b/clients/web/src/components/onboarding/OnboardingPage.tsx @@ -1,87 +1,87 @@ -import { useState } from 'react' -import WelcomeStep from './WelcomeStep' -import RoleSelectionStep from './RoleSelectionStep' -import EmployeeRoleStep from './EmployeeRoleStep' -import PropertyDetailsStep from './PropertyDetailsStep' -import InviteTeamStep from './InviteTeamStep' -import type { OnboardingFormData } from './types' +import { useState } from "react"; +import WelcomeStep from "./WelcomeStep"; +import RoleSelectionStep from "./RoleSelectionStep"; +import EmployeeRoleStep from "./EmployeeRoleStep"; +import PropertyDetailsStep from "./PropertyDetailsStep"; +import InviteTeamStep from "./InviteTeamStep"; +import type { OnboardingFormData } from "./types"; const INITIAL_FORM_DATA: OnboardingFormData = { role: null, employeeRole: null, - hotelName: '', - numberOfRooms: '', - propertyType: '', - inviteEmail: '', -} + hotelName: "", + numberOfRooms: "", + propertyType: "", + inviteEmail: "", +}; type Step = - | 'welcome' - | 'role' - | 'employeeRole' - | 'propertyDetails' - | 'inviteTeam' + | "welcome" + | "role" + | "employeeRole" + | "propertyDetails" + | "inviteTeam"; export default function OnboardingPage() { - const [currentStep, setCurrentStep] = useState('welcome') + const [currentStep, setCurrentStep] = useState("welcome"); const [formData, setFormData] = - useState(INITIAL_FORM_DATA) + useState(INITIAL_FORM_DATA); const updateForm = (updates: Partial) => { - setFormData((prev) => ({ ...prev, ...updates })) - } + setFormData((prev) => ({ ...prev, ...updates })); + }; const handleRoleSelected = (role: string) => { - updateForm({ role }) - if (role === 'employee') { - setCurrentStep('employeeRole') + updateForm({ role }); + if (role === "employee") { + setCurrentStep("employeeRole"); } else { - setCurrentStep('propertyDetails') + setCurrentStep("propertyDetails"); } - } + }; const renderStep = () => { switch (currentStep) { - case 'welcome': - return setCurrentStep('role')} /> - case 'role': + case "welcome": + return setCurrentStep("role")} />; + case "role": return ( - ) - case 'employeeRole': + ); + case "employeeRole": return ( setCurrentStep('propertyDetails')} - onBack={() => setCurrentStep('role')} + onNext={() => setCurrentStep("propertyDetails")} + onBack={() => setCurrentStep("role")} /> - ) - case 'propertyDetails': + ); + case "propertyDetails": return ( setCurrentStep('inviteTeam')} + onNext={() => setCurrentStep("inviteTeam")} onBack={() => setCurrentStep( - formData.role === 'employee' ? 'employeeRole' : 'role', + formData.role === "employee" ? "employeeRole" : "role", ) } /> - ) - case 'inviteTeam': - return + ); + case "inviteTeam": + return ; } - } + }; return (
{renderStep()}
- ) + ); } diff --git a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx index 7f3581fc8..aa686af68 100644 --- a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx +++ b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx @@ -1,14 +1,20 @@ -import LeftPanel from './LeftPanel' -import type { OnboardingFormData } from './types' +import LeftPanel from "./LeftPanel"; +import type { OnboardingFormData } from "./types"; interface PropertyDetailsStepProps { - formData: OnboardingFormData - updateForm: (updates: Partial) => void - onNext: () => void - onBack: () => void + formData: OnboardingFormData; + updateForm: (updates: Partial) => void; + onNext: () => void; + onBack: () => void; } -const PROPERTY_TYPES = ['Hotel', 'Motel', 'Resort', 'Bed & Breakfast', 'Hostel'] +const PROPERTY_TYPES = [ + "Hotel", + "Motel", + "Resort", + "Bed & Breakfast", + "Hostel", +]; export default function PropertyDetailsStep({ formData, @@ -17,7 +23,7 @@ export default function PropertyDetailsStep({ onBack, }: PropertyDetailsStepProps) { const isValid = - formData.hotelName && formData.numberOfRooms && formData.propertyType + formData.hotelName && formData.numberOfRooms && formData.propertyType; return (
@@ -29,36 +35,36 @@ export default function PropertyDetailsStep({
{/* Header */}

Property Details

Lorem ipsum dolor sit amet. @@ -69,19 +75,19 @@ export default function PropertyDetailsStep({

- ) + ); } diff --git a/clients/web/src/components/onboarding/RoleCard.tsx b/clients/web/src/components/onboarding/RoleCard.tsx index a43ea1d73..2cd4e2e51 100644 --- a/clients/web/src/components/onboarding/RoleCard.tsx +++ b/clients/web/src/components/onboarding/RoleCard.tsx @@ -1,8 +1,8 @@ interface RoleCardProps { - label: string - description: string - selected: boolean - onSelect: () => void + label: string; + description: string; + selected: boolean; + onSelect: () => void; } export default function RoleCard({ @@ -17,12 +17,12 @@ export default function RoleCard({ className={`p-4 rounded-xl border-2 text-left transition-colors w-full ${ selected - ? 'border-green-900 bg-green-50' - : 'border-gray-200 hover:border-gray-300 bg-white' + ? "border-green-900 bg-green-50" + : "border-gray-200 hover:border-gray-300 bg-white" }`} >

{label}

{description}

- ) + ); } diff --git a/clients/web/src/components/onboarding/RoleSelectionStep.tsx b/clients/web/src/components/onboarding/RoleSelectionStep.tsx index b34c041f0..0ead2f53a 100644 --- a/clients/web/src/components/onboarding/RoleSelectionStep.tsx +++ b/clients/web/src/components/onboarding/RoleSelectionStep.tsx @@ -1,24 +1,24 @@ -import LeftPanel from './LeftPanel' -import type { OnboardingFormData } from './types' +import LeftPanel from "./LeftPanel"; +import type { OnboardingFormData } from "./types"; interface RoleSelectionStepProps { - formData: OnboardingFormData - updateForm: (updates: Partial) => void - onNext: (role: string) => void + formData: OnboardingFormData; + updateForm: (updates: Partial) => void; + onNext: (role: string) => void; } const ROLES = [ { - id: 'manager', - label: 'Manager', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + id: "manager", + label: "Manager", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, { - id: 'employee', - label: 'Employee', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + id: "employee", + label: "Employee", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, -] +]; export default function RoleSelectionStep({ formData, @@ -34,29 +34,29 @@ export default function RoleSelectionStep({
{/* Logo placeholder */}
{/* Role title */} Role @@ -65,13 +65,13 @@ export default function RoleSelectionStep({ {/* Subtitle */} Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -81,8 +81,8 @@ export default function RoleSelectionStep({
{ROLES.map((role) => ( @@ -91,33 +91,33 @@ export default function RoleSelectionStep({ onClick={() => onNext(role.id)} className="text-left bg-[#FFFFFF]" style={{ - height: '184px', - borderRadius: '16px', - border: `2px solid ${formData.role === role.id ? '#0F172B' : '#F1F5F9'}`, - padding: '16px', + height: "184px", + borderRadius: "16px", + border: `2px solid ${formData.role === role.id ? "#0F172B" : "#F1F5F9"}`, + padding: "16px", }} >
{role.label}
{role.description} @@ -128,5 +128,5 @@ export default function RoleSelectionStep({
- ) + ); } diff --git a/clients/web/src/components/onboarding/WelcomeStep.tsx b/clients/web/src/components/onboarding/WelcomeStep.tsx index 22549bb12..974ad3f52 100644 --- a/clients/web/src/components/onboarding/WelcomeStep.tsx +++ b/clients/web/src/components/onboarding/WelcomeStep.tsx @@ -1,7 +1,7 @@ -import LeftPanel from './LeftPanel' +import LeftPanel from "./LeftPanel"; interface WelcomeStepProps { - onNext: () => void + onNext: () => void; } export default function WelcomeStep({ onNext }: WelcomeStepProps) { @@ -12,22 +12,22 @@ export default function WelcomeStep({ onNext }: WelcomeStepProps) { {/* Right panel */}
{/* Welcome card */}
{/* Logo placeholder */}
@@ -35,26 +35,26 @@ export default function WelcomeStep({ onNext }: WelcomeStepProps) {
{/* Welcome text */}

Welcome @@ -64,11 +64,11 @@ export default function WelcomeStep({ onNext }: WelcomeStepProps) { onClick={onNext} className="absolute bg-green-900 hover:bg-green-800 text-white flex items-center justify-center transition-colors" style={{ - width: '446.6px', - height: '56px', - top: '121px', - left: '0px', - borderRadius: '14px', + width: "446.6px", + height: "56px", + top: "121px", + left: "0px", + borderRadius: "14px", }} > Start › @@ -77,5 +77,5 @@ export default function WelcomeStep({ onNext }: WelcomeStepProps) {

- ) + ); } diff --git a/clients/web/src/components/onboarding/onboardingMocks.ts b/clients/web/src/components/onboarding/onboardingMocks.ts index d4e5317d9..f25b35696 100644 --- a/clients/web/src/components/onboarding/onboardingMocks.ts +++ b/clients/web/src/components/onboarding/onboardingMocks.ts @@ -1,30 +1,30 @@ export const ROLES = [ { - id: 'manager', - label: 'Manager', - description: 'Oversees operations and staff.', + id: "manager", + label: "Manager", + description: "Oversees operations and staff.", }, { - id: 'front_desk', - label: 'Front Desk', - description: 'Handles guest check-ins and requests.', + id: "front_desk", + label: "Front Desk", + description: "Handles guest check-ins and requests.", }, { - id: 'housekeeping', - label: 'Housekeeping', - description: 'Manages room cleaning and maintenance.', + id: "housekeeping", + label: "Housekeeping", + description: "Manages room cleaning and maintenance.", }, { - id: 'maintenance', - label: 'Maintenance', - description: 'Handles repairs and facilities.', + id: "maintenance", + label: "Maintenance", + description: "Handles repairs and facilities.", }, -] +]; export const PROPERTY_TYPES = [ - 'Hotel', - 'Motel', - 'Resort', - 'Bed & Breakfast', - 'Hostel', -] + "Hotel", + "Motel", + "Resort", + "Bed & Breakfast", + "Hostel", +]; diff --git a/clients/web/src/components/onboarding/types.ts b/clients/web/src/components/onboarding/types.ts index 310904109..57b09b223 100644 --- a/clients/web/src/components/onboarding/types.ts +++ b/clients/web/src/components/onboarding/types.ts @@ -1,8 +1,8 @@ export interface OnboardingFormData { - role: string | null - employeeRole: string | null - hotelName: string - numberOfRooms: string - propertyType: string - inviteEmail: string + role: string | null; + employeeRole: string | null; + hotelName: string; + numberOfRooms: string; + propertyType: string; + inviteEmail: string; } diff --git a/clients/web/src/routes/onboarding.tsx b/clients/web/src/routes/onboarding.tsx index d314025cc..72a9c71b0 100644 --- a/clients/web/src/routes/onboarding.tsx +++ b/clients/web/src/routes/onboarding.tsx @@ -1,6 +1,6 @@ -import { createFileRoute } from '@tanstack/react-router' -import OnboardingPage from '../components/onboarding/OnboardingPage' +import { createFileRoute } from "@tanstack/react-router"; +import OnboardingPage from "../components/onboarding/OnboardingPage"; -export const Route = createFileRoute('/onboarding')({ +export const Route = createFileRoute("/onboarding")({ component: OnboardingPage, -}) +}); From ef4e3fab45ce3c50803d501191ec66f937dcdb74 Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 17 Mar 2026 20:36:17 -0400 Subject: [PATCH 08/23] final fix --- clients/package-lock.json | 27 +++++++++++++++++++++++++++ clients/package.json | 5 +++++ 2 files changed, 32 insertions(+) create mode 100644 clients/package-lock.json create mode 100644 clients/package.json diff --git a/clients/package-lock.json b/clients/package-lock.json new file mode 100644 index 000000000..5f298b31e --- /dev/null +++ b/clients/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "clients", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "prettier": "^3.8.1" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/clients/package.json b/clients/package.json new file mode 100644 index 000000000..12f6fc62f --- /dev/null +++ b/clients/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "prettier": "^3.8.1" + } +} From 2913be674a7e019940779c43176dc8db4c487d7e Mon Sep 17 00:00:00 2001 From: Tars919 Date: Wed, 18 Mar 2026 12:17:44 -0400 Subject: [PATCH 09/23] fixed the responsive view on the pages --- .../onboarding/EmployeeRoleStep.tsx | 157 +++++------ .../components/onboarding/InviteTeamStep.tsx | 214 ++++++++------- .../src/components/onboarding/LeftPanel.tsx | 56 ++-- .../components/onboarding/OnboardingPage.tsx | 9 +- .../onboarding/PropertyDetailsStep.tsx | 248 ++++++++---------- .../onboarding/RoleSelectionStep.tsx | 153 +++++------ .../src/components/onboarding/WelcomeStep.tsx | 116 ++++---- 7 files changed, 436 insertions(+), 517 deletions(-) diff --git a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx index 00893c80a..e10227cf8 100644 --- a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx +++ b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx @@ -9,123 +9,86 @@ interface EmployeeRoleStepProps { } const EMPLOYEE_ROLES = [ - { - id: "front_desk", - label: "Role", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, - { - id: "housekeeping", - label: "Role", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, - { - id: "maintenance", - label: "Role", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, - { - id: "concierge", - label: "Role", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, + { id: "front_desk", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { id: "housekeeping", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { id: "maintenance", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { id: "concierge", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, ]; -export default function EmployeeRoleStep({ - formData, - updateForm, - onNext, -}: EmployeeRoleStepProps) { +export default function EmployeeRoleStep({ formData, updateForm, onNext }: EmployeeRoleStepProps) { return ( -
+
- - {/* Right panel */} -
- {/* Card */} -
+
+ {/* Card: 663/1371 wide, 652/982 tall */} +
{/* Header */} -
-

+
+

Employee Role

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

- {/* 2x2 Role cards grid */} -
+ {/* 2x2 grid */} +
{EMPLOYEE_ROLES.map((role) => ( @@ -135,4 +98,4 @@ export default function EmployeeRoleStep({
); -} +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/InviteTeamStep.tsx b/clients/web/src/components/onboarding/InviteTeamStep.tsx index 01337de9b..5938e5b36 100644 --- a/clients/web/src/components/onboarding/InviteTeamStep.tsx +++ b/clients/web/src/components/onboarding/InviteTeamStep.tsx @@ -7,125 +7,135 @@ interface InviteTeamStepProps { updateForm: (updates: Partial) => void; } -export default function InviteTeamStep({ - formData, - updateForm, -}: InviteTeamStepProps) { +export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepProps) { const [invited, setInvited] = useState(false); return ( -
+
+
+ {/* Card: 662.6/1371 = 48.33vw, 625/982 = 63.64vh */} +
+ {/* Inner content block: 566.6px wide, everything stacked */} +
+ {/* Logo */} +
- {/* Right panel */} -
- {/* Card */} -
- {/* Header */} -
- {/* Logo placeholder */} -
-
-

+ {/* Header */} +
+

Invite your team

-

+

SelfServe is better when the whole staff is connected.

-

- {/* Email invite */} -
-
-
- updateForm({ inviteEmail: e.target.value })} - placeholder="Enter email address..." - className="flex-1 bg-transparent text-sm outline-none" - /> + {/* Email input */} +
+
+
+ updateForm({ inviteEmail: e.target.value })} + placeholder="Enter email address..." + style={{ flex: 1, background: "transparent", border: "none", outline: "none", fontSize: "14px" }} + /> + +
+ {invited && ( +

Invite sent!

+ )} +

+ You can also do this later from your settings. +

+
+ + {/* Actions */} +
+
- {invited && ( -

Invite sent!

- )} -

- You can also do this later from your settings. -

-
- - {/* Actions */} -
- -
); -} +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/LeftPanel.tsx b/clients/web/src/components/onboarding/LeftPanel.tsx index b86f5200c..a7ce294cd 100644 --- a/clients/web/src/components/onboarding/LeftPanel.tsx +++ b/clients/web/src/components/onboarding/LeftPanel.tsx @@ -1,42 +1,36 @@ export default function LeftPanel() { return ( -
- {/* SelfServe wordmark */} -
- +
+
+ SelfServe
- {/* Lorem ipsum text */} -
-

+

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec risus nunc, ullamcorper vitae risus vel, tristique vehicula lectus.

); -} +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/OnboardingPage.tsx b/clients/web/src/components/onboarding/OnboardingPage.tsx index c89f5a85c..a4c924767 100644 --- a/clients/web/src/components/onboarding/OnboardingPage.tsx +++ b/clients/web/src/components/onboarding/OnboardingPage.tsx @@ -24,8 +24,7 @@ type Step = export default function OnboardingPage() { const [currentStep, setCurrentStep] = useState("welcome"); - const [formData, setFormData] = - useState(INITIAL_FORM_DATA); + const [formData, setFormData] = useState(INITIAL_FORM_DATA); const updateForm = (updates: Partial) => { setFormData((prev) => ({ ...prev, ...updates })); @@ -69,7 +68,7 @@ export default function OnboardingPage() { onNext={() => setCurrentStep("inviteTeam")} onBack={() => setCurrentStep( - formData.role === "employee" ? "employeeRole" : "role", + formData.role === "employee" ? "employeeRole" : "role" ) } /> @@ -80,8 +79,8 @@ export default function OnboardingPage() { }; return ( -
+
{renderStep()}
); -} +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx index aa686af68..0a64795e0 100644 --- a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx +++ b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx @@ -8,166 +8,136 @@ interface PropertyDetailsStepProps { onBack: () => void; } -const PROPERTY_TYPES = [ - "Hotel", - "Motel", - "Resort", - "Bed & Breakfast", - "Hostel", -]; +const PROPERTY_TYPES = ["Hotel", "Motel", "Resort", "Bed & Breakfast", "Hostel"]; -export default function PropertyDetailsStep({ - formData, - updateForm, - onNext, - onBack, -}: PropertyDetailsStepProps) { - const isValid = - formData.hotelName && formData.numberOfRooms && formData.propertyType; +export default function PropertyDetailsStep({ formData, updateForm, onNext, onBack }: PropertyDetailsStepProps) { + const isValid = formData.hotelName && formData.numberOfRooms && formData.propertyType; return ( -
+
- - {/* Right panel */} -
- {/* Card */} -
- {/* Header */} -
-

+ {/* Outer card: 662.6/1371 wide, 705/982 tall */} +
+ {/* Inner container: 256.68px wide, gap 32px */} +
+ {/* Header */} +
+

- Property Details -

-

+ Property Details +

+

- Lorem ipsum dolor sit amet. -

-
- - {/* Form fields */} -
-
- - updateForm({ hotelName: e.target.value })} - placeholder="Lorem ipsum dolor sit amet" - className="border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-green-900" - /> + margin: 0, + }}> + Lorem ipsum dolor sit amet. +

-
- - updateForm({ numberOfRooms: e.target.value })} - placeholder="e.g. 150" - className="border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-green-900" - /> + + {/* Form fields */} +
+
+ + updateForm({ hotelName: e.target.value })} + placeholder="Lorem ipsum dolor sit amet" + style={{ border: "1px solid #E2E8F0", borderRadius: "8px", padding: "8px 12px", fontSize: "14px", outline: "none", width: "100%", boxSizing: "border-box" }} + /> +
+
+ + updateForm({ numberOfRooms: e.target.value })} + placeholder="e.g. 150" + style={{ border: "1px solid #E2E8F0", borderRadius: "8px", padding: "8px 12px", fontSize: "14px", outline: "none", width: "100%", boxSizing: "border-box" }} + /> +
+
+ + +
-
-
- - {/* Actions */} -
- - -
); -} +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/RoleSelectionStep.tsx b/clients/web/src/components/onboarding/RoleSelectionStep.tsx index 0ead2f53a..d4da88a33 100644 --- a/clients/web/src/components/onboarding/RoleSelectionStep.tsx +++ b/clients/web/src/components/onboarding/RoleSelectionStep.tsx @@ -8,118 +8,95 @@ interface RoleSelectionStepProps { } const ROLES = [ - { - id: "manager", - label: "Manager", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, - { - id: "employee", - label: "Employee", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - }, + { id: "manager", label: "Manager", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { id: "employee", label: "Employee", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, ]; -export default function RoleSelectionStep({ - formData, - onNext, -}: RoleSelectionStepProps) { +export default function RoleSelectionStep({ formData, onNext }: RoleSelectionStepProps) { return ( -
+
+
+ {/* Card: 642.6/1371 wide, 492/982 tall */} +
+ {/* Logo */} +
- {/* Right panel */} -
- {/* Outer card */} -
- {/* Logo placeholder */} -
- - {/* Role title */} - + {/* Title */} + Role {/* Subtitle */} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. {/* Role cards grid */} -
+
{ROLES.map((role) => ( @@ -129,4 +106,4 @@ export default function RoleSelectionStep({
); -} +} \ No newline at end of file diff --git a/clients/web/src/components/onboarding/WelcomeStep.tsx b/clients/web/src/components/onboarding/WelcomeStep.tsx index 974ad3f52..f655ab26b 100644 --- a/clients/web/src/components/onboarding/WelcomeStep.tsx +++ b/clients/web/src/components/onboarding/WelcomeStep.tsx @@ -6,69 +6,75 @@ interface WelcomeStepProps { export default function WelcomeStep({ onNext }: WelcomeStepProps) { return ( -
+
- {/* Right panel */} -
- {/* Welcome card */} -
- {/* Logo placeholder */} -
+ {/* Right panel — flex centered, padded from top */} +
+ {/* Card: 616.6/1371 wide, 691/982 tall */} +
- {/* Inner container: Welcome + Start */} -
- {/* Welcome text */} -

+ {/* Logo box: 80×80px */} +
+ + {/* Welcome + Start: 61px gap above text matches Figma inner container */} +
+

Welcome

- {/* Start button */}
); -} +} \ No newline at end of file From b40ab0afa2a7e9f8efff0af14360ddd33081a77d Mon Sep 17 00:00:00 2001 From: Tars919 Date: Tue, 24 Mar 2026 21:43:59 -0400 Subject: [PATCH 10/23] address PR review: named exports, type props, design tokens, RoleCard, responsiveness fixes, revert prettier --- .github/workflows/BackendCI.yaml | 14 +-- backend/README.md | 13 +-- clients/mobile/prettier.config.js | 6 -- clients/package-lock.json | 27 ----- clients/package.json | 5 - clients/shared/src/api/client.ts | 2 +- clients/shared/src/api/config.ts | 18 ++-- clients/shared/src/api/orval-mutator.ts | 1 - clients/shared/src/utils/index.ts | 2 +- clients/shared/tsconfig.json | 2 +- .../onboarding/EmployeeRoleStep.tsx | 98 ++++++------------- .../components/onboarding/InviteTeamStep.tsx | 34 ++++--- .../src/components/onboarding/LeftPanel.tsx | 23 +++-- .../components/onboarding/OnboardingPage.tsx | 14 +-- .../onboarding/PropertyDetailsStep.tsx | 31 +++--- .../src/components/onboarding/RoleCard.tsx | 23 ++--- .../onboarding/RoleSelectionStep.tsx | 49 ++++------ .../src/components/onboarding/WelcomeStep.tsx | 44 ++++----- clients/web/src/routes/onboarding.tsx | 2 +- package-lock.json | 6 -- 20 files changed, 165 insertions(+), 249 deletions(-) delete mode 100644 clients/mobile/prettier.config.js delete mode 100644 clients/package-lock.json delete mode 100644 clients/package.json delete mode 100644 package-lock.json diff --git a/.github/workflows/BackendCI.yaml b/.github/workflows/BackendCI.yaml index 60d6f4bb2..6b5dbd4a9 100644 --- a/.github/workflows/BackendCI.yaml +++ b/.github/workflows/BackendCI.yaml @@ -38,9 +38,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version-file: "./backend/go.mod" + go-version-file: './backend/go.mod' cache: true - cache-dependency-path: "./backend/go.sum" + cache-dependency-path: './backend/go.sum' - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest - name: Generate swagger docs @@ -71,9 +71,9 @@ jobs: fetch-depth: 2 - uses: actions/setup-go@v5 with: - go-version-file: "./backend/go.mod" + go-version-file: './backend/go.mod' cache: true - cache-dependency-path: "./backend/go.sum" + cache-dependency-path: './backend/go.sum' - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest - name: Generate swagger docs @@ -96,9 +96,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version-file: "./backend/go.mod" + go-version-file: './backend/go.mod' cache: true - cache-dependency-path: "./backend/go.sum" + cache-dependency-path: './backend/go.sum' - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest - name: Generate swagger docs @@ -114,4 +114,4 @@ jobs: with: name: selfserve-binary path: backend/bin/selfserve - retention-days: 7 + retention-days: 7 \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index 95a862098..ed5272ba3 100644 --- a/backend/README.md +++ b/backend/README.md @@ -19,16 +19,14 @@ ollama pull qwen2.5:7b-instruct ``` 3. **Download dependencies**: - ```bash make download ``` 4. **Set up environment variables**: - + (Slack us for these) Create a `config/.env` file with the following variables: - ```env # Application Configuration APP_PORT=8080 @@ -47,7 +45,6 @@ > **Note**: All database variables (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME) are required. The application will fail to start if they are missing. **LLM Configuration** – used for parsing request text (e.g. `/request/parse`). Add this to your existing .env: - ```env LLM_SERVER_ADDRESS=http://127.0.0.1:11434 LLM_MODEL=qwen2.5:3b @@ -55,23 +52,21 @@ ``` 5. **Run with hot reload** (development): - ```bash air ``` Or run directly: - ```bash make dev ``` Or run with GenKit UI: - ```bash make genkit-run ``` + ## Directory Structure ``` @@ -134,7 +129,7 @@ Logic traversal: ┌──────────────▼──────────────────────────┐ │ Storage Layer │ │ (internal/service/storage/postgres/) │ -│ The Database itself │ +│ The Database itself │ └─────────────────────────────────────────┘ ``` @@ -156,6 +151,7 @@ Logic traversal: ## Development + ```bash # Build make build @@ -194,3 +190,4 @@ The application reads configuration from environment variables (loaded from `con - `LLM_TIMEOUT`: Response timeout in seconds (default: 60) - `LLM_MAX_OUTPUT_TOKENS`: Max tokens for generation; lower values reduce latency (default: 1024) - `LLM_TEMPERATURE`: Sampling temperature 0–1; lower is more deterministic and often faster for extraction + diff --git a/clients/mobile/prettier.config.js b/clients/mobile/prettier.config.js deleted file mode 100644 index 701f2ded6..000000000 --- a/clients/mobile/prettier.config.js +++ /dev/null @@ -1,6 +0,0 @@ -// @ts-check - -/** @type {import('prettier').Config} */ -const config = {}; - -module.exports = config; diff --git a/clients/package-lock.json b/clients/package-lock.json deleted file mode 100644 index 5f298b31e..000000000 --- a/clients/package-lock.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "clients", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "prettier": "^3.8.1" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - } - } -} diff --git a/clients/package.json b/clients/package.json deleted file mode 100644 index 12f6fc62f..000000000 --- a/clients/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "prettier": "^3.8.1" - } -} diff --git a/clients/shared/src/api/client.ts b/clients/shared/src/api/client.ts index 2b97be39e..37d71095e 100644 --- a/clients/shared/src/api/client.ts +++ b/clients/shared/src/api/client.ts @@ -13,7 +13,7 @@ export const createRequest = ( let fullUrl = `${baseUrl}${config.url}`; if (config.params && Object.keys(config.params).length > 0) { const searchParams = new URLSearchParams(config.params); - fullUrl += "?" + searchParams.toString(); + fullUrl += '?' + searchParams.toString(); } try { diff --git a/clients/shared/src/api/config.ts b/clients/shared/src/api/config.ts index 0c874238c..fee2aae85 100644 --- a/clients/shared/src/api/config.ts +++ b/clients/shared/src/api/config.ts @@ -6,19 +6,19 @@ */ export type Config = { - API_BASE_URL: string; - getToken: () => Promise; -}; + API_BASE_URL: string + getToken: () => Promise +} -let config: Config | null = null; +let config: Config | null = null export const setConfig = (c: Config) => { - config = c; -}; + config = c +} export const getConfig = (): Config => { if (!config) { - throw new Error("Config not initialized. Call setConfig() at app startup."); + throw new Error('Config not initialized. Call setConfig() at app startup.') } - return config; -}; + return config +} \ No newline at end of file diff --git a/clients/shared/src/api/orval-mutator.ts b/clients/shared/src/api/orval-mutator.ts index d843869eb..927ca8e5e 100755 --- a/clients/shared/src/api/orval-mutator.ts +++ b/clients/shared/src/api/orval-mutator.ts @@ -20,4 +20,3 @@ export const useCustomInstance = (): (( }; export default useCustomInstance; -export const customInstance = useCustomInstance; diff --git a/clients/shared/src/utils/index.ts b/clients/shared/src/utils/index.ts index 2c20000fd..dea19a2cb 100644 --- a/clients/shared/src/utils/index.ts +++ b/clients/shared/src/utils/index.ts @@ -5,4 +5,4 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); -} +} \ No newline at end of file diff --git a/clients/shared/tsconfig.json b/clients/shared/tsconfig.json index 8d92de00b..f31e1f167 100644 --- a/clients/shared/tsconfig.json +++ b/clients/shared/tsconfig.json @@ -17,7 +17,7 @@ "resolveJsonModule": true, "isolatedModules": true, "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], } }, "include": ["src/**/*.ts", "src/**/*.tsx"], diff --git a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx index e10227cf8..d8f90f39f 100644 --- a/clients/web/src/components/onboarding/EmployeeRoleStep.tsx +++ b/clients/web/src/components/onboarding/EmployeeRoleStep.tsx @@ -1,21 +1,17 @@ -import LeftPanel from "./LeftPanel"; +import { LeftPanel } from "./LeftPanel"; import type { OnboardingFormData } from "./types"; +import { ROLES } from "./onboardingMocks"; +import { RoleCard } from "./RoleCard"; -interface EmployeeRoleStepProps { +type EmployeeRoleStepProps = { formData: OnboardingFormData; updateForm: (updates: Partial) => void; onNext: () => void; onBack: () => void; } -const EMPLOYEE_ROLES = [ - { id: "front_desk", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { id: "housekeeping", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { id: "maintenance", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { id: "concierge", label: "Role", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, -]; -export default function EmployeeRoleStep({ formData, updateForm, onNext }: EmployeeRoleStepProps) { +export function EmployeeRoleStep({ formData, updateForm, onNext }: EmployeeRoleStepProps) { return (
@@ -24,74 +20,42 @@ export default function EmployeeRoleStep({ formData, updateForm, onNext }: Emplo display: "flex", justifyContent: "center", alignItems: "flex-start", - paddingTop: "19.45vh", // 191/982 + paddingTop: "clamp(80px, 19.45vh, 191px)", + paddingLeft: "clamp(40px, 9.6vw, 132px)", + paddingRight: "clamp(40px, 9.6vw, 132px)", + overflow: "hidden", }}> - {/* Card: 663/1371 wide, 652/982 tall */} -
+
{/* Header */} -
-

+
+

Employee Role

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

{/* 2x2 grid */} -
- {EMPLOYEE_ROLES.map((role) => ( - + label={role.label} + description={role.description} + selected={formData.employeeRole === role.id} + onSelect={() => { updateForm({ employeeRole: role.id }); onNext(); }} + /> ))}

diff --git a/clients/web/src/components/onboarding/InviteTeamStep.tsx b/clients/web/src/components/onboarding/InviteTeamStep.tsx index 5938e5b36..93509938f 100644 --- a/clients/web/src/components/onboarding/InviteTeamStep.tsx +++ b/clients/web/src/components/onboarding/InviteTeamStep.tsx @@ -1,13 +1,13 @@ import { useState } from "react"; -import LeftPanel from "./LeftPanel"; +import { LeftPanel } from "./LeftPanel"; import type { OnboardingFormData } from "./types"; -interface InviteTeamStepProps { +type InviteTeamStepProps = { formData: OnboardingFormData; updateForm: (updates: Partial) => void; } -export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepProps) { +export function InviteTeamStep({ formData, updateForm }: InviteTeamStepProps) { const [invited, setInvited] = useState(false); return ( @@ -18,12 +18,16 @@ export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepP display: "flex", justifyContent: "center", alignItems: "flex-start", - paddingTop: "19.45vh", + paddingTop: "clamp(80px, 19.45vh, 191px)", + paddingLeft: "clamp(40px, 9.6vw, 132px)", + paddingRight: "clamp(40px, 9.6vw, 132px)", + overflow: "hidden", }}> - {/* Card: 662.6/1371 = 48.33vw, 625/982 = 63.64vh */}
- {/* Inner content block: 566.6px wide, everything stacked */}
{/* Email input */} -
+
{invited && ( -

Invite sent!

+

Invite sent!

)}

You can also do this later from your settings. @@ -110,14 +114,14 @@ export default function InviteTeamStep({ formData, updateForm }: InviteTeamStepP

{/* Actions */} -
+
{invited && ( -

Invite sent!

+

+ Invite sent! +

)} -

+

You can also do this later from your settings.

{/* Actions */} -
+
@@ -142,4 +222,4 @@ export function InviteTeamStep({ formData, updateForm }: InviteTeamStepProps) {
); -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/LeftPanel.tsx b/clients/web/src/components/onboarding/LeftPanel.tsx index 1fca50b28..4316da6fa 100644 --- a/clients/web/src/components/onboarding/LeftPanel.tsx +++ b/clients/web/src/components/onboarding/LeftPanel.tsx @@ -9,37 +9,42 @@ export function LeftPanel() { overflow: "hidden", }} > -
- +
+ SelfServe -

- +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec risus nunc, ullamcorper vitae risus vel, tristique vehicula lectus.

); -} \ No newline at end of file +} diff --git a/clients/web/src/components/onboarding/OnboardingPage.tsx b/clients/web/src/components/onboarding/OnboardingPage.tsx index 4d4a0786d..3fe2be050 100644 --- a/clients/web/src/components/onboarding/OnboardingPage.tsx +++ b/clients/web/src/components/onboarding/OnboardingPage.tsx @@ -24,7 +24,8 @@ type Step = export function OnboardingPage() { const [currentStep, setCurrentStep] = useState("welcome"); - const [formData, setFormData] = useState(INITIAL_FORM_DATA); + const [formData, setFormData] = + useState(INITIAL_FORM_DATA); const updateForm = (updates: Partial) => { setFormData((prev) => ({ ...prev, ...updates })); @@ -68,7 +69,7 @@ export function OnboardingPage() { onNext={() => setCurrentStep("inviteTeam")} onBack={() => setCurrentStep( - formData.role === "employee" ? "employeeRole" : "role" + formData.role === "employee" ? "employeeRole" : "role", ) } /> @@ -78,9 +79,5 @@ export function OnboardingPage() { } }; - return ( -
- {renderStep()} -
- ); -} \ No newline at end of file + return
{renderStep()}
; +} diff --git a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx index 46c26538c..24e5b144b 100644 --- a/clients/web/src/components/onboarding/PropertyDetailsStep.tsx +++ b/clients/web/src/components/onboarding/PropertyDetailsStep.tsx @@ -6,76 +6,120 @@ type PropertyDetailsStepProps = { updateForm: (updates: Partial) => void; onNext: () => void; onBack: () => void; -} +}; -const PROPERTY_TYPES = ["Hotel", "Motel", "Resort", "Bed & Breakfast", "Hostel"]; +const PROPERTY_TYPES = [ + "Hotel", + "Motel", + "Resort", + "Bed & Breakfast", + "Hostel", +]; -export function PropertyDetailsStep({ formData, updateForm, onNext, onBack }: PropertyDetailsStepProps) { - const isValid = formData.hotelName && formData.numberOfRooms && formData.propertyType; +export function PropertyDetailsStep({ + formData, + updateForm, + onNext, + onBack, +}: PropertyDetailsStepProps) { + const isValid = + formData.hotelName && formData.numberOfRooms && formData.propertyType; return (
-
-
-
+
+ justifyContent: "center", + padding: "48px", + boxSizing: "border-box", + }} + > +
{/* Header */} -
-

+
+

Property Details

-

+

Lorem ipsum dolor sit amet.

{/* Form fields */} -
-
-