From 917b90a26d917cfe30c677bb2f05677f2ab4b86b Mon Sep 17 00:00:00 2001 From: Rampeur Date: Mon, 11 Aug 2025 01:14:50 +0200 Subject: [PATCH] =?UTF-8?q?Mise=20=C3=A0=20jour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 + .env.production | 2 + .env.sample | 3 +- .husky/pre-commit | 4 +- src/app/_components/timeClock/timeClock.tsx | 336 +++++++++--------- .../_components/articleArea/articleArea.tsx | 114 +++--- .../_components/commentCard/presentation.tsx | 57 +-- .../_components/commentForm/presentation.tsx | 103 +++--- src/app/article/[slug]/page.tsx | 33 +- src/app/articles/page.tsx | 78 ++++ src/app/editor/[slug]/page.tsx | 33 +- src/app/editor/page.tsx | 19 +- src/app/error.tsx | 44 ++- src/app/layout.tsx | 8 +- .../_components/loginForm/presentation.tsx | 108 +++--- src/app/login/page.tsx | 27 +- src/app/not-found.tsx | 16 +- src/app/page.tsx | 96 +---- src/app/profile/[username]/layout.tsx | 60 ++-- src/app/profile/[username]/page.tsx | 54 +-- .../registrationForm/presentation.tsx | 174 ++++----- src/app/register/page.tsx | 27 +- src/app/settings/_components/presentation.tsx | 208 ++++++----- src/app/settings/_components/types.ts | 6 +- src/app/settings/page.tsx | 35 +- .../components/button/buttonOffcanvas.tsx | 11 +- .../common/components/footer/footer.tsx | 230 ++++++------ .../components/articleEditor/presentation.tsx | 230 ++++++------ .../deleteArticleButton/presentation.tsx | 24 +- .../favoriteButton/presentation.tsx | 47 +-- .../features/article/components/tag/tag.tsx | 21 +- .../auth/components/header/presentation.tsx | 31 +- .../components/logoutButton/container.tsx | 12 +- .../components/logoutButton/presentation.tsx | 14 +- .../components/followButton/presentation.tsx | 41 +-- .../features/profile/fetch/fetchProfile.ts | 1 + src/styles/fonts/icomoon.eot | Bin 5860 -> 12480 bytes src/styles/fonts/icomoon.svg | 26 ++ src/styles/fonts/icomoon.ttf | Bin 5696 -> 12316 bytes src/styles/fonts/icomoon.woff | Bin 5772 -> 12392 bytes src/styles/icomoon.scss | 191 ++++++---- src/styles/styles.scss | 50 ++- src/utils/types/models.ts | 80 ++--- 43 files changed, 1415 insertions(+), 1241 deletions(-) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 src/app/articles/page.tsx diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..8b1a401 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +APP_ENV="development" +API_BASE_URL=http://localhost:3200 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..a122a52 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +APP_ENV="production" +API_BASE_URL=http://api.adastra-cbd.com/api diff --git a/.env.sample b/.env.sample index 596913e..7451781 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,2 @@ -API_BASE_URL=http://localhost/api +APP_ENV="development" +API_BASE_URL=http://localhost:3200/api diff --git a/.husky/pre-commit b/.husky/pre-commit index 458b644..6be6160 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,3 @@ npm test -prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown -git update-index --again \ No newline at end of file +#prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown +#git update-index --again \ No newline at end of file diff --git a/src/app/_components/timeClock/timeClock.tsx b/src/app/_components/timeClock/timeClock.tsx index 9db3209..c2ca8bd 100644 --- a/src/app/_components/timeClock/timeClock.tsx +++ b/src/app/_components/timeClock/timeClock.tsx @@ -3,182 +3,182 @@ import React, { useEffect } from "react"; const TimeClock: React.FC = () => { - useEffect(() => { - const numbers = [ - [1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1], // 0 - [1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1], // 1 - [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1], // 2 - [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 3 - [1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1], // 4 - [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1], // 5 - [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1], // 6 - [1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0], // 7 - [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 8 - [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 9 - ]; + useEffect(() => { + const numbers = [ + [1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1], // 0 + [1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1], // 1 + [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1], // 2 + [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 3 + [1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1], // 4 + [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1], // 5 + [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1], // 6 + [1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0], // 7 + [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 8 + [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 9 + ]; - const blocks: Array = []; - const digits = Array.from(document.querySelectorAll(".block")); + const blocks: Array = []; + const digits = Array.from(document.querySelectorAll(".block")); - for (let i = 0; i < 4; i++) { - blocks.push(digits.slice(i * 15, i * 15 + 15)); - } - - const setNum = (block: Array, num: number) => { - const n = numbers[num]; - for (let i = 0; i < block.length; i++) { - block[i].classList[n[i] === 1 ? "add" : "remove"]("active"); - } - }; - - const time = { - s: "", - m: "", - h: "", - p: 0, - }; - - // time loop - const animator = () => { - const d = new Date(); - let h = d.getHours().toString(), - m = d.getMinutes().toString(), - s = d.getSeconds().toString(); - - s = s.length === 1 ? "0" + s : s; - m = m.length === 1 ? "0" + m : m; - h = h.length === 1 ? "0" + h : h; - - if (s !== time.s) { - for (let i = 0; i < digits.length; i++) { - const d = digits[i]; - if (i === +s) { - d.classList.add("second"); - if (time.p !== null) digits[time.p].classList.remove("second"); - time.p = i; - time.s = s; - } + for (let i = 0; i < 4; i++) { + blocks.push(digits.slice(i * 15, i * 15 + 15)); } - } - if (m !== time.m) { - setNum(blocks[2], parseInt(Array.from(m)[0])); - setNum(blocks[3], parseInt(Array.from(m)[1])); - time.m = m; - } + const setNum = (block: Array, num: number) => { + const n = numbers[num]; + for (let i = 0; i < block.length; i++) { + block[i].classList[n[i] === 1 ? "add" : "remove"]("active"); + } + }; - if (h !== time.h) { - setNum(blocks[0], parseInt(Array.from(h)[0])); - setNum(blocks[1], parseInt(Array.from(h)[1])); - time.h = h; - } - window.requestAnimationFrame(animator); - }; + const time = { + s: "", + m: "", + h: "", + p: 0, + }; - // init - window.requestAnimationFrame(animator); + // time loop + const animator = () => { + const d = new Date(); + let h = d.getHours().toString(), + m = d.getMinutes().toString(), + s = d.getSeconds().toString(); - // toggle button - const d = new Date(); - const days = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"]; - const months = [ - "Janvier", - "Février", - "Mars", - "Avril", - "Mai", - "Juin", - "Juillet", - "Août", - "Septembre", - "Octobre", - "Novembre", - "Décembre", - ]; - const lib_d = days[d.getDay()]; - const lib_m = months[d.getMonth()]; - const lib_n = d.getDate(); - const lib_y = d.getFullYear(); - document.getElementById("datelib")!.innerHTML = lib_d + " " + lib_n + " " + lib_m + " " + lib_y; - }, []); + s = s.length === 1 ? "0" + s : s; + m = m.length === 1 ? "0" + m : m; + h = h.length === 1 ? "0" + h : h; - return ( - <> -
-
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - ); + if (s !== time.s) { + for (let i = 0; i < digits.length; i++) { + const d = digits[i]; + if (i === +s) { + d.classList.add("second"); + if (time.p !== null) digits[time.p].classList.remove("second"); + time.p = i; + time.s = s; + } + } + } + + if (m !== time.m) { + setNum(blocks[2], parseInt(Array.from(m)[0])); + setNum(blocks[3], parseInt(Array.from(m)[1])); + time.m = m; + } + + if (h !== time.h) { + setNum(blocks[0], parseInt(Array.from(h)[0])); + setNum(blocks[1], parseInt(Array.from(h)[1])); + time.h = h; + } + window.requestAnimationFrame(animator); + }; + + // init + window.requestAnimationFrame(animator); + + // toggle button + const d = new Date(); + const days = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"]; + const months = [ + "Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Août", + "Septembre", + "Octobre", + "Novembre", + "Décembre", + ]; + const lib_d = days[d.getDay()]; + const lib_m = months[d.getMonth()]; + const lib_n = d.getDate(); + const lib_y = d.getFullYear(); + document.getElementById("datelib")!.innerHTML = lib_d + " " + lib_n + " " + lib_m + " " + lib_y; + }, []); + + return ( + <> +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + ); }; export default TimeClock; diff --git a/src/app/article/[slug]/_components/articleArea/articleArea.tsx b/src/app/article/[slug]/_components/articleArea/articleArea.tsx index bcefd94..4c07695 100644 --- a/src/app/article/[slug]/_components/articleArea/articleArea.tsx +++ b/src/app/article/[slug]/_components/articleArea/articleArea.tsx @@ -1,3 +1,4 @@ +import { Container, Col, Row } from "react-bootstrap"; import { Button } from "@/modules/common/components/button"; import { DeleteArticleButton } from "@/modules/features/article/components/deleteArticleButton"; import { FavoriteButton } from "@/modules/features/article/components/favoriteButton"; @@ -15,66 +16,65 @@ import { getSession } from "@/utils/auth/session"; import { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser"; const Actions = ({ article, currentUser }: { article: Article; currentUser?: User }) => { - const profile = article.author; + const profile = article.author; - return ( -
- {profile.image && } -
- - {profile.username} - - {article.createdAt.toDateString()} -
- {showFollowButton(profile.username, currentUser) && ( - - )} - - {showEditArticleButton(profile.username, currentUser) && ( - - )} - {showDeleteArticleButton(profile.username, currentUser) && ( - - )} -
- ); + return ( +
+ {profile.image && } +
+ + {profile.username} + + {article.createdAt.toDateString()} +
+ {showFollowButton(profile.username, currentUser) && ( + + )} + + {showEditArticleButton(profile.username, currentUser) && ( + + )} + {showDeleteArticleButton(profile.username, currentUser) && ( + + )} +
+ ); }; export const ArticleArea = async ({ slug, children }: { slug: string; children: ReactNode }) => { - const article = await fetchArticle(slug); - const body = await convertMarkdownToHtml(article.body); - const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined; + const article = await fetchArticle(slug); + const body = await convertMarkdownToHtml(article.body); + const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined; - return ( -
-
-
-

{article.title}

- -
-
- -
-
-
-
-
    - {article.tagList.map((tag, index) => ( - - {tag} - - ))} -
-
-
-
-
- -
- {children} -
-
- ); + return ( +
+ +

{article.title}

+ +
+ + + +
+
    + {article.tagList.map((tag, index) => ( +
  • + + {tag} + +
  • + ))} +
+ + +
+
+ +
+ {children} + +
+ ); }; diff --git a/src/app/article/[slug]/_components/commentCard/presentation.tsx b/src/app/article/[slug]/_components/commentCard/presentation.tsx index eb361be..764e3f6 100644 --- a/src/app/article/[slug]/_components/commentCard/presentation.tsx +++ b/src/app/article/[slug]/_components/commentCard/presentation.tsx @@ -1,38 +1,39 @@ +import { Button, Form } from "react-bootstrap"; import Link from "next/link"; import Image from "next/image"; import { Comment } from "@/utils/types/models"; import styles from "./presentation.module.css"; type Props = { - comment: Comment; - showDeleteCommentButton?: boolean; - deleteCommentAction?: () => void; - isPending?: boolean; + comment: Comment; + showDeleteCommentButton?: boolean; + deleteCommentAction?: () => void; + isPending?: boolean; }; export const CommentCard = ({ comment, showDeleteCommentButton, deleteCommentAction }: Props) => { - return ( -
-
-

{comment.body}

-
-
- - {comment.author.image && } - -   - - {comment.author.username} - - {comment.createdAt.toDateString()} - {showDeleteCommentButton && ( -
- - - )} -
-
- ); + return ( +
+
+

{comment.body}

+
+
+ + {comment.author.image && } + +   + + {comment.author.username} + + {comment.createdAt.toDateString()} + {showDeleteCommentButton && ( +
+ + + )} +
+
+ ); }; diff --git a/src/app/article/[slug]/_components/commentForm/presentation.tsx b/src/app/article/[slug]/_components/commentForm/presentation.tsx index 1118aa7..3a4b99e 100644 --- a/src/app/article/[slug]/_components/commentForm/presentation.tsx +++ b/src/app/article/[slug]/_components/commentForm/presentation.tsx @@ -1,5 +1,5 @@ +import { Button, Form } from "react-bootstrap"; import Image from "next/image"; -import { Button } from "@/modules/common/components/button"; import { ErrorMessage } from "@/modules/common/components/errorMessage"; import { SubmissionResult, useForm } from "@conform-to/react"; import { parseWithZod } from "@conform-to/zod"; @@ -7,57 +7,62 @@ import { startTransition } from "react"; import { inputsSchema } from "./types"; type Props = { - slug?: string; - authorImage?: string; - result?: SubmissionResult; - postCommentAction?: (formData: FormData) => void; - isPending?: boolean; + slug?: string; + authorImage?: string; + result?: SubmissionResult; + postCommentAction?: (formData: FormData) => void; + isPending?: boolean; }; export const CommentForm = ({ slug, authorImage, result, postCommentAction, isPending }: Props) => { - const [form, fields] = useForm({ - defaultValue: { - slug, - }, - lastResult: result, - onValidate({ formData }) { - return parseWithZod(formData, { schema: inputsSchema }); - }, - // onSubmit is defined to avoid resetting form after successful submission - // see https://github.com/edmundhung/conform/discussions/606 - onSubmit(event, { formData }) { - event.preventDefault(); - startTransition(() => { - postCommentAction?.(formData); - }); - }, - shouldRevalidate: "onBlur", - }); + const [form, fields] = useForm({ + defaultValue: { + slug, + }, + lastResult: result, + onValidate({ formData }) { + return parseWithZod(formData, { schema: inputsSchema }); + }, + // onSubmit is defined to avoid resetting form after successful submission + // see https://github.com/edmundhung/conform/discussions/606 + onSubmit(event, { formData }) { + event.preventDefault(); + startTransition(() => { + postCommentAction?.(formData); + }); + }, + shouldRevalidate: "onBlur", + }); - const errors = Object.values(form.allErrors).flat(); + const errors = Object.values(form.allErrors).flat(); - return ( - <> -
- -
- - -
-
- {authorImage && } - -
-
- - ); + return ( + <> +
+ +
+ + +
+
+ {authorImage && } + +
+ + + ); }; diff --git a/src/app/article/[slug]/page.tsx b/src/app/article/[slug]/page.tsx index cbf57f3..311d170 100644 --- a/src/app/article/[slug]/page.tsx +++ b/src/app/article/[slug]/page.tsx @@ -1,30 +1,31 @@ +import { Col, Row } from "react-bootstrap"; +import { Suspense } from "react"; import { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser"; import { getSession } from "@/utils/auth/session"; -import { Suspense } from "react"; import { ArticleArea } from "./_components/articleArea"; import { CommentForm } from "./_components/commentForm"; import { CommentList } from "./_components/commentList"; type Params = Promise<{ - slug: string; + slug: string; }>; const Page = async (props: { params: Params }) => { - const currentUserPromise = (await getSession()) ? fetchCurrentUser() : undefined; - const params = await props.params; + const currentUserPromise = (await getSession()) ? fetchCurrentUser() : undefined; + const params = await props.params; - return ( - -
-
- - ⌛Loading comments...

}> - -
-
-
-
- ); + return ( + + + + + ⌛Loading comments...

}> + +
+ +
+
+ ); }; export default Page; diff --git a/src/app/articles/page.tsx b/src/app/articles/page.tsx new file mode 100644 index 0000000..dcff1fe --- /dev/null +++ b/src/app/articles/page.tsx @@ -0,0 +1,78 @@ +import { Container, Col, Row } from "react-bootstrap"; +import { getSession } from "@/utils/auth/session"; +import { ExhaustiveError } from "@/utils/errors"; +import clsx from "clsx"; +import Link from "next/link"; +import { Suspense } from "react"; +import { FeedArticleList } from "../_components/feedArticleList"; +import { GlobalArticleList } from "../_components/globalArticleList"; +import { TagArticleList } from "../_components/tagArticleList"; +import { TagList } from "../_components/tagList"; +import { SearchParams } from "../_types"; +//import { ArticlesList } from "./_components/articlesList"; + +const ArticleList = (searchParams: SearchParams) => { + switch (searchParams.tab) { + case "yours": + return ; + case "global": + return ; + case "tag": + return ; + default: + throw new ExhaustiveError(searchParams.tab, "all tab cases are not covered"); + } +}; + +const Page = async (props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) => { + const session = await getSession(); + const searchParams = SearchParams.parse(await props.searchParams); + + return ( +
+ +
+ + +
+
    + {session && ( +
  • + + Your Feed + +
  • + )} +
  • + + Global Feed + +
  • + {searchParams.tag && ( +
  • + + #{searchParams.tag} + +
  • + )} +
+
+ ⌛Chargement...

}> + +
+ + + ⌛Chargement...

}> + +
+ +
+
+
+ ); +}; + +export default Page; diff --git a/src/app/editor/[slug]/page.tsx b/src/app/editor/[slug]/page.tsx index aa9f731..b8da965 100644 --- a/src/app/editor/[slug]/page.tsx +++ b/src/app/editor/[slug]/page.tsx @@ -1,29 +1,30 @@ +import { Container, Col, Row } from "react-bootstrap"; import { UpdateArticleEditor } from "@/modules/features/article/components/articleEditor"; import { fetchArticle } from "@/modules/features/article/fetch/fetchArticle"; import { Suspense } from "react"; type Props = { - params: Promise<{ - slug: string; - }>; + params: Promise<{ + slug: string; + }>; }; const Page = async ({ params }: Props) => { - const article = fetchArticle((await params).slug); + const article = fetchArticle((await params).slug); - return ( -
-
-
-
- ⌛Loading...

}> - -
-
+ return ( +
+ + + + ⌛ Chargement...

}> + +
+ +
+
-
-
- ); + ); }; export default Page; diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx index 59303cb..f68f9d1 100644 --- a/src/app/editor/page.tsx +++ b/src/app/editor/page.tsx @@ -1,15 +1,16 @@ +import { Container, Col, Row } from "react-bootstrap"; import { CreateArticleEditor } from "@/modules/features/article/components/articleEditor"; const Page = () => ( -
-
-
-
- -
-
-
-
+
+ + + + + + + +
); export default Page; diff --git a/src/app/error.tsx b/src/app/error.tsx index e2688ee..e6375a6 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -1,25 +1,37 @@ "use client"; -import { Button } from "@/modules/common/components/button"; +//import { Button } from "@/modules/common/components/button"; +import { Container, Button, Col, Row } from "react-bootstrap"; +import TimeClock from "./_components/timeClock/timeClock"; const Error = ({ - reset, + reset, }: { - error: Error & { - digest?: string; - }; - reset: () => void; + error: Error & { + digest?: string; + }; + reset: () => void; }) => { - return ( -
-
-

⚠️Something went wrong!

- -
-
- ); + return ( +
+ + + +

⚠️ Une erreur est survenue!

+

La page que vous vouliez consulter n'est pas d'isponible pour le moment.

+ + + + +
+
+ + + +
+ ); }; export default Error; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ab35f01..2bd51b9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -58,11 +58,11 @@ const RootLayout = ({ }>) => { return ( - +
-
- {children} -
+
+ {children} +