Skip to content

Commit

Permalink
feat: admin
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenthanhanit committed Dec 12, 2023
1 parent 4a15fe5 commit bc09449
Show file tree
Hide file tree
Showing 18 changed files with 5,812 additions and 559 deletions.
125 changes: 125 additions & 0 deletions app/admin/[slug]/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use client'

import React from 'react'
import "react-datepicker/dist/react-datepicker.css";
import ScrollToTop from "react-scroll-to-top";
import {Button} from "@nextui-org/button";
import {Input} from "@nextui-org/input";
import {useForm, Controller} from "react-hook-form";
import 'react-quill/dist/quill.snow.css';
import ReactQuill from 'react-quill';
import DatePicker from "react-datepicker";
import moment from "moment/moment";
import {useParams} from "next/navigation";

export default function ListPage() {
const {
register,
handleSubmit,
control,
watch,
reset,
formState: {errors},
} = useForm({
mode: "onBlur",
defaultValues: {
name: "",
publish_at: undefined,
content: "",
thumbnail: "",
},
});

const params = useParams();
const type = params['slug'];

const onSubmit = async (data: any) => {
data.publish_at = moment(data.publish_at).format("x");
const res = await fetch('/admin/api', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({type, data}),
})

};

const inputRegister = register("name", {required: true});

return (
<main className="grow">
<section className="relative">
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="py-12 md:py-20">
{/* Section header */}
<div className="max-w-3xl mx-auto text-center pb-2 md:pb-2">
<h1 className="h2 mb-4 text-3xl">Thêm mới</h1>
</div>

{/* Section content */}
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col items-start gap-4"
>
<div className="flex flex-col mt-4 w-full gap-4">
<Input
label="Tên bài viết"
placeholder="Nhập tên bài viết"
variant="bordered"
errorMessage={errors.name && "Tên yêu cầu"}
radius={'md'}
// @ts-ignore
validationState={errors.name ? "invalid" : "valid"}
{...inputRegister}
/>

<Controller
name="publish_at"
control={control}
rules={{required: true}}
render={({field}) => {
return <DatePicker
showIcon
className="border w-full rounded-md"
selected={field.value}
placeholderText="Ngày đăng"
{...field}
/>
}}
/>

<Controller
name="content"
control={control}
rules={{required: true}}
render={({field}) => <ReactQuill theme="snow" {...field} className={'h-96'} modules={{
toolbar: [
[{'header': [1, 2, false]}],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{'list': 'ordered'}, {'list': 'bullet'}, {'indent': '-1'}, {'indent': '+1'}],
['link', 'image'],
['clean']
],
}}/>}
/>
</div>
<div className="flex items-center gap-4">
<Button
color="primary"
variant="bordered"
type="submit"
className="mt-10 shadow-sm hover:bg-blue-500 hover:text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Save
</Button>
</div>
</form>

</div>
</div>
</section>
<ScrollToTop smooth color={'rgb(51 140 245)'}/>
</main>
)
}
65 changes: 65 additions & 0 deletions app/admin/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client'

import {useEffect, useState} from "react";
import {useParams} from 'next/navigation'
import {useSession} from "next-auth/react";
import ScrollToTop from "react-scroll-to-top";
import List from "@/components/admin/list";
import {Spinner} from "@nextui-org/spinner";

export default function ListPage() {
const {data: session} = useSession();
const [list, setList] = useState<[]>([]);
const [loading, setLoading] = useState(true);

const params = useParams();
const type = params['slug'];

// This code inside useEffect will run when the component is mounted
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`/admin/api?type=${type}`);

return await res.json();
}

if (session?.user) {
fetchData().then(res => {
setList(res)
setLoading(false);
});
}
}, [session]);

if (loading) {
return <main className="grow">
<section className="relative">
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="py-12 md:py-20 text-center">
<Spinner label="Loading" color="primary" labelColor="primary"/>
</div>
</div>
</section>
</main>
}

return (
<main className="grow">
<section className="relative">
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="py-12 md:py-20">
{/* Section header */}
<div className="max-w-3xl mx-auto text-center pb-2 md:pb-2">
<h1 className="h2 mb-4 text-3xl">Danh sách {type === 'news' ? 'tin tức' : 'dự án'}</h1>
</div>

{/* Section content */}
// @ts-ignore
<List data={list} type={type}/>
</div>
</div>
</section>
<ScrollToTop smooth color={'rgb(51 140 245)'}/>
</main>
)
}
40 changes: 40 additions & 0 deletions app/admin/api/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {type NextRequest, type NextResponse} from 'next/server'
import dbConnect from "@/lib/dbConnect";
import News from "@/models/News";
import Project from "@/models/Project";

export async function GET(req: NextRequest) {
const searchParams = req.nextUrl.searchParams
const type = searchParams.get('type')

await dbConnect();

if (type === 'news') {
const data = await News.find({});
return new Response(JSON.stringify(data), {
status: 200,
})
}

const data = await Project.find({});

return new Response(JSON.stringify(data), {
status: 200,
})
}

export async function POST(req: NextRequest, res: NextResponse) {
const {data, type} = await req.json()

await dbConnect();

if (type === 'news') {
await News.create(data);
} else {
await Project.create(data);
}

return new Response('Successfully', {
status: 200,
})
}
11 changes: 11 additions & 0 deletions app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";

export default function AdminLayout({children}: {
children: React.ReactNode;
}) {
return (
<>
{children}
</>
);
}
48 changes: 48 additions & 0 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import {getServerSession} from "next-auth/next";
import {config} from "@/auth";
import Link from "next/link";
import {Button} from "@nextui-org/button";

export default async function AdminPage() {
const session = await getServerSession(config);

if (!session) {
return <main className="grow">
<section className="relative">
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="py-20 md:pt-20">
<div className="max-w-3xl mx-auto text-center pb-2 md:pb-2">
<Button
href={'/api/auth/signin'}
as={Link}
color="primary"
variant="bordered"
className={'shadow-sm hover:bg-blue-500 hover:text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600'}
>
Đăng nhập
</Button>
</div>
</div>
</div>
</section>
</main>
}

return (
<main className="grow">
<section className="relative">
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="py-20 md:pt-20">
<div className="max-w-3xl mx-auto text-center pb-2 md:pb-2">
<h1 className="h2 mb-4 text-3xl font-bold">Xin chào, {session?.user?.name}</h1>
<p className="text-xl text-gray-600" data-aos="fade-up">Chào mừng đến với trang quản lý của Little Universe</p>
</div>
{/* Section content */}

</div>
</div>
</section>
</main>
);
}
30 changes: 26 additions & 4 deletions app/contact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,32 @@ import axios from "axios";
import AOS from 'aos'
import 'aos/dist/aos.css'
import {Button} from "@nextui-org/button";
import Input from "@/components/input";
import TextArea from "@/components/text-area";
import {validate} from "@/utils/validate";
import Input from "@/components/form/input";
import TextArea from "@/components/form/text-area";

const validate = ({
name,
email,
message,
}: {
name: string;
email: string;
message: string;
}) => {
const errors: { name?: string; email?: string; message?: string } = {};
if (!name || name.trim() === "") {
errors.name = "Name is required";
}
if (!email || email.trim() === "") {
errors.email = "Email is required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(email)) {
errors.email = "Invalid email address";
}
if (!message || message.trim() === "") {
errors.message = "Message is required";
}
return errors;
};

export default function ContactPage() {
const [values, setValues] = useState({
Expand All @@ -33,7 +56,6 @@ export default function ContactPage() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const errors = validate(values);
console.log(errors)
if (errors && Object.keys(errors).length > 0) {
return setErrors(errors);
}
Expand Down
Loading

0 comments on commit bc09449

Please sign in to comment.