Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
7 changes: 7 additions & 0 deletions app/(host)/us/host/fleet/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import FleetPageComponents from "@/components/hostComponents/pages/fleet/fleetPageComponents";

export default function FleetPage() {
return (
<FleetPageComponents />
)
}
36 changes: 36 additions & 0 deletions app/(host)/us/host/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { cookies } from "next/headers";
import { SidebarProvider } from "@/components/ui/sidebar";
import { TooltipProvider } from "@/components/ui/tooltip";
import type { Metadata } from "next";
import "@/app/globals.css";
import { HostSidebar } from "@/components/hostComponents/dashboard/sidebar";
import { DashboardHeader } from "@/components/hostComponents/dashboard/header";
import { ChakraUIProvider } from "@/components/providers/chakraProvider";

export const metadata: Metadata = {
title: "Swing Rides Host Dashboard",
description: "Drive your fleet. Own your data.",
};

export default async function HostDashboardLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const sidebarOpen = cookieStore.get("sidebar_state")?.value !== "false";

return (
<SidebarProvider defaultOpen={sidebarOpen}>
<ChakraUIProvider>
<TooltipProvider>
<HostSidebar />
<div className="flex flex-col flex-1 min-w-0 overflow-x-clip bg-slate-100">
<DashboardHeader />
{children}
</div>
</TooltipProvider>
</ChakraUIProvider>
</SidebarProvider>
);
}
8 changes: 8 additions & 0 deletions app/(host)/us/host/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import LoginPageComponent from '@/components/hostPages/loginPageComponent'
import React from 'react'

export default function HostLoginPage() {
return (
<LoginPageComponent/>
)
}
8 changes: 8 additions & 0 deletions app/(host)/us/host/maintenance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import MaintenancePageComponents from '@/components/hostComponents/pages/maintenance/maintenancePageComponents'
import React from 'react'

export default function MaintenancePage() {
return (
<MaintenancePageComponents />
)
}
5 changes: 2 additions & 3 deletions app/(host)/us/host/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import DashboardPageComponent from '@/components/hostComponents/pages/dashboardPageComponents'
import React from 'react'

export default function HostDashboard() {
return (
<div>
HOST DASHBOARD
</div>
<DashboardPageComponent />
)
}
7 changes: 7 additions & 0 deletions app/(host)/us/host/register/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import RegisterPageComponents from "@/components/hostPages/registerPageComponents";

export default function HostRegisterPage() {
return (
<RegisterPageComponents />
)
}
30 changes: 30 additions & 0 deletions app/(host)/us/host/report-an-issue/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import SingleIssueReportPage from '@/components/hostComponents/pages/reportAnIssuePageComponents/singleIssueReportPage'

export default async function ReportPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;

return (
<SingleIssueReportPage
reportId={id}
issueType='Booking Issue'
bookingReference='BR-12345'
dateSubmitted='Apr 28, 2026 at 3:42 PM'
urgent={true}
issueDescription={`Renter returned the vehicle with significant scratches on the rear bumper. The damage was not present during the pre-rental inspection, as documented in my photos. The scratches appear to be from reversing into a concrete barrier or pole. The renter did not mention this damage during drop-off and left quickly. I have attached photos of the damage. The repair estimate from my usual body shop is $180. The renter's deposit was $75, so there is a shortfall of $105. I would like SwingRides to review the evidence and help recover the full repair cost from the renter.`}
attachedPhotos={[
{ id: "IMG-001", src: "/images/host/damage-front-bumper.webp" },
{ id: "IMG-002", src: "/images/host/damage-side-panel.jpeg" },
{ id: "IMG-003", src: "/images/host/receipt-oil-change.png" },
{ id: "IMG-004", src: "/images/host/tire-tread-depth.webp" }
]}
status='inReview'
responseMessage={`Thank you for reporting this issue. Our claims team has reviewed the photos and documentation you provided. Based on our assessment, the damage appears consistent with impact damage that occurred during the rental period. We have contacted the renter and requested their side of the story. We are currently waiting for the renter to respond to our inquiry. Once we receive their response, we will make a determination on liability and the appropriate resolution. You should expect a final decision within the next 2-3 business days. We will notify you via email as soon as we have an update.`}
responseDate='Apr 29, 2026 at 11:18 AM'
respondedBy='Sarah Admin'
/>
)
}
7 changes: 7 additions & 0 deletions app/(host)/us/host/report-an-issue/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ReportAnIssuePageComponents from '@/components/hostComponents/pages/reportAnIssuePageComponents/reportAnIssuePageComponents'

export default function ReportAnIssuePage() {
return (
<ReportAnIssuePageComponents />
)
}
7 changes: 7 additions & 0 deletions app/(host)/us/host/reviews/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ReviewsPageComponents from "@/components/hostComponents/pages/reviewsPageComponents/reviewsPageComponents";

export default function ReviewsPage() {
return (
<ReviewsPageComponents />
)
}
4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ export default function RootLayout({
return (
<html
lang="en"
className={`${bebasNeue.variable} ${bebasNeueGoogle.variable} ${dmSans.variable} h-full antialiased`}
className={`${bebasNeue.variable} ${bebasNeueGoogle.variable} ${dmSans.variable} h-full antialiased bg-slate-100`}
>
<body className="flex flex-col">
<body className="flex flex-col bg-slate-100">
<ReduxProvider>
<ChakraUIProvider>{children}</ChakraUIProvider>
</ReduxProvider>
Expand Down
3 changes: 3 additions & 0 deletions components/forms/MainForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function MainForm({
isLoading = false,
className,
rowPairs = [],
footerSlot,
}: MainFormProps) {
const {
register,
Expand Down Expand Up @@ -113,6 +114,8 @@ export default function MainForm({
})}
</div>

{footerSlot}

<Button
type='submit'
disabled={isLoading}
Expand Down
227 changes: 227 additions & 0 deletions components/forms/hostLoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
'use client'

import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { Mail, X } from 'lucide-react'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import MainForm from './MainForm'
import { validators } from './form.validators'
import { FormFieldConfig } from './types'
import { RegisterOptions } from 'react-hook-form'
import { cn } from '@/lib/utils'

const fields: FormFieldConfig[] = [
{
name: 'email',
type: 'email',
label: 'Email Address',
placeholder: 'john@company.com',
icon: <Mail className='size-4' />,
validation: validators.email() as RegisterOptions,
},
{
name: 'password',
type: 'password',
label: 'Password',
placeholder: 'Enter your password',
validation: validators.required('Password') as RegisterOptions,
},
]

export default function HostLoginForm() {
const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false)

const onSubmit = (values: Record<string, unknown>) => {
console.log(values)
}

return (
<>
<div className='flex flex-col gap-5 w-full'>
{/* The two main fields via MainForm */}
<MainForm
fields={fields}
onSubmit={onSubmit}
submitLabel='Sign In'
className='w-full'
footerSlot={
<RememberForgotRow
onForgotPassword={() => setForgotPasswordOpen(true)}
/>
}
/>
</div>

{/* Forgot password dialog */}
{forgotPasswordOpen && (
<ForgotPasswordDialog onClose={() => setForgotPasswordOpen(false)} />
)}
</>
)
}

// ─── Remember me + Forgot password row ───────────────────────────────────────

const RememberForgotRow = ({ onForgotPassword }: { onForgotPassword: () => void }) => {
const [remembered, setRemembered] = useState(false)

return (
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<Checkbox
id='rememberMe'
checked={remembered}
onCheckedChange={(v) => setRemembered(!!v)}
className='border-[#E5E7EB] data-[state=checked]:bg-[#1A56DB] data-[state=checked]:border-[#1A56DB]'
/>
<label
htmlFor='rememberMe'
className='text-[#6B7280] text-sm font-normal font-text cursor-pointer select-none'
>
Remember me
</label>
</div>
<button
type='button'
onClick={onForgotPassword}
className='text-[#1A56DB] text-sm font-medium font-text hover:underline cursor-pointer'
>
Forgot password?
</button>
</div>
)
}

// ─── Forgot password dialog ───────────────────────────────────────────────────

type ForgotPasswordFormValues = {
resetEmail: string
}

const ForgotPasswordDialog = ({ onClose }: { onClose: () => void }) => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<ForgotPasswordFormValues>({ mode: 'onTouched' })

const onSubmit = (values: ForgotPasswordFormValues) => {
console.log('reset password for:', values.resetEmail)
// TODO: call reset password API here
onClose()
}

return (
<div
className='fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-md px-4'
onClick={onClose}
>
<div
className='w-full max-w-md bg-white rounded-[10px] border border-[#E5E7EB] p-6 flex flex-col gap-5'
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className='flex items-start justify-between gap-4'>
<div className='flex flex-col gap-1'>
<h3 className='text-[#1F2937] text-lg font-bold font-text leading-6'>
Reset your password
</h3>
<span className='text-[#6B7280] text-sm font-normal font-text leading-5'>
Enter your email and we&apos;ll send you a reset link.
</span>
</div>
<button
type='button'
onClick={onClose}
aria-label='Close'
className='text-[#6B7280] hover:text-[#1F2937] transition-colors duration-150 cursor-pointer shrink-0'
>
<X className='w-5 h-5' />
</button>
</div>

{/* Form */}
<form
onSubmit={handleSubmit(onSubmit)}
className='flex flex-col gap-4'
noValidate
>
<div className='flex flex-col gap-1.5'>
<Label
htmlFor='resetEmail'
className='text-[#1F2937] text-sm font-semibold font-text'
>
Email Address <span className='text-[#EF4444]'>*</span>
</Label>
<div className='relative flex items-center'>
<span className='absolute left-3 text-[#9CA3AF] pointer-events-none'>
<Mail className='w-4 h-4' />
</span>
<Input
id='resetEmail'
type='email'
placeholder='john@company.com'
autoComplete='email'
className={cn(
'pl-9 border-[#E5E7EB] focus-visible:ring-[#1A56DB] font-text text-sm text-[#1F2937] placeholder:text-[#9CA3AF]',
errors.resetEmail && 'border-[#EF4444] focus-visible:ring-[#EF4444]'
)}
{...register('resetEmail', validators.email() as RegisterOptions<ForgotPasswordFormValues, 'resetEmail'>)}
/>
</div>
{errors.resetEmail && (
<span className='text-[#EF4444] text-xs font-normal font-text flex items-center gap-1'>
<ErrorIcon />
{errors.resetEmail.message as string}
</span>
)}
</div>

{/* Actions */}
<div className='flex gap-3 justify-end pt-1'>
<Button
type='button'
variant='outline'
onClick={onClose}
className='border-[#E5E7EB] text-[#6B7280] hover:bg-[#F3F4F6] font-medium font-text cursor-pointer transition-colors duration-300'
>
Cancel
</Button>
<Button
type='submit'
disabled={isSubmitting}
className='bg-[#1A56DB] hover:bg-[#1E429F] text-white font-medium font-text cursor-pointer transition-colors duration-300 disabled:opacity-50 disabled:pointer-events-none'
>
{isSubmitting ? (
<span className='flex items-center gap-2'>
<LoadingSpinner />
Sending...
</span>
) : 'Send Reset Link'}
</Button>
</div>
</form>
</div>
</div>
)
}

// ─── Icons ────────────────────────────────────────────────────────────────────

const ErrorIcon = () => (
<svg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path d='M6 1L11 10H1L6 1Z' stroke='#EF4444' strokeWidth='1' strokeLinecap='round' strokeLinejoin='round' />
<path d='M6 5V7' stroke='#EF4444' strokeWidth='1' strokeLinecap='round' />
<circle cx='6' cy='8.5' r='0.5' fill='#EF4444' />
</svg>
)

const LoadingSpinner = () => (
<svg className='animate-spin w-4 h-4' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<circle className='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' strokeWidth='4' />
<path className='opacity-75' fill='currentColor' d='M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z' />
</svg>
)
Loading