second commit
9
app/(dashboard)/chat/chat.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const chat = () => {
|
||||
return (
|
||||
<div>chat</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default chat
|
||||
12
app/(dashboard)/dashboard/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import DashboardLayout from '../layout'
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<div className='fade-in'>
|
||||
<p>Dashboard</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
9
app/(dashboard)/home/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<div>Home</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
25
app/(dashboard)/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import ReactConfetti from 'react-confetti'
|
||||
import Navbar from '@/app/components/Navbar'
|
||||
import SideBar from '@/app/components/SideBar'
|
||||
|
||||
const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<main className=" gap-3 w-full bg-gray-100 h-screen flex fade-in p-2" >
|
||||
{/* <ReactConfetti width={window.innerWidth} height={window.innerHeight} tweenDuration={100} /> */}
|
||||
<SideBar />
|
||||
<div className="w-full h-full flex flex-col gap-3 ">
|
||||
<Navbar />
|
||||
<div className=' h-full rounded-xl bg-white drop-shadow-md shadow-gray-400'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardLayout
|
||||
9
app/(dashboard)/profile/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const Profile = () => {
|
||||
return (
|
||||
<div>Profile</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
||||
14
app/api/auth/logout/route.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const response = NextResponse.redirect(new URL("/login", req.url));
|
||||
response.cookies.set("isAuth", "", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
path: "/",
|
||||
expires: new Date(0),
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
13
app/api/auth/route.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { username, password } = await req.json();
|
||||
|
||||
if (username === "Wdipl" && password === "Admin@123") {
|
||||
const response = NextResponse.json({ success: true });
|
||||
response.cookies.set("isAuth", "true", { httpOnly: true });
|
||||
return response;
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: false }, { status: 401 });
|
||||
}
|
||||
10
app/api/auth/schema.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import * as yup from "yup";
|
||||
// Validation Schema with Yup
|
||||
export const schema = yup.object({
|
||||
username: yup.string().required("Username is required"),
|
||||
password: yup
|
||||
.string()
|
||||
.min(6, "Password must be at least 6 characters")
|
||||
.required("Password is required"),
|
||||
});
|
||||
34
app/api/products/route.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// import { NextRequest, NextResponse } from "next/server";
|
||||
// import schema from "./schema";
|
||||
|
||||
// export function GET(req: NextRequest) {
|
||||
// return NextResponse.json([
|
||||
// {
|
||||
// id: 1,
|
||||
// naame: 'Milk',
|
||||
// price: 2.5
|
||||
// }, {
|
||||
// id: 2,
|
||||
// naame: 'Bread',
|
||||
// price: 3.5
|
||||
// },
|
||||
// ])
|
||||
// }
|
||||
|
||||
// export async function POST(req:NextRequest) {
|
||||
// const body = await req.json()
|
||||
// console.log(body);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// const validation = schema.safeParse(body)
|
||||
// if (!validation.success)
|
||||
// return NextResponse.json(validation.error.errors, {status:400})
|
||||
|
||||
|
||||
|
||||
// return NextResponse.json({id:10, name: body.name, price: body.price }, { status: 201})
|
||||
|
||||
// }
|
||||
10
app/api/products/schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
// import * as yup from "yup";
|
||||
// // Validation Schema with Yup
|
||||
// export const schema = yup.object({
|
||||
// username: yup.string().required("Username is required"),
|
||||
// password: yup
|
||||
// .string()
|
||||
// .min(6, "Password must be at least 6 characters")
|
||||
// .required("Password is required"),
|
||||
// });
|
||||
15
app/api/user/[id]/route.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
// import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
|
||||
|
||||
// export async function PUT(req:NextRequest, {params} : { params: { id: number}}){
|
||||
// const body = await req.json()
|
||||
// if(!body.name)
|
||||
// return NextResponse.json({error:'Name is equied'}, { status: 400})
|
||||
|
||||
// if(params.id>10)
|
||||
// return NextResponse.json({error:'User noty found'}, { status: 404})
|
||||
|
||||
// return NextResponse.json({id:1, name: body.name})
|
||||
|
||||
// }
|
||||
26
app/api/user/route.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
// import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
// export function GET(req:NextRequest){
|
||||
// return NextResponse.json([
|
||||
// {
|
||||
// id:1,
|
||||
// name: 'John'
|
||||
// },
|
||||
// {
|
||||
// id:1,
|
||||
// name:'Mosh'
|
||||
// }
|
||||
// ])
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export async function POST(req:NextRequest){
|
||||
// const body = await req.json()
|
||||
|
||||
// if(!body?.name)
|
||||
// return NextResponse.json({error:'Name is Required'}, {status:400})
|
||||
// return NextResponse.json({id:1, name: body.name})
|
||||
// }
|
||||
26
app/components/DashboardLayout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
// 'use client'
|
||||
// import React from 'react'
|
||||
// import Button from './PrimaryButton'
|
||||
// import { useRouter } from 'next/navigation'
|
||||
// import Sidebar from './SideBar'
|
||||
// import Navbar from './Navbar'
|
||||
// import ReactConfetti from 'react-confetti'
|
||||
|
||||
// const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
// const router = useRouter()
|
||||
|
||||
// return (
|
||||
// <main className=" gap-3 w-full bg-gray-100 h-screen flex fade-in p-2" >
|
||||
// {/* <ReactConfetti width={window.innerWidth} height={window.innerHeight} tweenDuration={100} /> */}
|
||||
// <Sidebar />
|
||||
// <div className="w-5/6 h-full">
|
||||
// <Navbar />
|
||||
// <div className='p-3 h-full'>
|
||||
// {children}
|
||||
// </div>
|
||||
// </div>
|
||||
// </main>
|
||||
// )
|
||||
// }
|
||||
|
||||
// export default DashboardLayout
|
||||
44
app/components/InputField.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface InputFieldProps {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
register: any;
|
||||
name: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const InputField: React.FC<InputFieldProps> = ({
|
||||
label,
|
||||
placeholder,
|
||||
type = "text",
|
||||
register,
|
||||
name,
|
||||
error,
|
||||
}) => {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
{label && (
|
||||
<label htmlFor={name} className="block text-sm text-gray-700 mb-2" >
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
id={name}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
{...register(name)}
|
||||
className={`w-full px-4 py-3 text-xs border rounded-md focus:outline-none ${
|
||||
error ? "border-red-500 border-2" : "border-gray-300 focus:ring-2 focus:ring-blue-500 "
|
||||
}`}
|
||||
/>
|
||||
{error && <span className="text-red-500 text-xs font-bold ps-2 pt-1 flex align-middle">
|
||||
{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputField;
|
||||
37
app/components/Navbar.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
const Navbar = () => {
|
||||
return (
|
||||
<nav className="bg-white drop-shadow-md shadow-gray-400 rounded-xl">
|
||||
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-5">
|
||||
<div className="relative flex h-14 items-center justify-end">
|
||||
<div className=" inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||
<button type="button" className="relative rounded-full bg-gray-800 p-1 text-white hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
||||
<span className="absolute -inset-1.5"></span>
|
||||
<span className="sr-only">View notifications</span>
|
||||
<svg className="size-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
|
||||
<path strokeLinejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="relative ml-3">
|
||||
<div>
|
||||
<button type="button" className="relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
|
||||
<span className="absolute -inset-1.5"></span>
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<img className="size-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
34
app/components/PrimaryButton.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface ButtonProps {
|
||||
type?: "button" | "submit" | "reset";
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
isLoading?:boolean
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
type = "button",
|
||||
children,
|
||||
onClick,
|
||||
className = "",
|
||||
disabled = false,
|
||||
isLoading
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`w-full bg-pink-600 text-white py-2 rounded-lg hover:bg-pink-700 flex align-middle justify-center transition duration-300 ${className} `}
|
||||
>
|
||||
{isLoading?<span className="loading"/>: children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
86
app/components/SideBar.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { LuLayoutDashboard } from 'react-icons/lu';
|
||||
import { RiUser4Line } from 'react-icons/ri';
|
||||
import { TbHome2, TbLogout2 } from 'react-icons/tb';
|
||||
|
||||
|
||||
|
||||
export default function Sidebar() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname(); // Get the current route
|
||||
|
||||
|
||||
// Define the routes with relevant icons
|
||||
const routes = [
|
||||
{
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
icon: <LuLayoutDashboard size={18} />,
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
path: '/home',
|
||||
icon: <TbHome2 size={18}/>,
|
||||
},
|
||||
{
|
||||
name: 'Profile',
|
||||
path: '/profile',
|
||||
icon: <RiUser4Line size={18} />,
|
||||
},
|
||||
];
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/auth/logout', {
|
||||
method: 'GET',
|
||||
credentials: 'include', // Ensure cookies are sent
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('Logout successful');
|
||||
router.push('/login'); // Redirect to login page
|
||||
} else {
|
||||
console.error('Failed to log out:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while logging out:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// <div className="bg-blue-950 text-white h-full z-10 rounded-xl shadow-md transition-width ease-in-out duration-150 w-16 hover:w-1/5">
|
||||
<div className="bg-blue-950 text-white h-full z-10 rounded-xl shadow-md transition-width ease-in-out duration-150 w-1/5">
|
||||
<div className="flex items-center justify-start py-2 ps-3 gap-2">
|
||||
<img src="/images/logo2.png" width={40} alt="Logo" /><h1 className="text-lg font-black ">
|
||||
P ink A ura</h1>
|
||||
</div>
|
||||
<nav className="mt-6">
|
||||
<ul className="p-2">
|
||||
{/* Map through the routes */}
|
||||
{routes.map((route) => (
|
||||
<li
|
||||
key={route.path}
|
||||
onClick={() => router.push(route.path)}
|
||||
className={`flex items-center gap-3 px-4 mb-1 rounded-md py-2 text-sm cursor-pointer transition duration-150 ${
|
||||
pathname === route.path
|
||||
? 'bg-pink-500 text-white' // Active class
|
||||
: 'hover:bg-pink-500 hover:text-white' // Hover class
|
||||
}`}
|
||||
>
|
||||
{route.icon} {/* Render the icon */}
|
||||
<p>{route.name}</p>
|
||||
</li>
|
||||
))}
|
||||
{/* Logout option */}
|
||||
<li
|
||||
onClick={handleLogout}
|
||||
className="flex items-center gap-3 px-4 mb-0 rounded-md py-2 hover:bg-pink-500 hover:text-white text-sm cursor-pointer transition duration-150"
|
||||
><TbLogout2 size={18} />
|
||||
<p>Logout</p>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,23 @@
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* fade-in animation */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
/* transform: translateY(10px); Optional slight slide effect */
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
/* transform: translateY(0); */
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a reusable class for the fade-in effect */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@@ -23,10 +23,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<html lang="en" data-theme="corporate">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
113
app/login/page.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useRouter } from "next/navigation"; // Import the useRouter hook
|
||||
import { useState } from "react";
|
||||
import { schema } from "./schema";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import InputField from "../components/InputField";
|
||||
import Button from "../components/PrimaryButton";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
|
||||
interface LoginFormInputs {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function LoginForm() {
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const router = useRouter(); // Initialize the router
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<LoginFormInputs>({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<LoginFormInputs> = async (data) => {
|
||||
console.log("Form Data:", data);
|
||||
try {
|
||||
const response = await fetch("http://localhost:3000/api/auth/", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include", // Ensures cookies are included with the request
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
console.log("Login Successful:", result);
|
||||
|
||||
// Redirect to dashboard if login is successful
|
||||
if (result.success) {
|
||||
router.push("/dashboard");
|
||||
} else {
|
||||
console.error("Login Failed:", result.message);
|
||||
}
|
||||
} else {
|
||||
setError('Something went wrong!')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error while logging in:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="fade-in flex items-center justify-center min-h-screen bg-gray-100">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="bg-white p-8 shadow-lg rounded-lg w-full max-w-md"
|
||||
>
|
||||
<h2 className="text-2xl font-bold mb-6 text-center text-gray-700">
|
||||
Login
|
||||
</h2>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-100 text-red-700 p-2 ps-3 rounded mb-4 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<InputField
|
||||
label="Username"
|
||||
placeholder="Enter your username"
|
||||
name="username"
|
||||
register={register}
|
||||
error={errors.username?.message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
|
||||
|
||||
|
||||
<InputField
|
||||
label="Password"
|
||||
placeholder="Enter your Password"
|
||||
name="password"
|
||||
register={register}
|
||||
error={errors.password?.message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={isSubmitting} isLoading={isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
<p className="mt-4 text-center text-sm text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="text-blue-600 hover:underline">
|
||||
Sign up
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
app/login/schema.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import * as yup from "yup";
|
||||
// Validation Schema with Yup
|
||||
export const schema = yup.object({
|
||||
username: yup.string().required("Username is required"),
|
||||
password: yup
|
||||
.string()
|
||||
.min(6, "Password must be at least 6 characters")
|
||||
.required("Password is required"),
|
||||
});
|
||||
104
app/page.tsx
@@ -1,101 +1,9 @@
|
||||
import Image from "next/image";
|
||||
import React from 'react'
|
||||
|
||||
export default function Home() {
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
<div>page</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default page
|
||||
27
middleware.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const isAuth = req.cookies.get("isAuth");
|
||||
console.log(isAuth);
|
||||
|
||||
|
||||
if (!isAuth) {
|
||||
console.log("No token found, redirecting to login");
|
||||
return NextResponse.redirect(new URL("/login", req.url));
|
||||
}
|
||||
|
||||
try {
|
||||
// jwt.verify(token, process.env.JWT_SECRET);
|
||||
console.log("Token is valid, proceeding to the dashboard");
|
||||
return NextResponse.next();
|
||||
} catch (error) {
|
||||
console.log("Invalid token, redirecting to login");
|
||||
return NextResponse.redirect(new URL("/login", req.url));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// You can apply this middleware to all pages or specific ones
|
||||
export const config = {
|
||||
matcher: ["/","/dashboard/:path*"], // Apply to certain paths
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { NextConfig } from "next";
|
||||
// import { middleware } from "./app/middleware";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
// middleware: [middleware],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
4168
package-lock.json
generated
20
package.json
@@ -9,19 +9,29 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"axios": "^1.7.9",
|
||||
"next": "15.1.0",
|
||||
"next-pwa": "^5.6.0",
|
||||
"react": "^19.0.0",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.1.0"
|
||||
"react-hook-form": "^7.54.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"yup": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/next-pwa": "^5.6.9",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"daisyui": "^4.12.22",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.0",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
BIN
public/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/icon-144x144.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/icon-152x152.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
public/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
public/icon-72x72.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
public/icon-96x96.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/images/logo.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
public/images/logo2.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
52
public/manifest.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "Daisy Ui",
|
||||
"short_name": "Daisy Ui",
|
||||
"theme_color": "#000",
|
||||
"background_color": "#fff",
|
||||
"display": "standalone",
|
||||
"orientation": "",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
100
public/sw.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// If the loader is already loaded, just stop.
|
||||
if (!self.define) {
|
||||
let registry = {};
|
||||
|
||||
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||
let nextDefineUri;
|
||||
|
||||
const singleRequire = (uri, parentUri) => {
|
||||
uri = new URL(uri + ".js", parentUri).href;
|
||||
return registry[uri] || (
|
||||
|
||||
new Promise(resolve => {
|
||||
if ("document" in self) {
|
||||
const script = document.createElement("script");
|
||||
script.src = uri;
|
||||
script.onload = resolve;
|
||||
document.head.appendChild(script);
|
||||
} else {
|
||||
nextDefineUri = uri;
|
||||
importScripts(uri);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
let promise = registry[uri];
|
||||
if (!promise) {
|
||||
throw new Error(`Module ${uri} didn’t register its module`);
|
||||
}
|
||||
return promise;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
self.define = (depsNames, factory) => {
|
||||
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||
if (registry[uri]) {
|
||||
// Module is already loading or loaded.
|
||||
return;
|
||||
}
|
||||
let exports = {};
|
||||
const require = depUri => singleRequire(depUri, uri);
|
||||
const specialDeps = {
|
||||
module: { uri },
|
||||
exports,
|
||||
require
|
||||
};
|
||||
registry[uri] = Promise.all(depsNames.map(
|
||||
depName => specialDeps[depName] || require(depName)
|
||||
)).then(deps => {
|
||||
factory(...deps);
|
||||
return exports;
|
||||
});
|
||||
};
|
||||
}
|
||||
define(['./workbox-e43f5367'], (function (workbox) { 'use strict';
|
||||
|
||||
importScripts();
|
||||
self.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
workbox.registerRoute("/", new workbox.NetworkFirst({
|
||||
"cacheName": "start-url",
|
||||
plugins: [{
|
||||
cacheWillUpdate: async ({
|
||||
request,
|
||||
response,
|
||||
event,
|
||||
state
|
||||
}) => {
|
||||
if (response && response.type === 'opaqueredirect') {
|
||||
return new Response(response.body, {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: response.headers
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}]
|
||||
}), 'GET');
|
||||
workbox.registerRoute(/.*/i, new workbox.NetworkOnly({
|
||||
"cacheName": "dev",
|
||||
plugins: []
|
||||
}), 'GET');
|
||||
|
||||
}));
|
||||
2455
public/workbox-e43f5367.js
Normal file
@@ -14,5 +14,10 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require('daisyui'),
|
||||
],
|
||||
daisyui: {
|
||||
themes: ["corporate"],
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||