Skip to content

Commit 8c17ab6

Browse files
committed
feat: logout
1 parent 4b14e29 commit 8c17ab6

File tree

7 files changed

+60
-27
lines changed

7 files changed

+60
-27
lines changed
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,11 @@ export default function SetupPage() {
3030
<form
3131
onSubmit={form.handleSubmit(async ({ endpointURL, username, password }) => {
3232
try {
33-
const { token } = await ky
34-
.post('/api/auth', { json: { endpointURL, username, password } })
35-
.json<{ token: string }>()
33+
await ky.post('/api/login', { json: { endpointURL, username, password } }).json<{ token: string }>()
3634

37-
console.log(token)
38-
39-
router.replace('/')
35+
router.replace('/orchestrate')
4036
} catch (err) {
41-
toast({ description: (err as Error).message })
37+
toast({ variant: 'destructive', description: (err as Error).message })
4238
}
4339
})}
4440
>

src/app/(protected)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function ProtectedLayout({ children }: { children: ReactNode }) {
99
const jwtToken = cookies().get('jwtToken')
1010

1111
if (!jwtToken) {
12-
redirect('/setup')
12+
redirect('/login')
1313
}
1414

1515
const { endpointURL, token } = jwt.decode(jwtToken.value, { json: true })!
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { gql, request } from 'graphql-request'
22
import jwt from 'jsonwebtoken'
3+
import { cookies } from 'next/headers'
34
import { NextResponse } from 'next/server'
45
import { TokenQuery } from '~/apis/gql/graphql'
56

@@ -18,14 +19,11 @@ export const POST = async (req: Request) => {
1819

1920
const jwtToken = jwt.sign({ endpointURL, token }, process.env.NEXT_PUBLIC_JWT_SECRET!)
2021

21-
const cookieMaxAge = 60 * 60 * 24 * 30 // 30 days
22+
cookies().set('jwtToken', jwtToken, {
23+
maxAge: 60 * 60 * 24 * 30, // 30 days
24+
httpOnly: true,
25+
path: '/'
26+
})
2227

23-
return NextResponse.json(
24-
{ token },
25-
{
26-
headers: {
27-
'Set-Cookie': `jwtToken=${jwtToken}; endpointURL=${endpointURL}; Max-Age=${cookieMaxAge}; Path=/; HttpOnly`
28-
}
29-
}
30-
)
28+
return new NextResponse()
3129
}

src/app/api/logout/route.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { cookies } from 'next/headers'
2+
import { NextResponse } from 'next/server'
3+
4+
export const POST = () => {
5+
cookies().delete('jwtToken')
6+
7+
return new NextResponse()
8+
}

src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const notoSansSC = Noto_Sans_SC({ subsets: ['latin', 'latin-ext', 'cyrillic', 'v
1212

1313
export const metadata: Metadata = { title: 'daed', description: 'daed' }
1414

15-
export default function RootLayout({ children }: { children: ReactNode }) {
15+
export default async function RootLayout({ children }: { children: ReactNode }) {
1616
return (
1717
<html lang="en" suppressHydrationWarning>
1818
<body className={cn(notoSansSC.className)}>

src/components/Header.tsx

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
'use client'
22

33
import i18n from 'i18next'
4+
import ky from 'ky'
45
import { ActivityIcon, CogIcon, GlobeIcon, LanguagesIcon, NetworkIcon } from 'lucide-react'
56
import Image from 'next/image'
67
import Link from 'next/link'
7-
import { usePathname } from 'next/navigation'
8+
import { usePathname, useRouter } from 'next/navigation'
89
import { FC } from 'react'
910
import { useTranslation } from 'react-i18next'
1011
import { useUserQuery } from '~/apis/query'
1112
import { LogoText } from '~/components/LogoText'
1213
import { Avatar, AvatarFallback } from '~/components/ui/avatar'
1314
import { Button } from '~/components/ui/button'
15+
import {
16+
DropdownMenu,
17+
DropdownMenuContent,
18+
DropdownMenuItem,
19+
DropdownMenuSeparator,
20+
DropdownMenuTrigger
21+
} from '~/components/ui/dropdown-menu'
1422
import { ModeToggle } from '~/components/ui/mode-toggle'
1523
import {
1624
NavigationMenu,
@@ -24,6 +32,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '~/components/ui/tooltip
2432
const Header: FC = () => {
2533
const { t } = useTranslation()
2634
const pathname = usePathname()
35+
const router = useRouter()
2736
const userQuery = useUserQuery()
2837

2938
const navigationMenus = [
@@ -77,13 +86,33 @@ const Header: FC = () => {
7786

7887
<ModeToggle />
7988

80-
<Avatar>
81-
{userQuery.data?.user.avatar && (
82-
<Image width={40} height={40} src={userQuery.data.user.avatar} alt="avatar" />
83-
)}
89+
<DropdownMenu>
90+
<DropdownMenuTrigger>
91+
<Avatar>
92+
{userQuery.data?.user.avatar && (
93+
<Image width={40} height={40} src={userQuery.data.user.avatar} alt="avatar" />
94+
)}
95+
96+
<AvatarFallback>daed</AvatarFallback>
97+
</Avatar>
98+
</DropdownMenuTrigger>
99+
100+
<DropdownMenuContent>
101+
<DropdownMenuItem>Account Settings</DropdownMenuItem>
102+
103+
<DropdownMenuSeparator />
104+
105+
<DropdownMenuItem
106+
onClick={async () => {
107+
await ky.post('/api/logout')
84108

85-
<AvatarFallback>daed</AvatarFallback>
86-
</Avatar>
109+
router.replace('/login')
110+
}}
111+
>
112+
{t('actions.logout')}
113+
</DropdownMenuItem>
114+
</DropdownMenuContent>
115+
</DropdownMenu>
87116
</div>
88117
</div>
89118
)

src/contexts/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export const SessionProvider: FC<SessionContextProps & { children: ReactNode }>
1818
const router = useRouter()
1919

2020
if (!endpointURL || !token) {
21-
router.replace('/setup')
21+
router.replace('/login')
22+
23+
return null
2224
}
2325

2426
return <SessionContext.Provider value={{ endpointURL, token }}>{children}</SessionContext.Provider>
@@ -40,7 +42,7 @@ export const GraphqlClientProvider: FC<{ children: ReactNode }> = ({ children })
4042
const error = (response as ClientError).response?.errors?.[0]
4143

4244
if (error?.message === 'access denied') {
43-
router.replace('/setup')
45+
router.replace('/login')
4446
}
4547

4648
return response

0 commit comments

Comments
 (0)