- Introduced layout components for multiple admin dashboard sections including events, audit logs, bank accounts, blog articles, bookings, chat oversight, cities, contact messages, dashboard, discount codes, event categories, guest lists, identity verifications, manage events, manual notifications, notification rules, payments, provinces, push deliveries, reviews, settlements, sms messages, support tickets, user reports, and users. - Updated metadata for the auth layout to include the admin page title. - Modified branch protection documentation to clarify deployment notifications.
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/useDiscoveryCitiesQuery'
|
|
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>
|
|
)
|
|
}
|