diff --git a/package-lock.json b/package-lock.json index b280067d..2413ba6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "next-themes": "^0.3.0", "nostr-tools": "^2.7.0", "react": "^18", + "react-confetti": "^6.1.0", "react-dom": "^18", "react-hook-form": "^7.52.1", "sharp": "^0.33.4", @@ -12649,6 +12650,20 @@ "node": ">=0.10.0" } }, + "node_modules/react-confetti": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.1.0.tgz", + "integrity": "sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==", + "dependencies": { + "tween-functions": "^1.2.0" + }, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.1 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -13935,6 +13950,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tween-functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", + "integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 4887cf9f..d096ab53 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "next-themes": "^0.3.0", "nostr-tools": "^2.7.0", "react": "^18", + "react-confetti": "^6.1.0", "react-dom": "^18", "react-hook-form": "^7.52.1", "sharp": "^0.33.4", diff --git a/src/app/success/page.tsx b/src/app/success/page.tsx new file mode 100644 index 00000000..af239159 --- /dev/null +++ b/src/app/success/page.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { ChevronLeft } from 'lucide-react'; +import Link from 'next/link'; +import { ReactNode, useMemo } from 'react'; +import Confetti from 'react-confetti'; +import { useIsClient } from 'usehooks-ts'; + +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { ROUTES } from '@/utils/routes'; + +export default function Page({ + searchParams, +}: { + searchParams: { variant: string }; +}) { + const isClient = useIsClient(); + + const content = useMemo((): { + title: string; + description?: string; + button?: ReactNode; + } => { + switch (searchParams.variant) { + case 'waitlist': + return { + title: "You're On the List!", + description: + "You're one step closer to experiencing MiBanco. We'll notify you as soon as we're ready for you.", + button: ( + + +

Home

+ + ), + }; + + default: + return { title: 'Success!' }; + } + }, [searchParams]); + + return ( +
+ {isClient ? : null} +
+ {content.button || null} + + + {content.title} + + {content.description ? ( + +

{content.description}

+
+ ) : null} +
+
+
+ ); +} diff --git a/src/components/form/SignUpForm.tsx b/src/components/form/SignUpForm.tsx index 5aa3d59c..409aef9c 100644 --- a/src/components/form/SignUpForm.tsx +++ b/src/components/form/SignUpForm.tsx @@ -46,6 +46,7 @@ import { import { Checkbox } from '../ui/checkbox'; import { Progress } from '../ui/progress'; import { useToast } from '../ui/use-toast'; +import { WaitlistForm } from './WaitlistForm'; const FormSchema = z .object({ @@ -87,6 +88,9 @@ export function SignUpForm() { const workerRef = useRef(); + const [view, setView] = useState<'waitlist' | 'sign-up'>( + referralParam ? 'sign-up' : 'waitlist' + ); const [loading, setLoading] = useState(true); const [clickedGenerate, setClickedGenerate] = useState(false); const [showPassword, setShowPassword] = useState(false); @@ -207,7 +211,9 @@ export function SignUpForm() { setClickedGenerate(true); }; - return ( + return view === 'waitlist' ? ( + + ) : ( Sign up diff --git a/src/components/form/WaitlistForm.tsx b/src/components/form/WaitlistForm.tsx new file mode 100644 index 00000000..07e84d8d --- /dev/null +++ b/src/components/form/WaitlistForm.tsx @@ -0,0 +1,158 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Loader2 } from 'lucide-react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Dispatch, FC, SetStateAction, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { ROUTES } from '@/utils/routes'; + +import { Button } from '../ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '../ui/card'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '../ui/form'; +import { Input } from '../ui/input'; +import { useToast } from '../ui/use-toast'; + +const FormSchema = z.object({ + email: z.string().email().min(5, { + message: 'Invalid email.', + }), +}); + +export const WaitlistForm: FC<{ + setView: Dispatch>; +}> = ({ setView }) => { + const searchParams = useSearchParams(); + const emailParam = searchParams.get('email'); + + const { push } = useRouter(); + const { toast } = useToast(); + + const [loading, setLoading] = useState(false); + + const form = useForm>({ + reValidateMode: 'onChange', + resolver: zodResolver(FormSchema), + defaultValues: { + email: emailParam || '', + }, + }); + + const onSubmit = async (values: z.infer) => { + setLoading(true); + + try { + const result = await fetch('https://reflex.amboss.space/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + variables: { input: { email: values.email, interest: 'BANCO' } }, + query: `mutation Add_interest($input: WaitlistInput!) { + public { + waitlist { + add_interest(input: $input) { + email + } + } + } + }`, + }), + }); + + const response = await result.json(); + + if (response.data) { + push(ROUTES.success.waitlist); + } else { + toast({ + variant: 'destructive', + title: 'Error joining waitlist.', + description: response.errors + .map((e: { message: string }) => e.message) + .join(', '), + }); + } + } catch (error) { + console.log(error); + + toast({ + variant: 'destructive', + title: 'Error joining waitlist.', + description: 'Please try again.', + }); + } finally { + setLoading(false); + } + }; + + return ( + + + The MiBanco Waitlist + Join us . No bank required. + +
+ + + ( + + Email + + + + + + {"We'll notify you as soon as we're ready for you."} + + + )} + /> + + + +
+ + + +
+
+
+ +
+ ); +}; diff --git a/src/utils/routes.ts b/src/utils/routes.ts index 87a76e0f..f7047b17 100644 --- a/src/utils/routes.ts +++ b/src/utils/routes.ts @@ -28,6 +28,9 @@ export const ROUTES = { restore: '/setup/wallet/restore', }, }, + success: { + waitlist: '/success?variant=waitlist', + }, docs: { privacyPolicy: '/docs/privacy-policy', termsOfService: '/docs/terms-of-service', diff --git a/src/views/wallet/Settings.tsx b/src/views/wallet/Settings.tsx index b94e2cdf..d94970d4 100644 --- a/src/views/wallet/Settings.tsx +++ b/src/views/wallet/Settings.tsx @@ -255,7 +255,7 @@ export const WalletSettings: FC<{ walletId: string }> = ({ walletId }) => { } return ( -
+