Skip to content

Commit 00b62f3

Browse files
authored
Merge pull request #22 from Developer-DAO/registration
Registration
2 parents 2504f11 + b683a58 commit 00b62f3

15 files changed

+3397
-100
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
# testing
1010
/coverage
11+
/playwright-report
1112

1213
# next.js
1314
/.next/
@@ -34,6 +35,12 @@ yarn-error.log*
3435
# typescript
3536
*.tsbuildinfo
3637
next-env.d.ts
38+
39+
40+
# lockfiles
41+
yarn.lock
42+
43+
# tests
3744
/test-results/
3845
/playwright-report/
3946
/blob-report/

app/authentication/_components/user-login-form.tsx

+42-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
"use client"
22

33
import * as React from "react"
4-
4+
import { useAccount, useConnect, useDisconnect } from 'wagmi'
55
import { cn } from "@/lib/utils"
66
import { Icons } from "@/components/ui/icons"
77
import { Button } from "@/components/ui/button"
88
import { Input } from "@/components/ui/input"
99
import { Label } from "@/components/ui/label"
10+
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
11+
import { WalletButton } from "@/components/wallet-buttons"
1012

11-
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
13+
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> { }
1214

13-
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
15+
export default function UserLoginForm({ className, ...props }: UserAuthFormProps) {
1416
const [isLoading, setIsLoading] = React.useState<boolean>(false)
1517

18+
const account = useAccount()
19+
const { connectors, connect, status, error } = useConnect()
20+
const { disconnect } = useDisconnect()
21+
1622
async function onSubmit(event: React.SyntheticEvent) {
1723
event.preventDefault()
1824
setIsLoading(true)
@@ -22,17 +28,41 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
2228
}, 3000)
2329
}
2430

31+
const loginButtons = connectors.map((connector) =>
32+
connector.name.toLowerCase() === "injected" ? null : (
33+
<WalletButton
34+
connector={connector}
35+
isLoading={isLoading}
36+
connect={connect}
37+
key={connector.uid}
38+
/>
39+
)
40+
);
41+
2542
return (
2643
<div className={cn("grid gap-6", className)} {...props}>
27-
<Button variant="outline" type="button" disabled={isLoading} className="p-2">
28-
{isLoading ? (
29-
<Icons.spinner className="mr-2 h-6 w-6 animate-spin" />
30-
) : (
31-
<Icons.ethereum className="mr-2 h-6 w-6" />
32-
)}{" "}
33-
Wallet
34-
</Button>
3544

45+
{account.status === 'connected' ? (
46+
<HoverCard>
47+
<HoverCardTrigger asChild>
48+
<Button variant="destructive" onClick={() => disconnect()}>Disconnect</Button>
49+
</HoverCardTrigger>
50+
<HoverCardContent className="w-80">
51+
<div className="flex justify-between space-x-4">
52+
<div className="space-y-1">
53+
<h4 className="text-sm font-semibold">connected as</h4>
54+
<div className="text-xs">
55+
{JSON.stringify(account.addresses[0].replaceAll(account.addresses[0].slice(8, 36), '...'))}
56+
</div>
57+
</div>
58+
</div>
59+
</HoverCardContent>
60+
</HoverCard>
61+
) : (
62+
<div className="grid gap-1">
63+
{loginButtons}
64+
</div>
65+
)}
3666
<div className="relative">
3767
<div className="relative flex justify-center text-xs uppercase">
3868
<span className="bg-background px-2 text-neutral-500">
@@ -79,7 +109,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
79109
</Button>
80110
</div>
81111
</form>
82-
112+
83113

84114
</div>
85115
)
+144-54
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,162 @@
11
"use client"
22

33
import * as React from "react"
4-
4+
import { useAccount, useConnect, useDisconnect } from 'wagmi'
55
import { cn } from "@/lib/utils"
6-
import { Icons } from "@/components/ui/icons"
76
import { Button } from "@/components/ui/button"
87
import { Input } from "@/components/ui/input"
98
import { Label } from "@/components/ui/label"
9+
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
10+
import { WalletButton } from "@/components/wallet-buttons"
11+
import { SubmitButton } from "@/components/form-submit-button";
12+
import { registerUser } from "../authbfe";
13+
import { useFormState, useFormStatus } from "react-dom";
1014

11-
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
15+
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> { }
1216

13-
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
14-
const [isLoading, setIsLoading] = React.useState<boolean>(false)
1517

16-
async function onSubmit(event: React.SyntheticEvent) {
17-
event.preventDefault()
18-
setIsLoading(true)
18+
export default function UserRegForm({ className, ...props }: UserAuthFormProps) {
1919

20-
setTimeout(() => {
21-
setIsLoading(false)
22-
}, 3000)
23-
}
20+
const account = useAccount()
21+
const { connectors, connect, status, error } = useConnect()
22+
const { disconnect } = useDisconnect()
2423

25-
return (
26-
<div className={cn("grid gap-6", className)} {...props}>
27-
<Button variant="outline" type="button" disabled={isLoading} className="p-2">
28-
{isLoading ? (
29-
<Icons.spinner className="mr-2 h-6 w-6 animate-spin" />
30-
) : (
31-
<Icons.ethereum className="mr-2 h-6 w-6" />
32-
)}{" "}
33-
Wallet
34-
</Button>
24+
const [state, formAction] = useFormState(registerUser, null);
3525

36-
<div className="relative">
37-
<div className="relative flex justify-center text-xs uppercase">
38-
<span className="bg-background px-2 text-neutral-500">
39-
And continue with
40-
</span>
41-
</div>
42-
</div>
26+
const walletButtons = connectors.map((connector) =>
27+
connector.name.toLowerCase() === "injected" ? null : (
28+
<WalletButton
29+
connector={connector}
30+
isLoading={status === 'pending'}
31+
connect={connect}
32+
key={connector.uid}
33+
/>
34+
)
35+
);
4336

44-
<form onSubmit={onSubmit}>
45-
<div className="grid gap-2">
46-
<div className="grid gap-1">
47-
<Label className="sr-only" htmlFor="email">
48-
Email
49-
</Label>
50-
<Input
51-
id="email"
52-
placeholder="[email protected]"
53-
type="email"
54-
autoCapitalize="none"
55-
autoComplete="email"
56-
autoCorrect="off"
57-
disabled={isLoading}
58-
/>
37+
return (
38+
<>
39+
{!state ? (
40+
<div className={cn("grid gap-6", className)} {...props}>
41+
{account.status === 'connected' ? (
42+
<HoverCard>
43+
<HoverCardTrigger asChild>
44+
<Button variant="destructive" onClick={() => disconnect()}>Disconnect</Button>
45+
</HoverCardTrigger>
46+
<HoverCardContent className="w-80">
47+
<div className="flex justify-between space-x-4">
48+
<div className="space-y-1">
49+
<h4 className="text-sm font-semibold">connected as</h4>
50+
<div className="text-xs">
51+
{JSON.stringify(account.addresses[0].replaceAll(account.addresses[0].slice(8, 36), '...'))}
52+
</div>
53+
</div>
54+
</div>
55+
</HoverCardContent>
56+
</HoverCard>
57+
) : (
58+
<div className="grid gap-1">
59+
{walletButtons}
60+
</div>
61+
)}
62+
<div className="relative">
63+
<div className="relative flex justify-center text-xs uppercase">
64+
<span className="bg-background px-2 text-neutral-500">
65+
And continue with
66+
</span>
67+
</div>
5968
</div>
60-
<Button disabled={isLoading}>
61-
{isLoading && (
62-
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
63-
)}
64-
Sign In with Email
65-
</Button>
66-
</div>
67-
</form>
68-
6969

70-
</div>
70+
<form action={formAction}>
71+
<input type="hidden" name="wallet" id="wallet" value={account.addresses?.[0]} />
72+
<div className="grid gap-2">
73+
<div className="grid gap-1">
74+
<Label className="sr-only" htmlFor="email">
75+
Email
76+
</Label>
77+
<Input
78+
id="email"
79+
name="email"
80+
placeholder="[email protected]"
81+
type="email"
82+
autoCapitalize="none"
83+
autoComplete="email"
84+
autoCorrect="off"
85+
/>
86+
</div>
87+
<div className="grid gap-1">
88+
<Label className="sr-only" htmlFor="password">
89+
Password
90+
</Label>
91+
<Input
92+
id="password"
93+
name="password"
94+
placeholder="******"
95+
type="password"
96+
autoCapitalize="none"
97+
autoComplete="email"
98+
autoCorrect="off"
99+
/>
100+
</div>
101+
<SubmitButton />
102+
</div>
103+
</form>
104+
</div>
105+
) : (<>
106+
{state.success ? (<>
107+
<div className="relative items-center w-full py-12 mx-auto max-w-7xl">
108+
<div className="flex w-full mx-auto">
109+
<div className="relative inline-flex items-center m-auto align-middle">
110+
<div className="relative max-w-6xl p-6 overflow-hidden bg-white rounded-3xl">
111+
<div className="relative max-w-lg">
112+
<div><p className="text-2xl font-medium tracking-tight text-black sm:text-4xl">
113+
Welcome on board!
114+
</p>
115+
<p className="max-w-xl mt-4 text-base tracking-tight text-neutral-600">
116+
Check your email for a confirmation link. If you don't see it, check your spam folder please.
117+
</p>
118+
</div>
119+
<div className="flex flex-col items-center justify-center gap-3 mt-10 lg:flex-row lg:justify-start">
120+
<a href="/authentication/login" className="items-center justify-center w-full px-6 py-2.5 text-center text-white duration-200 bg-black border-2 border-black rounded-full inline-flex hover:bg-transparent hover:border-black hover:text-black focus:outline-none lg:w-auto focus-visible:outline-black text-sm focus-visible:ring-black">
121+
Login &nbsp; →
122+
</a>
123+
<a href="/contact" className="inline-flex items-center justify-center text-sm font-semibold text-black duration-200 hover:text-blue-500 focus:outline-none focus-visible:outline-gray-600">
124+
Contact us &nbsp; →
125+
</a>
126+
</div>
127+
</div>
128+
</div>
129+
</div>
130+
</div>
131+
</div>
132+
</>) : (<>
133+
<div className="relative items-center w-full py-12 mx-auto max-w-7xl">
134+
<div className="flex w-full mx-auto">
135+
<div className="relative inline-flex items-center m-auto align-middle">
136+
<div className="relative max-w-6xl p-6 overflow-hidden bg-white rounded-3xl">
137+
<div className="relative max-w-lg">
138+
<div><p className="text-2xl font-medium tracking-tight text-red-500 sm:text-4xl">
139+
This is an Error
140+
</p>
141+
<p className="max-w-xl mt-4 text-base tracking-tight text-neutral-600">
142+
We failed to register you. Are you already registered? Please contact us if the problem persists.
143+
</p>
144+
</div>
145+
<div className="flex flex-col items-center justify-center gap-3 mt-10 lg:flex-row lg:justify-start">
146+
<a href="/authentication/reset" className="items-center justify-center w-full px-6 py-2.5 text-center text-white duration-200 bg-black border-2 border-black rounded-full inline-flex hover:bg-transparent hover:border-black hover:text-black focus:outline-none lg:w-auto focus-visible:outline-black text-sm focus-visible:ring-black">
147+
Reset password &nbsp; →
148+
</a>
149+
<a href="/contact" className="inline-flex items-center justify-center text-sm font-semibold text-black duration-200 hover:text-blue-500 focus:outline-none focus-visible:outline-gray-600">
150+
Contact us &nbsp; →
151+
</a>
152+
</div>
153+
</div>
154+
</div>
155+
</div>
156+
</div>
157+
</div>
158+
</>)}
159+
</>)}
160+
</>
71161
)
72162
}

app/authentication/authbfe.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use server"
2+
export async function registerUser(prevState: any, formData: FormData) {
3+
4+
const rawFormData = {
5+
email: formData.get('email'),
6+
wallet: formData.get('wallet'),
7+
password: formData.get('password'),
8+
}
9+
10+
//console.log(rawFormData);
11+
12+
const response = await fetch('http://0.0.0.0:3000/api/register', {
13+
method: 'POST',
14+
headers: {
15+
'Content-Type': 'application/json',
16+
},
17+
body: JSON.stringify(rawFormData),
18+
})
19+
const result = await response;
20+
if (result.status === 200) {
21+
return { message: `Registered user ${rawFormData.email}`, success: true };
22+
}
23+
else {
24+
25+
return { message: `Error: ${result.statusText}`, success: false };
26+
}
27+
}

app/authentication/login/page.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import Image from "next/image"
33
import Link from "next/link"
44
import { cn } from "@/lib/utils"
55
import { buttonVariants } from "@/components/ui/button"
6-
import { UserAuthForm } from "@/app/authentication/_components/user-login-form";
76
import { Icons } from "@/components/ui/icons"
7+
import dynamic from "next/dynamic"
8+
9+
const UserLoginForm = dynamic(() => import('@/app/authentication/_components/user-login-form'), { ssr: false })
810

911
export const metadata: Metadata = {
1012
title: "Authentication",
@@ -49,7 +51,7 @@ export default function AuthenticationPage() {
4951
Connect wallet or enter your email and password below to log in.
5052
</p>
5153
</div>
52-
<UserAuthForm />
54+
<UserLoginForm />
5355
<p className="px-8 text-center text-sm text-neutral-400">
5456
By clicking continue, you agree to our{" "}
5557
<Link

0 commit comments

Comments
 (0)