Mise à jour

This commit is contained in:
Rampeur
2025-08-11 01:14:50 +02:00
parent e1e703c2d8
commit 917b90a26d
43 changed files with 1415 additions and 1241 deletions
+168 -168
View File
@@ -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<Element[]> = [];
const digits = Array.from(document.querySelectorAll(".block"));
const blocks: Array<Element[]> = [];
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<Element>, 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<Element>, 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 (
<>
<div className="date d-flex mb-2 pt-2">
<div className="flex-grow-1"></div>
<div className="datelib mt-2 pt-1 ps-1 font-monospace opacity-50 fs-5">
<span className="icon-calendar me-2" aria-hidden="true"></span>
<span id="datelib"></span>
</div>
</div>
<div className="time d-flex">
<div className="flex-grow-1"></div>
<div className="clock position-relative d-flex flex-column flex-wrap">
<div className="block" data-num="0"></div>
<div className="block" data-num="1"></div>
<div className="block" data-num="2"></div>
<div className="block" data-num="3"></div>
<div className="block" data-num="4"></div>
<div className="block" data-num="5"></div>
<div className="block" data-num="6"></div>
<div className="block" data-num="7"></div>
<div className="block" data-num="8"></div>
<div className="block" data-num="9"></div>
<div className="block" data-num="10"></div>
<div className="block" data-num="11"></div>
<div className="block" data-num="12"></div>
<div className="block" data-num="13"></div>
<div className="block" data-num="14"></div>
<div className="block" data-num="15"></div>
<div className="block" data-num="16"></div>
<div className="block" data-num="17"></div>
<div className="block" data-num="18"></div>
<div className="block" data-num="19"></div>
<div className="block" data-num="20"></div>
<div className="block" data-num="21"></div>
<div className="block" data-num="22"></div>
<div className="block" data-num="23"></div>
<div className="block" data-num="24"></div>
<div className="block" data-num="25"></div>
<div className="block" data-num="26"></div>
<div className="block" data-num="27"></div>
<div className="block" data-num="28"></div>
<div className="block" data-num="29"></div>
<div className="block" data-num="30"></div>
<div className="block" data-num="31"></div>
<div className="block" data-num="32"></div>
<div className="block" data-num="33"></div>
<div className="block" data-num="34"></div>
<div className="block" data-num="35"></div>
<div className="block" data-num="36"></div>
<div className="block" data-num="37"></div>
<div className="block" data-num="38"></div>
<div className="block" data-num="39"></div>
<div className="block" data-num="40"></div>
<div className="block" data-num="41"></div>
<div className="block" data-num="42"></div>
<div className="block" data-num="43"></div>
<div className="block" data-num="44"></div>
<div className="block" data-num="45"></div>
<div className="block" data-num="46"></div>
<div className="block" data-num="47"></div>
<div className="block" data-num="48"></div>
<div className="block" data-num="49"></div>
<div className="block" data-num="50"></div>
<div className="block" data-num="51"></div>
<div className="block" data-num="52"></div>
<div className="block" data-num="53"></div>
<div className="block" data-num="54"></div>
<div className="block" data-num="55"></div>
<div className="block" data-num="56"></div>
<div className="block" data-num="57"></div>
<div className="block" data-num="58"></div>
<div className="block" data-num="59"></div>
<div className="divider position-absolute"></div>
</div>
</div>
</>
);
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 (
<>
<div className="date d-flex mb-2 pt-2">
<div className="flex-grow-1"></div>
<div className="datelib mt-2 pt-1 ps-1 font-monospace fs-5">
<span className="icon-calendar me-2" aria-hidden="true"></span>
<span id="datelib"></span>
</div>
</div>
<div className="time d-flex">
<div className="flex-grow-1"></div>
<div className="clock position-relative d-flex flex-column flex-wrap">
<div className="block" data-num="0"></div>
<div className="block" data-num="1"></div>
<div className="block" data-num="2"></div>
<div className="block" data-num="3"></div>
<div className="block" data-num="4"></div>
<div className="block" data-num="5"></div>
<div className="block" data-num="6"></div>
<div className="block" data-num="7"></div>
<div className="block" data-num="8"></div>
<div className="block" data-num="9"></div>
<div className="block" data-num="10"></div>
<div className="block" data-num="11"></div>
<div className="block" data-num="12"></div>
<div className="block" data-num="13"></div>
<div className="block" data-num="14"></div>
<div className="block" data-num="15"></div>
<div className="block" data-num="16"></div>
<div className="block" data-num="17"></div>
<div className="block" data-num="18"></div>
<div className="block" data-num="19"></div>
<div className="block" data-num="20"></div>
<div className="block" data-num="21"></div>
<div className="block" data-num="22"></div>
<div className="block" data-num="23"></div>
<div className="block" data-num="24"></div>
<div className="block" data-num="25"></div>
<div className="block" data-num="26"></div>
<div className="block" data-num="27"></div>
<div className="block" data-num="28"></div>
<div className="block" data-num="29"></div>
<div className="block" data-num="30"></div>
<div className="block" data-num="31"></div>
<div className="block" data-num="32"></div>
<div className="block" data-num="33"></div>
<div className="block" data-num="34"></div>
<div className="block" data-num="35"></div>
<div className="block" data-num="36"></div>
<div className="block" data-num="37"></div>
<div className="block" data-num="38"></div>
<div className="block" data-num="39"></div>
<div className="block" data-num="40"></div>
<div className="block" data-num="41"></div>
<div className="block" data-num="42"></div>
<div className="block" data-num="43"></div>
<div className="block" data-num="44"></div>
<div className="block" data-num="45"></div>
<div className="block" data-num="46"></div>
<div className="block" data-num="47"></div>
<div className="block" data-num="48"></div>
<div className="block" data-num="49"></div>
<div className="block" data-num="50"></div>
<div className="block" data-num="51"></div>
<div className="block" data-num="52"></div>
<div className="block" data-num="53"></div>
<div className="block" data-num="54"></div>
<div className="block" data-num="55"></div>
<div className="block" data-num="56"></div>
<div className="block" data-num="57"></div>
<div className="block" data-num="58"></div>
<div className="block" data-num="59"></div>
<div className="divider position-absolute"></div>
</div>
</div>
</>
);
};
export default TimeClock;
@@ -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 (
<div className="article-meta">
<Link href={`/profile/${profile.username}`}>{profile.image && <Image src={profile.image} alt="" />}</Link>
<div className="info">
<Link href={`/profile/${profile.username}`} className="author">
{profile.username}
</Link>
<span className="date">{article.createdAt.toDateString()}</span>
</div>
{showFollowButton(profile.username, currentUser) && (
<FollowButton {...profile} className={styles["action-btn"]} />
)}
<FavoriteButton {...article} showMessage={true} className={styles["action-btn"]} />
{showEditArticleButton(profile.username, currentUser) && (
<Button component="a" href={`/editor/${article.slug}`} color="secondary" className={styles["action-btn"]}>
<i className="ion-edit"></i> Edit Article
</Button>
)}
{showDeleteArticleButton(profile.username, currentUser) && (
<DeleteArticleButton slug={article.slug} className={styles["action-btn"]} />
)}
</div>
);
return (
<div className="article-meta">
<Link href={`/profile/${profile.username}`}>{profile.image && <Image src={profile.image} alt="" />}</Link>
<div className="info">
<Link href={`/profile/${profile.username}`} className="author">
{profile.username}
</Link>
<span className="date">{article.createdAt.toDateString()}</span>
</div>
{showFollowButton(profile.username, currentUser) && (
<FollowButton {...profile} className={styles["action-btn"]} />
)}
<FavoriteButton {...article} showMessage={true} className={styles["action-btn"]} />
{showEditArticleButton(profile.username, currentUser) && (
<Button component="a" href={`/editor/${article.slug}`} color="secondary" className={styles["action-btn"]}>
<i className="ion-edit"></i> Edit Article
</Button>
)}
{showDeleteArticleButton(profile.username, currentUser) && (
<DeleteArticleButton slug={article.slug} className={styles["action-btn"]} />
)}
</div>
);
};
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 (
<div className="article-page">
<div className="banner">
<div className="container">
<h1>{article.title}</h1>
<Actions article={article} currentUser={currentUser} />
</div>
</div>
<div className="container page">
<div className="row article-content">
<div className="col-md-12">
<div dangerouslySetInnerHTML={{ __html: body }} />
<ul className="tag-list">
{article.tagList.map((tag, index) => (
<Tag component="li" variant="outline" key={index}>
{tag}
</Tag>
))}
</ul>
</div>
</div>
<hr />
<div className="article-actions">
<Actions article={article} currentUser={currentUser} />
</div>
{children}
</div>
</div>
);
return (
<section className="article-page">
<Container className="banner">
<h1>{article.title}</h1>
<Actions article={article} currentUser={currentUser} />
</Container>
<Container className="page">
<Row className="article-content">
<Col xs={12}>
<div dangerouslySetInnerHTML={{ __html: body }} />
<ul className="list-unstyled list-inline">
{article.tagList.map((tag, index) => (
<li className="list-inline-item me-2" key={index}>
<Tag component="badge" color="success">
{tag}
</Tag>
</li>
))}
</ul>
</Col>
</Row>
<hr />
<div className="article-actions">
<Actions article={article} currentUser={currentUser} />
</div>
{children}
</Container>
</section>
);
};
@@ -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 (
<div className="card">
<div className="card-block">
<p className="card-text">{comment.body}</p>
</div>
<div className="card-footer">
<Link href={`/profile/${comment.author.username}`} className="comment-author">
{comment.author.image && <Image src={comment.author.image} className="comment-author-img" alt="" />}
</Link>
&nbsp;
<Link href={`/profile/${comment.author.username}`} className="comment-author">
{comment.author.username}
</Link>
<span className="date-posted">{comment.createdAt.toDateString()}</span>
{showDeleteCommentButton && (
<form action={deleteCommentAction} className={styles["form"]}>
<button className="mod-options" type="submit">
<i className="ion-trash-a" />
</button>
</form>
)}
</div>
</div>
);
return (
<div className="card">
<div className="card-block">
<p className="card-text">{comment.body}</p>
</div>
<div className="card-footer">
<Link href={`/profile/${comment.author.username}`} className="comment-author">
{comment.author.image && <Image src={comment.author.image} className="comment-author-img" alt="" />}
</Link>
&nbsp;
<Link href={`/profile/${comment.author.username}`} className="comment-author">
{comment.author.username}
</Link>
<span className="date-posted">{comment.createdAt.toDateString()}</span>
{showDeleteCommentButton && (
<Form action={deleteCommentAction} className={styles["form"]}>
<Button className="mod-options" variant="primary" type="submit">
<i className="icon-trash" />
</Button>
</Form>
)}
</div>
</div>
);
};
@@ -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<string[]>;
postCommentAction?: (formData: FormData) => void;
isPending?: boolean;
slug?: string;
authorImage?: string;
result?: SubmissionResult<string[]>;
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 (
<>
<form id={form.id} action={postCommentAction} onSubmit={form.onSubmit} className="card comment-form">
<input type="hidden" key={fields.slug.key} name={fields.slug.name} defaultValue={fields.slug.initialValue} />
<div className="card-block">
<textarea
key={fields.body.key}
name={fields.body.name}
defaultValue={fields.body.initialValue}
placeholder="Write a comment..."
rows={3}
className="form-control"
></textarea>
<ErrorMessage messages={errors} />
</div>
<div className="card-footer">
{authorImage && <Image src={authorImage} alt="" className="comment-author-img" />}
<Button component="button" type="submit" disabled={isPending}>
Post Comment
</Button>
</div>
</form>
</>
);
return (
<>
<Form className="card comment-form" id={form.id} action={postCommentAction} onSubmit={form.onSubmit} noValidate={true}>
<Form.Control
type="hidden"
key={fields.slug.key}
name={fields.slug.name}
defaultValue={fields.slug.initialValue}
/>
<div className="card-block">
<textarea
key={fields.body.key}
name={fields.body.name}
defaultValue={fields.body.initialValue}
placeholder="Write a comment..."
rows={3}
className="form-control"
></textarea>
<ErrorMessage messages={errors} />
</div>
<div className="card-footer">
{authorImage && <Image src={authorImage} alt="" className="comment-author-img" />}
<Button variant="primary" type="submit" disabled={isPending}>
Post Comment
</Button>
</div>
</Form>
</>
);
};
+17 -16
View File
@@ -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 (
<ArticleArea slug={params.slug}>
<div className="row">
<div className="col-xs-12 col-md-8 offset-md-2">
<CommentForm slug={params.slug} currentUserPromise={currentUserPromise} />
<Suspense fallback={<p>Loading comments...</p>}>
<CommentList slug={params.slug} />
</Suspense>
</div>
</div>
</ArticleArea>
);
return (
<ArticleArea slug={params.slug}>
<Row>
<Col xs={12} md={{ span: 8, offset: 2 }}>
<CommentForm slug={params.slug} currentUserPromise={currentUserPromise} />
<Suspense fallback={<p>Loading comments...</p>}>
<CommentList slug={params.slug} />
</Suspense>
</Col>
</Row>
</ArticleArea>
);
};
export default Page;
+78
View File
@@ -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 <FeedArticleList currentPage={searchParams.page} />;
case "global":
return <GlobalArticleList currentPage={searchParams.page} />;
case "tag":
return <TagArticleList currentPage={searchParams.page} tag={searchParams.tag ?? ""} />;
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 (
<section className="auth-page">
<Container className="page">
<hr className="col-1 my-5 mx-0" />
<Row>
<Col md={9}>
<div className="feed-toggle">
<ul className="nav nav-pills outline-active">
{session && (
<li className="nav-item">
<Link className={clsx("nav-link", searchParams.tab === "yours" && "active")} href="/?tab=yours">
Your Feed
</Link>
</li>
)}
<li className="nav-item">
<Link className={clsx("nav-link", searchParams.tab === "global" && "active")} href="/?tab=global">
Global Feed
</Link>
</li>
{searchParams.tag && (
<li className="nav-item">
<Link
className={clsx("nav-link", searchParams.tab === "tag" && "active")}
href={`/?tab=tag&tag=${searchParams.tag}`}
>
#{searchParams.tag}
</Link>
</li>
)}
</ul>
</div>
<Suspense key={JSON.stringify(searchParams)} fallback={<p>Chargement...</p>}>
<ArticleList {...searchParams} />
</Suspense>
</Col>
<Col md={3}>
<Suspense fallback={<p>Chargement...</p>}>
<TagList />
</Suspense>
</Col>
</Row>
</Container>
</section>
);
};
export default Page;
+17 -16
View File
@@ -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 (
<div className="editor-page">
<div className="container page">
<div className="row">
<div className="col-md-10 offset-md-1 col-xs-12">
<Suspense fallback={<p>Loading...</p>}>
<UpdateArticleEditor defaultValues={article} />
</Suspense>
</div>
return (
<div className="editor-page">
<Container className="page">
<Row>
<Col xs={12} md={{ span: 10, offset: 1 }}>
<Suspense fallback={<p> Chargement...</p>}>
<UpdateArticleEditor defaultValues={article} />
</Suspense>
</Col>
</Row>
</Container>
</div>
</div>
</div>
);
);
};
export default Page;
+10 -9
View File
@@ -1,15 +1,16 @@
import { Container, Col, Row } from "react-bootstrap";
import { CreateArticleEditor } from "@/modules/features/article/components/articleEditor";
const Page = () => (
<div className="editor-page">
<div className="container page">
<div className="row">
<div className="col-md-10 offset-md-1 col-xs-12">
<CreateArticleEditor />
</div>
</div>
</div>
</div>
<section className="editor-page">
<Container className="page">
<Row>
<Col xs={12} md={{ span: 10, offset: 1 }}>
<CreateArticleEditor />
</Col>
</Row>
</Container>
</section>
);
export default Page;
+28 -16
View File
@@ -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 (
<div className="error-page">
<div className="container page">
<p className="error-message">Something went wrong!</p>
<Button component="button" size="lg" onClick={() => reset()}>
Try again
</Button>
</div>
</div>
);
return (
<section className="error-page">
<Container className="banner">
<Row>
<Col sm={6} md={7} lg={8}>
<h1 className="mt-4"> Une erreur est survenue!</h1>
<p className="fs-4">La page que vous vouliez consulter n&apos;est pas d&apos;isponible pour le moment.</p>
</Col>
<Col sm={6} md={5} lg={4}>
<TimeClock />
</Col>
</Row>
</Container>
<Container className="page">
<Button type="button" onClick={() => reset()}>
Try again
</Button>
</Container>
</section>
);
};
export default Error;
+4 -4
View File
@@ -58,11 +58,11 @@ const RootLayout = ({
}>) => {
return (
<html className="h-100" lang="en">
<body className="d-flex flex-column h-100" data-bs-theme="dark">
<body className="d-flex flex-column h-100" data-bs-theme="light">
<Header />
<div className="h-100">
{children}
</div>
<main className="h-100">
{children}
</main>
<Footer />
</body>
</html>
@@ -6,62 +6,62 @@ import { startTransition } from "react";
import { inputsSchema } from "./types";
type Props = {
action?: (formData: FormData) => void;
isPending?: boolean;
result?: SubmissionResult<string[]>;
action?: (formData: FormData) => void;
isPending?: boolean;
result?: SubmissionResult<string[]>;
};
export const LoginForm = ({ result, action, isPending }: Props) => {
const [form, fields] = useForm({
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(() => {
action?.(formData);
});
},
shouldValidate: "onBlur",
shouldRevalidate: "onBlur",
});
const [form, fields] = useForm({
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(() => {
action?.(formData);
});
},
shouldValidate: "onBlur",
shouldRevalidate: "onBlur",
});
return (
<>
<ErrorMessage messages={form.errors} />
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label>Adresse email</Form.Label>
<Form.Control
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
placeholder="Votre adresse email"
/>
<ErrorMessage messages={fields.email.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formPassword">
<Form.Label>Mot de passe</Form.Label>
<Form.Control
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
placeholder="Mot de passe"
autoComplete="new-password"
/>
<ErrorMessage messages={fields.password.errors} />
</Form.Group>
<div className="d-flex justify-content-end">
<Button variant="primary" type="submit" disabled={isPending}>
Se connecter
</Button>
</div>
</Form>
</>
);
return (
<>
<ErrorMessage messages={form.errors} />
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label>Adresse email</Form.Label>
<Form.Control
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
placeholder="Votre adresse email"
/>
<ErrorMessage messages={fields.email.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formPassword">
<Form.Label>Mot de passe</Form.Label>
<Form.Control
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
placeholder="Mot de passe"
autoComplete="new-password"
/>
<ErrorMessage messages={fields.password.errors} />
</Form.Group>
<div className="d-flex justify-content-end">
<Button variant="primary" type="submit" disabled={isPending}>
Se connecter
</Button>
</div>
</Form>
</>
);
};
+14 -13
View File
@@ -1,19 +1,20 @@
import { Container, Col, Row } from "react-bootstrap";
import Link from "next/link";
import { LoginForm } from "./_components/loginForm";
const Page = () => (
<div className="auth-page">
<div className="container page">
<div className="row">
<div className="col-md-6 offset-md-3 col-xs-12">
<h1 className="text-xs-center">Se connecter</h1>
<p className="text-xs-center">
<Link href="/register">Pas encore de compte?</Link>
</p>
<LoginForm />
</div>
</div>
</div>
</div>
<section className="auth-page">
<Container className="page">
<Row>
<Col xs={12} md={{ span: 6, offset: 3 }}>
<h1 className="text-xs-center">Se connecter</h1>
<p className="text-xs-center">
<Link href="/register">Pas encore de compte?</Link>
</p>
<LoginForm />
</Col>
</Row>
</Container>
</section>
);
export default Page;
+9 -7
View File
@@ -1,11 +1,13 @@
import { Container, Button } from "react-bootstrap";
const NotFound = () => {
return (
<div className="not-found-page">
<div className="container page">
<p className="error-message">This page could not be found.</p>
</div>
</div>
);
return (
<section className="not-found-page">
<Container className="page">
<p className="error-message"> Cette page n&apos;existe pas.</p>
</Container>
</section>
);
};
export default NotFound;
+15 -81
View File
@@ -1,89 +1,23 @@
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 { Container, Col, Row } from "react-bootstrap";
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 TimeClock from "./_components/timeClock/timeClock";
const ArticleList = (searchParams: SearchParams) => {
switch (searchParams.tab) {
case "yours":
return <FeedArticleList currentPage={searchParams.page} />;
case "global":
return <GlobalArticleList currentPage={searchParams.page} />;
case "tag":
return <TagArticleList currentPage={searchParams.page} tag={searchParams.tag ?? ""} />;
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 (
<div className="home-page h-100">
<Container className="banner">
<Row>
<Col sm={6} md={7} lg={8}>
<h1 className="mt-4">Ad Astra, bientôt en ligne!</h1>
<p className="fs-4">Encore un peu de patience, notre site sera disponible d&apos;ici peu.</p>
</Col>
<Col sm={6} md={5} lg={4}>
<TimeClock />
</Col>
</Row>
</Container>
<Container className="page">
<hr className="col-1 my-5 mx-0" />
<Row>
<Col md={9}>
<div className="feed-toggle">
<ul className="nav nav-pills outline-active">
{session && (
<li className="nav-item">
<Link className={clsx("nav-link", searchParams.tab === "yours" && "active")} href="/?tab=yours">
Your Feed
</Link>
</li>
)}
<li className="nav-item">
<Link className={clsx("nav-link", searchParams.tab === "global" && "active")} href="/?tab=global">
Global Feed
</Link>
</li>
{searchParams.tag && (
<li className="nav-item">
<Link
className={clsx("nav-link", searchParams.tab === "tag" && "active")}
href={`/?tab=tag&tag=${searchParams.tag}`}
>
#{searchParams.tag}
</Link>
</li>
)}
</ul>
</div>
<Suspense key={JSON.stringify(searchParams)} fallback={<p>Chargement...</p>}>
<ArticleList {...searchParams} />
</Suspense>
</Col>
<Col md={3}>
<Suspense fallback={<p>Chargement...</p>}>
<TagList />
</Suspense>
</Col>
</Row>
</Container>
</div>
);
return (
<div className="home-page">
<Container className="banner">
<Row>
<Col sm={6} md={7} lg={8}>
<h1 className="mt-4">Ad Astra, bientôt en ligne!</h1>
<p className="fs-4">Encore un peu de patience, notre site sera disponible d&apos;ici peu.</p>
</Col>
<Col sm={6} md={5} lg={4}>
<TimeClock />
</Col>
</Row>
</Container>
</div>
);
};
export default Page;
+30 -30
View File
@@ -8,43 +8,43 @@ import { getSession } from "@/utils/auth/session";
import { showEditProfileSettingsButton, showFollowButton } from "./_functions";
type Props = {
children: ReactNode;
params: Promise<{
username: string;
}>;
children: ReactNode;
params: Promise<{
username: string;
}>;
};
const Layout = async ({ children, params }: Props) => {
const profile = await fetchProfile((await params).username);
const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined;
const profile = await fetchProfile((await params).username);
const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined;
return (
<div className="profile-page">
<div className="user-info">
<div className="container">
<div className="row">
<div className="col-xs-12 col-md-10 offset-md-1">
{profile.image && <Image src={profile.image} className="user-img" alt="" />}
<h4>{profile.username}</h4>
{profile.bio && <p>{profile.bio}</p>}
{showFollowButton(profile.username, currentUser) && <FollowButton {...profile} color="secondary" />}
{showEditProfileSettingsButton(profile.username, currentUser) && (
<Button component="a" href="/settings" className="action-btn" color="secondary">
<i className="ion-plus-round"></i> Edit Profile Settings
</Button>
)}
return (
<div className="profile-page">
<div className="user-info">
<div className="container">
<div className="row">
<div className="col-xs-12 col-md-10 offset-md-1">
{profile.image && <Image src={profile.image} className="user-img" alt="" />}
<h4>{profile.username}</h4>
{profile.bio && <p>{profile.bio}</p>}
{showFollowButton(profile.username, currentUser) && <FollowButton {...profile} color="secondary" />}
{showEditProfileSettingsButton(profile.username, currentUser) && (
<Button component="a" href="/settings" className="action-btn" color="secondary">
<i className="ion-plus-round"></i> Edit Profile Settings
</Button>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="container">
<div className="row">
<div className="col-xs-12 col-md-10 offset-md-1">{children}</div>
<div className="container">
<div className="row">
<div className="col-xs-12 col-md-10 offset-md-1">{children}</div>
</div>
</div>
</div>
</div>
</div>
);
);
};
export default Layout;
+27 -27
View File
@@ -5,35 +5,35 @@ import { MyArticleList } from "./_components/myArticleList";
import { searchParamsSchema } from "./_types";
const ProfilePage = async (props: {
params: Promise<{
username: string;
}>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
params: Promise<{
username: string;
}>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) => {
const pathParams = decodePathParams(await props.params);
const searchParams = searchParamsSchema.parse(await props.searchParams);
const pathParams = decodePathParams(await props.params);
const searchParams = searchParamsSchema.parse(await props.searchParams);
return (
<>
<div className="articles-toggle">
<ul className="nav nav-pills outline-active">
<li className="nav-item">
<Link className="nav-link active" href={`/profile/${pathParams.username}`}>
My Articles
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" href={`/profile/${pathParams.username}/favorites`}>
Favorited Articles
</Link>
</li>
</ul>
</div>
<Suspense key={JSON.stringify(searchParams)} fallback={<p>Loading...</p>}>
<MyArticleList currentPage={searchParams.page} username={pathParams.username} />
</Suspense>
</>
);
return (
<>
<div className="articles-toggle">
<ul className="nav nav-pills outline-active">
<li className="nav-item">
<Link className="nav-link active" href={`/profile/${pathParams.username}`}>
My Articles
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" href={`/profile/${pathParams.username}/favorites`}>
Favorited Articles
</Link>
</li>
</ul>
</div>
<Suspense key={JSON.stringify(searchParams)} fallback={<p>Loading...</p>}>
<MyArticleList currentPage={searchParams.page} username={pathParams.username} />
</Suspense>
</>
);
};
export default ProfilePage;
@@ -7,95 +7,95 @@ import { startTransition } from "react";
import { inputsSchema } from "./types";
type Props = {
action?: (formData: FormData) => void;
isPending?: boolean;
result?: SubmissionResult<string[]>;
action?: (formData: FormData) => void;
isPending?: boolean;
result?: SubmissionResult<string[]>;
};
export const RegistrationForm = ({ result, action, isPending }: Props) => {
const [form, fields] = useForm({
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(() => {
action?.(formData);
});
},
shouldValidate: "onBlur",
shouldRevalidate: "onBlur",
});
const [form, fields] = useForm({
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(() => {
action?.(formData);
});
},
shouldValidate: "onBlur",
shouldRevalidate: "onBlur",
});
return (
<>
<ErrorMessage messages={form.errors} />
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
<Form.Group className="mb-3" controlId="formFirstname">
<Form.Label>Nom</Form.Label>
<Form.Control
type="text"
key={fields.lastname.key}
name={fields.lastname.name}
defaultValue={fields.lastname.initialValue}
placeholder="Votre nom"
/>
<ErrorMessage messages={fields.firstname.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formLastname">
<Form.Label>Prénom</Form.Label>
<Form.Control
type="text"
key={fields.firstname.key}
name={fields.firstname.name}
defaultValue={fields.firstname.initialValue}
placeholder="Votre prénom"
/>
<ErrorMessage messages={fields.lastname.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formUsername">
<Form.Label>Pseudo</Form.Label>
<Form.Control
type="text"
key={fields.username.key}
name={fields.username.name}
defaultValue={fields.username.initialValue}
placeholder="Votre pseudo"
/>
<ErrorMessage messages={fields.username.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label>Adresse email</Form.Label>
<Form.Control
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
placeholder="Votre adresse email"
/>
<ErrorMessage messages={fields.email.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formPassword">
<Form.Label>Mot de passe</Form.Label>
<Form.Control
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
placeholder="Mot de passe"
autoComplete="new-password"
/>
<ErrorMessage messages={fields.password.errors} />
</Form.Group>
<div className="d-flex justify-content-end">
<Button variant="primary" type="submit" disabled={isPending}>
Créer mon compte
</Button>
</div>
</Form>
</>
);
return (
<>
<ErrorMessage messages={form.errors} />
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
<Form.Group className="mb-3" controlId="formFirstname">
<Form.Label>Prénom</Form.Label>
<Form.Control
type="text"
key={fields.lastname.key}
name={fields.lastname.name}
defaultValue={fields.lastname.initialValue}
placeholder="Votre prénom"
/>
<ErrorMessage messages={fields.firstname.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formLastname">
<Form.Label>Nom</Form.Label>
<Form.Control
type="text"
key={fields.firstname.key}
name={fields.firstname.name}
defaultValue={fields.firstname.initialValue}
placeholder="Votre nom"
/>
<ErrorMessage messages={fields.lastname.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formUsername">
<Form.Label>Pseudo</Form.Label>
<Form.Control
type="text"
key={fields.username.key}
name={fields.username.name}
defaultValue={fields.username.initialValue}
placeholder="Votre pseudo"
/>
<ErrorMessage messages={fields.username.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label>Adresse email</Form.Label>
<Form.Control
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
placeholder="Votre adresse email"
/>
<ErrorMessage messages={fields.email.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formPassword">
<Form.Label>Mot de passe</Form.Label>
<Form.Control
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
placeholder="Mot de passe"
autoComplete="new-password"
/>
<ErrorMessage messages={fields.password.errors} />
</Form.Group>
<div className="d-flex justify-content-end">
<Button variant="primary" type="submit" disabled={isPending}>
Créer mon compte
</Button>
</div>
</Form>
</>
);
};
+14 -13
View File
@@ -1,20 +1,21 @@
import { Container, Col, Row } from "react-bootstrap";
import Link from "next/link";
import { RegistrationForm } from "./_components/registrationForm";
const Page = () => (
<div className="auth-page">
<div className="container page">
<div className="row">
<div className="col-md-6 offset-md-3 col-xs-12">
<h1 className="text-xs-center">Se connecter</h1>
<p className="text-xs-center">
<Link href="/login">Déjà un compte?</Link>
</p>
<RegistrationForm />
</div>
</div>
</div>
</div>
<section className="auth-page">
<Container className="page">
<Row>
<Col xs={12} md={{ span: 6, offset: 3 }}>
<h1 className="text-xs-center">Créer un compte</h1>
<p className="text-xs-center">
<Link href="/login">Déjà un compte?</Link>
</p>
<RegistrationForm />
</Col>
</Row>
</Container>
</section>
);
export default Page;
+110 -98
View File
@@ -1,4 +1,4 @@
import { Button } from "@/modules/common/components/button";
import { Button, Form } from "react-bootstrap";
import { ErrorMessage } from "@/modules/common/components/errorMessage";
import { User } from "@/utils/types/models";
import { SubmissionResult, useForm } from "@conform-to/react";
@@ -7,105 +7,117 @@ import { startTransition } from "react";
import { inputsSchema } from "./types";
type Props = {
user?: User;
action?: (formData: FormData) => void;
isPending?: boolean;
result?: SubmissionResult<string[]>;
user?: User;
action?: (formData: FormData) => void;
isPending?: boolean;
result?: SubmissionResult<string[]>;
};
export const SettingsForm = ({ user, action, isPending, result }: Props) => {
const [form, fields] = useForm({
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(() => {
action?.(formData);
});
},
defaultValue: { ...user },
shouldValidate: "onBlur",
shouldRevalidate: "onBlur",
});
const [form, fields] = useForm({
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(() => {
action?.(formData);
});
},
defaultValue: { ...user },
shouldValidate: "onBlur",
shouldRevalidate: "onBlur",
});
return (
<>
<ErrorMessage messages={form.errors} />
<form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
<fieldset>
<fieldset className="form-group">
<input
type="text"
key={fields.image.key}
name={fields.image.name}
defaultValue={fields.image.initialValue}
placeholder="URL of profile picture"
className="form-control"
/>
<ErrorMessage messages={fields.image.errors} />
</fieldset>
<fieldset className="form-group">
<input
type="text"
key={fields.username.key}
name={fields.username.name}
defaultValue={fields.username.initialValue}
placeholder="Your Name"
className="form-control form-control-lg"
/>
<ErrorMessage messages={fields.username.errors} />
</fieldset>
<fieldset className="form-group">
<textarea
key={fields.bio.key}
name={fields.bio.name}
defaultValue={fields.bio.initialValue}
placeholder="Short bio about you"
className="form-control form-control-lg"
rows={8}
></textarea>
<ErrorMessage messages={fields.bio.errors} />
</fieldset>
<fieldset className="form-group">
<input
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
placeholder="Email"
className="form-control form-control-lg"
/>
<ErrorMessage messages={fields.email.errors} />
</fieldset>
<fieldset className="form-group">
<input
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
placeholder="New Password"
autoComplete="new-password"
className="form-control form-control-lg"
/>
<ErrorMessage messages={fields.password.errors} />
</fieldset>
<Button
component="button"
size="lg"
color="primary"
variant="filled"
type="submit"
className="pull-xs-right"
disabled={isPending}
>
Update Settings
</Button>
</fieldset>
</form>
</>
);
return (
<>
<ErrorMessage messages={form.errors} />
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
<Form.Group className="mb-3" controlId="formUsername">
<Form.Label>Pseudo</Form.Label>
<Form.Control
type="text"
key={fields.username.key}
name={fields.username.name}
defaultValue={fields.username.initialValue}
placeholder="Votre pseudo"
/>
<ErrorMessage messages={fields.username.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label>Adresse email</Form.Label>
<Form.Control
type="email"
key={fields.email.key}
name={fields.email.name}
defaultValue={fields.email.initialValue}
placeholder="Votre adresse email"
/>
<ErrorMessage messages={fields.email.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formFirstname">
<Form.Label>Prénom</Form.Label>
<Form.Control
type="text"
key={fields.firstname.key}
name={fields.firstname.name}
defaultValue={fields.firstname.initialValue}
placeholder="Votre prénom"
/>
<ErrorMessage messages={fields.firstname.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formLastname">
<Form.Label>Nom</Form.Label>
<Form.Control
type="text"
key={fields.lastname.key}
name={fields.lastname.name}
defaultValue={fields.lastname.initialValue}
placeholder="Votre nom"
/>
<ErrorMessage messages={fields.lastname.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formPassword">
<Form.Label>Mot de passe</Form.Label>
<Form.Control
type="password"
key={fields.password.key}
name={fields.password.name}
defaultValue={fields.password.initialValue}
placeholder="Nouveau mot de passe"
autoComplete="new-password"
/>
<ErrorMessage messages={fields.password.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formBio">
<Form.Label>Bio</Form.Label>
<Form.Control as="textarea" rows={8}
key={fields.bio.key}
name={fields.bio.name}
defaultValue={fields.bio.initialValue}
placeholder="Courte description"
/>
<ErrorMessage messages={fields.bio.errors} />
</Form.Group>
<Form.Group className="mb-3" controlId="formImage">
<Form.Label>Image</Form.Label>
<Form.Control
type="text"
key={fields.image.key}
name={fields.image.name}
defaultValue={fields.image.initialValue}
/>
<ErrorMessage messages={fields.image.errors} />
</Form.Group>
<div className="d-flex justify-content-end">
<Button variant="primary" type="submit" disabled={isPending}>
Enregistrer
</Button>
</div>
</Form>
</>
);
};
+4 -2
View File
@@ -1,10 +1,12 @@
import { z } from "zod";
export const inputsSchema = z.object({
image: z.string().max(200).optional(),
username: z.string().max(200),
bio: z.string().max(200).optional(),
email: z.string().max(200).email(),
firstname: z.string().max(200),
lastname: z.string().max(200),
bio: z.string().max(200).optional(),
image: z.string().max(200).optional(),
password: z.string().min(4).max(200).optional(),
});
+18 -17
View File
@@ -1,27 +1,28 @@
import { Container, Col, Row } from "react-bootstrap";
import { LogoutButton } from "@/modules/features/auth/components/logoutButton";
import { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser";
import { Suspense } from "react";
import { SettingsForm } from "./_components";
const Page = async () => {
const user = fetchCurrentUser();
const user = fetchCurrentUser();
return (
<div className="settings-page">
<div className="container page">
<div className="row">
<div className="col-md-6 offset-md-3 col-xs-12">
<h1 className="text-xs-center">Your Settings</h1>
<Suspense fallback={<p>Loading...</p>}>
<SettingsForm user={user} />
</Suspense>
<hr />
<LogoutButton />
</div>
</div>
</div>
</div>
);
return (
<section className="settings-page">
<Container className="page">
<Row>
<Col xs={12} md={{ span: 6, offset: 3 }}>
<h1 className="text-xs-center">Vos paramètres</h1>
<Suspense fallback={<p> Chargement...</p>}>
<SettingsForm user={user} />
</Suspense>
<hr />
<LogoutButton />
</Col>
</Row>
</Container>
</section>
);
};
export default Page;