Skip to content

Commit 9614f4e

Browse files
authored
feat: link account (#858)
* feat: link account * feat: forbidden page
1 parent 4c5ab34 commit 9614f4e

File tree

13 files changed

+176
-45
lines changed

13 files changed

+176
-45
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ const config = {
5656
argsIgnorePattern: '^_'
5757
}
5858
],
59-
'unused-imports/no-unused-imports': 'warn'
59+
'unused-imports/no-unused-imports': 'warn',
60+
'react/no-unescaped-entities': 'off'
6061
},
6162
overrides: [
6263
{

src/app/admin/(withLayout)/layout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ReactNode } from 'react'
22

3-
import { redirect } from 'next/navigation'
4-
3+
import { Forbidden } from '@/components/Forbidden'
4+
import { Unauthorized } from '@/components/Unauthorized'
55
import { getServerUser } from '@/lib/session'
66

77
import { AdminLinks } from './AdminLinks'
@@ -14,11 +14,11 @@ export default async function AdminLayout({
1414
const user = await getServerUser()
1515

1616
if (!user) {
17-
redirect('/login')
17+
return <Unauthorized />
1818
}
1919

2020
if (!user.admin) {
21-
redirect('/')
21+
return <Forbidden />
2222
}
2323

2424
return (

src/app/login/ErrorMessage.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,14 @@ export const ErrorMessage = () => {
3131

3232
const errorMessage = error && (errors[error] ?? errors.default)
3333

34-
return <>{errorMessage && <p>{errorMessage}</p>}</>
34+
return (
35+
<>
36+
{errorMessage && (
37+
<p className="text-center">
38+
{errorMessage} <br />
39+
(Code: {error})
40+
</p>
41+
)}
42+
</>
43+
)
3544
}

src/app/login/SignIn.tsx

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/app/login/page.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Suspense } from 'react'
22

3+
import { LoginGitHub } from '@/components/Login/GitHub'
4+
import { LoginGoogle } from '@/components/Login/Google'
5+
36
import { ErrorMessage } from './ErrorMessage'
4-
import { SignIn } from './SignIn'
57

68
export default function Login() {
79
return (
@@ -11,16 +13,15 @@ export default function Login() {
1113
Continue to PROGRAMMING.IN.TH
1214
</h2>
1315
</div>
14-
<div className="sm:mx-auto sm:w-full sm:max-w-md">
16+
<div className="my-6 sm:mx-auto sm:w-full sm:max-w-md">
1517
<Suspense>
1618
<ErrorMessage />
1719
</Suspense>
1820
</div>
19-
<div className="mt-2 px-4 sm:mx-auto sm:w-full sm:max-w-md">
20-
<div className="bg-white px-4 py-2 dark:bg-slate-800 sm:rounded-lg sm:px-10">
21-
<div className="mt-12 flex flex-col gap-6">
22-
<SignIn />
23-
</div>
21+
<div className="px-4 sm:mx-auto sm:w-full sm:max-w-md">
22+
<div className="flex flex-col gap-6 bg-white px-4 py-2 dark:bg-slate-800 sm:rounded-lg sm:px-10">
23+
<LoginGoogle />
24+
<LoginGitHub />
2425
</div>
2526
</div>
2627
</div>

src/app/user/page.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { LoginGitHub } from '@/components/Login/GitHub'
2+
import { LoginGoogle } from '@/components/Login/Google'
3+
import { Unauthorized } from '@/components/Unauthorized'
4+
import prisma from '@/lib/prisma'
5+
import { getServerUser } from '@/lib/session'
6+
7+
export default async function User() {
8+
const user = await getServerUser()
9+
10+
if (!user || !user.id) return <Unauthorized />
11+
12+
const pUser = await prisma.user.findUniqueOrThrow({
13+
where: {
14+
id: user.id
15+
},
16+
include: {
17+
accounts: true
18+
}
19+
})
20+
21+
const linkedToGoogle = pUser.accounts.some(
22+
account => account.provider === 'google'
23+
)
24+
25+
const linkedToGitHub = pUser.accounts.some(
26+
account => account.provider === 'github'
27+
)
28+
29+
return (
30+
<div className="flex min-h-screen flex-col justify-center gap-4 pb-44 sm:px-6 lg:px-8">
31+
<h2 className="mt-6 px-4 text-center text-3xl font-extrabold text-prog-gray-500 dark:text-prog-gray-100">
32+
User Dashboard
33+
</h2>
34+
35+
<div className="px-4 sm:mx-auto sm:w-full sm:max-w-md">
36+
<div className="flex flex-col gap-6 bg-white px-4 py-2 dark:bg-slate-800 sm:rounded-lg sm:px-10">
37+
<LoginGoogle type={linkedToGoogle ? 'linked' : 'link'} />
38+
<LoginGitHub type={linkedToGitHub ? 'linked' : 'link'} />
39+
</div>
40+
</div>
41+
</div>
42+
)
43+
}

src/components/Forbidden.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const Forbidden = () => {
2+
return (
3+
<div className="flex min-h-screen flex-col items-center justify-center gap-16 pb-44 sm:px-6 lg:px-8">
4+
<div className="sm:mx-auto sm:w-full sm:max-w-md">
5+
<h2 className="mt-6 px-4 text-center text-3xl font-extrabold text-prog-gray-500 dark:text-prog-gray-100">
6+
Forbidden, you don't have access
7+
</h2>
8+
</div>
9+
10+
<a
11+
href="https://youtu.be/dQw4w9WgXcQ"
12+
className="w-fit rounded-md bg-prog-primary-500 px-6 py-4 text-center text-2xl font-medium text-white shadow-md transition-colors hover:bg-prog-primary-600"
13+
>
14+
Get access
15+
</a>
16+
</div>
17+
)
18+
}

src/components/Login/GitHub.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import { signIn } from 'next-auth/react'
4+
5+
import { AuthGitHubLogo } from '@/svg/Socials'
6+
7+
import { LoginProps, getProps } from './props'
8+
import styles from './style.module.css'
9+
10+
export const LoginGitHub = ({ type }: LoginProps) => {
11+
const { message, disabled } = getProps('GitHub', type)
12+
13+
return (
14+
<button
15+
className={styles.loginButton}
16+
onClick={() => signIn('github')}
17+
disabled={disabled}
18+
>
19+
<AuthGitHubLogo className="h-5 w-5" />
20+
<span>{message}</span>
21+
</button>
22+
)
23+
}

src/components/Login/Google.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import { signIn } from 'next-auth/react'
4+
5+
import { AuthGoogleLogo } from '@/svg/Socials'
6+
7+
import { LoginProps, getProps } from './props'
8+
import styles from './style.module.css'
9+
10+
export const LoginGoogle = ({ type }: LoginProps) => {
11+
const { message, disabled } = getProps('Google', type)
12+
13+
return (
14+
<button
15+
className={styles.loginButton}
16+
onClick={() => signIn('google')}
17+
disabled={disabled}
18+
>
19+
<AuthGoogleLogo className="h-5 w-5" />
20+
<span>{message}</span>
21+
</button>
22+
)
23+
}

src/components/Login/props.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
type SignInType = 'signIn' | 'linked' | 'link'
2+
3+
export type LoginProps = {
4+
type?: SignInType
5+
}
6+
7+
export function getProps(providerName: string, type: SignInType = 'signIn') {
8+
const message =
9+
type === 'signIn'
10+
? `Sign in with ${providerName}`
11+
: type === 'linked'
12+
? 'Account Linked'
13+
: `Link ${providerName} account`
14+
15+
const disabled = type === 'linked'
16+
17+
return { message, disabled }
18+
}

src/components/Login/style.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.loginButton {
2+
@apply inline-flex w-full justify-center gap-3 rounded-md px-4 py-4 text-sm font-medium shadow-md transition disabled:cursor-not-allowed;
3+
@apply bg-white text-gray-500 enabled:hover:bg-gray-50;
4+
@apply dark:bg-slate-600 dark:text-gray-200 enabled:dark:hover:bg-slate-700;
5+
}

src/components/Unauthorized.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Link from 'next/link'
2+
3+
export const Unauthorized = () => {
4+
return (
5+
<div className="flex min-h-screen flex-col items-center justify-center gap-16 pb-44 sm:px-6 lg:px-8">
6+
<div className="sm:mx-auto sm:w-full sm:max-w-md">
7+
<h2 className="mt-6 px-4 text-center text-3xl font-extrabold text-prog-gray-500 dark:text-prog-gray-100">
8+
Unauthorized, please log in
9+
</h2>
10+
</div>
11+
12+
<Link
13+
href="/login"
14+
className="w-fit rounded-md bg-prog-primary-500 px-6 py-4 text-center text-2xl font-medium text-white shadow-md transition-colors hover:bg-prog-primary-600"
15+
>
16+
Login
17+
</Link>
18+
</div>
19+
)
20+
}

src/lib/auth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ if (
1010
!process.env.GITHUB_SECRET ||
1111
!process.env.GOOGLE_CLIENT_ID ||
1212
!process.env.GOOGLE_CLIENT_SECRET
13-
)
13+
) {
1414
throw new Error('Failed to initialize authentication')
15+
}
1516

1617
export const authOptions: NextAuthOptions = {
1718
adapter: PrismaAdapter(prisma),

0 commit comments

Comments
 (0)