Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement authentication and database setup with UI improvements #18

Merged
merged 10 commits into from
Jan 1, 2025
Merged
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DATABASE_URL="mysql://username:password@host:port/database"
NEXTAUTH_SECRET="your-secret-key"
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ yarn-debug.log*
yarn-error.log*

# env files (can opt-in for committing if needed)
.env*
.env

# vercel
.vercel
Expand Down
12 changes: 12 additions & 0 deletions app/(user)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex flex-col min-h-screen">
<Navbar />
<main className="flex-1">{children}</main>
<Footer />
</div>
);
}
53 changes: 0 additions & 53 deletions app/_app.tsx

This file was deleted.

19 changes: 0 additions & 19 deletions app/_document.tsx

This file was deleted.

34 changes: 4 additions & 30 deletions app/admin/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
"use client"
import React from 'react';
// import { Card } from '@/components/CardName';
import Container from "@/components/Container";

import { useSession } from "next-auth/react";
function Page() {

const getName = "Lorem";
const greeting = (): string => {
const clock = new Date().getHours();
if (clock < 12) {
return "Good Morning";
} else if (clock < 18) {
return "Good Afternoon";
} else if (clock < 22) {
return "Good Evening";
} else {
return "Good Night";
}
}

const { data: session } = useSession();
return (
<>
<p style={{ fontSize: "23px", marginTop: "23px" }}>Hai, {getName} !!!!, {greeting()} 👋</p>

{/* konten */}
<div className='mt-16'>
<p>Short Summary</p>

<Container>
<p>lakjwd

</p>
</Container>
</div>
<p>{JSON.stringify(session)}</p>
</>
);
}
Expand Down
5 changes: 3 additions & 2 deletions app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client"
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"
import {
Expand All @@ -12,7 +13,7 @@ import {
import { LogOut, User } from "lucide-react"
import { Separator } from "@/components/ui/separator"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"

import { signOut } from "next-auth/react"


export default function Layout({ children }: { children: React.ReactNode }) {
Expand Down Expand Up @@ -49,7 +50,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<User className="mr-2 h-4 w-4" />
<span>Profile</span>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem onClick={() => signOut()}>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
Expand Down
6 changes: 6 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import NextAuth from "next-auth/next";
import { authOptions } from "@/lib/auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
121 changes: 121 additions & 0 deletions app/auth/login/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client"
import React, { useEffect } from 'react'
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { signIn } from "next-auth/react"
import toast from "react-hot-toast"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { useSessionContext } from '@/app/context/sessionContext'

const schema = z.object({
email: z.string().email({ message: "Invalid email address" }),
password: z.string().min(8, { message: "Password must be at least 8 characters" }),
})

export default function FormComponent() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
email: "",
password: "",
},
})

const { isLoading, session } = useSessionContext()

async function onSubmit(values: z.infer<typeof schema>) {
try {
const response = await signIn("credentials", {
email: values.email,
password: values.password,
redirect: false,
method: "post",
})

if (response?.ok) {
toast.success("Login Successful")
} else {
const errorMessage = response?.error === "CredentialsSignin"
? "Email or password is incorrect"
: response?.error
toast.error(errorMessage)
}
} catch (error) {
toast.error("An unexpected error occurred.")
console.error("Login error:", error)
}
}

useEffect(() => {
if (!isLoading && session) {
switch (session.user.role) {
case "ADMIN":
case "SUPERADMIN":
window.location.href = "/admin/dashboard"
break
case "MEMBER":
window.location.href = "/"
break
default:
window.location.href = "/"
break
}
}
}, [isLoading, session])

return (
<Form {...form}>
<form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
name="email"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="email"
{...field}
className={form.formState.errors.email ? "border-red-500" : ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="password"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="password"
{...field}
className={form.formState.errors.password ? "border-red-500" : ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? "Signing in..." : "Sign in"}
</Button>
</form>
</Form>
)
}
31 changes: 9 additions & 22 deletions app/auth/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import Link from "next/link"
import { Button } from "@/components/ui/button"

import From from './form'
export default function Page() {
return (
<div className="grid min-h-screen w-full grid-cols-1 lg:grid-cols-2">
Expand All @@ -22,24 +19,14 @@ export default function Page() {
<h1 className="text-3xl font-bold">Welcome back!</h1>
<p className="text-gray-500 dark:text-gray-400">Enter your email and password to sign in.</p>
</div>
<form className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="[email protected]" required />
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="password">Password</Label>
<Link href="#" className="text-sm font-medium underline" prefetch={false}>
Forgot password?
</Link>
</div>
<Input id="password" type="password" required />
</div>
<Button type="submit" className="w-full">
Sign in
</Button>
</form>
<From/>
<div className="mt-5 2xl:mt-8 text-center ">
Don&apos;t have an account?{" "}
<Link href="/auth/register" className="text-primary">
{" "}
Sign Up{" "}
</Link>
</div>
</div>
</div>
</div>
Expand Down
38 changes: 38 additions & 0 deletions app/context/sessionContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client"
import React, { createContext, useContext, useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import { Session } from "next-auth";

interface SessionContextType {
session: Session | null;
isLoading: boolean;
}

const SessionContext = createContext<SessionContextType | undefined>(undefined);

interface SessionProviderProps {
children: React.ReactNode;
}

export const SessionProvider: React.FC<SessionProviderProps> = ({ children }) => {
const { data: session, status } = useSession();
const [isLoading, setIsLoading] = useState<boolean>(status === "loading");

useEffect(() => {
setIsLoading(status === "loading");
}, [status]);

return (
<SessionContext.Provider value={{ session, isLoading }}>
{children}
</SessionContext.Provider>
);
};

export const useSessionContext = (): SessionContextType => {
const context = useContext(SessionContext);
if (!context) {
throw new Error("useSessionContext must be used within a SessionProvider");
}
return context;
};
Loading
Loading