Skip to content

Commit cbaa56c

Browse files
feat: move to OTP Login (#17)
* feat: move to OTP Login * fix: bump backend package * fix: biome fix and merge conflict
1 parent 0508774 commit cbaa56c

30 files changed

Lines changed: 713 additions & 460 deletions

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"dependencies": {
1616
"@hookform/resolvers": "^3.9.1",
17-
"@polinetwork/backend": "^0.12.1",
17+
"@polinetwork/backend": "^0.14.0",
1818
"@radix-ui/react-alert-dialog": "^1.1.3",
1919
"@radix-ui/react-avatar": "^1.1.1",
2020
"@radix-ui/react-collapsible": "^1.1.1",
@@ -27,19 +27,20 @@
2727
"@radix-ui/react-separator": "^1.1.0",
2828
"@radix-ui/react-slot": "^1.1.0",
2929
"@radix-ui/react-tooltip": "^1.1.4",
30-
"@t3-oss/env-nextjs": "^0.10.1",
31-
"@tanstack/react-query": "^5.90.12",
30+
"@t3-oss/env-nextjs": "^0.13.10",
31+
"@tanstack/react-query": "^5.90.19",
3232
"@tanstack/react-table": "^8.21.2",
3333
"@trpc/client": "11.5.1",
3434
"@trpc/next": "11.5.1",
3535
"@trpc/react-query": "11.5.1",
3636
"@trpc/tanstack-react-query": "11.5.1",
37+
"better-auth": "^1.4.15",
3738
"babel-plugin-react-compiler": "1.0.0",
38-
"better-auth": "^1.3.15",
3939
"class-variance-authority": "^0.7.1",
4040
"clsx": "^2.1.1",
4141
"cmdk": "^1.1.1",
4242
"geist": "^1.3.0",
43+
"input-otp": "^1.4.2",
4344
"lucide-react": "^0.525.0",
4445
"next": "^15.5.9",
4546
"next-themes": "^0.4.4",
@@ -53,7 +54,7 @@
5354
"tailwind-merge": "^3.0.1",
5455
"tailwind-scrollbar": "^4.0.2",
5556
"tailwindcss-animate": "^1.0.7",
56-
"zod": "^3.23.3"
57+
"zod": "^4.3.5"
5758
},
5859
"devDependencies": {
5960
"@biomejs/biome": "2.3.10",

pnpm-lock.yaml

Lines changed: 176 additions & 285 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/dashboard/(active)/account/page.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { CircleAlert, UserIcon } from "lucide-react"
12
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
23
import { getInitials } from "@/lib/utils"
34
import { getServerSession } from "@/server/auth"
5+
import { SetName } from "./set-name"
46
import { Telegram } from "./telegram"
57

68
export default async function Account() {
@@ -16,14 +18,24 @@ export default async function Account() {
1618
<div className="flex gap-4">
1719
<Avatar className="h-32 w-32 rounded-lg">
1820
{user.image && <AvatarImage src={user.image} alt={`propic of ${user.name}`} />}
19-
<AvatarFallback className="rounded-lg">{getInitials(user.name)}</AvatarFallback>
21+
<AvatarFallback className="rounded-lg text-3xl">
22+
{user.name ? getInitials(user.name) : <UserIcon size={48} />}
23+
</AvatarFallback>
2024
</Avatar>
2125

22-
<div>
23-
<p>{user.name}</p>
24-
<p>{user.email}</p>
25-
<div className="mt-2 flex items-center gap-2">
26-
<p className="text-accent-foreground/70">Telegram: </p>
26+
<div className="flex flex-col gap-2">
27+
<div className="flex items-center gap-2">
28+
{!user.name && <CircleAlert className="text-yellow-500" />}
29+
<span className="text-accent-foreground/70">Name:</span>
30+
{user.name ? <p>{user.name}</p> : <SetName />}
31+
</div>
32+
33+
<div className="flex items-center gap-2">
34+
<span className="text-accent-foreground/70">Email:</span>
35+
<p>{user.email}</p>
36+
</div>
37+
<div className="flex items-center gap-2">
38+
<span className="text-accent-foreground/70">Telegram:</span>
2739
<Telegram />
2840
</div>
2941
</div>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client"
2+
3+
import { useRouter } from "next/navigation"
4+
import { type FormEvent, useState } from "react"
5+
import { Button } from "@/components/ui/button"
6+
import {
7+
Dialog,
8+
DialogClose,
9+
DialogContent,
10+
DialogDescription,
11+
DialogFooter,
12+
DialogHeader,
13+
DialogTitle,
14+
DialogTrigger,
15+
} from "@/components/ui/dialog"
16+
import { Input } from "@/components/ui/input"
17+
import { Label } from "@/components/ui/label"
18+
import { auth } from "@/lib/auth"
19+
20+
export function SetName({ initialName }: { initialName?: string }) {
21+
const router = useRouter()
22+
const [name, setName] = useState<string>(initialName ?? "")
23+
const [open, setOpen] = useState<boolean>(false)
24+
25+
async function handleUpdate(e: FormEvent<HTMLFormElement>) {
26+
e.preventDefault()
27+
await auth.updateUser({ name })
28+
setOpen(false)
29+
router.refresh()
30+
}
31+
32+
return (
33+
<Dialog open={open} onOpenChange={setOpen}>
34+
<DialogTrigger asChild>
35+
<Button variant="outline">Set name</Button>
36+
</DialogTrigger>
37+
<DialogContent className="sm:max-w-[425px]">
38+
<DialogHeader>
39+
<DialogTitle>Set your name</DialogTitle>
40+
<DialogDescription>Make changes to your profile here. Click save when you&apos;re done.</DialogDescription>
41+
</DialogHeader>
42+
<form onSubmit={handleUpdate}>
43+
<div className="grid gap-4">
44+
<div className="grid gap-3">
45+
<Label htmlFor="name">Name</Label>
46+
<Input
47+
id="name"
48+
name="name"
49+
type="text"
50+
autoComplete="name"
51+
value={name}
52+
onChange={(e) => setName(e.target.value)}
53+
/>
54+
</div>
55+
</div>
56+
<DialogFooter>
57+
<DialogClose asChild>
58+
<Button variant="outline">Cancel</Button>
59+
</DialogClose>
60+
<Button type="submit">Save changes</Button>
61+
</DialogFooter>
62+
</form>
63+
</DialogContent>
64+
</Dialog>
65+
)
66+
}

src/app/dashboard/(active)/account/telegram.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function ShowTelegram({ username, userId }: { username: string; userId: number }
2020
<>
2121
<span>@{username}</span>
2222
{!isLoading && data?.roles?.length && (
23-
<span className="text-foreground/30 text-xs">(roles: {data.roles.join(" ")})</span>
23+
<span className="text-foreground/30 text-xs">(roles: {data.roles.join(", ")})</span>
2424
)}
2525
</>
2626
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use client"
2+
3+
import type { User } from "better-auth"
4+
import { UserRoundPenIcon } from "lucide-react"
5+
import Link from "next/link"
6+
import { Button } from "@/components/ui/button"
7+
8+
export function CompleteProfile({ user }: { user: User }) {
9+
return (
10+
!user.name && (
11+
<div className="flex items-center p-2 pl-4 mb-4 gap-2 rounded-lg border text-accent-foreground bg-yellow-400/10 border-yellow-400">
12+
<UserRoundPenIcon size={16} />
13+
<p className="grow">Your profile is incomplete, please enter the missing information.</p>
14+
<Link href="/dashboard/account">
15+
<Button>Go complete</Button>
16+
</Link>
17+
</div>
18+
)
19+
)
20+
}

src/app/dashboard/(active)/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { getServerSession } from "@/server/auth"
2+
import { CompleteProfile } from "./complete-profile"
23

34
export default async function AdminHome() {
4-
const session = await getServerSession()
5+
const { data: session } = await getServerSession()
56
return (
67
session && (
78
<div className="container mx-auto px-4 py-8">
9+
<CompleteProfile user={session.user} />
810
<h2 className="text-accent-foreground mb-4 text-3xl font-bold">Home</h2>
911
</div>
1012
)

src/app/dashboard/layout.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { redirect } from "next/navigation"
2-
import { Header } from "@/components/header"
32
import { SidebarProvider } from "@/components/ui/sidebar"
3+
import { getQueryClient, trpc } from "@/lib/trpc/server"
44
import { getServerSession } from "@/server/auth"
55

66
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
77
const session = await getServerSession()
88
// console.log(session)
99
if (!session.data) redirect("/login")
1010

11-
return (
12-
<div className="flex h-screen w-full flex-col items-center justify-start overflow-y-hidden">
13-
<Header />
14-
<SidebarProvider>{children}</SidebarProvider>
15-
</div>
16-
)
11+
const tgId = session.data.user.telegramId
12+
if (!tgId) redirect("/onboarding/link")
13+
// if (session?.user.role === USER_ROLE.INACTIVE) ;
14+
// if (session?.user.role === USER_ROLE.DISABLED) redirect("/dashboard/disabled");
15+
16+
const qc = getQueryClient()
17+
const { roles } = await qc.fetchQuery(trpc.tg.permissions.getRoles.queryOptions({ userId: tgId }))
18+
if (!roles || roles.length === 0) redirect("/onboarding/no-role")
19+
if (roles.includes("creator")) redirect("/onboarding/unauthorized")
20+
21+
return <SidebarProvider>{children}</SidebarProvider>
1722
}

src/app/layout.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GeistSans } from "geist/font/sans"
22
import type { Metadata } from "next"
33
import "@/index.css"
4-
import { HEADER_HEIGHT } from "@/components/header"
4+
import { HEADER_HEIGHT, Header } from "@/components/header"
55
import { ThemeProvider } from "@/components/theme-provider"
66
import { Toaster } from "@/components/ui/sonner"
77
import { TooltipProvider } from "@/components/ui/tooltip"
@@ -37,8 +37,13 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
3737
disableTransitionOnChange
3838
>
3939
<TooltipProvider>
40-
<TRPCReactProvider>{children}</TRPCReactProvider>
41-
<Toaster richColors position="bottom-right" />
40+
<TRPCReactProvider>
41+
<div className="flex h-screen w-full flex-col items-center justify-start">
42+
<Header />
43+
{children}
44+
</div>
45+
</TRPCReactProvider>
46+
<Toaster richColors position="bottom-center" />
4247
</TooltipProvider>
4348
</ThemeProvider>
4449
</body>

src/app/login/can-i-access.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export function CanIAccess() {
3434
className="place-self-center justify-self-center"
3535
/>
3636
<div className="text-center">
37-
<p className="text-primary text-xl font-bold dark:text-white">1. Login with GitHub</p>
38-
<p className="text-muted-foreground text-xs">(provider might change)</p>
37+
<p className="text-primary text-xl font-bold dark:text-white">1. Login with email</p>
38+
<p className="text-muted-foreground text-sm">We send an OTP to your email</p>
3939
</div>
4040
</div>
4141
</Card>
@@ -49,7 +49,7 @@ export function CanIAccess() {
4949
className="place-self-center justify-self-center"
5050
/>
5151
<div className="text-center">
52-
<p className="text-primary text-lg font-bold dark:text-white">2. Link your Telegram account</p>
52+
<p className="text-primary text-xl font-bold dark:text-white">2. Link your Telegram account</p>
5353
<p className="text-muted-foreground text-sm">This allows to verify your role</p>
5454
</div>
5555
</div>

0 commit comments

Comments
 (0)