Mise à jour
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
APP_ENV="development"
|
||||||
|
API_BASE_URL=http://localhost:3200
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
APP_ENV="production"
|
||||||
|
API_BASE_URL=http://api.adastra-cbd.com/api
|
||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
API_BASE_URL=http://localhost/api
|
APP_ENV="development"
|
||||||
|
API_BASE_URL=http://localhost:3200/api
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
npm test
|
npm test
|
||||||
prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown
|
#prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown
|
||||||
git update-index --again
|
#git update-index --again
|
||||||
@@ -3,182 +3,182 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
const TimeClock: React.FC = () => {
|
const TimeClock: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const numbers = [
|
const numbers = [
|
||||||
[1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1], // 0
|
[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, 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, 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, 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, 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, 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, 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, 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, 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
|
[1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], // 9
|
||||||
];
|
];
|
||||||
|
|
||||||
const blocks: Array<Element[]> = [];
|
const blocks: Array<Element[]> = [];
|
||||||
const digits = Array.from(document.querySelectorAll(".block"));
|
const digits = Array.from(document.querySelectorAll(".block"));
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
blocks.push(digits.slice(i * 15, i * 15 + 15));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (m !== time.m) {
|
const setNum = (block: Array<Element>, num: number) => {
|
||||||
setNum(blocks[2], parseInt(Array.from(m)[0]));
|
const n = numbers[num];
|
||||||
setNum(blocks[3], parseInt(Array.from(m)[1]));
|
for (let i = 0; i < block.length; i++) {
|
||||||
time.m = m;
|
block[i].classList[n[i] === 1 ? "add" : "remove"]("active");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (h !== time.h) {
|
const time = {
|
||||||
setNum(blocks[0], parseInt(Array.from(h)[0]));
|
s: "",
|
||||||
setNum(blocks[1], parseInt(Array.from(h)[1]));
|
m: "",
|
||||||
time.h = h;
|
h: "",
|
||||||
}
|
p: 0,
|
||||||
window.requestAnimationFrame(animator);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// init
|
// time loop
|
||||||
window.requestAnimationFrame(animator);
|
const animator = () => {
|
||||||
|
const d = new Date();
|
||||||
|
let h = d.getHours().toString(),
|
||||||
|
m = d.getMinutes().toString(),
|
||||||
|
s = d.getSeconds().toString();
|
||||||
|
|
||||||
// toggle button
|
s = s.length === 1 ? "0" + s : s;
|
||||||
const d = new Date();
|
m = m.length === 1 ? "0" + m : m;
|
||||||
const days = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"];
|
h = h.length === 1 ? "0" + h : h;
|
||||||
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 (
|
if (s !== time.s) {
|
||||||
<>
|
for (let i = 0; i < digits.length; i++) {
|
||||||
<div className="date d-flex mb-2 pt-2">
|
const d = digits[i];
|
||||||
<div className="flex-grow-1"></div>
|
if (i === +s) {
|
||||||
<div className="datelib mt-2 pt-1 ps-1 font-monospace opacity-50 fs-5">
|
d.classList.add("second");
|
||||||
<span className="icon-calendar me-2" aria-hidden="true"></span>
|
if (time.p !== null) digits[time.p].classList.remove("second");
|
||||||
<span id="datelib"></span>
|
time.p = i;
|
||||||
</div>
|
time.s = s;
|
||||||
</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>
|
if (m !== time.m) {
|
||||||
<div className="block" data-num="1"></div>
|
setNum(blocks[2], parseInt(Array.from(m)[0]));
|
||||||
<div className="block" data-num="2"></div>
|
setNum(blocks[3], parseInt(Array.from(m)[1]));
|
||||||
<div className="block" data-num="3"></div>
|
time.m = m;
|
||||||
<div className="block" data-num="4"></div>
|
}
|
||||||
<div className="block" data-num="5"></div>
|
|
||||||
<div className="block" data-num="6"></div>
|
if (h !== time.h) {
|
||||||
<div className="block" data-num="7"></div>
|
setNum(blocks[0], parseInt(Array.from(h)[0]));
|
||||||
<div className="block" data-num="8"></div>
|
setNum(blocks[1], parseInt(Array.from(h)[1]));
|
||||||
<div className="block" data-num="9"></div>
|
time.h = h;
|
||||||
<div className="block" data-num="10"></div>
|
}
|
||||||
<div className="block" data-num="11"></div>
|
window.requestAnimationFrame(animator);
|
||||||
<div className="block" data-num="12"></div>
|
};
|
||||||
<div className="block" data-num="13"></div>
|
|
||||||
<div className="block" data-num="14"></div>
|
// init
|
||||||
<div className="block" data-num="15"></div>
|
window.requestAnimationFrame(animator);
|
||||||
<div className="block" data-num="16"></div>
|
|
||||||
<div className="block" data-num="17"></div>
|
// toggle button
|
||||||
<div className="block" data-num="18"></div>
|
const d = new Date();
|
||||||
<div className="block" data-num="19"></div>
|
const days = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"];
|
||||||
<div className="block" data-num="20"></div>
|
const months = [
|
||||||
<div className="block" data-num="21"></div>
|
"Janvier",
|
||||||
<div className="block" data-num="22"></div>
|
"Février",
|
||||||
<div className="block" data-num="23"></div>
|
"Mars",
|
||||||
<div className="block" data-num="24"></div>
|
"Avril",
|
||||||
<div className="block" data-num="25"></div>
|
"Mai",
|
||||||
<div className="block" data-num="26"></div>
|
"Juin",
|
||||||
<div className="block" data-num="27"></div>
|
"Juillet",
|
||||||
<div className="block" data-num="28"></div>
|
"Août",
|
||||||
<div className="block" data-num="29"></div>
|
"Septembre",
|
||||||
<div className="block" data-num="30"></div>
|
"Octobre",
|
||||||
<div className="block" data-num="31"></div>
|
"Novembre",
|
||||||
<div className="block" data-num="32"></div>
|
"Décembre",
|
||||||
<div className="block" data-num="33"></div>
|
];
|
||||||
<div className="block" data-num="34"></div>
|
const lib_d = days[d.getDay()];
|
||||||
<div className="block" data-num="35"></div>
|
const lib_m = months[d.getMonth()];
|
||||||
<div className="block" data-num="36"></div>
|
const lib_n = d.getDate();
|
||||||
<div className="block" data-num="37"></div>
|
const lib_y = d.getFullYear();
|
||||||
<div className="block" data-num="38"></div>
|
document.getElementById("datelib")!.innerHTML = lib_d + " " + lib_n + " " + lib_m + " " + lib_y;
|
||||||
<div className="block" data-num="39"></div>
|
}, []);
|
||||||
<div className="block" data-num="40"></div>
|
|
||||||
<div className="block" data-num="41"></div>
|
return (
|
||||||
<div className="block" data-num="42"></div>
|
<>
|
||||||
<div className="block" data-num="43"></div>
|
<div className="date d-flex mb-2 pt-2">
|
||||||
<div className="block" data-num="44"></div>
|
<div className="flex-grow-1"></div>
|
||||||
<div className="block" data-num="45"></div>
|
<div className="datelib mt-2 pt-1 ps-1 font-monospace fs-5">
|
||||||
<div className="block" data-num="46"></div>
|
<span className="icon-calendar me-2" aria-hidden="true"></span>
|
||||||
<div className="block" data-num="47"></div>
|
<span id="datelib"></span>
|
||||||
<div className="block" data-num="48"></div>
|
</div>
|
||||||
<div className="block" data-num="49"></div>
|
</div>
|
||||||
<div className="block" data-num="50"></div>
|
<div className="time d-flex">
|
||||||
<div className="block" data-num="51"></div>
|
<div className="flex-grow-1"></div>
|
||||||
<div className="block" data-num="52"></div>
|
<div className="clock position-relative d-flex flex-column flex-wrap">
|
||||||
<div className="block" data-num="53"></div>
|
<div className="block" data-num="0"></div>
|
||||||
<div className="block" data-num="54"></div>
|
<div className="block" data-num="1"></div>
|
||||||
<div className="block" data-num="55"></div>
|
<div className="block" data-num="2"></div>
|
||||||
<div className="block" data-num="56"></div>
|
<div className="block" data-num="3"></div>
|
||||||
<div className="block" data-num="57"></div>
|
<div className="block" data-num="4"></div>
|
||||||
<div className="block" data-num="58"></div>
|
<div className="block" data-num="5"></div>
|
||||||
<div className="block" data-num="59"></div>
|
<div className="block" data-num="6"></div>
|
||||||
<div className="divider position-absolute"></div>
|
<div className="block" data-num="7"></div>
|
||||||
</div>
|
<div className="block" data-num="8"></div>
|
||||||
</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;
|
export default TimeClock;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Container, Col, Row } from "react-bootstrap";
|
||||||
import { Button } from "@/modules/common/components/button";
|
import { Button } from "@/modules/common/components/button";
|
||||||
import { DeleteArticleButton } from "@/modules/features/article/components/deleteArticleButton";
|
import { DeleteArticleButton } from "@/modules/features/article/components/deleteArticleButton";
|
||||||
import { FavoriteButton } from "@/modules/features/article/components/favoriteButton";
|
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";
|
import { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser";
|
||||||
|
|
||||||
const Actions = ({ article, currentUser }: { article: Article; currentUser?: User }) => {
|
const Actions = ({ article, currentUser }: { article: Article; currentUser?: User }) => {
|
||||||
const profile = article.author;
|
const profile = article.author;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="article-meta">
|
<div className="article-meta">
|
||||||
<Link href={`/profile/${profile.username}`}>{profile.image && <Image src={profile.image} alt="" />}</Link>
|
<Link href={`/profile/${profile.username}`}>{profile.image && <Image src={profile.image} alt="" />}</Link>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<Link href={`/profile/${profile.username}`} className="author">
|
<Link href={`/profile/${profile.username}`} className="author">
|
||||||
{profile.username}
|
{profile.username}
|
||||||
</Link>
|
</Link>
|
||||||
<span className="date">{article.createdAt.toDateString()}</span>
|
<span className="date">{article.createdAt.toDateString()}</span>
|
||||||
</div>
|
</div>
|
||||||
{showFollowButton(profile.username, currentUser) && (
|
{showFollowButton(profile.username, currentUser) && (
|
||||||
<FollowButton {...profile} className={styles["action-btn"]} />
|
<FollowButton {...profile} className={styles["action-btn"]} />
|
||||||
)}
|
)}
|
||||||
<FavoriteButton {...article} showMessage={true} className={styles["action-btn"]} />
|
<FavoriteButton {...article} showMessage={true} className={styles["action-btn"]} />
|
||||||
{showEditArticleButton(profile.username, currentUser) && (
|
{showEditArticleButton(profile.username, currentUser) && (
|
||||||
<Button component="a" href={`/editor/${article.slug}`} color="secondary" className={styles["action-btn"]}>
|
<Button component="a" href={`/editor/${article.slug}`} color="secondary" className={styles["action-btn"]}>
|
||||||
<i className="ion-edit"></i> Edit Article
|
<i className="ion-edit"></i> Edit Article
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{showDeleteArticleButton(profile.username, currentUser) && (
|
{showDeleteArticleButton(profile.username, currentUser) && (
|
||||||
<DeleteArticleButton slug={article.slug} className={styles["action-btn"]} />
|
<DeleteArticleButton slug={article.slug} className={styles["action-btn"]} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ArticleArea = async ({ slug, children }: { slug: string; children: ReactNode }) => {
|
export const ArticleArea = async ({ slug, children }: { slug: string; children: ReactNode }) => {
|
||||||
const article = await fetchArticle(slug);
|
const article = await fetchArticle(slug);
|
||||||
const body = await convertMarkdownToHtml(article.body);
|
const body = await convertMarkdownToHtml(article.body);
|
||||||
const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined;
|
const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="article-page">
|
<section className="article-page">
|
||||||
<div className="banner">
|
<Container className="banner">
|
||||||
<div className="container">
|
<h1>{article.title}</h1>
|
||||||
<h1>{article.title}</h1>
|
<Actions article={article} currentUser={currentUser} />
|
||||||
<Actions article={article} currentUser={currentUser} />
|
</Container>
|
||||||
</div>
|
<Container className="page">
|
||||||
</div>
|
<Row className="article-content">
|
||||||
|
<Col xs={12}>
|
||||||
<div className="container page">
|
<div dangerouslySetInnerHTML={{ __html: body }} />
|
||||||
<div className="row article-content">
|
<ul className="list-unstyled list-inline">
|
||||||
<div className="col-md-12">
|
{article.tagList.map((tag, index) => (
|
||||||
<div dangerouslySetInnerHTML={{ __html: body }} />
|
<li className="list-inline-item me-2" key={index}>
|
||||||
<ul className="tag-list">
|
<Tag component="badge" color="success">
|
||||||
{article.tagList.map((tag, index) => (
|
{tag}
|
||||||
<Tag component="li" variant="outline" key={index}>
|
</Tag>
|
||||||
{tag}
|
</li>
|
||||||
</Tag>
|
))}
|
||||||
))}
|
</ul>
|
||||||
</ul>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
<hr />
|
||||||
<hr />
|
<div className="article-actions">
|
||||||
<div className="article-actions">
|
<Actions article={article} currentUser={currentUser} />
|
||||||
<Actions article={article} currentUser={currentUser} />
|
</div>
|
||||||
</div>
|
{children}
|
||||||
{children}
|
</Container>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
|
import { Button, Form } from "react-bootstrap";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Comment } from "@/utils/types/models";
|
import { Comment } from "@/utils/types/models";
|
||||||
import styles from "./presentation.module.css";
|
import styles from "./presentation.module.css";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comment: Comment;
|
comment: Comment;
|
||||||
showDeleteCommentButton?: boolean;
|
showDeleteCommentButton?: boolean;
|
||||||
deleteCommentAction?: () => void;
|
deleteCommentAction?: () => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommentCard = ({ comment, showDeleteCommentButton, deleteCommentAction }: Props) => {
|
export const CommentCard = ({ comment, showDeleteCommentButton, deleteCommentAction }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-block">
|
<div className="card-block">
|
||||||
<p className="card-text">{comment.body}</p>
|
<p className="card-text">{comment.body}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-footer">
|
<div className="card-footer">
|
||||||
<Link href={`/profile/${comment.author.username}`} className="comment-author">
|
<Link href={`/profile/${comment.author.username}`} className="comment-author">
|
||||||
{comment.author.image && <Image src={comment.author.image} className="comment-author-img" alt="" />}
|
{comment.author.image && <Image src={comment.author.image} className="comment-author-img" alt="" />}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link href={`/profile/${comment.author.username}`} className="comment-author">
|
<Link href={`/profile/${comment.author.username}`} className="comment-author">
|
||||||
{comment.author.username}
|
{comment.author.username}
|
||||||
</Link>
|
</Link>
|
||||||
<span className="date-posted">{comment.createdAt.toDateString()}</span>
|
<span className="date-posted">{comment.createdAt.toDateString()}</span>
|
||||||
{showDeleteCommentButton && (
|
{showDeleteCommentButton && (
|
||||||
<form action={deleteCommentAction} className={styles["form"]}>
|
<Form action={deleteCommentAction} className={styles["form"]}>
|
||||||
<button className="mod-options" type="submit">
|
<Button className="mod-options" variant="primary" type="submit">
|
||||||
<i className="ion-trash-a" />
|
<i className="icon-trash" />
|
||||||
</button>
|
</Button>
|
||||||
</form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Button, Form } from "react-bootstrap";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "@/modules/common/components/button";
|
|
||||||
import { ErrorMessage } from "@/modules/common/components/errorMessage";
|
import { ErrorMessage } from "@/modules/common/components/errorMessage";
|
||||||
import { SubmissionResult, useForm } from "@conform-to/react";
|
import { SubmissionResult, useForm } from "@conform-to/react";
|
||||||
import { parseWithZod } from "@conform-to/zod";
|
import { parseWithZod } from "@conform-to/zod";
|
||||||
@@ -7,57 +7,62 @@ import { startTransition } from "react";
|
|||||||
import { inputsSchema } from "./types";
|
import { inputsSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
slug?: string;
|
slug?: string;
|
||||||
authorImage?: string;
|
authorImage?: string;
|
||||||
result?: SubmissionResult<string[]>;
|
result?: SubmissionResult<string[]>;
|
||||||
postCommentAction?: (formData: FormData) => void;
|
postCommentAction?: (formData: FormData) => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommentForm = ({ slug, authorImage, result, postCommentAction, isPending }: Props) => {
|
export const CommentForm = ({ slug, authorImage, result, postCommentAction, isPending }: Props) => {
|
||||||
const [form, fields] = useForm({
|
const [form, fields] = useForm({
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
slug,
|
slug,
|
||||||
},
|
},
|
||||||
lastResult: result,
|
lastResult: result,
|
||||||
onValidate({ formData }) {
|
onValidate({ formData }) {
|
||||||
return parseWithZod(formData, { schema: inputsSchema });
|
return parseWithZod(formData, { schema: inputsSchema });
|
||||||
},
|
},
|
||||||
// onSubmit is defined to avoid resetting form after successful submission
|
// onSubmit is defined to avoid resetting form after successful submission
|
||||||
// see https://github.com/edmundhung/conform/discussions/606
|
// see https://github.com/edmundhung/conform/discussions/606
|
||||||
onSubmit(event, { formData }) {
|
onSubmit(event, { formData }) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
postCommentAction?.(formData);
|
postCommentAction?.(formData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
shouldRevalidate: "onBlur",
|
shouldRevalidate: "onBlur",
|
||||||
});
|
});
|
||||||
|
|
||||||
const errors = Object.values(form.allErrors).flat();
|
const errors = Object.values(form.allErrors).flat();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form id={form.id} action={postCommentAction} onSubmit={form.onSubmit} className="card comment-form">
|
<Form className="card comment-form" id={form.id} action={postCommentAction} onSubmit={form.onSubmit} noValidate={true}>
|
||||||
<input type="hidden" key={fields.slug.key} name={fields.slug.name} defaultValue={fields.slug.initialValue} />
|
<Form.Control
|
||||||
<div className="card-block">
|
type="hidden"
|
||||||
<textarea
|
key={fields.slug.key}
|
||||||
key={fields.body.key}
|
name={fields.slug.name}
|
||||||
name={fields.body.name}
|
defaultValue={fields.slug.initialValue}
|
||||||
defaultValue={fields.body.initialValue}
|
/>
|
||||||
placeholder="Write a comment..."
|
<div className="card-block">
|
||||||
rows={3}
|
<textarea
|
||||||
className="form-control"
|
key={fields.body.key}
|
||||||
></textarea>
|
name={fields.body.name}
|
||||||
<ErrorMessage messages={errors} />
|
defaultValue={fields.body.initialValue}
|
||||||
</div>
|
placeholder="Write a comment..."
|
||||||
<div className="card-footer">
|
rows={3}
|
||||||
{authorImage && <Image src={authorImage} alt="" className="comment-author-img" />}
|
className="form-control"
|
||||||
<Button component="button" type="submit" disabled={isPending}>
|
></textarea>
|
||||||
Post Comment
|
<ErrorMessage messages={errors} />
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
<div className="card-footer">
|
||||||
</form>
|
{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 { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser";
|
||||||
import { getSession } from "@/utils/auth/session";
|
import { getSession } from "@/utils/auth/session";
|
||||||
import { Suspense } from "react";
|
|
||||||
import { ArticleArea } from "./_components/articleArea";
|
import { ArticleArea } from "./_components/articleArea";
|
||||||
import { CommentForm } from "./_components/commentForm";
|
import { CommentForm } from "./_components/commentForm";
|
||||||
import { CommentList } from "./_components/commentList";
|
import { CommentList } from "./_components/commentList";
|
||||||
|
|
||||||
type Params = Promise<{
|
type Params = Promise<{
|
||||||
slug: string;
|
slug: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const Page = async (props: { params: Params }) => {
|
const Page = async (props: { params: Params }) => {
|
||||||
const currentUserPromise = (await getSession()) ? fetchCurrentUser() : undefined;
|
const currentUserPromise = (await getSession()) ? fetchCurrentUser() : undefined;
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArticleArea slug={params.slug}>
|
<ArticleArea slug={params.slug}>
|
||||||
<div className="row">
|
<Row>
|
||||||
<div className="col-xs-12 col-md-8 offset-md-2">
|
<Col xs={12} md={{ span: 8, offset: 2 }}>
|
||||||
<CommentForm slug={params.slug} currentUserPromise={currentUserPromise} />
|
<CommentForm slug={params.slug} currentUserPromise={currentUserPromise} />
|
||||||
<Suspense fallback={<p>⌛Loading comments...</p>}>
|
<Suspense fallback={<p>⌛Loading comments...</p>}>
|
||||||
<CommentList slug={params.slug} />
|
<CommentList slug={params.slug} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</ArticleArea>
|
</ArticleArea>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
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 { UpdateArticleEditor } from "@/modules/features/article/components/articleEditor";
|
||||||
import { fetchArticle } from "@/modules/features/article/fetch/fetchArticle";
|
import { fetchArticle } from "@/modules/features/article/fetch/fetchArticle";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
slug: string;
|
slug: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page = async ({ params }: Props) => {
|
const Page = async ({ params }: Props) => {
|
||||||
const article = fetchArticle((await params).slug);
|
const article = fetchArticle((await params).slug);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="editor-page">
|
<div className="editor-page">
|
||||||
<div className="container page">
|
<Container className="page">
|
||||||
<div className="row">
|
<Row>
|
||||||
<div className="col-md-10 offset-md-1 col-xs-12">
|
<Col xs={12} md={{ span: 10, offset: 1 }}>
|
||||||
<Suspense fallback={<p>⌛Loading...</p>}>
|
<Suspense fallback={<p>⌛ Chargement...</p>}>
|
||||||
<UpdateArticleEditor defaultValues={article} />
|
<UpdateArticleEditor defaultValues={article} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
+10
-9
@@ -1,15 +1,16 @@
|
|||||||
|
import { Container, Col, Row } from "react-bootstrap";
|
||||||
import { CreateArticleEditor } from "@/modules/features/article/components/articleEditor";
|
import { CreateArticleEditor } from "@/modules/features/article/components/articleEditor";
|
||||||
|
|
||||||
const Page = () => (
|
const Page = () => (
|
||||||
<div className="editor-page">
|
<section className="editor-page">
|
||||||
<div className="container page">
|
<Container className="page">
|
||||||
<div className="row">
|
<Row>
|
||||||
<div className="col-md-10 offset-md-1 col-xs-12">
|
<Col xs={12} md={{ span: 10, offset: 1 }}>
|
||||||
<CreateArticleEditor />
|
<CreateArticleEditor />
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
+28
-16
@@ -1,25 +1,37 @@
|
|||||||
"use client";
|
"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 = ({
|
const Error = ({
|
||||||
reset,
|
reset,
|
||||||
}: {
|
}: {
|
||||||
error: Error & {
|
error: Error & {
|
||||||
digest?: string;
|
digest?: string;
|
||||||
};
|
};
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="error-page">
|
<section className="error-page">
|
||||||
<div className="container page">
|
<Container className="banner">
|
||||||
<p className="error-message">⚠️Something went wrong!</p>
|
<Row>
|
||||||
<Button component="button" size="lg" onClick={() => reset()}>
|
<Col sm={6} md={7} lg={8}>
|
||||||
Try again
|
<h1 className="mt-4">⚠️ Une erreur est survenue!</h1>
|
||||||
</Button>
|
<p className="fs-4">La page que vous vouliez consulter n'est pas d'isponible pour le moment.</p>
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
<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;
|
export default Error;
|
||||||
|
|||||||
+4
-4
@@ -58,11 +58,11 @@ const RootLayout = ({
|
|||||||
}>) => {
|
}>) => {
|
||||||
return (
|
return (
|
||||||
<html className="h-100" lang="en">
|
<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 />
|
<Header />
|
||||||
<div className="h-100">
|
<main className="h-100">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,62 +6,62 @@ import { startTransition } from "react";
|
|||||||
import { inputsSchema } from "./types";
|
import { inputsSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
action?: (formData: FormData) => void;
|
action?: (formData: FormData) => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
result?: SubmissionResult<string[]>;
|
result?: SubmissionResult<string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LoginForm = ({ result, action, isPending }: Props) => {
|
export const LoginForm = ({ result, action, isPending }: Props) => {
|
||||||
const [form, fields] = useForm({
|
const [form, fields] = useForm({
|
||||||
lastResult: result,
|
lastResult: result,
|
||||||
onValidate({ formData }) {
|
onValidate({ formData }) {
|
||||||
return parseWithZod(formData, { schema: inputsSchema });
|
return parseWithZod(formData, { schema: inputsSchema });
|
||||||
},
|
},
|
||||||
// onSubmit is defined to avoid resetting form after successful submission
|
// onSubmit is defined to avoid resetting form after successful submission
|
||||||
// see https://github.com/edmundhung/conform/discussions/606
|
// see https://github.com/edmundhung/conform/discussions/606
|
||||||
onSubmit(event, { formData }) {
|
onSubmit(event, { formData }) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
action?.(formData);
|
action?.(formData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
shouldValidate: "onBlur",
|
shouldValidate: "onBlur",
|
||||||
shouldRevalidate: "onBlur",
|
shouldRevalidate: "onBlur",
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ErrorMessage messages={form.errors} />
|
<ErrorMessage messages={form.errors} />
|
||||||
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
||||||
<Form.Group className="mb-3" controlId="formEmail">
|
<Form.Group className="mb-3" controlId="formEmail">
|
||||||
<Form.Label>Adresse email</Form.Label>
|
<Form.Label>Adresse email</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="email"
|
type="email"
|
||||||
key={fields.email.key}
|
key={fields.email.key}
|
||||||
name={fields.email.name}
|
name={fields.email.name}
|
||||||
defaultValue={fields.email.initialValue}
|
defaultValue={fields.email.initialValue}
|
||||||
placeholder="Votre adresse email"
|
placeholder="Votre adresse email"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.email.errors} />
|
<ErrorMessage messages={fields.email.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group className="mb-3" controlId="formPassword">
|
<Form.Group className="mb-3" controlId="formPassword">
|
||||||
<Form.Label>Mot de passe</Form.Label>
|
<Form.Label>Mot de passe</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="password"
|
type="password"
|
||||||
key={fields.password.key}
|
key={fields.password.key}
|
||||||
name={fields.password.name}
|
name={fields.password.name}
|
||||||
defaultValue={fields.password.initialValue}
|
defaultValue={fields.password.initialValue}
|
||||||
placeholder="Mot de passe"
|
placeholder="Mot de passe"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.password.errors} />
|
<ErrorMessage messages={fields.password.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<div className="d-flex justify-content-end">
|
<div className="d-flex justify-content-end">
|
||||||
<Button variant="primary" type="submit" disabled={isPending}>
|
<Button variant="primary" type="submit" disabled={isPending}>
|
||||||
Se connecter
|
Se connecter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-13
@@ -1,19 +1,20 @@
|
|||||||
|
import { Container, Col, Row } from "react-bootstrap";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { LoginForm } from "./_components/loginForm";
|
import { LoginForm } from "./_components/loginForm";
|
||||||
|
|
||||||
const Page = () => (
|
const Page = () => (
|
||||||
<div className="auth-page">
|
<section className="auth-page">
|
||||||
<div className="container page">
|
<Container className="page">
|
||||||
<div className="row">
|
<Row>
|
||||||
<div className="col-md-6 offset-md-3 col-xs-12">
|
<Col xs={12} md={{ span: 6, offset: 3 }}>
|
||||||
<h1 className="text-xs-center">Se connecter</h1>
|
<h1 className="text-xs-center">Se connecter</h1>
|
||||||
<p className="text-xs-center">
|
<p className="text-xs-center">
|
||||||
<Link href="/register">Pas encore de compte?</Link>
|
<Link href="/register">Pas encore de compte?</Link>
|
||||||
</p>
|
</p>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
|
||||||
|
import { Container, Button } from "react-bootstrap";
|
||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
return (
|
return (
|
||||||
<div className="not-found-page">
|
<section className="not-found-page">
|
||||||
<div className="container page">
|
<Container className="page">
|
||||||
<p className="error-message">This page could not be found.</p>
|
<p className="error-message">⚠️ Cette page n'existe pas.</p>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotFound;
|
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 { 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";
|
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 Page = async (props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) => {
|
||||||
const session = await getSession();
|
return (
|
||||||
const searchParams = SearchParams.parse(await props.searchParams);
|
<div className="home-page">
|
||||||
|
<Container className="banner">
|
||||||
return (
|
<Row>
|
||||||
<div className="home-page h-100">
|
<Col sm={6} md={7} lg={8}>
|
||||||
<Container className="banner">
|
<h1 className="mt-4">Ad Astra, bientôt en ligne!</h1>
|
||||||
<Row>
|
<p className="fs-4">Encore un peu de patience, notre site sera disponible d'ici peu.</p>
|
||||||
<Col sm={6} md={7} lg={8}>
|
</Col>
|
||||||
<h1 className="mt-4">Ad Astra, bientôt en ligne!</h1>
|
<Col sm={6} md={5} lg={4}>
|
||||||
<p className="fs-4">Encore un peu de patience, notre site sera disponible d'ici peu.</p>
|
<TimeClock />
|
||||||
</Col>
|
</Col>
|
||||||
<Col sm={6} md={5} lg={4}>
|
</Row>
|
||||||
<TimeClock />
|
</Container>
|
||||||
</Col>
|
</div>
|
||||||
</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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -8,43 +8,43 @@ import { getSession } from "@/utils/auth/session";
|
|||||||
import { showEditProfileSettingsButton, showFollowButton } from "./_functions";
|
import { showEditProfileSettingsButton, showFollowButton } from "./_functions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
username: string;
|
username: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Layout = async ({ children, params }: Props) => {
|
const Layout = async ({ children, params }: Props) => {
|
||||||
const profile = await fetchProfile((await params).username);
|
const profile = await fetchProfile((await params).username);
|
||||||
const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined;
|
const currentUser = (await getSession()) ? await fetchCurrentUser() : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile-page">
|
<div className="profile-page">
|
||||||
<div className="user-info">
|
<div className="user-info">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xs-12 col-md-10 offset-md-1">
|
<div className="col-xs-12 col-md-10 offset-md-1">
|
||||||
{profile.image && <Image src={profile.image} className="user-img" alt="" />}
|
{profile.image && <Image src={profile.image} className="user-img" alt="" />}
|
||||||
<h4>{profile.username}</h4>
|
<h4>{profile.username}</h4>
|
||||||
{profile.bio && <p>{profile.bio}</p>}
|
{profile.bio && <p>{profile.bio}</p>}
|
||||||
{showFollowButton(profile.username, currentUser) && <FollowButton {...profile} color="secondary" />}
|
{showFollowButton(profile.username, currentUser) && <FollowButton {...profile} color="secondary" />}
|
||||||
{showEditProfileSettingsButton(profile.username, currentUser) && (
|
{showEditProfileSettingsButton(profile.username, currentUser) && (
|
||||||
<Button component="a" href="/settings" className="action-btn" color="secondary">
|
<Button component="a" href="/settings" className="action-btn" color="secondary">
|
||||||
<i className="ion-plus-round"></i> Edit Profile Settings
|
<i className="ion-plus-round"></i> Edit Profile Settings
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xs-12 col-md-10 offset-md-1">{children}</div>
|
<div className="col-xs-12 col-md-10 offset-md-1">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|||||||
@@ -5,35 +5,35 @@ import { MyArticleList } from "./_components/myArticleList";
|
|||||||
import { searchParamsSchema } from "./_types";
|
import { searchParamsSchema } from "./_types";
|
||||||
|
|
||||||
const ProfilePage = async (props: {
|
const ProfilePage = async (props: {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
username: string;
|
username: string;
|
||||||
}>;
|
}>;
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
}) => {
|
}) => {
|
||||||
const pathParams = decodePathParams(await props.params);
|
const pathParams = decodePathParams(await props.params);
|
||||||
const searchParams = searchParamsSchema.parse(await props.searchParams);
|
const searchParams = searchParamsSchema.parse(await props.searchParams);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="articles-toggle">
|
<div className="articles-toggle">
|
||||||
<ul className="nav nav-pills outline-active">
|
<ul className="nav nav-pills outline-active">
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<Link className="nav-link active" href={`/profile/${pathParams.username}`}>
|
<Link className="nav-link active" href={`/profile/${pathParams.username}`}>
|
||||||
My Articles
|
My Articles
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<Link className="nav-link" href={`/profile/${pathParams.username}/favorites`}>
|
<Link className="nav-link" href={`/profile/${pathParams.username}/favorites`}>
|
||||||
Favorited Articles
|
Favorited Articles
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<Suspense key={JSON.stringify(searchParams)} fallback={<p>⌛Loading...</p>}>
|
<Suspense key={JSON.stringify(searchParams)} fallback={<p>⌛Loading...</p>}>
|
||||||
<MyArticleList currentPage={searchParams.page} username={pathParams.username} />
|
<MyArticleList currentPage={searchParams.page} username={pathParams.username} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProfilePage;
|
export default ProfilePage;
|
||||||
|
|||||||
@@ -7,95 +7,95 @@ import { startTransition } from "react";
|
|||||||
import { inputsSchema } from "./types";
|
import { inputsSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
action?: (formData: FormData) => void;
|
action?: (formData: FormData) => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
result?: SubmissionResult<string[]>;
|
result?: SubmissionResult<string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RegistrationForm = ({ result, action, isPending }: Props) => {
|
export const RegistrationForm = ({ result, action, isPending }: Props) => {
|
||||||
const [form, fields] = useForm({
|
const [form, fields] = useForm({
|
||||||
lastResult: result,
|
lastResult: result,
|
||||||
onValidate({ formData }) {
|
onValidate({ formData }) {
|
||||||
return parseWithZod(formData, { schema: inputsSchema });
|
return parseWithZod(formData, { schema: inputsSchema });
|
||||||
},
|
},
|
||||||
// onSubmit is defined to avoid resetting form after successful submission
|
// onSubmit is defined to avoid resetting form after successful submission
|
||||||
// see https://github.com/edmundhung/conform/discussions/606
|
// see https://github.com/edmundhung/conform/discussions/606
|
||||||
onSubmit(event, { formData }) {
|
onSubmit(event, { formData }) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
action?.(formData);
|
action?.(formData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
shouldValidate: "onBlur",
|
shouldValidate: "onBlur",
|
||||||
shouldRevalidate: "onBlur",
|
shouldRevalidate: "onBlur",
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ErrorMessage messages={form.errors} />
|
<ErrorMessage messages={form.errors} />
|
||||||
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
||||||
<Form.Group className="mb-3" controlId="formFirstname">
|
<Form.Group className="mb-3" controlId="formFirstname">
|
||||||
<Form.Label>Nom</Form.Label>
|
<Form.Label>Prénom</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
key={fields.lastname.key}
|
key={fields.lastname.key}
|
||||||
name={fields.lastname.name}
|
name={fields.lastname.name}
|
||||||
defaultValue={fields.lastname.initialValue}
|
defaultValue={fields.lastname.initialValue}
|
||||||
placeholder="Votre nom"
|
placeholder="Votre prénom"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.firstname.errors} />
|
<ErrorMessage messages={fields.firstname.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group className="mb-3" controlId="formLastname">
|
<Form.Group className="mb-3" controlId="formLastname">
|
||||||
<Form.Label>Prénom</Form.Label>
|
<Form.Label>Nom</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
key={fields.firstname.key}
|
key={fields.firstname.key}
|
||||||
name={fields.firstname.name}
|
name={fields.firstname.name}
|
||||||
defaultValue={fields.firstname.initialValue}
|
defaultValue={fields.firstname.initialValue}
|
||||||
placeholder="Votre prénom"
|
placeholder="Votre nom"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.lastname.errors} />
|
<ErrorMessage messages={fields.lastname.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group className="mb-3" controlId="formUsername">
|
<Form.Group className="mb-3" controlId="formUsername">
|
||||||
<Form.Label>Pseudo</Form.Label>
|
<Form.Label>Pseudo</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
key={fields.username.key}
|
key={fields.username.key}
|
||||||
name={fields.username.name}
|
name={fields.username.name}
|
||||||
defaultValue={fields.username.initialValue}
|
defaultValue={fields.username.initialValue}
|
||||||
placeholder="Votre pseudo"
|
placeholder="Votre pseudo"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.username.errors} />
|
<ErrorMessage messages={fields.username.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group className="mb-3" controlId="formEmail">
|
<Form.Group className="mb-3" controlId="formEmail">
|
||||||
<Form.Label>Adresse email</Form.Label>
|
<Form.Label>Adresse email</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="email"
|
type="email"
|
||||||
key={fields.email.key}
|
key={fields.email.key}
|
||||||
name={fields.email.name}
|
name={fields.email.name}
|
||||||
defaultValue={fields.email.initialValue}
|
defaultValue={fields.email.initialValue}
|
||||||
placeholder="Votre adresse email"
|
placeholder="Votre adresse email"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.email.errors} />
|
<ErrorMessage messages={fields.email.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group className="mb-3" controlId="formPassword">
|
<Form.Group className="mb-3" controlId="formPassword">
|
||||||
<Form.Label>Mot de passe</Form.Label>
|
<Form.Label>Mot de passe</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="password"
|
type="password"
|
||||||
key={fields.password.key}
|
key={fields.password.key}
|
||||||
name={fields.password.name}
|
name={fields.password.name}
|
||||||
defaultValue={fields.password.initialValue}
|
defaultValue={fields.password.initialValue}
|
||||||
placeholder="Mot de passe"
|
placeholder="Mot de passe"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
<ErrorMessage messages={fields.password.errors} />
|
<ErrorMessage messages={fields.password.errors} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<div className="d-flex justify-content-end">
|
<div className="d-flex justify-content-end">
|
||||||
<Button variant="primary" type="submit" disabled={isPending}>
|
<Button variant="primary" type="submit" disabled={isPending}>
|
||||||
Créer mon compte
|
Créer mon compte
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-13
@@ -1,20 +1,21 @@
|
|||||||
|
import { Container, Col, Row } from "react-bootstrap";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { RegistrationForm } from "./_components/registrationForm";
|
import { RegistrationForm } from "./_components/registrationForm";
|
||||||
|
|
||||||
const Page = () => (
|
const Page = () => (
|
||||||
<div className="auth-page">
|
<section className="auth-page">
|
||||||
<div className="container page">
|
<Container className="page">
|
||||||
<div className="row">
|
<Row>
|
||||||
<div className="col-md-6 offset-md-3 col-xs-12">
|
<Col xs={12} md={{ span: 6, offset: 3 }}>
|
||||||
<h1 className="text-xs-center">Se connecter</h1>
|
<h1 className="text-xs-center">Créer un compte</h1>
|
||||||
<p className="text-xs-center">
|
<p className="text-xs-center">
|
||||||
<Link href="/login">Déjà un compte?</Link>
|
<Link href="/login">Déjà un compte?</Link>
|
||||||
</p>
|
</p>
|
||||||
<RegistrationForm />
|
<RegistrationForm />
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Page;
|
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 { ErrorMessage } from "@/modules/common/components/errorMessage";
|
||||||
import { User } from "@/utils/types/models";
|
import { User } from "@/utils/types/models";
|
||||||
import { SubmissionResult, useForm } from "@conform-to/react";
|
import { SubmissionResult, useForm } from "@conform-to/react";
|
||||||
@@ -7,105 +7,117 @@ import { startTransition } from "react";
|
|||||||
import { inputsSchema } from "./types";
|
import { inputsSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user?: User;
|
user?: User;
|
||||||
action?: (formData: FormData) => void;
|
action?: (formData: FormData) => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
result?: SubmissionResult<string[]>;
|
result?: SubmissionResult<string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsForm = ({ user, action, isPending, result }: Props) => {
|
export const SettingsForm = ({ user, action, isPending, result }: Props) => {
|
||||||
const [form, fields] = useForm({
|
const [form, fields] = useForm({
|
||||||
lastResult: result,
|
lastResult: result,
|
||||||
onValidate({ formData }) {
|
onValidate({ formData }) {
|
||||||
return parseWithZod(formData, { schema: inputsSchema });
|
return parseWithZod(formData, { schema: inputsSchema });
|
||||||
},
|
},
|
||||||
// onSubmit is defined to avoid resetting form after successful submission
|
// onSubmit is defined to avoid resetting form after successful submission
|
||||||
// see https://github.com/edmundhung/conform/discussions/606
|
// see https://github.com/edmundhung/conform/discussions/606
|
||||||
onSubmit(event, { formData }) {
|
onSubmit(event, { formData }) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
action?.(formData);
|
action?.(formData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
defaultValue: { ...user },
|
defaultValue: { ...user },
|
||||||
shouldValidate: "onBlur",
|
shouldValidate: "onBlur",
|
||||||
shouldRevalidate: "onBlur",
|
shouldRevalidate: "onBlur",
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ErrorMessage messages={form.errors} />
|
<ErrorMessage messages={form.errors} />
|
||||||
<form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
||||||
<fieldset>
|
<Form.Group className="mb-3" controlId="formUsername">
|
||||||
<fieldset className="form-group">
|
<Form.Label>Pseudo</Form.Label>
|
||||||
<input
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
key={fields.image.key}
|
key={fields.username.key}
|
||||||
name={fields.image.name}
|
name={fields.username.name}
|
||||||
defaultValue={fields.image.initialValue}
|
defaultValue={fields.username.initialValue}
|
||||||
placeholder="URL of profile picture"
|
placeholder="Votre pseudo"
|
||||||
className="form-control"
|
/>
|
||||||
/>
|
<ErrorMessage messages={fields.username.errors} />
|
||||||
<ErrorMessage messages={fields.image.errors} />
|
</Form.Group>
|
||||||
</fieldset>
|
<Form.Group className="mb-3" controlId="formEmail">
|
||||||
<fieldset className="form-group">
|
<Form.Label>Adresse email</Form.Label>
|
||||||
<input
|
<Form.Control
|
||||||
type="text"
|
type="email"
|
||||||
key={fields.username.key}
|
key={fields.email.key}
|
||||||
name={fields.username.name}
|
name={fields.email.name}
|
||||||
defaultValue={fields.username.initialValue}
|
defaultValue={fields.email.initialValue}
|
||||||
placeholder="Your Name"
|
placeholder="Votre adresse email"
|
||||||
className="form-control form-control-lg"
|
/>
|
||||||
/>
|
<ErrorMessage messages={fields.email.errors} />
|
||||||
<ErrorMessage messages={fields.username.errors} />
|
</Form.Group>
|
||||||
</fieldset>
|
<Form.Group className="mb-3" controlId="formFirstname">
|
||||||
<fieldset className="form-group">
|
<Form.Label>Prénom</Form.Label>
|
||||||
<textarea
|
<Form.Control
|
||||||
key={fields.bio.key}
|
type="text"
|
||||||
name={fields.bio.name}
|
key={fields.firstname.key}
|
||||||
defaultValue={fields.bio.initialValue}
|
name={fields.firstname.name}
|
||||||
placeholder="Short bio about you"
|
defaultValue={fields.firstname.initialValue}
|
||||||
className="form-control form-control-lg"
|
placeholder="Votre prénom"
|
||||||
rows={8}
|
/>
|
||||||
></textarea>
|
<ErrorMessage messages={fields.firstname.errors} />
|
||||||
<ErrorMessage messages={fields.bio.errors} />
|
</Form.Group>
|
||||||
</fieldset>
|
<Form.Group className="mb-3" controlId="formLastname">
|
||||||
<fieldset className="form-group">
|
<Form.Label>Nom</Form.Label>
|
||||||
<input
|
<Form.Control
|
||||||
type="email"
|
type="text"
|
||||||
key={fields.email.key}
|
key={fields.lastname.key}
|
||||||
name={fields.email.name}
|
name={fields.lastname.name}
|
||||||
defaultValue={fields.email.initialValue}
|
defaultValue={fields.lastname.initialValue}
|
||||||
placeholder="Email"
|
placeholder="Votre nom"
|
||||||
className="form-control form-control-lg"
|
/>
|
||||||
/>
|
<ErrorMessage messages={fields.lastname.errors} />
|
||||||
<ErrorMessage messages={fields.email.errors} />
|
</Form.Group>
|
||||||
</fieldset>
|
<Form.Group className="mb-3" controlId="formPassword">
|
||||||
<fieldset className="form-group">
|
<Form.Label>Mot de passe</Form.Label>
|
||||||
<input
|
<Form.Control
|
||||||
type="password"
|
type="password"
|
||||||
key={fields.password.key}
|
key={fields.password.key}
|
||||||
name={fields.password.name}
|
name={fields.password.name}
|
||||||
defaultValue={fields.password.initialValue}
|
defaultValue={fields.password.initialValue}
|
||||||
placeholder="New Password"
|
placeholder="Nouveau mot de passe"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
className="form-control form-control-lg"
|
/>
|
||||||
/>
|
<ErrorMessage messages={fields.password.errors} />
|
||||||
<ErrorMessage messages={fields.password.errors} />
|
</Form.Group>
|
||||||
</fieldset>
|
<Form.Group className="mb-3" controlId="formBio">
|
||||||
<Button
|
<Form.Label>Bio</Form.Label>
|
||||||
component="button"
|
<Form.Control as="textarea" rows={8}
|
||||||
size="lg"
|
key={fields.bio.key}
|
||||||
color="primary"
|
name={fields.bio.name}
|
||||||
variant="filled"
|
defaultValue={fields.bio.initialValue}
|
||||||
type="submit"
|
placeholder="Courte description"
|
||||||
className="pull-xs-right"
|
/>
|
||||||
disabled={isPending}
|
<ErrorMessage messages={fields.bio.errors} />
|
||||||
>
|
</Form.Group>
|
||||||
Update Settings
|
<Form.Group className="mb-3" controlId="formImage">
|
||||||
</Button>
|
<Form.Label>Image</Form.Label>
|
||||||
</fieldset>
|
<Form.Control
|
||||||
</form>
|
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";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const inputsSchema = z.object({
|
export const inputsSchema = z.object({
|
||||||
image: z.string().max(200).optional(),
|
|
||||||
username: z.string().max(200),
|
username: z.string().max(200),
|
||||||
bio: z.string().max(200).optional(),
|
|
||||||
email: z.string().max(200).email(),
|
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(),
|
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 { LogoutButton } from "@/modules/features/auth/components/logoutButton";
|
||||||
import { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser";
|
import { fetchCurrentUser } from "@/modules/features/auth/fetch/fetchCurrentUser";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { SettingsForm } from "./_components";
|
import { SettingsForm } from "./_components";
|
||||||
|
|
||||||
const Page = async () => {
|
const Page = async () => {
|
||||||
const user = fetchCurrentUser();
|
const user = fetchCurrentUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-page">
|
<section className="settings-page">
|
||||||
<div className="container page">
|
<Container className="page">
|
||||||
<div className="row">
|
<Row>
|
||||||
<div className="col-md-6 offset-md-3 col-xs-12">
|
<Col xs={12} md={{ span: 6, offset: 3 }}>
|
||||||
<h1 className="text-xs-center">Your Settings</h1>
|
<h1 className="text-xs-center">Vos paramètres</h1>
|
||||||
<Suspense fallback={<p>⌛Loading...</p>}>
|
<Suspense fallback={<p>⌛ Chargement...</p>}>
|
||||||
<SettingsForm user={user} />
|
<SettingsForm user={user} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<hr />
|
<hr />
|
||||||
<LogoutButton />
|
<LogoutButton />
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -15,24 +15,19 @@ const ExampleOffcanvas: React.FC<ExampleOffcanvasProps> = ({ className }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => setShow((s) => !s)} className={className}>
|
<Button size="sm" onClick={() => setShow((s) => !s)} className={className}>
|
||||||
<span className="icon-cart me-2"></span>Mon panier
|
<span className="icon-cart me-2"></span>Mon panier
|
||||||
</Button>
|
</Button>
|
||||||
<Offcanvas placement="end" show={show} onHide={() => setShow(false)}>
|
<Offcanvas placement="end" show={show} onHide={() => setShow(false)}>
|
||||||
<Offcanvas.Header closeButton>
|
<Offcanvas.Header closeButton>
|
||||||
<Offcanvas.Title as="h5">Offcanvas</Offcanvas.Title>
|
<Offcanvas.Title as="h5">Mon panier</Offcanvas.Title>
|
||||||
</Offcanvas.Header>
|
</Offcanvas.Header>
|
||||||
<Offcanvas.Body>
|
<Offcanvas.Body>
|
||||||
<div>
|
<div>
|
||||||
Some text as placeholder. In real life you can have the elements you
|
Some text as placeholder. In real life you can have the elements you
|
||||||
have chosen. Like, text, images, lists, etc.
|
have chosen. Like, text, images, lists, etc.
|
||||||
</div>
|
</div>
|
||||||
|
<DropdownButton variant="secondary" title="Dropdown button" className="mt-3">
|
||||||
<DropdownButton
|
|
||||||
variant="secondary"
|
|
||||||
title="Dropdown button"
|
|
||||||
className="mt-3"
|
|
||||||
>
|
|
||||||
<Dropdown.Item href="#">Action</Dropdown.Item>
|
<Dropdown.Item href="#">Action</Dropdown.Item>
|
||||||
<Dropdown.Item href="#">Another action</Dropdown.Item>
|
<Dropdown.Item href="#">Another action</Dropdown.Item>
|
||||||
<Dropdown.Item href="#">Something else here</Dropdown.Item>
|
<Dropdown.Item href="#">Something else here</Dropdown.Item>
|
||||||
|
|||||||
@@ -1,130 +1,112 @@
|
|||||||
|
import { Container, Col, Row } from "react-bootstrap";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import LogoSmall from "@/app/_components/layoutElements/logoSmall";
|
import LogoSmall from "@/app/_components/layoutElements/logoSmall";
|
||||||
|
import { LogoutButton } from "@/modules/features/auth/components/logoutButton";
|
||||||
//import { ComponentPropsWithoutRef } from "react";
|
//import { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
//type Props = ComponentPropsWithoutRef<"footer">;
|
//type Props = ComponentPropsWithoutRef<"footer">;
|
||||||
|
|
||||||
export const Footer = () => (
|
export const Footer = async () => {
|
||||||
<footer className="bd-footer pt-3 pb-2 pt-md-4 mt-5 bg-blue-gold">
|
return (
|
||||||
<div className="container py-4 py-md-5 px-4 px-md-3 text-body-secondary">
|
<footer className="bd-footer pt-3 pb-2 pt-md-4 mt-5 bg-dark-blue gold">
|
||||||
<div className="row">
|
<Container className="py-3 py-md-4 px-4 px-md-3">
|
||||||
<div className="col-lg-3 mb-3">
|
<Row>
|
||||||
<Link
|
<Col xs={12} lg={3}>
|
||||||
className="d-inline-flex align-items-center mb-2 text-body-emphasis text-decoration-none"
|
<Link
|
||||||
href="/"
|
className="d-inline-flex align-items-center mb-2 text-body-emphasis text-decoration-none"
|
||||||
aria-label="Bootstrap"
|
href="/"
|
||||||
>
|
aria-label="Bootstrap"
|
||||||
<LogoSmall />
|
>
|
||||||
</Link>
|
<LogoSmall />
|
||||||
<ul className="list-unstyled small">
|
</Link>
|
||||||
<li className="mb-2">
|
<p className="mt-2 mb-3 small">
|
||||||
L'équipe Ad Astra est réactive et à l'écoute de vos demandes afin de vous accompagner au mieux
|
L'équipe Ad Astra est réactive et à l'écoute de vos demandes afin de vous accompagner au mieux
|
||||||
et satisfaire vos besoins en fleurs et hashes CBD de qualité.
|
et satisfaire vos besoins en fleurs et hashes CBD de qualité.
|
||||||
</li>
|
</p>
|
||||||
<li className="mb-2">Exigez la qualité Ad Astra, vous la méritez.</li>
|
<LogoutButton variant="outline-danger" />
|
||||||
</ul>
|
</Col>
|
||||||
</div>
|
<Col xs={6} lg={{ span: 2, offset: 1 }} className="mb-3 mt-3">
|
||||||
<div className="col-6 col-lg-2 offset-lg-1 mb-3">
|
<h5>Nos produits</h5>
|
||||||
<h5>Nos produits</h5>
|
<ul className="list-unstyled">
|
||||||
<ul className="list-unstyled">
|
<li className="mb-2">
|
||||||
<li className="mb-2">
|
<i className="icon-home me-2" />
|
||||||
<Link href="/">Accueil</Link>
|
<Link href="/">Accueil</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/produits/fleurs">Lien 2</Link>
|
<i className="icon-marijuana me-2" />
|
||||||
</li>
|
<Link href="/produits/les-fleurs">Fleurs</Link>
|
||||||
<li className="mb-2">
|
</li>
|
||||||
<Link href="/produits/resines">Lien 3</Link>
|
<li className="mb-2">
|
||||||
</li>
|
<i className="icon-hash me-2" />
|
||||||
<li className="mb-2">
|
<Link href="/produits/les-resines">Résines</Link>
|
||||||
<Link href="/produits/molecules">Lien 4</Link>
|
</li>
|
||||||
</li>
|
<li className="mb-2">
|
||||||
<li className="mb-2">
|
<i className="icon-molecule me-2" />
|
||||||
<Link href="/produits/concentres">Lien 5</Link>
|
<Link href="/produits/les-molecules">10OH+</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</Col>
|
||||||
<div className="col-6 col-lg-2 mb-3">
|
<Col xs={6} lg={2} className="mb-3 mt-3">
|
||||||
<h5>Documentation</h5>
|
<h5>Documentation</h5>
|
||||||
<ul className="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/fleurs">Les fleurs</Link>
|
<Link href="/article/les-fleurs">Les fleurs</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/resines">Les hashes</Link>
|
<Link href="/article/les-resines">Les hashes</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/molecules">Les molécules</Link>
|
<Link href="/article/les-molecules">Les molécules</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/concentres">Les concentrés</Link>
|
<Link href="/article/les-concentres">Les concentrés</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</Col>
|
||||||
<div className="col-6 col-lg-2 mb-3">
|
<Col xs={6} lg={2} className="mb-3 mt-3">
|
||||||
<h5>Professionels</h5>
|
<h5>Professionels</h5>
|
||||||
<ul className="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/catalogue_ad_astra_2025.pdf" target="_blank" rel="noopener">
|
<Link href="/catalogue_ad_astra_2025.pdf" target="_blank" rel="noopener">
|
||||||
Catalogue B2B
|
Catalogue B2B
|
||||||
</Link>{" "}
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/pro/lien2" target="_blank" rel="noopener">
|
<Link href="/pro/lien2" target="_blank" rel="noopener">
|
||||||
Lien 2
|
Lien 2
|
||||||
</Link>{" "}
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
<Link href="/pro/lien3" target="_blank" rel="noopener">
|
<Link href="/pro/lien3" target="_blank" rel="noopener">
|
||||||
Lien 3
|
Lien 3
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mb-2">
|
</ul>
|
||||||
<Link href="/pro/lien4" target="_blank" rel="noopener">
|
</Col>
|
||||||
Lien 4
|
<Col xs={6} lg={2} className="mb-3 mt-3">
|
||||||
</Link>
|
<h5>Contact</h5>
|
||||||
</li>
|
<ul className="list-unstyled">
|
||||||
<li className="mb-2">
|
<li className="mb-2">
|
||||||
{" "}
|
<Link href="/contact" target="_blank" rel="noopener">
|
||||||
<Link href="/pro/lien5" target="_blank" rel="noopener">
|
Lien 1
|
||||||
Lien 5
|
</Link>
|
||||||
</Link>{" "}
|
</li>
|
||||||
</li>
|
<li className="mb-2">
|
||||||
</ul>
|
<Link href="/mentions-legales" target="_blank" rel="noopener">
|
||||||
</div>
|
Lien 2
|
||||||
<div className="col-6 col-lg-2 mb-3">
|
</Link>
|
||||||
<h5>Contact</h5>
|
</li>
|
||||||
<ul className="list-unstyled">
|
<li className="mb-2">
|
||||||
<li className="mb-2">
|
<Link href="/produits" target="_blank" rel="noopener">
|
||||||
<Link href="/contact" target="_blank" rel="noopener">
|
Lien 3
|
||||||
Lien 1
|
</Link>
|
||||||
</Link>{" "}
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li className="mb-2">
|
</Col>
|
||||||
<Link href="/mentions-legales" target="_blank" rel="noopener">
|
</Row>
|
||||||
Lien 2
|
</Container>
|
||||||
</Link>{" "}
|
<hr className="my-3" />
|
||||||
</li>
|
<p className="px-4 mb-2 small">Copyright © 2025 Ad Astra</p>
|
||||||
<li className="mb-2">
|
</footer>
|
||||||
<Link href="/produits" target="_blank" rel="noopener">
|
);
|
||||||
Lien 3
|
};
|
||||||
</Link>{" "}
|
|
||||||
</li>
|
|
||||||
<li className="mb-2">
|
|
||||||
<Link href="/produits" target="_blank" rel="noopener">
|
|
||||||
Lien 4
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="mb-2">
|
|
||||||
<Link href="/produits" target="_blank" rel="noopener">
|
|
||||||
Lien 5
|
|
||||||
</Link>{" "}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="my-3" />
|
|
||||||
<p className="text-muted px-4 mb-2 small">Copyright © 2025 Ad Astra</p>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
@@ -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 { ErrorMessage } from "@/modules/common/components/errorMessage";
|
||||||
import { Article } from "@/utils/types/models";
|
import { Article } from "@/utils/types/models";
|
||||||
import { SubmissionResult, useForm } from "@conform-to/react";
|
import { SubmissionResult, useForm } from "@conform-to/react";
|
||||||
@@ -9,130 +9,124 @@ import styles from "./presentation.module.css";
|
|||||||
import { inputsSchema } from "./types";
|
import { inputsSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
defaultValues?: Article;
|
defaultValues?: Article;
|
||||||
action?: (formData: FormData) => void;
|
action?: (formData: FormData) => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
result?: SubmissionResult<string[]>;
|
result?: SubmissionResult<string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ArticleEditor = ({ defaultValues, result, action, isPending }: Props) => {
|
export const ArticleEditor = ({ defaultValues, result, action, isPending }: Props) => {
|
||||||
const [form, fields] = useForm({
|
const [form, fields] = useForm({
|
||||||
lastResult: result,
|
lastResult: result,
|
||||||
onValidate({ formData }) {
|
onValidate({ formData }) {
|
||||||
return parseWithZod(formData, { schema: inputsSchema });
|
return parseWithZod(formData, { schema: inputsSchema });
|
||||||
},
|
},
|
||||||
// onSubmit is defined to avoid resetting form after successful submission
|
// onSubmit is defined to avoid resetting form after successful submission
|
||||||
// see https://github.com/edmundhung/conform/discussions/606
|
// see https://github.com/edmundhung/conform/discussions/606
|
||||||
onSubmit(event, { formData }) {
|
onSubmit(event, { formData }) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
action?.(formData);
|
action?.(formData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
defaultValue: { ...defaultValues },
|
defaultValue: { ...defaultValues },
|
||||||
shouldValidate: "onBlur",
|
shouldValidate: "onBlur",
|
||||||
shouldRevalidate: "onBlur",
|
shouldRevalidate: "onBlur",
|
||||||
});
|
});
|
||||||
|
|
||||||
const tagListErrors = Object.values(fields.tagList.allErrors).flat();
|
const tagListErrors = Object.values(fields.tagList.allErrors).flat();
|
||||||
|
|
||||||
const onTagFormKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
const onTagFormKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case "Enter":
|
case "Enter":
|
||||||
// prevent form submit
|
// prevent form submit
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const tag = fields.tag.value;
|
const tag = fields.tag.value;
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
form.insert({ name: fields.tagList.name, defaultValue: tag });
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
form.insert({ name: fields.tagList.name, defaultValue: tag });
|
};
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickRemoveTag = (index: number) => {
|
const onClickRemoveTag = (index: number) => {
|
||||||
form.remove({ name: fields.tagList.name, index });
|
form.remove({ name: fields.tagList.name, index });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ErrorMessage messages={form.errors} />
|
<ErrorMessage messages={form.errors} />
|
||||||
<form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
||||||
<input type="hidden" key={fields.slug.key} name={fields.slug.name} defaultValue={fields.slug.initialValue} />
|
<Form.Control
|
||||||
<fieldset>
|
type="hidden"
|
||||||
<fieldset className="form-group">
|
key={fields.slug.key}
|
||||||
<input
|
name={fields.slug.name}
|
||||||
type="text"
|
defaultValue={fields.slug.initialValue}
|
||||||
key={fields.title.key}
|
/>
|
||||||
name={fields.title.name}
|
<Form.Group className="mb-3" controlId="formLastname">
|
||||||
defaultValue={fields.title.initialValue}
|
<Form.Label>Titre</Form.Label>
|
||||||
placeholder="Article Title"
|
<Form.Control
|
||||||
className="form-control form-control-lg"
|
type="text"
|
||||||
/>
|
key={fields.title.key}
|
||||||
<ErrorMessage messages={fields.title.errors} />
|
name={fields.title.name}
|
||||||
</fieldset>
|
defaultValue={fields.title.initialValue}
|
||||||
<fieldset className="form-group">
|
placeholder="Titre"
|
||||||
<input
|
/>
|
||||||
type="text"
|
<ErrorMessage messages={fields.title.errors} />
|
||||||
key={fields.description.key}
|
</Form.Group>
|
||||||
name={fields.description.name}
|
<Form.Group className="mb-3" controlId="formLastname">
|
||||||
defaultValue={fields.description.initialValue}
|
<Form.Label>Catégorie</Form.Label>
|
||||||
placeholder="What's this article about?"
|
<Form.Control
|
||||||
className="form-control"
|
type="text"
|
||||||
/>
|
key={fields.description.key}
|
||||||
<ErrorMessage messages={fields.description.errors} />
|
name={fields.description.name}
|
||||||
</fieldset>
|
defaultValue={fields.description.initialValue}
|
||||||
<fieldset className="form-group">
|
placeholder="Catégorie"
|
||||||
<textarea
|
/>
|
||||||
key={fields.body.key}
|
<ErrorMessage messages={fields.description.errors} />
|
||||||
name={fields.body.name}
|
</Form.Group>
|
||||||
defaultValue={fields.body.initialValue}
|
<Form.Group className="mb-3" controlId="formBody">
|
||||||
rows={8}
|
<Form.Label>Description</Form.Label>
|
||||||
placeholder="Write your article (in markdown)"
|
<Form.Control as="textarea" rows={8}
|
||||||
className="form-control"
|
key={fields.body.key}
|
||||||
/>
|
name={fields.body.name}
|
||||||
<ErrorMessage messages={fields.body.errors} />
|
defaultValue={fields.body.initialValue}
|
||||||
</fieldset>
|
placeholder="Write your article (in markdown)"
|
||||||
<fieldset className="form-group">
|
/>
|
||||||
<input
|
<ErrorMessage messages={fields.body.errors} />
|
||||||
type="text"
|
</Form.Group>
|
||||||
key={fields.tag.key}
|
<Form.Group className="mb-3" controlId="formLastname">
|
||||||
name={fields.tag.name}
|
<Form.Label>Nom</Form.Label>
|
||||||
defaultValue={fields.tag.initialValue}
|
<Form.Control
|
||||||
placeholder="Enter tags"
|
type="text"
|
||||||
onKeyDown={onTagFormKeyDown}
|
key={fields.tag.key}
|
||||||
className="form-control"
|
name={fields.tag.name}
|
||||||
/>
|
defaultValue={fields.tag.initialValue}
|
||||||
<ul className="tag-list">
|
placeholder="Enter tags"
|
||||||
{fields.tagList.getFieldList().map((tagField, index) => (
|
onKeyDown={onTagFormKeyDown}
|
||||||
<li key={tagField.key}>
|
/>
|
||||||
<Tag component="span" variant="filled" className={styles["tag-form"]}>
|
<ul className="list-unstyled">
|
||||||
<input type="hidden" name={tagField.name} value={tagField.value} />
|
{fields.tagList.getFieldList().map((tagField, index) => (
|
||||||
<button onClick={() => onClickRemoveTag(index)}>
|
<li key={tagField.key}>
|
||||||
<i className="ion-close-round" />
|
<Tag component="badge" color="info" className={styles["tag-form"]}>
|
||||||
</button>
|
<input type="hidden" name={tagField.name} value={tagField.value} />
|
||||||
{tagField.value}
|
<span onClick={() => onClickRemoveTag(index)} className="me-2">
|
||||||
</Tag>
|
<i className="icon-close" />
|
||||||
</li>
|
</span>
|
||||||
))}
|
{tagField.value}
|
||||||
</ul>
|
</Tag>
|
||||||
<ErrorMessage messages={tagListErrors} />
|
</li>
|
||||||
</fieldset>
|
))}
|
||||||
<Button
|
</ul>
|
||||||
component="button"
|
<ErrorMessage messages={tagListErrors} />
|
||||||
size="lg"
|
</Form.Group>
|
||||||
color="primary"
|
<Button variant="primary" type="submit" disabled={isPending}>
|
||||||
variant="filled"
|
Enregistrer
|
||||||
type="submit"
|
</Button>
|
||||||
className="pull-xs-right"
|
</Form>
|
||||||
disabled={isPending}
|
</>
|
||||||
>
|
);
|
||||||
Publish Article
|
|
||||||
</Button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { Button } from "@/modules/common/components/button";
|
import { Button, Form } from "react-bootstrap";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { MouseEventHandler } from "react";
|
import { MouseEventHandler } from "react";
|
||||||
import styles from "./presentation.module.css";
|
import styles from "./presentation.module.css";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleClickDelete?: MouseEventHandler<HTMLButtonElement>;
|
handleClickDelete?: MouseEventHandler<HTMLButtonElement>;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteArticleButton = ({ action, isPending, className }: Props) => {
|
export const DeleteArticleButton = ({ action, isPending, className }: Props) => {
|
||||||
return (
|
return (
|
||||||
<form action={action} className={clsx(className, styles["form"])}>
|
<Form action={action} className={clsx(className, styles["form"])}>
|
||||||
<Button component="button" color="danger" type="submit" disabled={isPending}>
|
<Button variant="danger" type="submit" disabled={isPending}>
|
||||||
<i className="ion-trash-a"></i> Delete Article
|
Supprimer
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,33 +1,34 @@
|
|||||||
|
import { Form } from "react-bootstrap";
|
||||||
import { Button } from "@/modules/common/components/button";
|
import { Button } from "@/modules/common/components/button";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import styles from "./presentation.module.css";
|
import styles from "./presentation.module.css";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
favorited: boolean;
|
favorited: boolean;
|
||||||
favoritesCount: number;
|
favoritesCount: number;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
showMessage?: boolean;
|
showMessage?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FavoriteButton = ({ favorited, favoritesCount, action, disabled, showMessage, className }: Props) => {
|
export const FavoriteButton = ({ favorited, favoritesCount, action, disabled, showMessage, className }: Props) => {
|
||||||
const content = showMessage ? (
|
const content = showMessage ? (
|
||||||
<>
|
<>
|
||||||
<i className="ion-heart"></i> {favorited ? "Unfavorite Article " : "Favorite Article "}
|
<i className="ion-heart"></i> {favorited ? "Unfavorite Article " : "Favorite Article "}
|
||||||
<span className="counter">({favoritesCount})</span>
|
<span className="counter">({favoritesCount})</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<i className="ion-heart"></i> {favoritesCount}
|
<i className="ion-heart"></i> {favoritesCount}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form action={action} className={clsx(styles["form"], className)}>
|
<Form action={action} className={clsx(styles["form"], className)}>
|
||||||
<Button component="button" variant={favorited ? "filled" : "outline"} disabled={disabled}>
|
<Button component="button" variant={favorited ? "filled" : "outline"} disabled={disabled}>
|
||||||
{content}
|
{content}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { Badge } from "react-bootstrap";
|
||||||
import { ExhaustiveError } from "@/utils/errors";
|
import { ExhaustiveError } from "@/utils/errors";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ComponentPropsWithoutRef } from "react";
|
import { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
type TagVariant = "outline" | "filled";
|
type TagVariant = "outline" | "filled";
|
||||||
|
type TagColor = "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark";
|
||||||
|
|
||||||
const variantClassNameMaps: Record<TagVariant, string> = {
|
const variantClassNameMaps: Record<TagVariant, string> = {
|
||||||
outline: "tag-outline",
|
outline: "tag-outline",
|
||||||
@@ -12,6 +14,7 @@ const variantClassNameMaps: Record<TagVariant, string> = {
|
|||||||
|
|
||||||
type CommonTagProps = {
|
type CommonTagProps = {
|
||||||
variant?: TagVariant;
|
variant?: TagVariant;
|
||||||
|
color?: TagColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
@@ -57,8 +60,22 @@ const TagAsSpan = ({ children, variant = "filled", className, ...rest }: TagProp
|
|||||||
);
|
);
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
// badge pattern
|
||||||
|
|
||||||
export type TagProps = TagPropsAsListItem | TagPropsAsAnchor | TagPropsAsSpan;
|
type TagPropsAsBadge = CommonTagProps &
|
||||||
|
ComponentPropsWithoutRef<"span"> & {
|
||||||
|
component: "badge";
|
||||||
|
};
|
||||||
|
|
||||||
|
const TagAsBadge = ({ children, color = "primary", className, ...rest }: TagPropsAsBadge) => (
|
||||||
|
<Badge bg={color} {...rest}>
|
||||||
|
{children}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
export type TagProps = TagPropsAsListItem | TagPropsAsAnchor | TagPropsAsSpan | TagPropsAsBadge;
|
||||||
|
|
||||||
export const Tag = (props: TagProps) => {
|
export const Tag = (props: TagProps) => {
|
||||||
switch (props.component) {
|
switch (props.component) {
|
||||||
@@ -68,6 +85,8 @@ export const Tag = (props: TagProps) => {
|
|||||||
return <TagAsAnchor {...props} />;
|
return <TagAsAnchor {...props} />;
|
||||||
case "span":
|
case "span":
|
||||||
return <TagAsSpan {...props} />;
|
return <TagAsSpan {...props} />;
|
||||||
|
case "badge":
|
||||||
|
return <TagAsBadge {...props} />;
|
||||||
default:
|
default:
|
||||||
// compilation fails if all cases are not covered
|
// compilation fails if all cases are not covered
|
||||||
throw new ExhaustiveError(props);
|
throw new ExhaustiveError(props);
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { User } from "@/utils/types/models";
|
import { User } from "@/utils/types/models";
|
||||||
import { Container, Nav, Navbar, NavDropdown } from "react-bootstrap";
|
import { Button, Form, Container, Nav, Navbar, NavDropdown } from "react-bootstrap";
|
||||||
import { HeaderMenuItem } from "./headerMenuItem";
|
import { HeaderMenuItem } from "./headerMenuItem";
|
||||||
import LogoSmall from "@/app/_components/layoutElements/logoSmall";
|
import LogoSmall from "@/app/_components/layoutElements/logoSmall";
|
||||||
|
import { LogoutButton } from "@/modules/features/auth/components/logoutButton";
|
||||||
import ButtonOffcanvas from "@/modules/common/components/button/buttonOffcanvas";
|
import ButtonOffcanvas from "@/modules/common/components/button/buttonOffcanvas";
|
||||||
|
|
||||||
|
|
||||||
const UnauthenticatedMenus = () => {
|
const UnauthenticatedMenus = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderMenuItem href="/login" segment="login">
|
<HeaderMenuItem href="/login" segment="login">
|
||||||
Se connecter
|
Se connecter
|
||||||
</HeaderMenuItem>
|
</HeaderMenuItem>
|
||||||
|
<Navbar.Text>
|
||||||
|
/
|
||||||
|
</Navbar.Text>
|
||||||
<HeaderMenuItem href="/register" segment="register">
|
<HeaderMenuItem href="/register" segment="register">
|
||||||
Créer un compte
|
Créer un compte
|
||||||
</HeaderMenuItem>
|
</HeaderMenuItem>
|
||||||
@@ -22,16 +27,20 @@ const UnauthenticatedMenus = () => {
|
|||||||
const AuthenticatedMenus = ({ authUser }: { authUser: User }) => {
|
const AuthenticatedMenus = ({ authUser }: { authUser: User }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderMenuItem href="/editor" segment="editor">
|
<NavDropdown title={authUser.username} id="basic-nav-dropdown">
|
||||||
<span className="icon-barcode me-2"></span>Ajouter un produit
|
<NavDropdown.Item href={`/profile/${authUser.username}`}>
|
||||||
</HeaderMenuItem>
|
<span className="icon-user me-2"></span>Mon compte
|
||||||
<HeaderMenuItem href="/settings" segment="settings">
|
</NavDropdown.Item>
|
||||||
|
<NavDropdown.Item href="/settings">
|
||||||
<span className="icon-cog me-2"></span>Paramètres
|
<span className="icon-cog me-2"></span>Paramètres
|
||||||
</HeaderMenuItem>
|
</NavDropdown.Item>
|
||||||
<HeaderMenuItem href={`/profile/${authUser.username}`} segment="profile">
|
<NavDropdown.Item href="/editor">
|
||||||
<span className="icon-user me-2"></span>{authUser.username}
|
<span className="icon-barcode me-2"></span>Ajouter un produit
|
||||||
</HeaderMenuItem>
|
</NavDropdown.Item>
|
||||||
<ButtonOffcanvas className="ms-3" />
|
<NavDropdown.Divider />
|
||||||
|
<LogoutButton variant="outline-danger" />
|
||||||
|
</NavDropdown>
|
||||||
|
<ButtonOffcanvas className="ms-3" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -39,7 +48,7 @@ const AuthenticatedMenus = ({ authUser }: { authUser: User }) => {
|
|||||||
export const Header = ({ authUser }: { authUser?: User }) => {
|
export const Header = ({ authUser }: { authUser?: User }) => {
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<Navbar bg="dark" data-bs-theme="dark" expand="lg" className="bg-darker mb-4 pt-1 pb-2">
|
<Navbar bg="dark" data-bs-theme="dark" expand="lg" className="bg-dark-blue mb-4 pt-1 pb-2">
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar.Brand href="/">
|
<Navbar.Brand href="/">
|
||||||
<LogoSmall />
|
<LogoSmall />
|
||||||
|
|||||||
@@ -4,8 +4,16 @@ import { useActionState } from "react";
|
|||||||
import { logoutAction } from "./action";
|
import { logoutAction } from "./action";
|
||||||
import { LogoutButton as LogoutButtonPresentation } from "./presentation";
|
import { LogoutButton as LogoutButtonPresentation } from "./presentation";
|
||||||
|
|
||||||
export const LogoutButton = () => {
|
type Variant = "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark"
|
||||||
|
| "outline-primary" | "outline-secondary" | "outline-success" | "outline-danger" | "outline-warning" | "outline-info" | "outline-light" | "outline-dark";
|
||||||
|
const DEFAULT_VARIANT: Variant = "danger";
|
||||||
|
|
||||||
|
type CommonProps = {
|
||||||
|
variant?: Variant;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogoutButton = ({variant = DEFAULT_VARIANT}: CommonProps) => {
|
||||||
const [_state, action, isPending] = useActionState(logoutAction, undefined);
|
const [_state, action, isPending] = useActionState(logoutAction, undefined);
|
||||||
|
|
||||||
return <LogoutButtonPresentation action={action} isPending={isPending} />;
|
return <LogoutButtonPresentation variant={variant} action={action} isPending={isPending} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Button } from "@/modules/common/components/button";
|
import { Button, Form } from "react-bootstrap";
|
||||||
|
|
||||||
type Props = { action?: () => void; isPending?: boolean };
|
type Props = { action?: () => void; isPending?: boolean; variant?: string; };
|
||||||
|
|
||||||
export const LogoutButton = ({ action, isPending }: Props) => {
|
export const LogoutButton = ({ action, isPending, variant }: Props) => {
|
||||||
return (
|
return (
|
||||||
<form action={action}>
|
<Form action={action}>
|
||||||
<Button component="button" color="danger" type="submit" size="lg" disabled={isPending}>
|
<Button variant={variant} type="submit" disabled={isPending}>
|
||||||
Or click here to logout.
|
<i className="icon-switch me-2" />Se déconnecter
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
|
import { Form } from "react-bootstrap";
|
||||||
import { Button, Color } from "@/modules/common/components/button";
|
import { Button, Color } from "@/modules/common/components/button";
|
||||||
import styles from "./presentation.module.css";
|
import styles from "./presentation.module.css";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
username: string;
|
username: string;
|
||||||
color?: Color;
|
color?: Color;
|
||||||
following?: boolean;
|
following?: boolean;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FollowButton = ({ username, color, following, action, isPending, className }: Props) => {
|
export const FollowButton = ({ username, color, following, action, isPending, className }: Props) => {
|
||||||
return (
|
return (
|
||||||
<form action={action} className={clsx(className, styles["form"])}>
|
<Form action={action} className={clsx(className, styles["form"])}>
|
||||||
<Button
|
<Button
|
||||||
component="button"
|
component="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
className="action-btn"
|
className="action-btn"
|
||||||
color={color}
|
color={color}
|
||||||
variant={following ? "filled" : "outline"}
|
variant={following ? "filled" : "outline"}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
>
|
>
|
||||||
<i className="ion-plus-round"></i> {following ? "Unfollow" : "Follow"} {username}
|
<i className="ion-plus-round"></i> {following ? "Unfollow" : "Follow"} {username}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const fetchProfile = cache(async (username: string) => {
|
|||||||
const response = await client.sendRequest();
|
const response = await client.sendRequest();
|
||||||
|
|
||||||
if (response.result === "success") {
|
if (response.result === "success") {
|
||||||
|
console.log(response.data);
|
||||||
return Profile.parse(response.data.profile);
|
return Profile.parse(response.data.profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -8,7 +8,12 @@
|
|||||||
<missing-glyph horiz-adv-x="1024" />
|
<missing-glyph horiz-adv-x="1024" />
|
||||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||||
<glyph unicode="" glyph-name="home" d="M1024 369.556l-512 397.426-512-397.428v162.038l512 397.426 512-397.428zM896 384v-384h-256v256h-256v-256h-256v384l384 288z" />
|
<glyph unicode="" glyph-name="home" d="M1024 369.556l-512 397.426-512-397.428v162.038l512 397.426 512-397.428zM896 384v-384h-256v256h-256v-256h-256v384l384 288z" />
|
||||||
|
<glyph unicode="" glyph-name="close" d="M569.436 447.998l214.93 214.946c15.84 15.866 15.84 41.556 0 57.42-15.89 15.866-41.598 15.866-57.436 0l-214.93-214.93-214.93 214.93c-15.88 15.866-41.546 15.866-57.436 0-15.84-15.866-15.84-41.556 0-57.42l214.93-214.946-214.93-214.938c-15.84-15.84-15.84-41.546 0-57.436 7.966-7.914 18.338-11.886 28.72-11.886 10.414 0 20.794 3.962 28.712 11.886l214.93 214.93 214.93-214.93c7.916-7.914 18.338-11.886 28.712-11.886 10.38 0 20.754 3.962 28.72 11.886 15.84 15.89 15.84 41.598 0 57.436l-214.916 214.94z" />
|
||||||
|
<glyph unicode="" glyph-name="hash" d="M1023.731 372.060c-0.698 5.633-1.845 10.827-4.012 15.228-12.298 25.835-95.102 172.248-146.822 239.683-38.699 53.765-42.736 59.39-106.637 57.508-66.542-1.957-286.595-18.614-288.812-18.784-6.333-1.548-12.807-3.755-18.699-5.418-35.353 35.473-100.463 91.806-143.344 121.231-45.726 31.378-61.557 15.527-103.922-26.815-7.715-7.715-16.49-16.467-26.533-26.040-63.759-60.821-161.499-182.063-162.514-183.332-27.448-34.555-27.261-51.845-12.585-89.644 12.939-33.238 64.896-167.997 82.986-220.238 11.56-32.939 18.173-46.618 28.765-57.014 5.456-5.304 11.704-9.114 19.786-13.742 10.423-5.952 29.403-14.844 51.349-25.125 21.806-10.231 46.504-21.828 66.427-32.271 38.766-21.008 66.332-2.815 98.628 18.986 16.983 10.979 62.19 63.173 110.622 119.252 8.119-30.717 18.32-66.347 24.992-78.949 18.704-35.333 32.879-40.046 79.286-40.046 30.75 0 162.935 18.626 259.451 32.231 43.654 6.149 78.136 11.011 89.923 12.253 39.754 4.202 76.109 13.203 84.24 76.239 4.548 40.133 19.901 91.165 17.425 124.807v0zM470.964 654.085c16.327 5.186 225.27 17.751 295.572 20.99 58.906 1.728 60.395-0.349 98.72-53.603 51.339-66.99 133.864-212.576 145.974-238.256 1.060-2.204 1.823-4.733 2.381-7.493-28.658-3.015-175.662-18.487-276.262-27.304-33.617-2.942-63.066-6.82-89.058-10.233-48.409-6.373-83.198-10.959-101.451-3.501 8.371 9.031 15.594 16.392 21.098 21.379 36.41 34.059 77.436 71.429 51.234 115.681-21.621 34.909-104.208 124.605-142.713 169.548-2.75 3.294-6.234 7.104-10.276 11.385 1.529 0.456 3.117 0.923 4.78 1.406zM310.086 773.748c51.416-36.358 119.339-94.277 159.155-138.499 38.101-44.494 120.783-134.292 141.955-168.466 3.857-6.209 6.072-12.395 6.75-18.629-41.734 36.131-113.335 89.492-155.622 120.905-0.005 0.002-0.007 0.007-0.012 0.010-0.025 8.52-10.966 32.348-87.704 121.795-32.752 38.173-66.457 75.391-83.188 93.696 5.556-2.429 11.665-6.004 18.666-10.812zM18.624 459.091c-13.619 35.049-13.759 48.995 11.173 80.375 1.551 1.905 98.558 122.199 161.646 182.367 10.136 9.667 18.933 18.464 26.698 26.227 26.164 26.164 40.275 40.253 56.986 40.34 8.012-8.687 49.581-53.934 90.245-101.224 82.42-95.853 87.829-114.586 87.395-118.112-5.675-10.867-32.879-38.704-64.357-70.913-27.416-28.054-58.488-59.851-88.080-92.97-59.049-66.113-156.477-190.526-176.071-215.648-6.912 8.657-13.31 22.347-22.526 48.987-18.14 52.386-70.17 187.309-83.108 220.57zM352.688 134.178c-28.907-18.661-52.421-38.31-89.173-18.557-32.301 17.153-89.098 41.886-117.449 57.253-6.002 3.436-10.872 6.214-15.2 9.837 38.692 51.194 188.229 237.935 264.274 308.865 30.832 31.548 54.057 55.331 63.233 68.708 45.828-34.006 123.054-91.903 159.058-124.291-4.092-21.666-24.651-44.718-55.817-72.965-6.024-5.458-13.809-13.435-22.863-23.219-45.008-47.561-154.737-183.215-186.062-205.631v0zM501.719 170.982c-5.276 9.962-14.44 39.482-25.843 83.183 21.038 24.379 42.205 48.84 59.899 68.442-2.621-31.169-10.565-128.056-12.455-178.744-8.129 5.134-14.392 13.505-21.601 27.119v0zM1006.483 304.659c-15.096-69.813-2.613-118.172-85.405-124.284-75.952-9.386-309.284-44.793-348.387-44.429-17.988-0.002-30.436 0.588-40.1 3.673 1.646 51.049 10.515 158.003 12.844 185.526 7.543-2.628 16.826-3.725 27.987-3.725 19.547 0 44.763 3.314 76.092 7.431 95.252 12.403 322.168 31.705 365.128 37.517 0.561-16.061-3.566-37.714-8.159-61.709v0z" />
|
||||||
|
<glyph unicode="" glyph-name="molecule" d="M568.662 567.666l181.009-103.915c2.325-1.185 4.651-4.654 4.651-7.027v-209.009c0-1.188 0-2.328 1.185-2.328l156.46-88.768h2.325l45.527 26.868c2.325 1.188 5.839 1.188 8.164 0l51.364-30.382c2.325-1.185 4.651-4.651 4.651-7.024v-59.576c0-3.513-1.185-5.839-4.651-7.024l-51.364-30.382c-2.325-1.185-5.839-1.185-8.164 0l-51.364 30.382c-2.328 1.185-4.654 4.651-4.654 7.024v53.737c0 1.188 0 2.328-1.185 2.328l-156.46 86.441h-2.325l-181.009-103.915c-2.325-1.185-5.836-1.185-9.349 0l-181.009 103.915h-3.51l-246.381-142.458c-1.188 0-1.188-1.188-1.188-2.328v-53.737c0-3.513-2.325-5.839-4.651-7.024l-52.552-30.334c-2.325-1.188-5.839-1.188-8.164 0l-51.364 30.379c-2.325 1.188-4.654 4.654-4.654 7.027v59.576c0 3.51 1.188 5.839 4.654 7.024l51.364 30.382c2.325 1.185 5.839 1.185 8.164 0l45.527-26.868h3.51l247.56 143.556c1.188 0 1.188 1.185 1.188 2.325v202.038c0 1.185 0 2.325-1.188 2.325l-134.294 73.581h-2.328l-46.709-26.868c-2.328-1.185-5.839-1.185-8.167 0l-51.364 30.382c-2.325 1.185-4.651 4.654-4.651 7.024v59.576c0 3.513 1.185 5.839 4.651 7.027l51.364 30.382c2.328 1.185 5.839 1.185 8.167 0l51.364-30.382c2.325-1.185 4.651-4.654 4.651-7.027v-52.552c0-1.185 0-2.325 1.188-2.325l134.294-73.581h2.325l175.171 101.586c1.185 0 1.185 1.185 1.185 2.325v199.669c0 1.185 0 2.325-1.185 2.325l-44.34 26.868c-2.325 1.185-4.654 4.654-4.654 7.024v59.576c0 3.513 2.328 5.839 4.654 8.164l51.364 29.197c2.325 1.185 5.839 1.185 9.349 0l51.365-30.382c2.325-1.185 4.651-4.654 4.651-7.024v-59.576c0-3.513-2.325-5.839-4.651-8.167l-45.527-25.683c-1.185 0-1.185-1.185-1.185-2.325v-199.716c-1.14-1.14 0-1.14 1.185-2.325h0.008zM414.526 329.464v100.446c0 1.188 0 2.325 1.185 2.325l81.744 46.713c10.492 5.839 1.185 21.029-9.349 15.191l-86.399-50.225c-3.51-1.185-4.651-4.651-4.651-8.164v-107.425c-0.051-10.54 17.468-10.54 17.468 1.14v0zM18.623 86.548c-1.185 0-1.185-1.185-1.185-2.325v-46.713c0-1.188 0-2.325 1.185-2.325l39.686-23.358h2.328l39.686 23.358c1.188 0 1.188 1.185 1.188 2.325v46.713c0 1.185 0 2.325-1.188 2.325l-39.686 23.355h-2.328l-39.686-23.355zM130.749 592.214c-1.185 0-1.185-1.185-1.185-2.328v-46.713c0-1.185 0-2.325 1.185-2.325l39.686-23.355h2.325l39.688 23.355c1.185 0 1.185 1.185 1.185 2.325v46.713c0 1.185 0 2.328-1.185 2.328l-39.688 23.355h-2.325l-39.686-23.355zM923.655 140.29c-1.188 0-1.188-1.188-1.188-2.325v-46.713c0-1.188 0-2.325 1.188-2.325l39.686-23.358h2.325l39.686 23.358c1.188 0 1.188 1.185 1.188 2.325v46.713c0 1.185 0 2.325-1.188 2.325l-39.686 23.355h-2.325l-39.686-23.355zM559.315 885.303h-3.51l-39.688-23.355c-1.185 0-1.185-1.185-1.185-2.325v-46.713c0-1.185 0-2.325 1.185-2.325l39.688-23.355h3.51l39.688 23.355c1.185 0 1.185 1.185 1.185 2.325v46.713c0 1.185 0 2.325-1.185 2.325l-39.688 23.355zM559.315 552.484h-2.325l-175.167-101.586c-1.188 0-1.188-1.185-1.188-2.328v-202.038c0-1.188 0-2.328 1.188-2.328l175.167-101.586h3.513l175.167 101.586c1.188 0 1.188 1.188 1.188 2.328l-0.048 203.218c0 1.185 0 2.328-1.185 2.328l-176.311 100.406z" />
|
||||||
|
<glyph unicode="" glyph-name="marijuana" d="M960.897 196.35l26.114 12.546-26.114 12.546c-4.734 2.303-114.817 54.529-253.825 56.32 6.271 6.271 12.417 12.674 18.303 19.074 133.632 143.998 193.535 309.887 195.97 316.927l10.24 28.8-26.752-11.009c-6.529-2.689-160.387-67.072-294.145-211.202-0.514-0.511-1.023-1.151-1.537-1.666 0.643 16.003 1.023 32.003 1.023 47.617 0 132.739-23.423 252.033-43.137 328.833-21.374 83.2-42.366 134.529-43.263 136.579l-11.774 28.286-11.649-28.16c-0.894-2.174-22.014-53.374-43.263-136.575-19.711-76.672-43.137-196.099-43.137-328.833 0-15.617 0.383-31.614 1.023-47.617-0.514 0.514-1.023 1.154-1.537 1.666-133.886 144-287.745 208.512-294.273 211.202l-26.752 11.009 10.24-28.8c2.434-7.040 62.334-172.798 195.97-316.931 5.889-6.397 12.031-12.671 18.303-19.071-139.006-1.791-249.219-54.017-253.825-56.32l-26.112-12.546 26.112-12.546c4.737-2.306 118.787-56.32 261.25-56.32 25.986 0 52.609 1.791 79.615 5.506-63.997-71.554-92.797-151.427-93.952-154.88l-10.24-28.8 26.752 11.009c3.329 1.409 81.535 34.049 149.246 107.137 9.471 10.24 18.689 21.12 27.52 32.64-0.254-59.009-1.534-118.017-4.097-176.769 8.577 0 17.026 0 25.603 0-2.56 58.752-3.843 117.76-3.971 176.641 8.834-11.52 18.049-22.529 27.523-32.64 67.837-73.089 146.046-105.857 149.246-107.137l26.752-11.009-10.24 28.8c-1.28 3.454-29.952 83.329-93.956 154.88 27.009-3.586 53.632-5.506 79.617-5.506 142.472 0 256.393 54.017 261.127 56.32zM572.929 155.39c8.317-7.551 16.254-15.489 23.551-23.423 38.397-41.346 64.383-87.426 79.103-117.632-28.157 15.871-70.78 43.774-109.183 85.12-15.489 16.766-30.334 35.583-44.032 56.192l-10.369 15.617-10.369-15.617c-13.697-20.48-28.414-39.426-44.032-56.192-38.4-41.346-81.152-69.377-109.312-85.249 14.589 30.209 40.574 76.289 79.103 117.76 7.426 7.934 15.363 15.874 23.554 23.423l36.48 33.409-47.103-9.346c-39.554-7.809-78.72-11.906-116.097-11.906-94.337 0-177.41 25.474-220.162 41.346 42.752 16.003 125.695 41.346 220.162 41.346 8.577 0 17.28-0.257 25.986-0.64l36.863-1.794-27.906 25.986c-14.974 13.951-29.186 28.157-42.497 42.494-95.743 103.169-153.472 220.547-177.282 275.588 51.074-25.726 160-87.935 255.747-191.103 8.32-8.96 16.769-18.431 25.086-28.286l25.089-29.573-2.814 39.941c-2.049 28.157-3.071 56.189-3.071 83.455 0 190.722 49.409 355.716 72.575 422.531 23.166-66.814 72.575-231.682 72.575-422.531 0-27.266-1.023-55.297-3.071-83.455l-2.814-39.941 25.086 29.573c8.32 9.854 16.769 19.329 25.089 28.286 95.746 103.169 204.672 165.379 255.747 191.103-23.937-55.040-81.666-172.415-177.282-275.588-13.314-14.337-27.523-28.543-42.497-42.494l-27.906-25.986 36.863 1.794c8.706 0.383 17.537 0.64 25.986 0.64 94.337 0 177.41-25.472 220.162-41.346-42.752-16-125.695-41.346-220.162-41.346-37.503 0-76.543 3.971-116.097 11.906l-47.103 9.346 36.354-33.409z" />
|
||||||
<glyph unicode="" glyph-name="pencil" d="M864 960c88.364 0 160-71.634 160-160 0-36.020-11.91-69.258-32-96l-64-64-224 224 64 64c26.742 20.090 59.978 32 96 32zM64 224l-64-288 288 64 592 592-224 224-592-592zM715.578 596.422l-448-448-55.156 55.156 448 448 55.156-55.156z" />
|
<glyph unicode="" glyph-name="pencil" d="M864 960c88.364 0 160-71.634 160-160 0-36.020-11.91-69.258-32-96l-64-64-224 224 64 64c26.742 20.090 59.978 32 96 32zM64 224l-64-288 288 64 592 592-224 224-592-592zM715.578 596.422l-448-448-55.156 55.156 448 448 55.156-55.156z" />
|
||||||
|
<glyph unicode="" glyph-name="cannabis" d="M488.786 290.304c-55.296 137.556-167.252 315.392-391.852 430.764 75.098-242.348 231.77-384 358.060-462.164-98.982 50.176-245.076 93.182-423.596 52.564 163.84-105.472 325.632-109.568 436.908-92.506-67.244-16.382-153.26-54.95-221.524-144.722 130.050 16.384 210.604 88.064 255.662 143.020v-281.26h19.116v281.26c45.056-54.956 125.612-126.636 255.662-143.020-68.268 89.772-154.284 128.34-221.524 144.722 111.276-17.068 273.068-12.972 436.908 92.506-178.516 40.614-324.61-2.388-423.596-52.564 126.292 78.162 282.964 219.82 358.060 462.164-224.596-115.372-336.558-293.204-391.852-430.764 54.272 160.084 90.11 400.384-23.212 669.696-113.329-269.312-77.489-509.612-23.217-669.696v0z" />
|
||||||
<glyph unicode="" glyph-name="image" d="M959.884 832c0.040-0.034 0.082-0.076 0.116-0.116v-767.77c-0.034-0.040-0.076-0.082-0.116-0.116h-895.77c-0.040 0.034-0.082 0.076-0.114 0.116v767.772c0.034 0.040 0.076 0.082 0.114 0.114h895.77zM960 896h-896c-35.2 0-64-28.8-64-64v-768c0-35.2 28.8-64 64-64h896c35.2 0 64 28.8 64 64v768c0 35.2-28.8 64-64 64v0zM832 672c0-53.020-42.98-96-96-96s-96 42.98-96 96 42.98 96 96 96 96-42.98 96-96zM896 128h-768v128l224 384 256-320h64l224 192z" />
|
<glyph unicode="" glyph-name="image" d="M959.884 832c0.040-0.034 0.082-0.076 0.116-0.116v-767.77c-0.034-0.040-0.076-0.082-0.116-0.116h-895.77c-0.040 0.034-0.082 0.076-0.114 0.116v767.772c0.034 0.040 0.076 0.082 0.114 0.114h895.77zM960 896h-896c-35.2 0-64-28.8-64-64v-768c0-35.2 28.8-64 64-64h896c35.2 0 64 28.8 64 64v768c0 35.2-28.8 64-64 64v0zM832 672c0-53.020-42.98-96-96-96s-96 42.98-96 96 42.98 96 96 96 96-42.98 96-96zM896 128h-768v128l224 384 256-320h64l224 192z" />
|
||||||
<glyph unicode="" glyph-name="bullhorn" d="M1024 530.744c0 200.926-58.792 363.938-131.482 365.226 0.292 0.006 0.578 0.030 0.872 0.030h-82.942c0 0-194.8-146.336-475.23-203.754-8.56-45.292-14.030-99.274-14.030-161.502s5.466-116.208 14.030-161.5c280.428-57.418 475.23-203.756 475.23-203.756h82.942c-0.292 0-0.578 0.024-0.872 0.032 72.696 1.288 131.482 164.298 131.482 365.224zM864.824 220.748c-9.382 0-19.532 9.742-24.746 15.548-12.63 14.064-24.792 35.96-35.188 63.328-23.256 61.232-36.066 143.31-36.066 231.124 0 87.81 12.81 169.89 36.066 231.122 10.394 27.368 22.562 49.266 35.188 63.328 5.214 5.812 15.364 15.552 24.746 15.552 9.38 0 19.536-9.744 24.744-15.552 12.634-14.064 24.796-35.958 35.188-63.328 23.258-61.23 36.068-143.312 36.068-231.122 0-87.804-12.81-169.888-36.068-231.124-10.39-27.368-22.562-49.264-35.188-63.328-5.208-5.806-15.36-15.548-24.744-15.548zM251.812 530.744c0 51.95 3.81 102.43 11.052 149.094-47.372-6.554-88.942-10.324-140.34-10.324-67.058 0-67.058 0-67.058 0l-55.466-94.686v-88.17l55.46-94.686c0 0 0 0 67.060 0 51.398 0 92.968-3.774 140.34-10.324-7.236 46.664-11.048 97.146-11.048 149.096zM368.15 317.828l-127.998 24.51 81.842-321.544c4.236-16.634 20.744-25.038 36.686-18.654l118.556 47.452c15.944 6.376 22.328 23.964 14.196 39.084l-123.282 229.152zM864.824 411.27c-3.618 0-7.528 3.754-9.538 5.992-4.87 5.42-9.556 13.86-13.562 24.408-8.962 23.6-13.9 55.234-13.9 89.078s4.938 65.478 13.9 89.078c4.006 10.548 8.696 18.988 13.562 24.408 2.010 2.24 5.92 5.994 9.538 5.994 3.616 0 7.53-3.756 9.538-5.994 4.87-5.42 9.556-13.858 13.56-24.408 8.964-23.598 13.902-55.234 13.902-89.078 0-33.842-4.938-65.478-13.902-89.078-4.004-10.548-8.696-18.988-13.56-24.408-2.008-2.238-5.92-5.992-9.538-5.992z" />
|
<glyph unicode="" glyph-name="bullhorn" d="M1024 530.744c0 200.926-58.792 363.938-131.482 365.226 0.292 0.006 0.578 0.030 0.872 0.030h-82.942c0 0-194.8-146.336-475.23-203.754-8.56-45.292-14.030-99.274-14.030-161.502s5.466-116.208 14.030-161.5c280.428-57.418 475.23-203.756 475.23-203.756h82.942c-0.292 0-0.578 0.024-0.872 0.032 72.696 1.288 131.482 164.298 131.482 365.224zM864.824 220.748c-9.382 0-19.532 9.742-24.746 15.548-12.63 14.064-24.792 35.96-35.188 63.328-23.256 61.232-36.066 143.31-36.066 231.124 0 87.81 12.81 169.89 36.066 231.122 10.394 27.368 22.562 49.266 35.188 63.328 5.214 5.812 15.364 15.552 24.746 15.552 9.38 0 19.536-9.744 24.744-15.552 12.634-14.064 24.796-35.958 35.188-63.328 23.258-61.23 36.068-143.312 36.068-231.122 0-87.804-12.81-169.888-36.068-231.124-10.39-27.368-22.562-49.264-35.188-63.328-5.208-5.806-15.36-15.548-24.744-15.548zM251.812 530.744c0 51.95 3.81 102.43 11.052 149.094-47.372-6.554-88.942-10.324-140.34-10.324-67.058 0-67.058 0-67.058 0l-55.466-94.686v-88.17l55.46-94.686c0 0 0 0 67.060 0 51.398 0 92.968-3.774 140.34-10.324-7.236 46.664-11.048 97.146-11.048 149.096zM368.15 317.828l-127.998 24.51 81.842-321.544c4.236-16.634 20.744-25.038 36.686-18.654l118.556 47.452c15.944 6.376 22.328 23.964 14.196 39.084l-123.282 229.152zM864.824 411.27c-3.618 0-7.528 3.754-9.538 5.992-4.87 5.42-9.556 13.86-13.562 24.408-8.962 23.6-13.9 55.234-13.9 89.078s4.938 65.478 13.9 89.078c4.006 10.548 8.696 18.988 13.562 24.408 2.010 2.24 5.92 5.994 9.538 5.994 3.616 0 7.53-3.756 9.538-5.994 4.87-5.42 9.556-13.858 13.56-24.408 8.964-23.598 13.902-55.234 13.902-89.078 0-33.842-4.938-65.478-13.902-89.078-4.004-10.548-8.696-18.988-13.56-24.408-2.008-2.238-5.92-5.992-9.538-5.992z" />
|
||||||
<glyph unicode="" glyph-name="books" horiz-adv-x="1152" d="M224 832h-192c-17.6 0-32-14.4-32-32v-704c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v704c0 17.6-14.4 32-32 32zM192 640h-128v64h128v-64zM544 832h-192c-17.6 0-32-14.4-32-32v-704c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v704c0 17.6-14.4 32-32 32zM512 640h-128v64h128v-64zM765.088 782.52l-171.464-86.394c-15.716-7.918-22.096-27.258-14.178-42.976l287.978-571.548c7.918-15.718 27.258-22.098 42.976-14.178l171.464 86.392c15.716 7.92 22.096 27.26 14.178 42.974l-287.978 571.55c-7.92 15.718-27.26 22.1-42.976 14.18z" />
|
<glyph unicode="" glyph-name="books" horiz-adv-x="1152" d="M224 832h-192c-17.6 0-32-14.4-32-32v-704c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v704c0 17.6-14.4 32-32 32zM192 640h-128v64h128v-64zM544 832h-192c-17.6 0-32-14.4-32-32v-704c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v704c0 17.6-14.4 32-32 32zM512 640h-128v64h128v-64zM765.088 782.52l-171.464-86.394c-15.716-7.918-22.096-27.258-14.178-42.976l287.978-571.548c7.918-15.718 27.258-22.098 42.976-14.178l171.464 86.392c15.716 7.92 22.096 27.26 14.178 42.974l-287.978 571.55c-7.92 15.718-27.26 22.1-42.976 14.18z" />
|
||||||
@@ -20,16 +25,37 @@
|
|||||||
<glyph unicode="" glyph-name="envelop" d="M928 832h-832c-52.8 0-96-43.2-96-96v-640c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v640c0 52.8-43.2 96-96 96zM398.74 409.628l-270.74-210.892v501.642l270.74-290.75zM176.38 704h671.24l-335.62-252-335.62 252zM409.288 398.302l102.712-110.302 102.71 110.302 210.554-270.302h-626.528l210.552 270.302zM625.26 409.628l270.74 290.75v-501.642l-270.74 210.892z" />
|
<glyph unicode="" glyph-name="envelop" d="M928 832h-832c-52.8 0-96-43.2-96-96v-640c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v640c0 52.8-43.2 96-96 96zM398.74 409.628l-270.74-210.892v501.642l270.74-290.75zM176.38 704h671.24l-335.62-252-335.62 252zM409.288 398.302l102.712-110.302 102.71 110.302 210.554-270.302h-626.528l210.552 270.302zM625.26 409.628l270.74 290.75v-501.642l-270.74 210.892z" />
|
||||||
<glyph unicode="" glyph-name="location" d="M512 960c-176.732 0-320-143.268-320-320 0-320 320-704 320-704s320 384 320 704c0 176.732-143.27 320-320 320zM512 448c-106.040 0-192 85.96-192 192s85.96 192 192 192 192-85.96 192-192-85.96-192-192-192z" />
|
<glyph unicode="" glyph-name="location" d="M512 960c-176.732 0-320-143.268-320-320 0-320 320-704 320-704s320 384 320 704c0 176.732-143.27 320-320 320zM512 448c-106.040 0-192 85.96-192 192s85.96 192 192 192 192-85.96 192-192-85.96-192-192-192z" />
|
||||||
<glyph unicode="" glyph-name="calendar" d="M320 576h128v-128h-128zM512 576h128v-128h-128zM704 576h128v-128h-128zM128 192h128v-128h-128zM320 192h128v-128h-128zM512 192h128v-128h-128zM320 384h128v-128h-128zM512 384h128v-128h-128zM704 384h128v-128h-128zM128 384h128v-128h-128zM832 960v-64h-128v64h-448v-64h-128v64h-128v-1024h960v1024h-128zM896 0h-832v704h832v-704z" />
|
<glyph unicode="" glyph-name="calendar" d="M320 576h128v-128h-128zM512 576h128v-128h-128zM704 576h128v-128h-128zM128 192h128v-128h-128zM320 192h128v-128h-128zM512 192h128v-128h-128zM320 384h128v-128h-128zM512 384h128v-128h-128zM704 384h128v-128h-128zM128 384h128v-128h-128zM832 960v-64h-128v64h-448v-64h-128v64h-128v-1024h960v1024h-128zM896 0h-832v704h832v-704z" />
|
||||||
|
<glyph unicode="" glyph-name="floppy-disk" d="M896 960h-896v-1024h1024v896l-128 128zM512 832h128v-256h-128v256zM896 64h-768v768h64v-320h576v320h74.978l53.022-53.018v-714.982z" />
|
||||||
<glyph unicode="" glyph-name="user" d="M576 253.388v52.78c70.498 39.728 128 138.772 128 237.832 0 159.058 0 288-192 288s-192-128.942-192-288c0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h896c0 128.968-166.898 235.64-384 253.388z" />
|
<glyph unicode="" glyph-name="user" d="M576 253.388v52.78c70.498 39.728 128 138.772 128 237.832 0 159.058 0 288-192 288s-192-128.942-192-288c0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h896c0 128.968-166.898 235.64-384 253.388z" />
|
||||||
<glyph unicode="" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" />
|
<glyph unicode="" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" />
|
||||||
|
<glyph unicode="" glyph-name="lock" d="M592 512h-16v192c0 105.87-86.13 192-192 192h-128c-105.87 0-192-86.13-192-192v-192h-16c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h544c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48zM192 704c0 35.29 28.71 64 64 64h128c35.29 0 64-28.71 64-64v-192h-256v192z" />
|
||||||
|
<glyph unicode="" glyph-name="unlocked" d="M768 896c105.87 0 192-86.13 192-192v-192h-128v192c0 35.29-28.71 64-64 64h-128c-35.29 0-64-28.71-64-64v-192h16c26.4 0 48-21.6 48-48v-480c0-26.4-21.6-48-48-48h-544c-26.4 0-48 21.6-48 48v480c0 26.4 21.6 48 48 48h400v192c0 105.87 86.13 192 192 192h128z" />
|
||||||
|
<glyph unicode="" glyph-name="equalizer" d="M448 832v16c0 26.4-21.6 48-48 48h-160c-26.4 0-48-21.6-48-48v-16h-192v-128h192v-16c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v16h576v128h-576zM256 704v128h128v-128h-128zM832 528c0 26.4-21.6 48-48 48h-160c-26.4 0-48-21.6-48-48v-16h-576v-128h576v-16c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v16h192v128h-192v16zM640 384v128h128v-128h-128zM448 208c0 26.4-21.6 48-48 48h-160c-26.4 0-48-21.6-48-48v-16h-192v-128h192v-16c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v16h576v128h-576v16zM256 64v128h128v-128h-128z" />
|
||||||
<glyph unicode="" glyph-name="cog" d="M933.79 349.75c-53.726 93.054-21.416 212.304 72.152 266.488l-100.626 174.292c-28.75-16.854-62.176-26.518-97.846-26.518-107.536 0-194.708 87.746-194.708 195.99h-201.258c0.266-33.41-8.074-67.282-25.958-98.252-53.724-93.056-173.156-124.702-266.862-70.758l-100.624-174.292c28.97-16.472 54.050-40.588 71.886-71.478 53.638-92.908 21.512-211.92-71.708-266.224l100.626-174.292c28.65 16.696 61.916 26.254 97.4 26.254 107.196 0 194.144-87.192 194.7-194.958h201.254c-0.086 33.074 8.272 66.57 25.966 97.218 53.636 92.906 172.776 124.594 266.414 71.012l100.626 174.29c-28.78 16.466-53.692 40.498-71.434 71.228zM512 240.668c-114.508 0-207.336 92.824-207.336 207.334 0 114.508 92.826 207.334 207.336 207.334 114.508 0 207.332-92.826 207.332-207.334-0.002-114.51-92.824-207.334-207.332-207.334z" />
|
<glyph unicode="" glyph-name="cog" d="M933.79 349.75c-53.726 93.054-21.416 212.304 72.152 266.488l-100.626 174.292c-28.75-16.854-62.176-26.518-97.846-26.518-107.536 0-194.708 87.746-194.708 195.99h-201.258c0.266-33.41-8.074-67.282-25.958-98.252-53.724-93.056-173.156-124.702-266.862-70.758l-100.624-174.292c28.97-16.472 54.050-40.588 71.886-71.478 53.638-92.908 21.512-211.92-71.708-266.224l100.626-174.292c28.65 16.696 61.916 26.254 97.4 26.254 107.196 0 194.144-87.192 194.7-194.958h201.254c-0.086 33.074 8.272 66.57 25.966 97.218 53.636 92.906 172.776 124.594 266.414 71.012l100.626 174.29c-28.78 16.466-53.692 40.498-71.434 71.228zM512 240.668c-114.508 0-207.336 92.824-207.336 207.334 0 114.508 92.826 207.334 207.336 207.334 114.508 0 207.332-92.826 207.332-207.334-0.002-114.51-92.824-207.334-207.332-207.334z" />
|
||||||
|
<glyph unicode="" glyph-name="magic-wand" d="M256 768l-128 128h-64v-64l128-128zM320 960h64v-128h-64zM576 640h128v-64h-128zM640 832v64h-64l-128-128 64-64zM0 640h128v-64h-128zM320 384h64v-128h-64zM64 384v-64h64l128 128-64 64zM1010 78l-636.118 636.118c-18.668 18.668-49.214 18.668-67.882 0l-60.118-60.118c-18.668-18.668-18.668-49.214 0-67.882l636.118-636.118c18.668-18.668 49.214-18.668 67.882 0l60.118 60.118c18.668 18.668 18.668 49.214 0 67.882zM480 416l-192 192 64 64 192-192-64-64z" />
|
||||||
|
<glyph unicode="" glyph-name="trophy" d="M832 768v128h-640v-128h-192v-128c0-106.038 85.958-192 192-192 20.076 0 39.43 3.086 57.62 8.802 46.174-66.008 116.608-113.796 198.38-130.396v-198.406h-64c-70.694 0-128-57.306-128-128h512c0 70.694-57.306 128-128 128h-64v198.406c81.772 16.6 152.206 64.386 198.38 130.396 18.19-5.716 37.544-8.802 57.62-8.802 106.042 0 192 85.962 192 192v128h-192zM192 524c-63.962 0-116 52.038-116 116v64h116v-64c0-40.186 7.43-78.632 20.954-114.068-6.802-1.246-13.798-1.932-20.954-1.932zM948 640c0-63.962-52.038-116-116-116-7.156 0-14.152 0.686-20.954 1.932 13.524 35.436 20.954 73.882 20.954 114.068v64h116v-64z" />
|
||||||
|
<glyph unicode="" glyph-name="gift" d="M771.516 640c18.126 12.88 35.512 27.216 51.444 43.148 33.402 33.402 55.746 74.5 62.912 115.722 7.858 45.186-3.672 87.14-31.63 115.1-22.3 22.298-52.51 34.086-87.364 34.086-49.632 0-101.922-23.824-143.46-65.362-66.476-66.476-105.226-158.238-126.076-223.722-15.44 65.802-46.206 154.644-106.018 214.458-32.094 32.092-73.114 48.57-111.846 48.57-31.654 0-61.78-11.004-84.26-33.486-49.986-49.988-43.232-137.786 15.086-196.104 20.792-20.792 45.098-38.062 70.72-52.412h-217.024v-256h64v-448h768v448.002h64v256h-188.484zM674.326 831.782c27.724 27.724 62.322 44.274 92.55 44.274 10.7 0 25.708-2.254 36.45-12.998 26.030-26.028 11.412-86.308-31.28-128.998-43.946-43.946-103.060-74.168-154.432-94.060h-50.672c18.568 57.548 52.058 136.456 107.384 191.782zM233.934 799.11c-0.702 9.12-0.050 26.248 12.196 38.494 10.244 10.244 23.788 12.396 33.348 12.396v0c21.258 0 43.468-10.016 60.932-27.48 33.872-33.872 61.766-87.772 80.668-155.876 0.51-1.84 1.008-3.67 1.496-5.486-1.816 0.486-3.646 0.984-5.486 1.496-68.104 18.904-122.002 46.798-155.874 80.67-15.828 15.826-25.77 36.16-27.28 55.786zM448 0h-256v416h256v-416zM448 448h-320v128h320v-128zM832 0h-256v416h256v-416zM896 448h-320v128h320v-128z" />
|
||||||
|
<glyph unicode="" glyph-name="rocket" d="M704 896l-320-320h-192l-192-256c0 0 203.416 56.652 322.066 30.084l-322.066-414.084 421.902 328.144c58.838-134.654-37.902-328.144-37.902-328.144l256 192v192l320 320 64 320-320-64z" />
|
||||||
<glyph unicode="" glyph-name="fire" d="M321.008-64c-68.246 142.008-31.902 223.378 20.55 300.044 57.44 83.956 72.244 167.066 72.244 167.066s45.154-58.7 27.092-150.508c79.772 88.8 94.824 230.28 82.782 284.464 180.314-126.012 257.376-398.856 153.522-601.066 552.372 312.532 137.398 780.172 65.154 832.85 24.082-52.676 28.648-141.85-20-185.126-82.352 312.276-285.972 376.276-285.972 376.276 24.082-161.044-87.296-337.144-194.696-468.73-3.774 64.216-7.782 108.528-41.55 169.98-7.58-116.656-96.732-211.748-120.874-328.628-32.702-158.286 24.496-274.18 241.748-396.622z" />
|
<glyph unicode="" glyph-name="fire" d="M321.008-64c-68.246 142.008-31.902 223.378 20.55 300.044 57.44 83.956 72.244 167.066 72.244 167.066s45.154-58.7 27.092-150.508c79.772 88.8 94.824 230.28 82.782 284.464 180.314-126.012 257.376-398.856 153.522-601.066 552.372 312.532 137.398 780.172 65.154 832.85 24.082-52.676 28.648-141.85-20-185.126-82.352 312.276-285.972 376.276-285.972 376.276 24.082-161.044-87.296-337.144-194.696-468.73-3.774 64.216-7.782 108.528-41.55 169.98-7.58-116.656-96.732-211.748-120.874-328.628-32.702-158.286 24.496-274.18 241.748-396.622z" />
|
||||||
<glyph unicode="" glyph-name="lab" d="M956.29 155.518l-316.29 527.024v213.458h32c17.6 0 32 14.4 32 32s-14.4 32-32 32h-320c-17.6 0-32-14.4-32-32s14.4-32 32-32h32v-213.458l-316.288-527.024c-72.442-120.734-16.512-219.518 124.288-219.518h640c140.8 0 196.73 98.784 124.29 219.518zM241.038 320l206.962 344.938v231.062h128v-231.062l206.964-344.938h-541.926z" />
|
<glyph unicode="" glyph-name="lab" d="M956.29 155.518l-316.29 527.024v213.458h32c17.6 0 32 14.4 32 32s-14.4 32-32 32h-320c-17.6 0-32-14.4-32-32s14.4-32 32-32h32v-213.458l-316.288-527.024c-72.442-120.734-16.512-219.518 124.288-219.518h640c140.8 0 196.73 98.784 124.29 219.518zM241.038 320l206.962 344.938v231.062h128v-231.062l206.964-344.938h-541.926z" />
|
||||||
<glyph unicode="" glyph-name="bin" d="M128 640v-640c0-35.2 28.8-64 64-64h576c35.2 0 64 28.8 64 64v640h-704zM320 64h-64v448h64v-448zM448 64h-64v448h64v-448zM576 64h-64v448h64v-448zM704 64h-64v448h64v-448zM848 832h-208v80c0 26.4-21.6 48-48 48h-224c-26.4 0-48-21.6-48-48v-80h-208c-26.4 0-48-21.6-48-48v-80h832v80c0 26.4-21.6 48-48 48zM576 832h-192v63.198h192v-63.198z" />
|
<glyph unicode="" glyph-name="bin" d="M128 640v-640c0-35.2 28.8-64 64-64h576c35.2 0 64 28.8 64 64v640h-704zM320 64h-64v448h64v-448zM448 64h-64v448h64v-448zM576 64h-64v448h64v-448zM704 64h-64v448h64v-448zM848 832h-208v80c0 26.4-21.6 48-48 48h-224c-26.4 0-48-21.6-48-48v-80h-208c-26.4 0-48-21.6-48-48v-80h832v80c0 26.4-21.6 48-48 48zM576 832h-192v63.198h192v-63.198z" />
|
||||||
<glyph unicode="" glyph-name="briefcase" d="M960 704h-256v64c0 35.2-28.8 64-64 64h-256c-35.204 0-64-28.8-64-64v-64h-256c-35.2 0-64-28.8-64-64v-576c0-35.202 28.796-64 64-64h896c35.2 0 64 28.798 64 64v576c0 35.2-28.8 64-64 64zM384 767.884c0.034 0.040 0.074 0.082 0.114 0.116h255.772c0.042-0.034 0.082-0.076 0.118-0.116v-63.884h-256.004v63.884zM960 448h-128v-96c0-17.602-14.4-32-32-32h-64c-17.604 0-32 14.398-32 32v96h-384v-96c0-17.602-14.4-32-32-32h-64c-17.602 0-32 14.398-32 32v96h-128v64h896v-64z" />
|
<glyph unicode="" glyph-name="briefcase" d="M960 704h-256v64c0 35.2-28.8 64-64 64h-256c-35.204 0-64-28.8-64-64v-64h-256c-35.2 0-64-28.8-64-64v-576c0-35.202 28.796-64 64-64h896c35.2 0 64 28.798 64 64v576c0 35.2-28.8 64-64 64zM384 767.884c0.034 0.040 0.074 0.082 0.114 0.116h255.772c0.042-0.034 0.082-0.076 0.118-0.116v-63.884h-256.004v63.884zM960 448h-128v-96c0-17.602-14.4-32-32-32h-64c-17.604 0-32 14.398-32 32v96h-384v-96c0-17.602-14.4-32-32-32h-64c-17.602 0-32 14.398-32 32v96h-128v64h896v-64z" />
|
||||||
<glyph unicode="" glyph-name="truck" d="M1024 384l-128 256h-192v128c0 35.2-28.8 64-64 64h-576c-35.2 0-64-28.8-64-64v-512l64-64h81.166c-10.898-18.832-17.166-40.678-17.166-64 0-70.692 57.308-128 128-128s128 57.308 128 128c0 23.322-6.268 45.168-17.166 64h354.334c-10.898-18.832-17.168-40.678-17.168-64 0-70.692 57.308-128 128-128s128 57.308 128 128c0 23.322-6.27 45.168-17.168 64h81.168v192zM704 384v192h132.668l96-192h-228.668z" />
|
<glyph unicode="" glyph-name="truck" d="M1024 384l-128 256h-192v128c0 35.2-28.8 64-64 64h-576c-35.2 0-64-28.8-64-64v-512l64-64h81.166c-10.898-18.832-17.166-40.678-17.166-64 0-70.692 57.308-128 128-128s128 57.308 128 128c0 23.322-6.268 45.168-17.166 64h354.334c-10.898-18.832-17.168-40.678-17.168-64 0-70.692 57.308-128 128-128s128 57.308 128 128c0 23.322-6.27 45.168-17.168 64h81.168v192zM704 384v192h132.668l96-192h-228.668z" />
|
||||||
|
<glyph unicode="" glyph-name="power" d="M384 960l-384-512h384l-256-512 896 640h-512l384 384z" />
|
||||||
|
<glyph unicode="" glyph-name="switch" d="M640 813.412v-135.958c36.206-15.804 69.5-38.408 98.274-67.18 60.442-60.44 93.726-140.8 93.726-226.274s-33.286-165.834-93.726-226.274c-60.44-60.44-140.798-93.726-226.274-93.726s-165.834 33.286-226.274 93.726c-60.44 60.44-93.726 140.8-93.726 226.274s33.286 165.834 93.726 226.274c28.774 28.774 62.068 51.378 98.274 67.182v135.956c-185.048-55.080-320-226.472-320-429.412 0-247.424 200.578-448 448-448 247.424 0 448 200.576 448 448 0 202.94-134.95 374.332-320 429.412zM448 960h128v-512h-128z" />
|
||||||
<glyph unicode="" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
|
<glyph unicode="" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
|
||||||
<glyph unicode="" glyph-name="link" d="M440.236 324.234c-13.31 0-26.616 5.076-36.77 15.23-95.134 95.136-95.134 249.934 0 345.070l192 192c46.088 46.086 107.36 71.466 172.534 71.466s126.448-25.38 172.536-71.464c95.132-95.136 95.132-249.934 0-345.070l-87.766-87.766c-20.308-20.308-53.23-20.308-73.54 0-20.306 20.306-20.306 53.232 0 73.54l87.766 87.766c54.584 54.586 54.584 143.404 0 197.99-26.442 26.442-61.6 41.004-98.996 41.004s-72.552-14.562-98.996-41.006l-192-191.998c-54.586-54.586-54.586-143.406 0-197.992 20.308-20.306 20.306-53.232 0-73.54-10.15-10.152-23.462-15.23-36.768-15.23zM256-52c-65.176 0-126.45 25.38-172.534 71.464-95.134 95.136-95.134 249.934 0 345.070l87.764 87.764c20.308 20.306 53.234 20.306 73.54 0 20.308-20.306 20.308-53.232 0-73.54l-87.764-87.764c-54.586-54.586-54.586-143.406 0-197.992 26.44-26.44 61.598-41.002 98.994-41.002s72.552 14.562 98.998 41.006l192 191.998c54.584 54.586 54.584 143.406 0 197.992-20.308 20.308-20.306 53.232 0 73.54 20.306 20.306 53.232 20.306 73.54-0.002 95.132-95.134 95.132-249.932 0.002-345.068l-192.002-192c-46.090-46.088-107.364-71.466-172.538-71.466z" />
|
<glyph unicode="" glyph-name="link" d="M440.236 324.234c-13.31 0-26.616 5.076-36.77 15.23-95.134 95.136-95.134 249.934 0 345.070l192 192c46.088 46.086 107.36 71.466 172.534 71.466s126.448-25.38 172.536-71.464c95.132-95.136 95.132-249.934 0-345.070l-87.766-87.766c-20.308-20.308-53.23-20.308-73.54 0-20.306 20.306-20.306 53.232 0 73.54l87.766 87.766c54.584 54.586 54.584 143.404 0 197.99-26.442 26.442-61.6 41.004-98.996 41.004s-72.552-14.562-98.996-41.006l-192-191.998c-54.586-54.586-54.586-143.406 0-197.992 20.308-20.306 20.306-53.232 0-73.54-10.15-10.152-23.462-15.23-36.768-15.23zM256-52c-65.176 0-126.45 25.38-172.534 71.464-95.134 95.136-95.134 249.934 0 345.070l87.764 87.764c20.308 20.306 53.234 20.306 73.54 0 20.308-20.306 20.308-53.232 0-73.54l-87.764-87.764c-54.586-54.586-54.586-143.406 0-197.992 26.44-26.44 61.598-41.002 98.994-41.002s72.552 14.562 98.998 41.006l192 191.998c54.584 54.586 54.584 143.406 0 197.992-20.308 20.308-20.306 53.232 0 73.54 20.306 20.306 53.232 20.306 73.54-0.002 95.132-95.134 95.132-249.932 0.002-345.068l-192.002-192c-46.090-46.088-107.364-71.466-172.538-71.466z" />
|
||||||
|
<glyph unicode="" glyph-name="eye" d="M512 768c-223.318 0-416.882-130.042-512-320 95.118-189.958 288.682-320 512-320 223.312 0 416.876 130.042 512 320-95.116 189.958-288.688 320-512 320zM764.45 598.296c60.162-38.374 111.142-89.774 149.434-150.296-38.292-60.522-89.274-111.922-149.436-150.296-75.594-48.218-162.89-73.704-252.448-73.704-89.56 0-176.858 25.486-252.452 73.704-60.158 38.372-111.138 89.772-149.432 150.296 38.292 60.524 89.274 111.924 149.434 150.296 3.918 2.5 7.876 4.922 11.86 7.3-9.96-27.328-15.41-56.822-15.41-87.596 0-141.382 114.616-256 256-256 141.382 0 256 114.618 256 256 0 30.774-5.452 60.268-15.408 87.598 3.978-2.378 7.938-4.802 11.858-7.302v0zM512 544c0-53.020-42.98-96-96-96s-96 42.98-96 96 42.98 96 96 96 96-42.982 96-96z" />
|
||||||
|
<glyph unicode="" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
|
||||||
|
<glyph unicode="" glyph-name="star-full" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />
|
||||||
<glyph unicode="" glyph-name="heart" d="M755.188 896c-107.63 0-200.258-87.554-243.164-179-42.938 91.444-135.578 179-243.216 179-148.382 0-268.808-120.44-268.808-268.832 0-301.846 304.5-380.994 512.022-679.418 196.154 296.576 511.978 387.206 511.978 679.418 0 148.392-120.43 268.832-268.812 268.832z" />
|
<glyph unicode="" glyph-name="heart" d="M755.188 896c-107.63 0-200.258-87.554-243.164-179-42.938 91.444-135.578 179-243.216 179-148.382 0-268.808-120.44-268.808-268.832 0-301.846 304.5-380.994 512.022-679.418 196.154 296.576 511.978 387.206 511.978 679.418 0 148.392-120.43 268.832-268.812 268.832z" />
|
||||||
|
<glyph unicode="" glyph-name="warning" d="M512 867.226l429.102-855.226h-858.206l429.104 855.226zM512 960c-22.070 0-44.14-14.882-60.884-44.648l-437.074-871.112c-33.486-59.532-5-108.24 63.304-108.24h869.308c68.3 0 96.792 48.708 63.3 108.24h0.002l-437.074 871.112c-16.742 29.766-38.812 44.648-60.882 44.648v0zM576 128c0-35.346-28.654-64-64-64s-64 28.654-64 64c0 35.346 28.654 64 64 64s64-28.654 64-64zM512 256c-35.346 0-64 28.654-64 64v192c0 35.346 28.654 64 64 64s64-28.654 64-64v-192c0-35.346-28.654-64-64-64z" />
|
||||||
|
<glyph unicode="" glyph-name="notification" d="M512 864c-111.118 0-215.584-43.272-294.156-121.844s-121.844-183.038-121.844-294.156c0-111.118 43.272-215.584 121.844-294.156s183.038-121.844 294.156-121.844c111.118 0 215.584 43.272 294.156 121.844s121.844 183.038 121.844 294.156c0 111.118-43.272 215.584-121.844 294.156s-183.038 121.844-294.156 121.844zM512 960v0c282.77 0 512-229.23 512-512s-229.23-512-512-512c-282.77 0-512 229.23-512 512s229.23 512 512 512zM448 256h128v-128h-128zM448 768h128v-384h-128z" />
|
||||||
|
<glyph unicode="" glyph-name="question" d="M448 256h128v-128h-128zM704 704c35.346 0 64-28.654 64-64v-192l-192-128h-128v64l192 128v64h-320v128h384zM512 864c-111.118 0-215.584-43.272-294.156-121.844s-121.844-183.038-121.844-294.156c0-111.118 43.272-215.584 121.844-294.156s183.038-121.844 294.156-121.844c111.118 0 215.584 43.272 294.156 121.844s121.844 183.038 121.844 294.156c0 111.118-43.272 215.584-121.844 294.156s-183.038 121.844-294.156 121.844zM512 960v0c282.77 0 512-229.23 512-512s-229.23-512-512-512c-282.77 0-512 229.23-512 512s229.23 512 512 512z" />
|
||||||
|
<glyph unicode="" glyph-name="cancel-circle" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM672 704l-160-160-160 160-96-96 160-160-160-160 96-96 160 160 160-160 96 96-160 160 160 160z" />
|
||||||
|
<glyph unicode="" glyph-name="circle-up" d="M0 448c0-282.77 229.23-512 512-512s512 229.23 512 512-229.23 512-512 512-512-229.23-512-512zM928 448c0-229.75-186.25-416-416-416s-416 186.25-416 416 186.25 416 416 416 416-186.25 416-416zM706.744 290.744l90.512 90.512-285.256 285.254-285.254-285.256 90.508-90.508 194.746 194.744z" />
|
||||||
|
<glyph unicode="" glyph-name="circle-right" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM354.744 253.256l90.512-90.512 285.254 285.256-285.256 285.254-90.508-90.508 194.744-194.746z" />
|
||||||
|
<glyph unicode="" glyph-name="circle-down" d="M1024 448c0 282.77-229.23 512-512 512s-512-229.23-512-512 229.23-512 512-512 512 229.23 512 512zM96 448c0 229.75 186.25 416 416 416s416-186.25 416-416-186.25-416-416-416-416 186.25-416 416zM317.256 605.256l-90.512-90.512 285.256-285.254 285.254 285.256-90.508 90.508-194.746-194.744z" />
|
||||||
|
<glyph unicode="" glyph-name="circle-left" d="M512-64c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 864c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416zM669.256 642.744l-90.512 90.512-285.254-285.256 285.256-285.254 90.508 90.508-194.744 194.746z" />
|
||||||
<glyph unicode="" glyph-name="new-tab" d="M192 896v-768h768v768h-768zM896 192h-640v640h640v-640zM128 64v672l-64 64v-800h800l-64 64h-672zM352 704l160-160-192-192 96-96 192 192 160-160v416z" />
|
<glyph unicode="" glyph-name="new-tab" d="M192 896v-768h768v768h-768zM896 192h-640v640h640v-640zM128 64v672l-64 64v-800h800l-64 64h-672zM352 704l160-160-192-192 96-96 192 192 160-160v416z" />
|
||||||
</font></defs></svg>
|
</font></defs></svg>
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Binary file not shown.
+122
-69
@@ -1,127 +1,180 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: 'icomoon';
|
||||||
src: url('fonts/icomoon.eot?4ozr25');
|
src: url('fonts/icomoon.eot?eqbps9');
|
||||||
src: url('fonts/icomoon.eot?4ozr25#iefix') format('embedded-opentype'),
|
src: url('fonts/icomoon.eot?eqbps9#iefix') format('embedded-opentype'),
|
||||||
url('fonts/icomoon.ttf?4ozr25') format('truetype'),
|
url('fonts/icomoon.ttf?eqbps9') format('truetype'),
|
||||||
url('fonts/icomoon.woff?4ozr25') format('woff'),
|
url('fonts/icomoon.woff?eqbps9') format('woff'),
|
||||||
url('fonts/icomoon.svg?4ozr25#icomoon') format('svg');
|
url('fonts/icomoon.svg?eqbps9#icomoon') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class^="icon-"],
|
[class^="icon-"],
|
||||||
[class*=" icon-"] {
|
[class*=" icon-"] {
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
font-family: 'icomoon' !important;
|
font-family: 'icomoon' !important;
|
||||||
speak: never;
|
font-style: normal;
|
||||||
font-style: normal;
|
font-weight: normal;
|
||||||
font-weight: normal;
|
font-variant: normal;
|
||||||
font-variant: normal;
|
text-transform: none;
|
||||||
text-transform: none;
|
line-height: 1;
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
/* Better Font Rendering =========== */
|
/* Better Font Rendering =========== */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-close:before {
|
||||||
|
content: "\e901";
|
||||||
|
}
|
||||||
.icon-home:before {
|
.icon-home:before {
|
||||||
content: "\e900";
|
content: "\e900";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-pencil:before {
|
.icon-pencil:before {
|
||||||
content: "\e905";
|
content: "\e905";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-image:before {
|
.icon-image:before {
|
||||||
content: "\e90d";
|
content: "\e90d";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-bullhorn:before {
|
.icon-bullhorn:before {
|
||||||
content: "\e91a";
|
content: "\e91a";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-books:before {
|
.icon-books:before {
|
||||||
content: "\e920";
|
content: "\e920";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-library:before {
|
.icon-library:before {
|
||||||
content: "\e921";
|
content: "\e921";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-barcode:before {
|
.icon-barcode:before {
|
||||||
content: "\e937";
|
content: "\e937";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-cart:before {
|
.icon-cart:before {
|
||||||
content: "\e93a";
|
content: "\e93a";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-credit-card:before {
|
.icon-credit-card:before {
|
||||||
content: "\e93f";
|
content: "\e93f";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-phone:before {
|
.icon-phone:before {
|
||||||
content: "\e942";
|
content: "\e942";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-envelop:before {
|
.icon-envelop:before {
|
||||||
content: "\e945";
|
content: "\e945";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-location:before {
|
.icon-location:before {
|
||||||
content: "\e947";
|
content: "\e947";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-calendar:before {
|
.icon-calendar:before {
|
||||||
content: "\e953";
|
content: "\e953";
|
||||||
|
}
|
||||||
|
.icon-floppy-disk:before {
|
||||||
|
content: "\e962";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-user:before {
|
.icon-user:before {
|
||||||
content: "\e971";
|
content: "\e971";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-search:before {
|
.icon-search:before {
|
||||||
content: "\e986";
|
content: "\e986";
|
||||||
|
}
|
||||||
|
.icon-lock:before {
|
||||||
|
content: "\e98f";
|
||||||
|
}
|
||||||
|
.icon-unlocked:before {
|
||||||
|
content: "\e990";
|
||||||
|
}
|
||||||
|
.icon-equalizer:before {
|
||||||
|
content: "\e992";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-cog:before {
|
.icon-cog:before {
|
||||||
content: "\e994";
|
content: "\e994";
|
||||||
|
}
|
||||||
|
.icon-magic-wand:before {
|
||||||
|
content: "\e997";
|
||||||
|
}
|
||||||
|
.icon-trophy:before {
|
||||||
|
content: "\e99e";
|
||||||
|
}
|
||||||
|
.icon-gift:before {
|
||||||
|
content: "\e99f";
|
||||||
|
}
|
||||||
|
.icon-rocket:before {
|
||||||
|
content: "\e9a5";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-fire:before {
|
.icon-fire:before {
|
||||||
content: "\e9a9";
|
content: "\e9a9";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-lab:before {
|
.icon-lab:before {
|
||||||
content: "\e9aa";
|
content: "\e9aa";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-bin:before {
|
.icon-bin:before {
|
||||||
content: "\e9ac";
|
content: "\e9ac";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-briefcase:before {
|
.icon-briefcase:before {
|
||||||
content: "\e9ae";
|
content: "\e9ae";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-truck:before {
|
.icon-truck:before {
|
||||||
content: "\e9b0";
|
content: "\e9b0";
|
||||||
|
}
|
||||||
|
.icon-power:before {
|
||||||
|
content: "\e9b5";
|
||||||
|
}
|
||||||
|
.icon-switch:before {
|
||||||
|
content: "\e9b6";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-earth:before {
|
.icon-earth:before {
|
||||||
content: "\e9ca";
|
content: "\e9ca";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-link:before {
|
.icon-link:before {
|
||||||
content: "\e9cb";
|
content: "\e9cb";
|
||||||
|
}
|
||||||
|
.icon-eye:before {
|
||||||
|
content: "\e9ce";
|
||||||
|
}
|
||||||
|
.icon-star-empty:before {
|
||||||
|
content: "\e9d7";
|
||||||
|
}
|
||||||
|
.icon-star-full:before {
|
||||||
|
content: "\e9d9";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-heart:before {
|
.icon-heart:before {
|
||||||
content: "\e9da";
|
content: "\e9da";
|
||||||
|
}
|
||||||
|
.icon-warning:before {
|
||||||
|
content: "\ea07";
|
||||||
|
}
|
||||||
|
.icon-notification:before {
|
||||||
|
content: "\ea08";
|
||||||
|
}
|
||||||
|
.icon-question:before {
|
||||||
|
content: "\ea09";
|
||||||
|
}
|
||||||
|
.icon-cancel-circle:before {
|
||||||
|
content: "\ea0d";
|
||||||
|
}
|
||||||
|
.icon-circle-up:before {
|
||||||
|
content: "\ea41";
|
||||||
|
}
|
||||||
|
.icon-circle-right:before {
|
||||||
|
content: "\ea42";
|
||||||
|
}
|
||||||
|
.icon-circle-down:before {
|
||||||
|
content: "\ea43";
|
||||||
|
}
|
||||||
|
.icon-circle-left:before {
|
||||||
|
content: "\ea44";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-new-tab:before {
|
.icon-new-tab:before {
|
||||||
content: "\ea7e";
|
content: "\ea7e";
|
||||||
|
}
|
||||||
|
.icon-hash:before {
|
||||||
|
content: "\e902";
|
||||||
|
}
|
||||||
|
.icon-molecule:before {
|
||||||
|
content: "\e903";
|
||||||
|
}
|
||||||
|
.icon-marijuana:before {
|
||||||
|
content: "\e904";
|
||||||
|
}
|
||||||
|
.icon-cannabis:before {
|
||||||
|
content: "\e906";
|
||||||
}
|
}
|
||||||
+39
-11
@@ -2,7 +2,8 @@ $success: #7952b3;
|
|||||||
|
|
||||||
@import "bootstrap/scss/bootstrap.scss";
|
@import "bootstrap/scss/bootstrap.scss";
|
||||||
|
|
||||||
$dark-blue: #141d5e; /*#171f50;*/
|
$dark-blue: #141d5e;
|
||||||
|
/*#171f50;*/
|
||||||
$dark-grey: #222222;
|
$dark-grey: #222222;
|
||||||
$gold: #d7c067;
|
$gold: #d7c067;
|
||||||
$orange: #ff8300;
|
$orange: #ff8300;
|
||||||
@@ -21,16 +22,16 @@ body {
|
|||||||
.bg-darker {
|
.bg-darker {
|
||||||
background-color: $dark-grey !important;
|
background-color: $dark-grey !important;
|
||||||
}
|
}
|
||||||
|
.bg-dark-blue {
|
||||||
.bg-blue-gold {
|
|
||||||
--bs-bg-opacity: 1;
|
--bs-bg-opacity: 1;
|
||||||
--dark-blue: 20, 29, 94;
|
--dark-blue: 20, 29, 94;
|
||||||
background-color: rgba(var(--dark-blue), var(--bs-bg-opacity)) !important;
|
background-color: rgba(var(--dark-blue), var(--bs-bg-opacity)) !important;
|
||||||
h5 {
|
color: var(--bs-gray-400) !important;
|
||||||
|
&.gold h5 {
|
||||||
color: $gold;
|
color: $gold;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: var(--bs-secondary-color) !important;
|
color: var(--bs-gray-400) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,27 +46,31 @@ body {
|
|||||||
height: 100px;
|
height: 100px;
|
||||||
.divider {
|
.divider {
|
||||||
width: 2px;
|
width: 2px;
|
||||||
background-color: $time-grey;
|
background-color: var(--bs-gray-400);
|
||||||
height: 60px;
|
height: 60px;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: calc(50% - 1px);
|
left: calc(50% - 1px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.datelib {
|
.datelib {
|
||||||
color: $time-grey;
|
color: $dark-blue;
|
||||||
border-bottom: 1px solid $time-grey;
|
border-bottom: 1px solid $dark-blue;
|
||||||
width: 328px;
|
width: 328px;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
color: $time-grey-dark;
|
color: var(--bs-gray-400);
|
||||||
transition: 0.4s;
|
transition: 0.4s;
|
||||||
&.active:before {
|
&.active:before {
|
||||||
color: $time-grey;
|
color: $dark-blue;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
&.second:before {
|
&.second:before {
|
||||||
color: $gold;
|
color: $orange;
|
||||||
}
|
}
|
||||||
&:not(:nth-child(n + 16)):nth-child(n + 11),
|
&:not(:nth-child(n + 16)):nth-child(n + 11),
|
||||||
&:not(:nth-child(n + 46)):nth-child(n + 41) {
|
&:not(:nth-child(n + 46)):nth-child(n + 41) {
|
||||||
@@ -88,3 +93,26 @@ body {
|
|||||||
content: "0" attr(data-num);
|
content: "0" attr(data-num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-bs-theme=dark] {
|
||||||
|
.clock {
|
||||||
|
.divider {
|
||||||
|
background-color: $time-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.datelib {
|
||||||
|
color: $time-grey;
|
||||||
|
border-bottom: 1px solid $time-grey;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.block {
|
||||||
|
color: $time-grey-dark;
|
||||||
|
&.active:before {
|
||||||
|
color: $time-grey;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.second:before {
|
||||||
|
color: $gold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
-40
@@ -1,33 +1,33 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const Profile = z.object({
|
export const Profile = z.object({
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
firstname: z.string(),
|
firstname: z.string(),
|
||||||
lastname: z.string(),
|
lastname: z.string(),
|
||||||
bio: z
|
bio: z
|
||||||
.string()
|
.string()
|
||||||
.nullish()
|
.nullish()
|
||||||
.transform((x) => x ?? undefined),
|
.transform((x) => x ?? undefined),
|
||||||
image: z
|
image: z
|
||||||
.string()
|
.string()
|
||||||
.nullish()
|
.nullish()
|
||||||
.transform((x) => x ?? undefined),
|
.transform((x) => x ?? undefined),
|
||||||
following: z.boolean(),
|
following: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Profile = z.infer<typeof Profile>;
|
export type Profile = z.infer<typeof Profile>;
|
||||||
|
|
||||||
export const Article = z.object({
|
export const Article = z.object({
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
body: z.string(),
|
body: z.string(),
|
||||||
tagList: z.array(z.string()),
|
tagList: z.array(z.string()),
|
||||||
createdAt: z.coerce.date(),
|
createdAt: z.coerce.date(),
|
||||||
updatedAt: z.coerce.date(),
|
updatedAt: z.coerce.date(),
|
||||||
favorited: z.boolean(),
|
favorited: z.boolean(),
|
||||||
favoritesCount: z.number(),
|
favoritesCount: z.number(),
|
||||||
author: Profile,
|
author: Profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Article = z.infer<typeof Article>;
|
export type Article = z.infer<typeof Article>;
|
||||||
@@ -37,29 +37,29 @@ export const ArticlePreview = Article.omit({ body: true, updatedAt: true });
|
|||||||
export type ArticlePreview = z.infer<typeof ArticlePreview>;
|
export type ArticlePreview = z.infer<typeof ArticlePreview>;
|
||||||
|
|
||||||
export const Comment = z.object({
|
export const Comment = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
createdAt: z.coerce.date(),
|
createdAt: z.coerce.date(),
|
||||||
updatedAt: z.coerce.date(),
|
updatedAt: z.coerce.date(),
|
||||||
body: z.string(),
|
body: z.string(),
|
||||||
author: Profile,
|
author: Profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Comment = z.infer<typeof Comment>;
|
export type Comment = z.infer<typeof Comment>;
|
||||||
|
|
||||||
export const User = z.object({
|
export const User = z.object({
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
firstname: z.string(),
|
firstname: z.string(),
|
||||||
lastname: z.string(),
|
lastname: z.string(),
|
||||||
image: z
|
image: z
|
||||||
.string()
|
.string()
|
||||||
.nullish()
|
.nullish()
|
||||||
.transform((x) => x ?? undefined),
|
.transform((x) => x ?? undefined),
|
||||||
bio: z
|
bio: z
|
||||||
.string()
|
.string()
|
||||||
.nullish()
|
.nullish()
|
||||||
.transform((x) => x ?? undefined),
|
.transform((x) => x ?? undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type User = z.infer<typeof User>;
|
export type User = z.infer<typeof User>;
|
||||||
|
|||||||
Reference in New Issue
Block a user