From 552f369ec716eed08cb6c67fcbfa15c82df6dc29 Mon Sep 17 00:00:00 2001 From: Rama Priyanto Date: Tue, 24 Dec 2024 00:29:21 +0700 Subject: [PATCH] fix:navbar, feat:news ticker landing, seo score --- app/(admin)/admin/article/create/page.tsx | 14 +- .../admin/article/detail/[id]/page.tsx | 14 +- components/form/form-detail-article.tsx | 644 ++++++++++-------- components/form/form-edit-article.tsx | 45 +- components/landing/NewsTicker.tsx | 79 +++ components/layout/humas-layout.tsx | 3 + components/layout/navbar/NavbarHumas.tsx | 170 ++--- styles/globals.css | 7 +- 8 files changed, 574 insertions(+), 402 deletions(-) create mode 100644 components/landing/NewsTicker.tsx diff --git a/app/(admin)/admin/article/create/page.tsx b/app/(admin)/admin/article/create/page.tsx index e0230bd..5965afb 100644 --- a/app/(admin)/admin/article/create/page.tsx +++ b/app/(admin)/admin/article/create/page.tsx @@ -1,10 +1,10 @@ -import FormArticle from '@/components/form/form-article' -import { Card } from '@nextui-org/react' +import FormArticle from "@/components/form/form-article"; +import { Card } from "@nextui-org/react"; export default function CreateArticle() { - return ( - - - - ) + return ( + + + + ); } diff --git a/app/(admin)/admin/article/detail/[id]/page.tsx b/app/(admin)/admin/article/detail/[id]/page.tsx index ef37625..e9e3476 100644 --- a/app/(admin)/admin/article/detail/[id]/page.tsx +++ b/app/(admin)/admin/article/detail/[id]/page.tsx @@ -1,10 +1,10 @@ -import FormDetailArticle from '@/components/form/form-detail-article' -import { Card } from '@nextui-org/react' +import FormDetailArticle from "@/components/form/form-detail-article"; +import { Card } from "@nextui-org/react"; export default function DetailArticlePage() { - return ( - - - - ) + return ( + + + + ); } diff --git a/components/form/form-detail-article.tsx b/components/form/form-detail-article.tsx index 065cc3c..de5cc98 100644 --- a/components/form/form-detail-article.tsx +++ b/components/form/form-detail-article.tsx @@ -1,212 +1,337 @@ -'use client' -import { getArticleById } from '@/service/article'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { Button, Card, Chip, Input, Select, SelectItem } from '@nextui-org/react'; -import JoditEditor from 'jodit-react'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { useEffect, useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; -import Swal from 'sweetalert2'; -import withReactContent from 'sweetalert2-react-content'; +"use client"; +import { error } from "@/config/swal"; +import { getArticleById } from "@/service/article"; +import { getSeoScore } from "@/service/generate-article"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + Accordion, + AccordionItem, + Button, + Card, + Chip, + CircularProgress, + Input, + Select, + SelectItem, +} from "@nextui-org/react"; +import JoditEditor from "jodit-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { useForm } from "react-hook-form"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; import * as z from "zod"; +import { TimesIcon } from "../icons"; const articleSchema = z.object({ - title: z.string().min(1, { message: "Required" }), - article: z.string().min(1, { message: "Required" }), - slug: z.string().min(1, { message: "Required" }), - tags: z.string().min(0, { message: "Required" }).optional(), - description: z.string().min(1, { message: "Required" }).optional(), - + title: z.string().min(1, { message: "Required" }), + article: z.string().min(1, { message: "Required" }), + slug: z.string().min(1, { message: "Required" }), + tags: z.string().min(0, { message: "Required" }).optional(), + description: z.string().min(1, { message: "Required" }).optional(), }); +const TypeId = [ + { + key: "1", + label: "Article", + }, + { + key: "2", + label: "Magazine", + }, +]; + export default function FormDetailArticle() { - // const [id, setId] = useState(); - const [title, setTitle] = useState(""); - const [slug, setSlug] = useState(""); - const [tags, setTags] = useState([]); - const [newTags, setNewTags] = useState(""); - const editor = useRef(null); - const [content, setContent] = useState(''); - const MySwal = withReactContent(Swal); - const [article, setArticle] = useState(); - const [typeArticle, setTypeArticle] = useState([]); - const pathname = usePathname(); - const splitPathname = pathname.split('/'); - const id = splitPathname[splitPathname.length - 1]; - console.log(id, "pathnamesplit") + // const [id, setId] = useState(); + const [title, setTitle] = useState(""); + const [slug, setSlug] = useState(""); + const [tags, setTags] = useState([]); + const [newTags, setNewTags] = useState(""); + const editor = useRef(null); + const [content, setContent] = useState(""); + const MySwal = withReactContent(Swal); + const [article, setArticle] = useState(); + const [typeArticle, setTypeArticle] = useState(""); + const pathname = usePathname(); + const splitPathname = pathname.split("/"); + const id = splitPathname[splitPathname.length - 1]; + console.log(id, "pathnamesplit"); - const formOptions = { resolver: zodResolver(articleSchema) }; - type MicroIssueSchema = z.infer; - const { - register, - control, - handleSubmit, - setValue, - formState: { errors }, - } = useForm(formOptions); + const formOptions = { resolver: zodResolver(articleSchema) }; + type MicroIssueSchema = z.infer; + const { + register, + control, + handleSubmit, + setValue, + formState: { errors }, + } = useForm(formOptions); - const editorConfig = { - readonly: true, + const editorConfig = { + readonly: true, + }; + + const handleClose = (tagsToRemove: string) => { + setTags(tags.filter((tag) => tag !== tagsToRemove)); + if (tags.length === 1) { + setTags([]); + } + }; + + const handleAddTags = (e: any) => { + if (newTags.trim() !== "") { + setTags([...tags, newTags.trim()]); + setNewTags(""); + e.preventDefault(); + } + }; + + const handleKeyDown = (event: any) => { + if (event.key === "Enter") { + handleAddTags(event); + } + }; + + useEffect(() => { + async function initState() { + const res = await getArticleById(id); + setArticle(res.data?.data); + setTitle(res.data?.data?.title); + setTypeArticle(String(res.data.data?.typeId)); + console.log("ii", String(res.data.data?.typeId)); + const tagsArray = res.data.data?.tags + ? res.data.data.tags.split(",") + : []; + setTags(tagsArray); + + console.log("Data Aritcle", tagsArray); } - const TypeId = [ - { - key: 1, - label: "Article" - }, - { - key: 2, - label: "Magazine" - }, - ] + initState(); + fetchSeoScore(); + }, []); - const CategoryArticle = [ - { - key: 1, - label: "Article" - }, - { - key: 2, - label: "Magazine" - }, - ] + const [totalScoreSEO, setTotalScoreSEO] = useState(); + const [errorSEO, setErrorSEO] = useState([]); + const [warningSEO, setWarningSEO] = useState([]); + const [optimizedSEO, setOptimizedSEO] = useState([]); - const handleClose = (tagsToRemove: string) => { - setTags(tags.filter((tag) => tag !== tagsToRemove)); - if (tags.length === 1) { - setTags([]); - } - }; - - const handleAddTags = (e: any) => { - if (newTags.trim() !== "") { - setTags([...tags, newTags.trim()]); - setNewTags(""); - e.preventDefault(); - } - }; - - const handleKeyDown = (event: any) => { - if (event.key === "Enter") { - handleAddTags(event); - } - }; - - - useEffect(() => { - async function initState() { - const res = await getArticleById(id); - setArticle(res.data?.data); - setTitle(res.data?.data?.title) - setTypeArticle(res.data.data?.type_id) - const tagsArray = res.data.data?.tags ? res.data.data.tags.split(",") : []; - setTags(tagsArray); - - console.log("Data Aritcle", tagsArray); - } - - initState(); - }, []); - - - async function save(data: any,) { - const formData = { - id: id, - title: title, - typeId: parseInt(String(Array.from(article)[0])), - slug: slug, - tags: tags.join(','), - description: content, - htmlDescription: content - }; - - console.log("Form Data:", formData); - // const response = await createArticle(formData); - - // if (response?.error) { - // error(response.message); - // return false; - // } - }; - - async function onSubmit(data: any) { - MySwal.fire({ - title: "Simpan Data", - text: "", - icon: "warning", - showCancelButton: true, - cancelButtonColor: "#d33", - confirmButtonColor: "#3085d6", - confirmButtonText: "Simpan", - }).then((result) => { - if (result.isConfirmed) { - save(data); - } - }); + const fetchSeoScore = async () => { + const res = await getSeoScore("1931"); + if (res.error) { + error(res.message); + return false; } - return ( -
-
- -
- -
- {(title.length === 0 && errors.title) && errors.title.message} + setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0); + let errorList: any[] = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.error, + ]; + setErrorSEO(errorList); + let warningList: any[] = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.warning, + ]; + setWarningSEO(warningList); + let optimizedList: any[] = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.optimized, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized, + ]; + setOptimizedSEO(optimizedList); + }; -
-
-
- -
- {errors.article?.message} -
- {/*

{article}

*/} -
-
- setSlug(e.target.value)} - label="Slug" - variant='bordered' - placeholder="Enter Text" - labelPlacement='outside' - /> -
- {(slug.length === 0 && errors.slug) && errors.slug.message} -
-
-
-

Tags

- {/* { + if (result.isConfirmed) { + save(data); + } + }); + } + return ( +
+
+

SEO Score

+
+ +
+ {/* */} +
+
+
+ {/* */} + Error : {errorSEO.length || 0} +
+
+ {/*

+ ! +

*/} + Warning : {warningSEO.length || 0} +
+
+ {/* */} + Optimize : {optimizedSEO.length || 0} +
+
+
+ + } + title={`${errorSEO?.length || 0} Errors`} + > +
+ {errorSEO?.map((item: any) => ( +

+ {item} +

+ ))} +
+
+ + // ! + //

+ // } + title={`${warningSEO?.length || 0} Warnings`} + > +
+ {warningSEO?.map((item: any) => ( +

+ {item} +

+ ))} +
+
+ } + title={`${optimizedSEO?.length || 0} Optimized`} + > +
+ {optimizedSEO?.map((item: any) => ( +

+ {item} +

+ ))} +
+
+
+
+ + +
+ +
+ {title.length === 0 && errors.title && errors.title.message} +
+
+
+ +
+ {errors.article?.message} +
+ {/*

{article}

*/} +
+
+ setSlug(e.target.value)} + label="Slug" + variant="bordered" + placeholder="Enter Text" + labelPlacement="outside" + /> +
+ {slug.length === 0 && errors.slug && errors.slug.message} +
+
+
+

Tags

+ {/* */} -
- {(tags.length === 0 && errors.tags) && errors.tags.message} -
-
- {tags.map((tag, index) => ( - handleClose('')}> - {tag} - - ))} -
-
-
-

Description

- setContent(newContent)} - className="dark:text-black" +
+ {tags.length === 0 && errors.tags && errors.tags.message} +
+
+ {tags.map((tag, index) => ( + handleClose("")} + > + {tag} + + ))} +
+
+
+

Description

+ setContent(newContent)} + className="dark:text-black" + /> +
+ {content.length === 0 && + errors.description && + errors.description.message} +
+
- /> -
- {(content.length === 0 && errors.description) && errors.description.message} -
-
-
-

Attachment (Opsional)

-
- -
-
-
- - - - -
- - -
- ) +
+ + + + +
+
+ +
+ ); } diff --git a/components/form/form-edit-article.tsx b/components/form/form-edit-article.tsx index adde604..6f822d6 100644 --- a/components/form/form-edit-article.tsx +++ b/components/form/form-edit-article.tsx @@ -27,6 +27,17 @@ const articleSchema = z.object({ description: z.string().min(1, { message: "Required" }).optional(), }); +const TypeId = [ + { + key: "1", + label: "Article", + }, + { + key: "2", + label: "Magazine", + }, +]; + export default function FormUpdateArticle() { // const [id, setId] = useState(); const [title, setTitle] = useState(""); @@ -57,28 +68,6 @@ export default function FormUpdateArticle() { readonly: true, }; - const TypeId = [ - { - key: 1, - label: "Article", - }, - { - key: 2, - label: "Magazine", - }, - ]; - - const CategoryArticle = [ - { - key: 1, - label: "Article", - }, - { - key: 2, - label: "Magazine", - }, - ]; - const handleClose = (tagsToRemove: string) => { setTags(tags.filter((tag) => tag !== tagsToRemove)); if (tags.length === 1) { @@ -105,7 +94,7 @@ export default function FormUpdateArticle() { const res = await getArticleById(id); setArticle(res.data?.data); setTitle(res.data?.data?.title); - setTypeId(res.data?.data?.typeId); + setTypeId(String(res.data?.data?.typeId)); setSlug(res.data?.data?.slug); const tagsArray = res.data.data?.tags ? res.data.data.tags.split(",") @@ -248,7 +237,7 @@ export default function FormUpdateArticle() { errors.description.message} -
+

Attachment (Opsional)

- +
diff --git a/components/landing/NewsTicker.tsx b/components/landing/NewsTicker.tsx new file mode 100644 index 0000000..abff716 --- /dev/null +++ b/components/landing/NewsTicker.tsx @@ -0,0 +1,79 @@ +"use client"; +import { useEffect, useState } from "react"; +import { ChevronLeftIcon, ChevronRightIcon } from "../icons"; +import { getListArticle } from "@/service/article"; +import { convertDateFormat } from "@/utils/global"; + +export default function NewsTicker() { + const [article, setArticle] = useState([]); + const [currentNewsIndex, setCurrentNewsIndex] = useState(0); + const [animate, setAnimate] = useState(false); + + useEffect(() => { + async function getArticle() { + const req = { page: 1, search: "", limit: "10" }; + const response = await getListArticle(req); + setArticle(response?.data?.data); + } + getArticle(); + }, []); + + const triggerAnimation = (newIndex: number) => { + setAnimate(true); + setTimeout(() => { + setCurrentNewsIndex(newIndex); + setAnimate(false); + }, 300); + }; + + const handlePrev = () => { + const newIndex = (currentNewsIndex - 1 + article.length) % article.length; + triggerAnimation(newIndex); + }; + + const handleNext = () => { + const newIndex = (currentNewsIndex + 1) % article.length; + triggerAnimation(newIndex); + }; + + useEffect(() => { + const interval = setInterval(() => { + triggerAnimation((currentNewsIndex + 1) % article.length); + }, 7000); + + return () => clearInterval(interval); + }, [article.length]); + + return ( +
+
+ BREAKING NEWS +
+
+
+

{article[currentNewsIndex]?.title}

+

+ {convertDateFormat(article[currentNewsIndex]?.createdAt)} +

+
+ +
+ ); +} diff --git a/components/layout/humas-layout.tsx b/components/layout/humas-layout.tsx index 30b39ae..e86ebdf 100644 --- a/components/layout/humas-layout.tsx +++ b/components/layout/humas-layout.tsx @@ -1,6 +1,7 @@ import React from "react"; import NavbarHumas from "./navbar/NavbarHumas"; import Footer from "../landing/Footer"; +import NewsTicker from "../landing/NewsTicker"; interface Props { children: React.ReactNode; @@ -10,6 +11,8 @@ export const HumasLayout = ({ children }: Props) => { return (
+ + {children}
diff --git a/components/layout/navbar/NavbarHumas.tsx b/components/layout/navbar/NavbarHumas.tsx index 8cc98f4..9d8faf6 100644 --- a/components/layout/navbar/NavbarHumas.tsx +++ b/components/layout/navbar/NavbarHumas.tsx @@ -110,80 +110,9 @@ export default function NavbarHumas() { classNames={{ wrapper: "px-0" }} >
-
-
-
- -
- -
- - - - - - - - - - - - - - {token ? ( - // - - - - - - router.push("/admin/dashboard")} - > - Dashboard - - Profile - - - Logout - - - - ) : ( - - - - )} -
-
+
@@ -1053,19 +982,90 @@ export default function NavbarHumas() { -
-
{searchInput}
+
+
+ +
+ +
+ + + + + + + + + + + + + + {token ? ( + // + + + + + + router.push("/admin/dashboard")} + > + Dashboard + + Profile - - language === "id" ? setLanguage("en") : setLanguage("id") - } - > - {language === "id" ? : } - -
- + + Logout + + + + ) : ( + + + + )} +
+
diff --git a/styles/globals.css b/styles/globals.css index e419463..32458ef 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -2,13 +2,11 @@ @tailwind components; @tailwind utilities; - /* *{ border:1px solid green; } */ - .custom-scrollbar::-webkit-scrollbar { width: 1px; } @@ -34,3 +32,8 @@ --scroll-shadow-size: 40px; } +@layer utilities { + .clip-path-triangle { + clip-path: polygon(0 0, 100% 50%, 0 100%); + } +}