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.
+
+
+
+
+ );
+};
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 (
-