Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ definitions:
type: integer
type: array
limit:
minimum: 0
type: integer
type: object
GenerateRequestInput:
Expand Down
5 changes: 0 additions & 5 deletions clients/mobile/components/tasks/active-filter-chips.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import Feather from "@expo/vector-icons/Feather";
import { Pressable, Text, View } from "react-native";

interface FilterItem {
label: string;
value: string;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unused interface

interface ActiveFilterChipsProps {
filters: { label: string; value: string }[];
onRemoveFilter: (value: string) => void;
Expand Down
9 changes: 9 additions & 0 deletions clients/mobile/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,13 @@ module.exports = defineConfig([
{
ignores: ["dist/*"],
},
{
settings: {
"import/resolver": {
typescript: {
project: "./tsconfig.json",
},
},
},
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allows for path aliasing in our lint checks like @/

]);
45 changes: 29 additions & 16 deletions clients/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function NavLink({
to={to}
className={`flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors ${
isActive
? "bg-primary/10 font-semibold text-gray-900"
? "bg-bg-selected font-semibold text-subtle"
: "text-gray-500 hover:bg-gray-100 hover:text-gray-700"
}`}
>
Expand All @@ -29,6 +29,33 @@ function NavLink({
);
}

function ProfileLink({ displayName }: { displayName: string | undefined }) {
const pathname = useRouterState({ select: (s) => s.location.pathname });
const isActive = pathname === "/profile";
return (
<Link
to="/profile"
className={`flex items-center gap-3 rounded-lg p-3 transition-colors ${
isActive ? "bg-bg-selected" : "hover:bg-bg-selected"
}`}
>
<UserButton
appearance={{
elements: {
avatarBox: "size-10",
},
}}
/>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clerk component :( will switch over to use an image component once we pull data from backend instead of clerk

<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-text-subtle">
{displayName || "User"}
</p>
<p className="truncate text-xs text-primary">Hotel Chain</p>
</div>
</Link>
);
}

export function Sidebar() {
const { user } = useUser();

Expand Down Expand Up @@ -69,21 +96,7 @@ export function Sidebar() {
Settings
</NavLink>
<LogoutButton />
<div className="flex items-center gap-3 pt-4">
<UserButton
appearance={{
elements: {
avatarBox: "size-10",
},
}}
/>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-gray-700">
{displayName || "User"}
</p>
<p className="truncate text-xs text-gray-500">Hotel Chain</p>
</div>
</div>
<ProfileLink displayName={displayName} />
</div>
</aside>
);
Expand Down
21 changes: 21 additions & 0 deletions clients/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Route as IndexRouteImport } from './routes/index'
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'
import { Route as ProtectedProfileRouteImport } from './routes/_protected/profile'
import { Route as ProtectedHomeRouteImport } from './routes/_protected/home'
import { Route as ProtectedRoomsIndexRouteImport } from './routes/_protected/rooms.index'
import { Route as ProtectedGuestsIndexRouteImport } from './routes/_protected/guests.index'
Expand Down Expand Up @@ -55,6 +56,11 @@ const ProtectedRoomsRoute = ProtectedRoomsRouteImport.update({
path: '/rooms',
getParentRoute: () => ProtectedRoute,
} as any)
const ProtectedProfileRoute = ProtectedProfileRouteImport.update({
id: '/profile',
path: '/profile',
getParentRoute: () => ProtectedRoute,
} as any)
const ProtectedHomeRoute = ProtectedHomeRouteImport.update({
id: '/home',
path: '/home',
Expand All @@ -81,6 +87,7 @@ export interface FileRoutesByFullPath {
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/home': typeof ProtectedHomeRoute
'/profile': typeof ProtectedProfileRoute
'/rooms': typeof ProtectedRoomsRouteWithChildren
'/settings': typeof ProtectedSettingsRoute
'/test-api': typeof ProtectedTestApiRoute
Expand All @@ -93,6 +100,7 @@ export interface FileRoutesByTo {
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/home': typeof ProtectedHomeRoute
'/profile': typeof ProtectedProfileRoute
'/settings': typeof ProtectedSettingsRoute
'/test-api': typeof ProtectedTestApiRoute
'/guests/$guestId': typeof ProtectedGuestsGuestIdRoute
Expand All @@ -106,6 +114,7 @@ export interface FileRoutesById {
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/_protected/home': typeof ProtectedHomeRoute
'/_protected/profile': typeof ProtectedProfileRoute
'/_protected/rooms': typeof ProtectedRoomsRouteWithChildren
'/_protected/settings': typeof ProtectedSettingsRoute
'/_protected/test-api': typeof ProtectedTestApiRoute
Expand All @@ -120,6 +129,7 @@ export interface FileRouteTypes {
| '/sign-in'
| '/sign-up'
| '/home'
| '/profile'
| '/rooms'
| '/settings'
| '/test-api'
Expand All @@ -132,6 +142,7 @@ export interface FileRouteTypes {
| '/sign-in'
| '/sign-up'
| '/home'
| '/profile'
| '/settings'
| '/test-api'
| '/guests/$guestId'
Expand All @@ -144,6 +155,7 @@ export interface FileRouteTypes {
| '/sign-in'
| '/sign-up'
| '/_protected/home'
| '/_protected/profile'
| '/_protected/rooms'
| '/_protected/settings'
| '/_protected/test-api'
Expand Down Expand Up @@ -210,6 +222,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProtectedRoomsRouteImport
parentRoute: typeof ProtectedRoute
}
'/_protected/profile': {
id: '/_protected/profile'
path: '/profile'
fullPath: '/profile'
preLoaderRoute: typeof ProtectedProfileRouteImport
parentRoute: typeof ProtectedRoute
}
'/_protected/home': {
id: '/_protected/home'
path: '/home'
Expand Down Expand Up @@ -255,6 +274,7 @@ const ProtectedRoomsRouteWithChildren = ProtectedRoomsRoute._addFileChildren(

interface ProtectedRouteChildren {
ProtectedHomeRoute: typeof ProtectedHomeRoute
ProtectedProfileRoute: typeof ProtectedProfileRoute
ProtectedRoomsRoute: typeof ProtectedRoomsRouteWithChildren
ProtectedSettingsRoute: typeof ProtectedSettingsRoute
ProtectedTestApiRoute: typeof ProtectedTestApiRoute
Expand All @@ -264,6 +284,7 @@ interface ProtectedRouteChildren {

const ProtectedRouteChildren: ProtectedRouteChildren = {
ProtectedHomeRoute: ProtectedHomeRoute,
ProtectedProfileRoute: ProtectedProfileRoute,
ProtectedRoomsRoute: ProtectedRoomsRouteWithChildren,
ProtectedSettingsRoute: ProtectedSettingsRoute,
ProtectedTestApiRoute: ProtectedTestApiRoute,
Expand Down
113 changes: 113 additions & 0 deletions clients/web/src/routes/_protected/profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { createFileRoute } from "@tanstack/react-router";
import { useUser } from "@clerk/clerk-react";

export const Route = createFileRoute("/_protected/profile")({
component: ProfilePage,
});

function DetailRow({ label, value }: { label: string; value: string }) {
return (
<div className="grid grid-cols-[40%_1fr] items-center py-3">
<p className="text-sm font-medium text-text-default">{label}</p>
<p className="text-sm text-text-subtle">{value}</p>
</div>
);
}

function ProfileInfoCard({
displayName,
email,
phone,
}: {
displayName: string;
email: string;
phone: string;
}) {
return (
<section className="h-56 rounded-lg border border-stroke-subtle bg-white p-6">
<div className="divide-y divide-stroke-subtle">
<DetailRow label="Government Name" value={displayName} />
<DetailRow label="Date of Birth" value="—" />
<DetailRow label="Phone" value={phone || "—"} />
<DetailRow label="Email" value={email || "—"} />
</div>
</section>
);
}

function NotesFromManagerCard() {
return (
<section className="h-56 rounded-lg border border-stroke-subtle bg-white p-6">
<h2 className="text-sm font-medium text-text-default">
Notes from Manager
</h2>
</section>
);
}

function ProfilePage() {
const { user } = useUser();

const displayName =
user?.fullName ||
[user?.firstName, user?.lastName].filter(Boolean).join(" ") ||
"User";

const email = user?.primaryEmailAddress?.emailAddress ?? "";
const phone = user?.primaryPhoneNumber?.phoneNumber ?? "";

return (
<main className="flex h-screen flex-col overflow-hidden">
{/* Page header */}
<header className="flex-row gap-1.5 border-b border-stroke-subtle px-10 py-5">
<h1 className="text-2xl font-semibold text-text-default">Profile</h1>
<p className="text-sm font-medium text-text-subtle">
Overview of profile
</p>
</header>

{/* Scrollable content */}
<div className="flex flex-1 flex-col overflow-y-auto px-6 py-6">
{/* User hero */}
<div className="flex items-center gap-11 mb-6 pl-4">
{user?.imageUrl ? (
<img
src={user.imageUrl}
alt={displayName}
className="size-30 rounded-full object-cover"
/>
) : (
<div className="flex size-30 items-center justify-center rounded-full border-2 border-text-default bg-gray-100">
<span className="text-[40px] font-semibold text-text-default">
{displayName.charAt(0)}
</span>
</div>
)}
<div>
<h2 className="text-[40px] font-bold text-text-default leading-none">
{user?.firstName}
</h2>
<h2 className="text-[40px] font-bold text-text-default leading-none">
{user?.lastName}
</h2>
<p className="pt-1 text-base font-bold text-primary">Hotel Chain</p>
</div>
</div>

{/* Info + Notes row */}
<div className="flex flex-1 gap-4">
<div className="flex-1">
<ProfileInfoCard
displayName={displayName}
email={email}
phone={phone}
/>
</div>
<div className="flex-1">
<NotesFromManagerCard />
</div>
</div>
</div>
</main>
);
}
Loading