Skip to content

Commit

Permalink
Signup/login frontend (#28)
Browse files Browse the repository at this point in the history
* first couple of screens in flow

* almost the entire sign up flow

* small ui issue

* fix

* remove unused asset

* fix imports/lint

* lint

* test

* fix

---------

Co-authored-by: owen <[email protected]>
Co-authored-by: ispapa <[email protected]>
  • Loading branch information
3 people authored Dec 4, 2024
1 parent 3b35c1b commit 92e62f5
Show file tree
Hide file tree
Showing 34 changed files with 1,089 additions and 253 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/bun-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ jobs:
- uses: ./.github/actions/setup-bun

- name: Apply database schema
run: bun db:push
run: |
bun prisma db push \
--schema=./packages/db/prisma/schema.prisma
- name: Run tests
run: bun test
7 changes: 5 additions & 2 deletions apps/web/app/(registration)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { SignInForm } from "@good-dog/components/registration";
import {
RegistrationPageLayout,
SignInForm,
} from "@good-dog/components/registration";

export default function Page() {
return <SignInForm />;
return <RegistrationPageLayout form={<SignInForm />} formLocation="right" />;
}
7 changes: 5 additions & 2 deletions apps/web/app/(registration)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { SignUpForm } from "@good-dog/components/registration";
import {
RegistrationPageLayout,
SignUpForm,
} from "@good-dog/components/registration";

export default function Page() {
return <SignUpForm />;
return <RegistrationPageLayout form={<SignUpForm />} formLocation="left" />;
}
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@good-dog/components": "workspace:*",
"@good-dog/env": "workspace:*",
"@good-dog/tailwind": "workspace:*",
"@good-dog/trpc": "workspace:*",
"@good-dog/ui": "workspace:*",
"next": "14.2.18",
Expand Down
18 changes: 18 additions & 0 deletions apps/web/public/icons/back_button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"@good-dog/trpc": "workspace:*",
"@hookform/resolvers": "^3.9.1",
"clsx": "^2.1.1",
"next": "14.2.18",
"react": "18.3.1",
"react-dom": "18.3.1",
Expand Down
22 changes: 22 additions & 0 deletions packages/components/src/CheckerColumn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const svgPattern = (sqSize: number) => {
return `<svg xmlns="http://www.w3.org/2000/svg" width="${2 * sqSize}" height="${2 * sqSize}"><rect width="${sqSize}" height="${sqSize}" fill="#0D0039"/><rect x="${sqSize}" width="${sqSize}" height="${sqSize}" fill="#ACDD92"/><rect y="${sqSize}" width="${sqSize}" height="${sqSize}" fill="#ACDD92"/><rect x="${sqSize}" y="${sqSize}" width="${sqSize}" height="${sqSize}" fill="#0D0039"/></svg>`;
};

export default function CheckerColumn(
props: Readonly<{
squareSize?: number;
numSquares: number;
className?: string;
}>,
) {
const sqSize = props.squareSize ?? 20;
const encodedPattern = `data:image/svg+xml;base64,${btoa(svgPattern(sqSize))}`;

const style = {
width: `${sqSize * props.numSquares}px`,
backgroundImage: `url(${encodedPattern})`,
backgroundRepeat: "repeat",
};

return <div style={style} className={props.className} />;
}
12 changes: 6 additions & 6 deletions packages/components/src/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,30 @@ export default function Footer() {
/>
<div className="flex flex-row items-end justify-between px-12 py-16">
<div className="ml-24 flex flex-grow justify-center space-x-4">
<a href="https://twitter.com/gdlicensing">
<Link href="https://x.com/gdlicensing">
<Image
src="/icons/twitter_icon.svg"
width={70}
height={70}
alt="good-dog-twitter"
/>
</a>
<a href="https://www.instagram.com/gooddoglicensing/">
</Link>
<Link href="https://www.instagram.com/gooddoglicensing/">
<Image
src="/icons/instagram_icon.svg"
width={70}
height={70}
alt="good-dog-instagram"
/>
</a>
<a href="mailto:[email protected]">
</Link>
<Link href="mailto:[email protected]">
<Image
src="/icons/email_icon.svg"
width={70}
height={70}
alt="good-dog-email"
/>
</a>
</Link>
</div>
<Link href="/">
<Image
Expand Down
65 changes: 65 additions & 0 deletions packages/components/src/GoodDogLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from "react";
import clsx from "clsx";

type GoodDogLogoProps = Omit<
React.ComponentPropsWithoutRef<"svg">,
"viewBox" | "fill" | "xmlns" | "transform"
> & {
variant: "dark" | "light";
facingDirection: "left" | "right";
};

const GoodDogLogo = React.forwardRef<React.ElementRef<"svg">, GoodDogLogoProps>(
({ variant, facingDirection, ...props }, ref) => {
const primaryColor = variant === "dark" ? "#0D0039" : "#ACDD92";
const secondaryColor = variant === "dark" ? "#ACDD92" : "#0D0039";

return (
<svg
ref={ref}
viewBox="0 0 120 120"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
className={clsx(
"transform",
facingDirection === "left" ? "scale-x-[-1]" : "scale-x-[1]",
"origin-center",
)}
>
<circle cx="60" cy="60" r="60" fill={primaryColor} />
<path
d="M72.1534 53.5103L64.221 53.9649C63.6761 53.9962 63.2597 54.4632 63.2909 55.0081L63.4095 57.0767C63.4407 57.6216 63.9077 58.038 64.4526 58.0068L72.3851 57.5522C72.93 57.521 73.3464 57.0539 73.3151 56.509L73.1966 54.4404C73.1654 53.8955 72.6983 53.4791 72.1534 53.5103Z"
fill={secondaryColor}
/>
<path
d="M84.9762 53.1849L84.4129 57.9877C84.1856 59.9148 85.4077 61.72 87.2854 62.2207L93.7287 63.9501L96.2553 52.1209L87.4138 51.2282C86.2049 51.1063 85.1178 51.976 84.9762 53.1849Z"
fill={secondaryColor}
/>
<path
d="M60.0009 4.44531C29.3687 4.44531 4.44531 29.3654 4.44531 60.0009C4.44531 66.0258 5.41049 71.8268 7.19262 77.2621L12.7037 75.0287C11.1884 70.2852 10.3748 65.2385 10.3748 60.0009C10.3748 32.6365 32.6365 10.3748 60.0009 10.3748C87.3652 10.3748 109.627 32.6365 109.627 60.0009C109.627 78.4612 99.4975 94.5992 84.5026 103.147L84.2325 109.989C102.759 100.97 115.556 81.953 115.556 60.0009C115.556 29.3654 90.6363 4.44531 60.0009 4.44531ZM72.4428 108.046C70.4927 108.553 68.4964 108.942 66.4607 109.209C64.3458 109.485 62.1882 109.627 60.0009 109.627C44.2681 109.627 30.2219 102.268 21.1235 90.8109C19.8618 89.2231 18.699 87.5596 17.6383 85.8236C16.604 84.1337 15.6684 82.3779 14.8383 80.5629L9.33709 82.7897C10.1573 84.608 11.0731 86.3704 12.0778 88.0768C13.1023 89.8227 14.219 91.506 15.4214 93.1201C25.5607 106.728 41.7678 115.556 60.0009 115.556C62.1025 115.556 64.1811 115.438 66.2235 115.211C68.2494 114.983 70.2456 114.647 72.199 114.206C74.2447 113.748 76.2475 113.175 78.201 112.496L78.4546 106.069C76.5078 106.853 74.5016 107.515 72.4428 108.046Z"
fill={secondaryColor}
/>
<path
d="M51.3208 43.5684L51.5448 43.9571H51.3208V43.5684Z"
fill={secondaryColor}
/>
<path
d="M99.4706 54.0593L95.9886 77.2599C95.847 78.2053 95.2606 79.0189 94.414 79.457L79.1952 87.3004L78.454 106.067L78.2003 112.494C76.2469 113.173 74.2441 113.746 72.1984 114.204L72.4422 108.044L73.3382 85.3404C73.381 84.2731 73.9937 83.3112 74.9424 82.8237L90.3491 74.8815L93.2216 55.7492L82.3081 52.6C81.6822 52.4188 81.1288 52.0367 80.7434 51.5096L75.1862 43.9529H58.2181C57.8162 43.0602 57.3715 42.1839 56.8774 41.3307L54.9668 38.0234H76.685C77.6305 38.0234 78.5166 38.4714 79.0766 39.2324L84.9236 47.1844L97.359 50.7717C98.7887 51.1835 99.688 52.5901 99.4706 54.0593Z"
fill={secondaryColor}
/>
<path
d="M63.9663 76.5447C60.412 72.0877 53.7018 71.5277 48.9747 75.2929C45.2194 78.2873 43.8524 83.1132 45.2326 87.2045C40.6669 84.7701 37.3563 80.6327 35.8311 78.4553L39.2043 77.0882C40.8448 76.4228 42.2745 75.708 43.5691 74.9042C50.5032 70.5922 54.4825 64.0632 54.7757 56.5197C54.8614 54.2731 54.6143 52.0363 54.0444 49.882C53.5338 47.9384 52.7597 46.0608 51.7418 44.2951L51.5442 43.9525L51.3202 43.5638L48.5498 38.7676L45.3907 40.5925L22.0814 54.0523C19.0673 55.7916 18.0296 59.6623 19.7689 62.6797L27.2005 75.5532L14.8376 80.5602L9.33643 82.7871C10.1567 84.6054 11.0724 86.3678 12.0771 88.0742L17.6376 85.821L30.2212 80.7283C32.313 84.0026 39.7775 94.3001 51.2609 95.0215C51.5969 95.0445 51.923 95.0544 52.2425 95.0544C55.7475 95.0544 58.4124 93.8389 60.3032 92.4751C60.8336 92.1754 61.3508 91.8328 61.8482 91.4375C66.572 87.669 67.5207 81.0017 63.9663 76.5447Z"
fill={secondaryColor}
/>
</g>
</svg>
);
},
);

GoodDogLogo.displayName = "GoodDogLogo";

export default GoodDogLogo;
114 changes: 91 additions & 23 deletions packages/components/src/registration/EmailVerifyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { z } from "zod";

import { trpc } from "@good-dog/trpc/client";
Expand All @@ -14,8 +14,7 @@ import {
DialogHeader,
DialogTitle,
} from "@good-dog/ui/dialog";
import { Input } from "@good-dog/ui/input";
import { Label } from "@good-dog/ui/label";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@good-dog/ui/input-otp";

const zConfirmEmail = z.object({
code: z
Expand All @@ -27,20 +26,30 @@ const zConfirmEmail = z.object({
export default function EmailVerifyModal({
email = "",
isOpen = true,
close,
}: {
email: string;
isOpen?: boolean;
close: () => void;
}) {
const confirmEmailForm = useForm<z.infer<typeof zConfirmEmail>>({
resolver: zodResolver(zConfirmEmail),
defaultValues: {
code: "",
},
});
const signUpFormContext = useFormContext<{
emailConfirmed: string;
}>();

const confirmEmailMutation = trpc.confirmEmail.useMutation({
onSuccess: (data) => {
switch (data.status) {
case "SUCCESS":
signUpFormContext.setValue("emailConfirmed", data.email);
close();
// TODO
// When the email is confirmed, show an toast or something
// When the email is confirmed, show a toast or something
break;
case "RESENT":
// TODO
Expand All @@ -61,39 +70,98 @@ export default function EmailVerifyModal({
},
});

const resendVerificationEmailMutation =
trpc.sendEmailVerification.useMutation({
onSuccess: (data) => {
switch (data.status) {
case "EMAIL_SENT":
// TODO
// alert somehow that a verification email was sent
break;
case "ALREADY_VERIFIED":
signUpFormContext.setValue("emailConfirmed", data.email);
close();
// TODO
// alert somehow that the email has already been verified
break;
}
},
onError: (err) => {
// TODO
// Alert toast to the user that there was an error sending the verification email
console.error(err);
},
});

const onSubmit = confirmEmailForm.handleSubmit((values) => {
confirmEmailMutation.mutate({
...values,
email,
});
});

const enteredCode = confirmEmailForm.watch("code");

return (
<Dialog open={isOpen && confirmEmailMutation.data?.status !== "SUCCESS"}>
<DialogContent className="sm:max-w-[425px]" hideCloseButton>
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) {
close();
}
}}
>
<DialogContent className="border-black sm:max-w-[512px]">
<DialogHeader>
<DialogTitle>Email Verification</DialogTitle>
<DialogDescription>
Enter the 6-digit code sent to your email address.
<DialogTitle>Verify Email</DialogTitle>
<DialogDescription className="font-afacad p-7 text-center text-2xl font-normal text-good-dog-violet">
A 6-digit code has been sent to your email. Please enter the code
below.
</DialogDescription>
<Button
disabled={
resendVerificationEmailMutation.isPending ||
resendVerificationEmailMutation.isSuccess
}
onClick={(e) => {
e.preventDefault();
resendVerificationEmailMutation.mutate({ email });
}}
>
Resend Email
</Button>
</DialogHeader>
<form onSubmit={onSubmit}>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="verification-code" className="text-right">
Code
</Label>
<Input
{...confirmEmailForm.register("code")}
type="text"
placeholder="Enter 6-digit code"
maxLength={6}
className="col-span-3"
/>
</div>
{confirmEmailMutation.isError && (
<p className="text-good-dog-error">
Invalid code: {confirmEmailMutation.error.message}
</p>
)}
<div className="flex items-center justify-center py-4">
<Controller
name="code"
control={confirmEmailForm.control}
render={({ field }) => (
<InputOTP onChange={field.onChange} maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
)}
/>
</div>
<DialogFooter>
<Button type="submit" disabled={confirmEmailMutation.isPending}>
<Button
type="submit"
disabled={
confirmEmailMutation.isPending || enteredCode.length !== 6
}
>
{confirmEmailMutation.isPending ? "Verifying..." : "Verify"}
</Button>
</DialogFooter>
Expand Down
Loading

0 comments on commit 92e62f5

Please sign in to comment.