Mise à jour
This commit is contained in:
@@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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'est pas d'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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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'existe pas.</p>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
|
||||
+15
-81
@@ -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'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'ici peu.</p>
|
||||
</Col>
|
||||
<Col sm={6} md={5} lg={4}>
|
||||
<TimeClock />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user