Skip to content

Commit aa1f010

Browse files
committed
feat: firebase integration
1 parent 2f59748 commit aa1f010

File tree

17 files changed

+978
-32
lines changed

17 files changed

+978
-32
lines changed

app/(auth)/login/page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import { LoginForm } from "@/components/login-form"
1+
import { LoginForm } from '@/components/login-form'
22

3-
export default function Page() {
3+
export default async function Page({
4+
searchParams,
5+
}: {
6+
searchParams: Promise<{
7+
email?: string
8+
}>
9+
}) {
10+
const { email } = await searchParams
411
return (
512
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
613
<div className="w-full max-w-sm">
7-
<LoginForm />
14+
<LoginForm email={email} />
815
</div>
916
</div>
1017
)

app/(auth)/signup/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { SignUpForm } from '@/components/signup-form'
2+
3+
export default async function Page() {
4+
return (
5+
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
6+
<div className="w-full max-w-sm">
7+
<SignUpForm />
8+
</div>
9+
</div>
10+
)
11+
}

app/(protected)/borrows/error.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client'
2+
3+
import { useEffect } from 'react'
4+
5+
export default function ProtectedLayoutError({
6+
error,
7+
reset,
8+
}: {
9+
error: Error & { digest?: string }
10+
reset: () => void
11+
}) {
12+
useEffect(() => {
13+
// Log the error to an error reporting service
14+
console.error('error boundary: ', error)
15+
}, [error])
16+
17+
return (
18+
<div>
19+
<h2>Something went wrong!</h2>
20+
<button
21+
onClick={
22+
// Attempt to recover by trying to re-render the segment
23+
() => reset()
24+
}
25+
>
26+
Try again
27+
</button>
28+
</div>
29+
)
30+
}

app/(protected)/borrows/page.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
TableRow,
2525
} from '@/components/ui/table'
2626
import { getListBorrows } from '@/lib/api/borrow'
27+
import { cookies } from 'next/headers'
2728
import Link from 'next/link'
2829

2930
export default async function Borrows({
@@ -39,17 +40,27 @@ export default async function Borrows({
3940
const skip = Number(sp?.skip ?? 0)
4041
const limit = Number(sp?.limit ?? 20)
4142
const library_id = sp?.library_id
42-
const res = await getListBorrows({
43-
sort_by: 'created_at',
44-
sort_in: 'desc',
45-
limit: limit,
46-
skip: skip,
47-
...(library_id ? { library_id } : {}),
48-
})
43+
44+
const cookieStore = await cookies()
45+
const token = cookieStore.get('auth')?.value
46+
47+
const res = await getListBorrows(
48+
{
49+
sort_by: 'created_at',
50+
sort_in: 'desc',
51+
limit: limit,
52+
skip: skip,
53+
...(library_id ? { library_id } : {}),
54+
},
55+
{
56+
headers: {
57+
Authorization: `Bearer ${token}`,
58+
},
59+
}
60+
)
4961

5062
if ('error' in res) {
51-
console.log(res)
52-
return <div>{JSON.stringify(res.message)}</div>
63+
return <div>{JSON.stringify(res.error)}</div>
5364
}
5465

5566
const prevSkip = skip - limit > 0 ? skip - limit : 0

app/(protected)/layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export default async function ProtectedLayout({
99
// REF: https://nextjs.org/docs/app/api-reference/functions/cookies
1010
const c = await cookies()
1111
const token = c.get('auth')
12-
console.log({ token })
1312
if (!token) {
1413
redirect('/login')
1514
}

app/staffs/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Badge } from '@/components/ui/badge'
12
import {
23
Breadcrumb,
34
BreadcrumbItem,
@@ -86,6 +87,7 @@ export default async function Staffs({
8687
<TableHead>Name</TableHead>
8788
<TableHead>User</TableHead>
8889
<TableHead>Library</TableHead>
90+
<TableHead>Role</TableHead>
8991
<TableHead>Registered At</TableHead>
9092
</TableRow>
9193
</TableHeader>
@@ -95,6 +97,9 @@ export default async function Staffs({
9597
<TableCell>{s.name}</TableCell>
9698
<TableCell>{s.user?.name}</TableCell>
9799
<TableCell>{s.library?.name}</TableCell>
100+
<TableCell>
101+
<Badge variant="outline">{s.role}</Badge>
102+
</TableCell>
98103
<TableCell>{s.created_at}</TableCell>
99104
</TableRow>
100105
))}

components/login-form.tsx

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,53 @@
1-
import { cn } from "@/lib/utils"
2-
import { Button } from "@/components/ui/button"
1+
'use client'
2+
3+
import { cn } from '@/lib/utils'
4+
import { Button } from '@/components/ui/button'
35
import {
46
Card,
57
CardContent,
68
CardDescription,
79
CardHeader,
810
CardTitle,
9-
} from "@/components/ui/card"
10-
import { Input } from "@/components/ui/input"
11-
import { Label } from "@/components/ui/label"
11+
} from '@/components/ui/card'
12+
import { Input } from '@/components/ui/input'
13+
import { Label } from '@/components/ui/label'
14+
import { useActionState } from 'react'
15+
import { login } from '@/lib/actions/login'
16+
import Link from 'next/link'
1217

1318
export function LoginForm({
1419
className,
1520
...props
16-
}: React.ComponentPropsWithoutRef<"div">) {
21+
}: React.ComponentPropsWithoutRef<'div'> & { email?: string }) {
22+
const initialState = {
23+
error: '',
24+
email: props.email ?? '',
25+
password: '',
26+
}
27+
const [state, action, isPending] = useActionState(login, initialState, '/')
1728
return (
18-
<div className={cn("flex flex-col gap-6", className)} {...props}>
29+
<div className={cn('flex flex-col gap-6', className)} {...props}>
1930
<Card>
2031
<CardHeader>
2132
<CardTitle className="text-2xl">Login</CardTitle>
2233
<CardDescription>
2334
Enter your email below to login to your account
2435
</CardDescription>
36+
{state.error && (
37+
<div className="text-sm text-red-400">{state.error}</div>
38+
)}
2539
</CardHeader>
2640
<CardContent>
27-
<form>
41+
<form action={action}>
2842
<div className="flex flex-col gap-6">
2943
<div className="grid gap-2">
3044
<Label htmlFor="email">Email</Label>
3145
<Input
3246
id="email"
3347
type="email"
34-
placeholder="m@example.com"
48+
name="email"
49+
// placeholder="m@example.com"
50+
defaultValue={state.email}
3551
required
3652
/>
3753
</div>
@@ -45,20 +61,26 @@ export function LoginForm({
4561
Forgot your password?
4662
</a>
4763
</div>
48-
<Input id="password" type="password" required />
64+
<Input
65+
id="password"
66+
name="password"
67+
type="password"
68+
// defaultValue={state.password}
69+
required
70+
/>
4971
</div>
50-
<Button type="submit" className="w-full">
72+
<Button type="submit" className="w-full" disabled={isPending}>
5173
Login
5274
</Button>
53-
<Button variant="outline" className="w-full">
75+
{/* <Button variant="outline" className="w-full">
5476
Login with Google
55-
</Button>
77+
</Button> */}
5678
</div>
5779
<div className="mt-4 text-center text-sm">
58-
Don&apos;t have an account?{" "}
59-
<a href="#" className="underline underline-offset-4">
80+
Don&apos;t have an account?{' '}
81+
<Link href="/signup" className="underline underline-offset-4">
6082
Sign up
61-
</a>
83+
</Link>
6284
</div>
6385
</form>
6486
</CardContent>

components/signup-form.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use client'
2+
3+
import { cn } from '@/lib/utils'
4+
import { Button } from '@/components/ui/button'
5+
import {
6+
Card,
7+
CardContent,
8+
CardDescription,
9+
CardHeader,
10+
CardTitle,
11+
} from '@/components/ui/card'
12+
import { Input } from '@/components/ui/input'
13+
import { Label } from '@/components/ui/label'
14+
import { useActionState } from 'react'
15+
import { register } from '@/lib/actions/register'
16+
import Link from 'next/link'
17+
18+
const initialState = {
19+
error: '',
20+
name: '',
21+
email: '',
22+
password: '',
23+
}
24+
25+
export function SignUpForm({
26+
className,
27+
...props
28+
}: React.ComponentPropsWithoutRef<'div'>) {
29+
const [state, action, isPending] = useActionState(
30+
register,
31+
initialState,
32+
'/login'
33+
)
34+
return (
35+
<div className={cn('flex flex-col gap-6', className)} {...props}>
36+
<Card>
37+
<CardHeader>
38+
<CardTitle className="text-2xl">Sign Up</CardTitle>
39+
<CardDescription>
40+
Enter your information below to create your account
41+
</CardDescription>
42+
{state.error && (
43+
<div className="text-sm text-red-400">{state.error}</div>
44+
)}
45+
</CardHeader>
46+
<CardContent>
47+
<form action={action}>
48+
<div className="flex flex-col gap-6">
49+
<div className="grid gap-2">
50+
<Label htmlFor="name">Name</Label>
51+
<Input
52+
id="name"
53+
type="name"
54+
name="name"
55+
defaultValue={state.name}
56+
required
57+
/>
58+
</div>
59+
<div className="grid gap-2">
60+
<Label htmlFor="email">Email</Label>
61+
<Input
62+
id="email"
63+
type="email"
64+
name="email"
65+
// placeholder="m@example.com"
66+
defaultValue={state.email}
67+
required
68+
/>
69+
</div>
70+
<div className="grid gap-2">
71+
<Label htmlFor="password">Password</Label>
72+
<Input
73+
id="password"
74+
name="password"
75+
type="password"
76+
// defaultValue={state.password}
77+
required
78+
/>
79+
</div>
80+
<Button type="submit" className="w-full" disabled={isPending}>
81+
Sign Up
82+
</Button>
83+
{/* <Button variant="outline" className="w-full">
84+
Login with Google
85+
</Button> */}
86+
</div>
87+
<div className="mt-4 text-center text-sm">
88+
Already have an account?{' '}
89+
<Link href="/login" className="underline underline-offset-4">
90+
Login
91+
</Link>
92+
</div>
93+
</form>
94+
</CardContent>
95+
</Card>
96+
</div>
97+
)
98+
}

firebase.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { initializeApp } from 'firebase/app'
2+
3+
const firebaseConfig = {
4+
apiKey: 'AIzaSyAyqBa-fgy4kVnBGQb6MEENYIcCvxvmqMc',
5+
authDomain: 'librarease-dev.firebaseapp.com',
6+
projectId: 'librarease-dev',
7+
storageBucket: 'librarease-dev.firebasestorage.app',
8+
messagingSenderId: '329808199046',
9+
appId: '1:329808199046:web:33be1f2dde6b1b0d24c007',
10+
measurementId: 'G-VJY7LPK50P',
11+
}
12+
13+
export const app = initializeApp(firebaseConfig)

lib/actions/login.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use server'
2+
3+
import { app } from '@/firebase.config'
4+
import {
5+
getAuth,
6+
signInWithEmailAndPassword,
7+
UserCredential,
8+
} from 'firebase/auth'
9+
import { cookies } from 'next/headers'
10+
import { redirect, RedirectType } from 'next/navigation'
11+
12+
const auth = getAuth(app)
13+
14+
type formState = {
15+
error: string
16+
email: string
17+
password: string
18+
}
19+
20+
export async function login(
21+
_: formState,
22+
formData: FormData
23+
): Promise<formState> {
24+
const email = formData.get('email') as string
25+
const password = formData.get('password') as string
26+
27+
let userCredentials: UserCredential
28+
try {
29+
userCredentials = await signInWithEmailAndPassword(auth, email, password)
30+
} catch (error) {
31+
console.log(error)
32+
return {
33+
error: 'Something went wrong',
34+
email,
35+
password,
36+
}
37+
}
38+
39+
const user = userCredentials.user
40+
const token = await user.getIdToken()
41+
42+
const cookieStore = await cookies()
43+
cookieStore.set('auth', token, {
44+
maxAge: 60 * 60 * 24 * 7,
45+
})
46+
47+
redirect('/', RedirectType.replace)
48+
}

0 commit comments

Comments
 (0)