Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
216 lines
7.8 KiB
TypeScript
216 lines
7.8 KiB
TypeScript
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { FormProvider } from 'react-hook-form'
|
|
|
|
import { Progress } from '@/components/heroui/Progress'
|
|
import Button from '@/components/formElements/Button'
|
|
import Checkbox from '@/components/formElements/Checkbox'
|
|
import Input from '@/components/formElements/Input'
|
|
import ConsumerInput from '@/components/consumer/ConsumerInput'
|
|
import { texts, format } from '@/texts'
|
|
import { useAuthFlow } from '@/features/auth/useAuthFlow'
|
|
import { useDiscoveryCitiesQuery } from '@/queries/consumer/useWalletAndBookmarkQueries'
|
|
import { showFormValidationToast } from '@/lib/formValidationToast'
|
|
import { OTP_LENGTH } from '@/validation/auth'
|
|
|
|
const GENDER_OPTIONS = [
|
|
{ name: texts.common.genderMale, selectKey: 'male' },
|
|
{ name: texts.common.genderFemale, selectKey: 'female' },
|
|
]
|
|
|
|
export function AdminAuthContent() {
|
|
const flow = useAuthFlow({})
|
|
const citiesQuery = useDiscoveryCitiesQuery(flow.step === 'profile')
|
|
const cityOptions = (citiesQuery.data ?? []).map((city) => ({ id: String(city.id), name: city.name }))
|
|
const title = flow.step === 'otp' ? texts.auth.otpTitle : flow.step === 'profile' ? texts.auth.profileTitle : null
|
|
|
|
return (
|
|
<div className="mx-auto flex w-full max-w-xs flex-col">
|
|
{title ? <div className="mb-5 text-center text-lg font-bold text-tertiary">{title}</div> : null}
|
|
{flow.accountBlockMessage ? (
|
|
<div className="rounded-xl border border-fourth-100 bg-fourth-100 px-4 py-3 text-center text-sm text-fourth-900">
|
|
{flow.accountBlockMessage}
|
|
</div>
|
|
) : null}
|
|
|
|
{flow.step === 'mobile' ? (
|
|
<FormProvider {...flow.sendOtpForm}>
|
|
<form onSubmit={flow.sendOtpForm.handleSubmit(flow.sendOtp, showFormValidationToast)}>
|
|
<ConsumerInput
|
|
generalType="input"
|
|
inputType="tel"
|
|
label={texts.auth.mobileLabel}
|
|
name="mobile"
|
|
placeholder={texts.auth.mobilePlaceholder}
|
|
/>
|
|
<Button
|
|
fullWidth
|
|
className="mt-4"
|
|
isLoading={flow.loading}
|
|
type="submit"
|
|
>
|
|
{texts.common.continue}
|
|
</Button>
|
|
</form>
|
|
</FormProvider>
|
|
) : null}
|
|
|
|
{flow.step === 'otp' ? (
|
|
<>
|
|
<FormProvider {...flow.checkOtpForm}>
|
|
<form onSubmit={flow.checkOtpForm.handleSubmit(flow.checkOtp, showFormValidationToast)}>
|
|
<ConsumerInput
|
|
autoFocus
|
|
className="px-2"
|
|
generalType="otp"
|
|
name="code"
|
|
otpLength={OTP_LENGTH}
|
|
/>
|
|
{flow.otpError ? <p className="mt-2 text-center text-sm text-fourth-900">{flow.otpError}</p> : null}
|
|
{flow.isRegistration ? (
|
|
<div className="mt-5">
|
|
<Checkbox
|
|
className="items-start gap-3 rounded-2xl border border-primary-100 bg-primary-50/60 p-4 data-[selected=true]:border-primary-100 data-[selected=true]:bg-primary-50/60"
|
|
isSelected={flow.termsAccepted}
|
|
onValueChange={flow.updateTermsAccepted}
|
|
>
|
|
<span
|
|
aria-describedby={flow.termsError ? 'terms-error' : undefined}
|
|
className="text-sm leading-7 text-tertiary-400"
|
|
>
|
|
{texts.auth.termsConfirmPrefix}{' '}
|
|
<Link
|
|
className="font-bold text-primary underline underline-offset-4"
|
|
href="/terms"
|
|
target="_blank"
|
|
>
|
|
{texts.auth.termsLink}
|
|
</Link>{' '}
|
|
{texts.auth.termsConfirmSuffix}
|
|
</span>
|
|
</Checkbox>
|
|
{flow.termsError ? (
|
|
<p
|
|
className="mt-2 text-sm text-fourth-900"
|
|
id="terms-error"
|
|
>
|
|
{flow.termsError}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
<Button
|
|
fullWidth
|
|
className="mt-6"
|
|
isLoading={flow.loading}
|
|
type="submit"
|
|
>
|
|
{flow.isRegistration ? texts.auth.confirmAndContinueRegister : texts.common.login}
|
|
</Button>
|
|
</form>
|
|
</FormProvider>
|
|
<div className="my-6 text-center text-sm text-secondary-20">
|
|
{flow.resendCodeTime > 0 ? (
|
|
<>
|
|
<Progress
|
|
aria-label={texts.auth.resendTimerAria}
|
|
className="mb-4 max-w-md [&>div]:bg-tertiary-50 [&>div>div]:bg-tertiary-100"
|
|
size="md"
|
|
value={((flow.otpTtl - flow.resendCodeTime) / flow.otpTtl) * 100}
|
|
/>
|
|
<p>{texts.auth.didNotReceiveCode}</p>
|
|
<p className="mt-1 text-tertiary-600">{format(texts.auth.resendInSeconds, { seconds: flow.resendCodeTime })}</p>
|
|
</>
|
|
) : (
|
|
<Button
|
|
className="text-primary"
|
|
variant="light"
|
|
onClick={() => flow.sendOtp({ mobile: flow.mobile })}
|
|
>
|
|
{texts.auth.resendCode}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<Button
|
|
fullWidth
|
|
color="warning"
|
|
variant="light"
|
|
onClick={flow.resetToMobile}
|
|
>
|
|
{texts.auth.changeMobile}
|
|
</Button>
|
|
</>
|
|
) : null}
|
|
|
|
{flow.step === 'profile' ? (
|
|
<FormProvider {...flow.completeProfileForm}>
|
|
<form
|
|
className="flex flex-col gap-5"
|
|
onSubmit={flow.completeProfileForm.handleSubmit(flow.submitProfile, showFormValidationToast)}
|
|
>
|
|
<div>
|
|
<ConsumerInput
|
|
required
|
|
generalType="input"
|
|
label={texts.common.firstName}
|
|
name="firstName"
|
|
placeholder={texts.common.firstNamePlaceholder}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<ConsumerInput
|
|
required
|
|
generalType="input"
|
|
label={texts.common.lastName}
|
|
name="lastName"
|
|
placeholder={texts.common.lastNamePlaceholder}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<ConsumerInput
|
|
required
|
|
generalType="radio"
|
|
label={texts.common.gender}
|
|
name="gender"
|
|
orientation="horizontal"
|
|
radioOptions={GENDER_OPTIONS}
|
|
selectKey="selectKey"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Input
|
|
required
|
|
generalType="birthDatePicker"
|
|
label={texts.common.birthDate}
|
|
name="dateOfBirth"
|
|
placeholder={texts.common.birthDatePlaceholder}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<ConsumerInput
|
|
required
|
|
generalType="select"
|
|
label={texts.common.city}
|
|
name="cityId"
|
|
placeholder={citiesQuery.isPending ? texts.auth.loadingCities : texts.common.selectCity}
|
|
selectKey="id"
|
|
selectOptions={cityOptions}
|
|
selectValue="name"
|
|
/>
|
|
{citiesQuery.isError ? <p className="mt-2 text-sm text-fourth-900">{texts.auth.citiesLoadFailed}</p> : null}
|
|
</div>
|
|
<Button
|
|
fullWidth
|
|
isLoading={flow.loading}
|
|
type="submit"
|
|
>
|
|
{texts.auth.completeRegistration}
|
|
</Button>
|
|
</form>
|
|
</FormProvider>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|