-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wallet/frontend): add card settings (#1564)
* Initial card settings - spending limit, card pin * Add dialogs * Add change PIN dialog + card front scaling * Add change card PIN form functionality * Remove console logs * Add spending limit settings * Remove leftover settings button * Add comments * Remove comments * Fix build * Move dialogs
- Loading branch information
1 parent
3745071
commit 7bc7189
Showing
9 changed files
with
670 additions
and
95 deletions.
There are no files selected for viewing
142 changes: 142 additions & 0 deletions
142
packages/wallet/frontend/src/components/dialogs/UserCardPINDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { Dialog, Transition } from '@headlessui/react' | ||
import { Fragment, useState } from 'react' | ||
import type { DialogProps } from '@/lib/types/dialog' | ||
import { cardServiceMock, changePinSchema, IUserCard } from '@/lib/api/card' | ||
import { UserCardFront } from '@/components/userCards/UserCard' | ||
import { Button } from '@/ui/Button' | ||
import { useZodForm } from '@/lib/hooks/useZodForm' | ||
import { Form } from '@/ui/forms/Form' | ||
import { useRouter } from 'next/router' | ||
import { getObjectKeys } from '@/utils/helpers' | ||
import { Input } from '@/ui/forms/Input' | ||
|
||
type UserCardPINDialogProos = Pick<DialogProps, 'onClose'> & { | ||
card: IUserCard | ||
} | ||
|
||
export const UserCardPINDialog = ({ | ||
card, | ||
onClose | ||
}: UserCardPINDialogProos) => { | ||
return ( | ||
<Transition.Root show={true} as={Fragment} appear={true}> | ||
<Dialog as="div" className="relative z-10" onClose={onClose}> | ||
<Transition.Child | ||
as={Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0" | ||
enterTo="opacity-100" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100" | ||
leaveTo="opacity-0" | ||
> | ||
<div className="fixed inset-0 bg-green-modal/75 transition-opacity dark:bg-black/75" /> | ||
</Transition.Child> | ||
|
||
<div className="fixed inset-0 z-10 overflow-y-auto"> | ||
<div className="flex min-h-full items-center justify-center p-4"> | ||
<Transition.Child | ||
as={Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0 translate-y-4" | ||
enterTo="opacity-100 translate-y-0" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100 translate-y-0" | ||
leaveTo="opacity-0 translate-y-4" | ||
> | ||
<Dialog.Panel className="relative w-full max-w-sm space-y-4 overflow-hidden rounded-lg bg-white p-8 shadow-xl dark:bg-purple"> | ||
<Dialog.Title | ||
as="h3" | ||
className="text-center text-2xl font-bold" | ||
> | ||
Card PIN | ||
</Dialog.Title> | ||
<div className="flex justify-between space-x-5"> | ||
<UserCardFront | ||
card={card} | ||
className="origin-top-left scale-[.3] [margin:0_calc(-20rem*(1-.3))_calc(-13rem*(1-0.3))_0] " | ||
/> | ||
<div> | ||
<p className="text-base pt-2">Physical Debit Card</p> | ||
<p className="text-black/50 dark:text-white/50 text-sm"> | ||
{card.number} | ||
</p> | ||
</div> | ||
</div> | ||
<ChangePinForm /> | ||
</Dialog.Panel> | ||
</Transition.Child> | ||
</div> | ||
</div> | ||
</Dialog> | ||
</Transition.Root> | ||
) | ||
} | ||
|
||
const ChangePinForm = () => { | ||
const [showForm, setShowForm] = useState(false) | ||
const router = useRouter() | ||
const form = useZodForm({ | ||
schema: changePinSchema | ||
}) | ||
|
||
if (!showForm) { | ||
return ( | ||
<Button | ||
className="w-full" | ||
aria-label="show change pin form" | ||
onClick={() => setShowForm(true)} | ||
> | ||
Change PIN | ||
</Button> | ||
) | ||
} | ||
return ( | ||
<Form | ||
form={form} | ||
onSubmit={async (data) => { | ||
const response = await cardServiceMock.changePin(data) | ||
|
||
if (response.success) { | ||
router.replace(router.asPath) | ||
} else { | ||
const { errors, message } = response | ||
form.setError('root', { | ||
message | ||
}) | ||
if (errors) { | ||
getObjectKeys(errors).map((field) => | ||
form.setError(field, { | ||
message: errors[field] | ||
}) | ||
) | ||
} | ||
} | ||
}} | ||
> | ||
<Input | ||
type="password" | ||
inputMode="numeric" | ||
maxLength={4} | ||
placeholder="Enter PIN" | ||
error={form.formState?.errors?.pin?.message} | ||
{...form.register('pin')} | ||
/> | ||
<Input | ||
type="password" | ||
inputMode="numeric" | ||
maxLength={4} | ||
placeholder="Repeat PIN" | ||
error={form.formState?.errors?.confirmPin?.message} | ||
{...form.register('confirmPin')} | ||
/> | ||
<Button | ||
aria-label="change pin" | ||
type="submit" | ||
loading={form.formState.isSubmitting} | ||
> | ||
Confirm PIN change | ||
</Button> | ||
</Form> | ||
) | ||
} |
177 changes: 177 additions & 0 deletions
177
packages/wallet/frontend/src/components/dialogs/UserCardSpendingLimitDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { Dialog, Transition } from '@headlessui/react' | ||
import { Fragment } from 'react' | ||
import type { DialogProps } from '@/lib/types/dialog' | ||
import { | ||
cardServiceMock, | ||
dailySpendingLimitSchema, | ||
monthlySpendingLimitSchema | ||
} from '@/lib/api/card' | ||
import { useRouter } from 'next/router' | ||
import { useZodForm } from '@/lib/hooks/useZodForm' | ||
import { getObjectKeys } from '@/utils/helpers' | ||
import { Form } from '@/ui/forms/Form' | ||
import { Input } from '@/ui/forms/Input' | ||
import { Button } from '@/ui/Button' | ||
|
||
type UserCardSpendingLimitDialogProos = Pick<DialogProps, 'onClose'> | ||
|
||
export const UserCardSpendingLimitDialog = ({ | ||
onClose | ||
}: UserCardSpendingLimitDialogProos) => { | ||
return ( | ||
<Transition.Root show={true} as={Fragment} appear={true}> | ||
<Dialog as="div" className="relative z-10" onClose={onClose}> | ||
<Transition.Child | ||
as={Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0" | ||
enterTo="opacity-100" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100" | ||
leaveTo="opacity-0" | ||
> | ||
<div className="fixed inset-0 bg-green-modal/75 transition-opacity dark:bg-black/75" /> | ||
</Transition.Child> | ||
|
||
<div className="fixed inset-0 z-10 overflow-y-auto"> | ||
<div className="flex min-h-full items-center justify-center p-4"> | ||
<Transition.Child | ||
as={Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0 translate-y-4" | ||
enterTo="opacity-100 translate-y-0" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100 translate-y-0" | ||
leaveTo="opacity-0 translate-y-4" | ||
> | ||
<Dialog.Panel className="relative w-full max-w-xl space-y-4 overflow-hidden rounded-lg bg-white p-8 shadow-xl dark:bg-purple"> | ||
<Dialog.Title | ||
as="h3" | ||
className="text-center text-2xl font-bold" | ||
> | ||
Spending Limit | ||
</Dialog.Title> | ||
<div className="space-y-10"> | ||
<DailySpendingLimitForm /> | ||
<MonthlySpendingLimitForm /> | ||
</div> | ||
</Dialog.Panel> | ||
</Transition.Child> | ||
</div> | ||
</div> | ||
</Dialog> | ||
</Transition.Root> | ||
) | ||
} | ||
|
||
// TODO: We will probably need to fetch the existing limitt | ||
const DailySpendingLimitForm = () => { | ||
const router = useRouter() | ||
const form = useZodForm({ | ||
schema: dailySpendingLimitSchema | ||
}) | ||
|
||
return ( | ||
<Form | ||
form={form} | ||
onSubmit={async (data) => { | ||
const response = await cardServiceMock.setDailySpendingLimit(data) | ||
|
||
if (response.success) { | ||
router.replace(router.asPath) | ||
} else { | ||
const { errors, message } = response | ||
form.setError('root', { | ||
message | ||
}) | ||
if (errors) { | ||
getObjectKeys(errors).map((field) => | ||
form.setError(field, { | ||
message: errors[field] | ||
}) | ||
) | ||
} | ||
} | ||
}} | ||
> | ||
<div className="flex gap-x-5"> | ||
<div className="flex-1"> | ||
<Input | ||
type="password" | ||
inputMode="numeric" | ||
className="w-full" | ||
maxLength={4} | ||
label="Daily Spending Limit" | ||
placeholder="Set daily spending limit" | ||
error={form.formState?.errors?.amount?.message} | ||
{...form.register('amount')} | ||
/> | ||
</div> | ||
<Button | ||
aria-label="change pin" | ||
className="self-end" | ||
type="submit" | ||
loading={form.formState.isSubmitting} | ||
> | ||
Save | ||
</Button> | ||
</div> | ||
</Form> | ||
) | ||
} | ||
|
||
// TODO: We will probably need to fetch the existing limitt | ||
const MonthlySpendingLimitForm = () => { | ||
const router = useRouter() | ||
const form = useZodForm({ | ||
schema: monthlySpendingLimitSchema | ||
}) | ||
|
||
return ( | ||
<Form | ||
form={form} | ||
onSubmit={async (data) => { | ||
const response = await cardServiceMock.setMonthlySpendingLimit(data) | ||
|
||
if (response.success) { | ||
router.replace(router.asPath) | ||
} else { | ||
const { errors, message } = response | ||
form.setError('root', { | ||
message | ||
}) | ||
if (errors) { | ||
getObjectKeys(errors).map((field) => | ||
form.setError(field, { | ||
message: errors[field] | ||
}) | ||
) | ||
} | ||
} | ||
}} | ||
> | ||
<div className="flex gap-x-5"> | ||
<div className="flex-1"> | ||
<Input | ||
type="password" | ||
inputMode="numeric" | ||
className="w-full" | ||
maxLength={4} | ||
label="Monthly Spending Limit" | ||
placeholder="Set monthly spending limit" | ||
error={form.formState?.errors?.amount?.message} | ||
{...form.register('amount')} | ||
/> | ||
</div> | ||
<Button | ||
aria-label="change pin" | ||
className="self-end" | ||
type="submit" | ||
loading={form.formState.isSubmitting} | ||
> | ||
Save | ||
</Button> | ||
</div> | ||
</Form> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.