feat: first form field auto focused

This commit is contained in:
Sonny
2023-04-21 19:00:03 +02:00
parent 57317affbe
commit 05ab09f7bc
21 changed files with 1009 additions and 792 deletions

View File

@@ -1,57 +1,59 @@
import { MutableRefObject, useState } from 'react';
import { MutableRefObject, useState } from "react";
interface SelectorProps {
name: string;
label?: string;
labelComponent?: JSX.Element;
disabled?: boolean;
innerRef?: MutableRefObject<any>;
placeholder?: string;
fieldClass?: string;
isChecked?: boolean;
onChangeCallback?: (value, { target }) => void;
name: string;
label?: string;
labelComponent?: JSX.Element;
disabled?: boolean;
innerRef?: MutableRefObject<any>;
placeholder?: string;
fieldClass?: string;
isChecked?: boolean;
onChangeCallback?: (value, { target }) => void;
}
export default function Selector({
name,
label,
labelComponent,
disabled = false,
innerRef = null,
fieldClass = '',
placeholder = 'Type something...',
isChecked,
onChangeCallback
export default function Checkbox({
name,
label,
labelComponent,
disabled = false,
innerRef = null,
fieldClass = "",
placeholder = "Type something...",
isChecked,
onChangeCallback,
}: SelectorProps): JSX.Element {
const [checkboxValue, setCheckboxValue] = useState<boolean>(isChecked);
const [checkboxValue, setCheckboxValue] = useState<boolean>(isChecked);
function onChange({ target }) {
setCheckboxValue(!checkboxValue);
if (onChangeCallback) {
onChangeCallback(!checkboxValue, { target });
}
function onChange({ target }) {
setCheckboxValue(!checkboxValue);
if (onChangeCallback) {
onChangeCallback(!checkboxValue, { target });
}
}
return (<div className={`checkbox-field ${fieldClass}`}>
{label && (
<label htmlFor={name} title={`${name} field`}>
{label}
</label>
)}
{labelComponent && (
<label htmlFor={name} title={`${name} field`}>
{labelComponent}
</label>
)}
<input
type='checkbox'
id={name}
name={name}
onChange={onChange}
checked={checkboxValue}
placeholder={placeholder}
ref={innerRef}
disabled={disabled}
/>
</div>);
}
return (
<div className={`checkbox-field ${fieldClass}`}>
{label && (
<label htmlFor={name} title={`${name} field`}>
{label}
</label>
)}
{labelComponent && (
<label htmlFor={name} title={`${name} field`}>
{labelComponent}
</label>
)}
<input
type="checkbox"
id={name}
name={name}
onChange={onChange}
checked={checkboxValue}
placeholder={placeholder}
ref={innerRef}
disabled={disabled}
/>
</div>
);
}

View File

@@ -7,6 +7,9 @@ import styles from "../styles/create.module.scss";
interface FormProps {
title: string;
categoryId?: string;
errorMessage?: string;
successMessage?: string;
infoMessage?: string;
@@ -21,6 +24,7 @@ interface FormProps {
}
export default function Form({
title,
categoryId = undefined,
errorMessage,
successMessage,
infoMessage,
@@ -45,7 +49,9 @@ export default function Form({
{textBtnConfirm}
</button>
</form>
<Link href="/"> Revenir à l'accueil</Link>
<Link href={categoryId ? `/?categoryId=${categoryId}` : "/"}>
Revenir à l'accueil
</Link>
<MessageManager
info={infoMessage}
error={errorMessage}

View File

@@ -3,6 +3,7 @@ import { AiFillDelete, AiFillEdit } from "react-icons/ai";
import { Category } from "../../../types";
import { useEffect, useRef } from "react";
import styles from "./categories.module.scss";
interface CategoryItemProps {
@@ -11,19 +12,28 @@ interface CategoryItemProps {
handleSelectCategory: (category: Category) => void;
}
let rendered = false;
export default function CategoryItem({
category,
categoryActive,
handleSelectCategory,
}: CategoryItemProps): JSX.Element {
const ref = useRef<HTMLLIElement>();
const className = `${styles["item"]} ${
category.id === categoryActive.id ? styles["active"] : ""
}`;
const onClick = () => handleSelectCategory(category);
useEffect(() => {
if (category.id === categoryActive.id && !rendered) {
rendered = true;
ref.current.scrollIntoView({ behavior: "smooth" });
}
}, [category.id, categoryActive.id]);
return (
<li className={className} onClick={onClick}>
<div className={styles["content"]}>
<li className={className} ref={ref}>
<div className={styles["content"]} onClick={onClick}>
<span className={styles["name"]}>{category.name}</span>
<span className={styles["links-count"]}> {category.links.length}</span>
</div>
@@ -35,12 +45,17 @@ export default function CategoryItem({
function MenuOptions({ id }: { id: number }): JSX.Element {
return (
<div className={styles["menu-item"]}>
<LinkTag href={`/category/edit/${id}`} className={styles["option-edit"]}>
<LinkTag
href={`/category/edit/${id}`}
className={styles["option-edit"]}
onClick={(event) => event.stopPropagation()}
>
<AiFillEdit />
</LinkTag>
<LinkTag
href={`/category/remove/${id}`}
className={styles["option-remove"]}
onClick={(event) => event.stopPropagation()}
>
<AiFillDelete color="red" />
</LinkTag>

View File

@@ -29,7 +29,7 @@ export default function SideMenu({
<BlockWrapper>
<Favorites favorites={favorites} />
</BlockWrapper>
<BlockWrapper style={{ minHeight: "0" }}>
<BlockWrapper style={{ minHeight: "0", flex: "1" }}>
<Categories
categories={categories}
categoryActive={categoryActive}

View File

@@ -1,62 +1,64 @@
import { MutableRefObject, useState } from 'react';
import { MutableRefObject, useState } from "react";
interface InputProps {
name: string;
label?: string;
labelComponent?: JSX.Element;
disabled?: boolean;
type?: string;
multiple?: boolean;
innerRef?: MutableRefObject<any>;
placeholder?: string;
fieldClass?: string;
value?: string;
onChangeCallback?: (value) => void;
name: string;
label?: string;
labelComponent?: JSX.Element;
disabled?: boolean;
type?: string;
multiple?: boolean;
innerRef?: MutableRefObject<any> | ((ref: any) => void);
placeholder?: string;
fieldClass?: string;
value?: string;
onChangeCallback?: (value) => void;
}
export default function TextBox({
name,
label,
labelComponent,
disabled = false,
type = 'text',
multiple = false,
innerRef = null,
placeholder = 'Type something...',
fieldClass = '',
value,
onChangeCallback
name,
label,
labelComponent,
disabled = false,
type = "text",
multiple = false,
innerRef = null,
placeholder = "Type something...",
fieldClass = "",
value,
onChangeCallback,
}: InputProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>(value);
const [inputValue, setInputValue] = useState<string>(value);
function onChange({ target }) {
setInputValue(target.value);
if (onChangeCallback) {
onChangeCallback(target.value);
}
function onChange({ target }) {
setInputValue(target.value);
if (onChangeCallback) {
onChangeCallback(target.value);
}
}
return (<div className={`input-field ${fieldClass}`}>
{label && (
<label htmlFor={name} title={`${name} field`}>
{label}
</label>
)}
{labelComponent && (
<label htmlFor={name} title={`${name} field`}>
{labelComponent}
</label>
)}
<input
id={name}
name={name}
type={type}
onChange={onChange}
value={inputValue}
multiple={multiple}
placeholder={placeholder}
ref={innerRef}
disabled={disabled}
/>
</div>);
}
return (
<div className={`input-field ${fieldClass}`}>
{label && (
<label htmlFor={name} title={`${name} field`}>
{label}
</label>
)}
{labelComponent && (
<label htmlFor={name} title={`${name} field`}>
{labelComponent}
</label>
)}
<input
id={name}
name={name}
type={type}
onChange={onChange}
value={inputValue}
multiple={multiple}
placeholder={placeholder}
ref={innerRef}
disabled={disabled}
/>
</div>
);
}

View File

@@ -2,6 +2,11 @@ DB_USER="my_user"
DB_PASSWORD=""
DB_DATABASE="my-links"
# Or if you need external Database
# DATABASE_IP="localhost"
# DATABASE_PORT="3306"
# DATABASE_URL="mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${DATABASE_IP}:${DATABASE_PORT}/${MYSQL_DATABASE}"
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_URL_INTERNAL=http://localhost:3000

11
hooks/useAutoFocus.tsx Normal file
View File

@@ -0,0 +1,11 @@
import { useCallback } from "react";
export default function useAutoFocus() {
const inputRef = useCallback((inputElement: any) => {
if (inputElement) {
inputElement.focus();
}
}, []);
return inputRef;
}

View File

@@ -1,14 +1,12 @@
import { SessionProvider } from "next-auth/react";
import { useEffect } from "react";
import { DefaultSeo } from "next-seo";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import "nprogress/nprogress.css";
import { useEffect } from "react";
import AuthRequired from "../components/AuthRequired";
import { DefaultSeo } from "next-seo";
import "nprogress/nprogress.css";
import "../styles/globals.scss";
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
@@ -25,7 +23,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) {
router.events.off("routeChangeComplete", nProgress.done);
router.events.off("routeChangeError", nProgress.done);
};
});
}, [router.events]);
return (
<SessionProvider session={session}>

View File

@@ -1,33 +1,47 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '../../../utils/back';
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../utils/back";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const name = req.body?.name as string;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const name = req.body?.name as string;
if (!name) {
return res.status(400).send({ error: 'Nom de catégorie manquant' });
if (!name) {
return res.status(400).send({ error: "Nom de catégorie manquant" });
}
try {
const category = await prisma.category.findFirst({
where: { name },
});
if (category) {
return res
.status(400)
.send({ error: "Une catégorie avec ce nom existe déjà" });
}
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la création de la catégorie (category/create->findCategory)",
});
}
try {
const category = await prisma.category.findFirst({
where: { name }
});
if (category) {
return res.status(400).send({ error: 'Une catégorie avec ce nom existe déjà' });
}
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la création de la catégorie (category/create->findCategory)' });
}
try {
await prisma.category.create({
data: { name }
});
return res.status(200).send({ success: 'Catégorie créée avec succès' });
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la création de la catégorie (category/create->createCategory)' });
}
}
try {
const category = await prisma.category.create({
data: { name },
});
return res.status(200).send({
success: "Catégorie créée avec succès",
categoryId: category.id,
});
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la création de la catégorie (category/create->createCategory)",
});
}
}

View File

@@ -1,39 +1,53 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '../../../../utils/back';
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../../utils/back";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { cid } = req.query;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { cid } = req.query;
let category;
try {
category = await prisma.category.findFirst({
where: { id: Number(cid) }
});
let category;
try {
category = await prisma.category.findFirst({
where: { id: Number(cid) },
});
if (!category) {
return res.status(400).send({ error: 'Catégorie introuvable' });
}
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de l\'édition de la catégorie (category/edit->findCategory)' });
if (!category) {
return res.status(400).send({ error: "Catégorie introuvable" });
}
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de l'édition de la catégorie (category/edit->findCategory)",
});
}
const name = req.body?.name as string;
if (!name) {
return res.status(400).send({ error: 'Nom de la catégorie manquante' });
} else if (name === category.name) {
return res.status(400).send({ error: 'Le nom de la catégorie doit être différent du nom actuel' });
}
const name = req.body?.name as string;
if (!name) {
return res.status(400).send({ error: "Nom de la catégorie manquante" });
} else if (name === category.name) {
return res.status(400).send({
error: "Le nom de la catégorie doit être différent du nom actuel",
});
}
try {
await prisma.category.update({
where: { id: Number(cid) },
data: { name }
});
try {
const category = await prisma.category.update({
where: { id: Number(cid) },
data: { name },
});
return res.status(200).send({ success: 'Catégorie mise à jour avec succès' });
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de l\'édition de la catégorie (category/edit->updateCategory)' });
}
}
return res.status(200).send({
success: "Catégorie mise à jour avec succès",
categoryId: category.id,
});
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de l'édition de la catégorie (category/edit->updateCategory)",
});
}
}

View File

@@ -39,16 +39,16 @@ export default async function handler(
const faviconPath = findFaviconPath(text);
if (!faviconPath) {
throw new Error("Unable to find favicon path");
throw new Error("[Favicon] Unable to find favicon path");
}
if (isBase64Image(faviconPath)) {
console.log("base64, convert it to buffer");
console.log("[Favicon] base64, convert it to buffer");
const buffer = convertBase64ToBuffer(faviconPath);
return sendImage({
content: buffer,
res,
type: "image/vnd.microsoft.icon",
type: "image/x-icon",
size: buffer.length,
});
}

View File

@@ -1,62 +1,78 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '../../../utils/back';
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../utils/back";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const name = req.body?.name as string;
const url = req.body?.url as string;
const favorite = Boolean(req.body?.favorite) || false;
const categoryId = Number(req.body?.categoryId);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const name = req.body?.name as string;
const url = req.body?.url as string;
const favorite = Boolean(req.body?.favorite) || false;
const categoryId = Number(req.body?.categoryId);
if (!name) {
return res.status(400).send({ error: 'Nom du lien manquant' });
if (!name) {
return res.status(400).send({ error: "Nom du lien manquant" });
}
if (!url) {
return res.status(400).send({ error: "URL du lien manquant" });
}
if (!categoryId) {
return res.status(400).send({ error: "Catégorie du lien manquante" });
}
try {
const link = await prisma.link.findFirst({
where: { name, categoryId },
});
if (link) {
return res.status(400).send({
error: "Un lien avec ce nom existe déjà dans cette catégorie",
});
}
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la création du lien (link/create->findLink)",
});
}
if (!url) {
return res.status(400).send({ error: 'URL du lien manquant' });
try {
const category = await prisma.category.findFirst({
where: { id: categoryId },
});
if (!category) {
return res.status(400).send({ error: "Cette catégorie n'existe pas" });
}
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la création du lien (link/create->findCategory)",
});
}
if (!categoryId) {
return res.status(400).send({ error: 'Catégorie du lien manquante' });
}
try {
const link = await prisma.link.findFirst({
where: { name, categoryId }
});
if (link) {
return res.status(400).send({ error: 'Un lien avec ce nom existe déjà dans cette catégorie' });
}
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la création du lien (link/create->findLink)' });
}
try {
const category = await prisma.category.findFirst({
where: { id: categoryId }
});
if (!category) {
return res.status(400).send({ error: 'Cette catégorie n\'existe pas' });
}
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la création du lien (link/create->findCategory)' });
}
try {
await prisma.link.create({
data: {
name,
url,
categoryId,
favorite
}
});
return res.status(200).send({ success: 'Lien créé avec succès' });
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la création du lien (link/create->createLink)' });
}
}
try {
await prisma.link.create({
data: {
name,
url,
categoryId,
favorite,
},
});
return res
.status(200)
.send({ success: "Lien créé avec succès", categoryId });
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la création du lien (link/create->createLink)",
});
}
}

View File

@@ -1,54 +1,65 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '../../../../utils/back';
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../../utils/back";
// TODO: Ajouter vérification -> l'utilisateur doit changer au moins un champ
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { lid } = req.query;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { lid } = req.query;
try {
const link = await prisma.link.findFirst({
where: { id: Number(lid) }
});
try {
const link = await prisma.link.findFirst({
where: { id: Number(lid) },
});
if (!link) {
return res.status(400).send({ error: 'Lien introuvable' });
}
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de l\'édition du lien (link/edit->findLink)' });
if (!link) {
return res.status(400).send({ error: "Lien introuvable" });
}
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de l'édition du lien (link/edit->findLink)",
});
}
const name = req.body?.name as string;
const url = req.body?.url as string;
const favorite = Boolean(req.body?.favorite) || false;
const categoryId = Number(req.body?.categoryId);
const name = req.body?.name as string;
const url = req.body?.url as string;
const favorite = Boolean(req.body?.favorite) || false;
const categoryId = Number(req.body?.categoryId);
if (!name) {
return res.status(400).send({ error: 'Nom du lien manquant' });
}
if (!name) {
return res.status(400).send({ error: "Nom du lien manquant" });
}
if (!url) {
return res.status(400).send({ error: 'URL du lien manquant' });
}
if (!url) {
return res.status(400).send({ error: "URL du lien manquant" });
}
if (!categoryId) {
return res.status(400).send({ error: 'Catégorie du lien manquante' });
}
if (!categoryId) {
return res.status(400).send({ error: "Catégorie du lien manquante" });
}
try {
await prisma.link.update({
where: { id: Number(lid) },
data: {
name,
url,
favorite,
categoryId
}
});
try {
await prisma.link.update({
where: { id: Number(lid) },
data: {
name,
url,
favorite,
categoryId,
},
});
return res.status(200).send({ success: 'Lien mis à jour avec succès' });
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de l\'édition du lien (link/remove->updateLink)' });
}
}
return res
.status(200)
.send({ success: "Lien mis à jour avec succès", categoryId });
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de l'édition du lien (link/remove->updateLink)",
});
}
}

View File

@@ -1,30 +1,42 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '../../../../utils/back';
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../../utils/back";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { lid } = req.query;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { lid } = req.query;
try {
const link = await prisma.link.findFirst({
where: { id: Number(lid) }
});
try {
const link = await prisma.link.findFirst({
where: { id: Number(lid) },
});
if (!link) {
return res.status(400).send({ error: 'Lien introuvable' });
}
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la suppression du lien (link/remove->findLink)' });
if (!link) {
return res.status(400).send({ error: "Lien introuvable" });
}
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la suppression du lien (link/remove->findLink)",
});
}
try {
await prisma.link.delete({
where: { id: Number(lid) }
});
try {
const link = await prisma.link.delete({
where: { id: Number(lid) },
});
return res.status(200).send({ success: 'Le lien a été supprimé avec succès' });
} catch (error) {
console.error(error);
return res.status(400).send({ error: 'Une erreur est survenue lors de la suppression du lien (link/remove->deleteLink)' });
}
}
return res.status(200).send({
success: "Le lien a été supprimé avec succès",
categoryId: link.categoryId,
});
} catch (error) {
console.error(error);
return res.status(400).send({
error:
"Une erreur est survenue lors de la suppression du lien (link/remove->deleteLink)",
});
}
}

View File

@@ -1,64 +1,68 @@
import axios from 'axios';
import { useRouter } from 'next/router';
import nProgress from 'nprogress';
import { useMemo, useState } from 'react';
import axios from "axios";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import { useMemo, useState } from "react";
import { redirectWithoutClientCache } from '../../utils/client';
import { HandleAxiosError } from '../../utils/front';
import FormLayout from "../../components/FormLayout";
import TextBox from "../../components/TextBox";
import FormLayout from '../../components/FormLayout';
import TextBox from '../../components/TextBox';
import { redirectWithoutClientCache } from "../../utils/client";
import { HandleAxiosError } from "../../utils/front";
import styles from '../../styles/create.module.scss';
import styles from "../../styles/create.module.scss";
function CreateCategory() {
const router = useRouter();
const info = useRouter().query?.info as string;
const [name, setName] = useState<string>('');
const router = useRouter();
const info = useRouter().query?.info as string;
const [name, setName] = useState<string>("");
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const [submitted, setSubmitted] = useState<boolean>(false);
const canSubmit = useMemo<boolean>(() => name.length !== 0 && !submitted, [name.length, submitted]);
const [error, setError] = useState<string | null>(null);
const [submitted, setSubmitted] = useState<boolean>(false);
const handleSubmit = async (event) => {
event.preventDefault();
setSuccess(null);
setError(null);
setSubmitted(false);
nProgress.start();
const canSubmit = useMemo<boolean>(
() => name.length !== 0 && !submitted,
[name.length, submitted]
);
try {
await axios.post('/api/category/create', { name });
redirectWithoutClientCache(router, '');
router.push('/')
} catch (error) {
setError(HandleAxiosError(error));
setSubmitted(true);
} finally {
nProgress.done();
}
const handleSubmit = async (event) => {
event.preventDefault();
setError(null);
setSubmitted(true);
nProgress.start();
try {
const { data } = await axios.post("/api/category/create", { name });
redirectWithoutClientCache(router, "");
router.push(`/?categoryId=${data?.categoryId}`);
setSubmitted(true);
} catch (error) {
setError(HandleAxiosError(error));
setSubmitted(false);
} finally {
nProgress.done();
}
};
return (<>
<FormLayout
title='Créer une catégorie'
errorMessage={error}
successMessage={success}
infoMessage={info}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name='name'
label='Nom de la catégorie'
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles['input-field']}
placeholder='Nom...'
/>
</FormLayout>
</>);
return (
<>
<FormLayout
title="Créer une catégorie"
errorMessage={error}
infoMessage={info}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name="name"
label="Nom de la catégorie"
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles["input-field"]}
placeholder="Nom..."
/>
</FormLayout>
</>
);
}
CreateCategory.authRequired = true;

View File

@@ -1,92 +1,99 @@
import axios, { AxiosResponse } from 'axios';
import nProgress from 'nprogress';
import { useEffect, useState } from 'react';
import axios from "axios";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import { useMemo, useState } from "react";
import FormLayout from '../../../components/FormLayout';
import TextBox from '../../../components/TextBox';
import FormLayout from "../../../components/FormLayout";
import TextBox from "../../../components/TextBox";
import { Category } from '../../../types';
import { prisma } from '../../../utils/back';
import { BuildCategory, HandleAxiosError } from '../../../utils/front';
import useAutoFocus from "../../../hooks/useAutoFocus";
import styles from '../../../styles/create.module.scss';
import { Category } from "../../../types";
import { prisma } from "../../../utils/back";
import { BuildCategory, HandleAxiosError } from "../../../utils/front";
function EditCategory({ category }: { category: Category; }) {
const [name, setName] = useState<string>(category.name);
import styles from "../../../styles/create.module.scss";
const [canSubmit, setCanSubmit] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
function EditCategory({ category }: { category: Category }) {
const autoFocusRef = useAutoFocus();
const router = useRouter();
useEffect(() => {
if (name !== category.name && name !== '') {
setCanSubmit(true);
} else {
setCanSubmit(false);
}
}, [category, name]);
const [name, setName] = useState<string>(category.name);
const handleSubmit = async (event) => {
event.preventDefault();
setSuccess(null);
setError(null);
setCanSubmit(false);
nProgress.start();
const [error, setError] = useState<string | null>(null);
const [submitted, setSubmitted] = useState<boolean>(false);
try {
const payload = { name };
const { data }: AxiosResponse<any> = await axios.put(`/api/category/edit/${category.id}`, payload);
setSuccess(data?.success || 'Catégorie modifiée avec succès');
} catch (error) {
setError(HandleAxiosError(error));
} finally {
setCanSubmit(true);
nProgress.done();
}
const canSubmit = useMemo<boolean>(
() => name !== category.name && name !== "" && !submitted,
[category.name, name, submitted]
);
const handleSubmit = async (event) => {
event.preventDefault();
setError(null);
setSubmitted(true);
nProgress.start();
try {
const payload = { name };
const { data } = await axios.put(
`/api/category/edit/${category.id}`,
payload
);
router.push(`/?categoryId=${data?.categoryId}`);
setSubmitted(true);
} catch (error) {
setError(HandleAxiosError(error));
setSubmitted(false);
} finally {
nProgress.done();
}
};
return (<>
<FormLayout
title='Modifier une catégorie'
errorMessage={error}
successMessage={success}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name='name'
label='Nom'
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles['input-field']}
placeholder={`Nom original : ${category.name}`}
/>
</FormLayout>
</>);
return (
<>
<FormLayout
title="Modifier une catégorie"
errorMessage={error}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name="name"
label="Nom"
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles["input-field"]}
placeholder={`Nom original : ${category.name}`}
innerRef={autoFocusRef}
/>
</FormLayout>
</>
);
}
EditCategory.authRequired = true;
export default EditCategory;
export async function getServerSideProps({ query }) {
const { cid } = query;
const categoryDB = await prisma.category.findFirst({
where: { id: Number(cid) },
include: { links: true }
});
const { cid } = query;
const categoryDB = await prisma.category.findFirst({
where: { id: Number(cid) },
include: { links: true },
});
if (!categoryDB) {
return {
redirect: {
destination: '/'
}
}
}
const category = BuildCategory(categoryDB);
if (!categoryDB) {
return {
props: {
category: JSON.parse(JSON.stringify(category))
}
}
}
redirect: {
destination: "/",
},
};
}
const category = BuildCategory(categoryDB);
return {
props: {
category: JSON.parse(JSON.stringify(category)),
},
};
}

View File

@@ -1,96 +1,103 @@
import axios, { AxiosResponse } from 'axios';
import nProgress from 'nprogress';
import { useEffect, useState } from 'react';
import axios from "axios";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import { useMemo, useState } from "react";
import FormLayout from '../../../components/FormLayout';
import TextBox from '../../../components/TextBox';
import Checkbox from "../../../components/Checkbox";
import FormLayout from "../../../components/FormLayout";
import TextBox from "../../../components/TextBox";
import { Category } from '../../../types';
import { BuildCategory, HandleAxiosError } from '../../../utils/front';
import { prisma } from '../../../utils/back';
import { Category } from "../../../types";
import { prisma } from "../../../utils/back";
import { BuildCategory, HandleAxiosError } from "../../../utils/front";
import styles from '../../../styles/create.module.scss';
import styles from "../../../styles/create.module.scss";
function RemoveCategory({ category }: { category: Category; }) {
const [canSubmit, setCanSubmit] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
function RemoveCategory({ category }: { category: Category }) {
const router = useRouter();
const [error, setError] = useState<string | null>(
category.links.length > 0
? "Vous devez supprimer tous les liens de cette catégorie avant de pouvoir supprimer cette catégorie"
: null
);
const [confirmDelete, setConfirmDelete] = useState<boolean>(false);
const [submitted, setSubmitted] = useState<boolean>(false);
useEffect(() => {
if (category.links.length > 0) {
setError('Vous devez supprimer tous les liens de cette catégorie avant de pouvoir supprimer cette catégorie')
setCanSubmit(false);
} else {
setCanSubmit(true);
}
}, [category]);
const canSubmit = useMemo<boolean>(
() => category.links.length === 0 && confirmDelete && !submitted,
[category.links.length, confirmDelete, submitted]
);
if (status === 'loading') {
return (<p>Chargement de la session en cours</p>)
const handleSubmit = async (event) => {
event.preventDefault();
setError(null);
setSubmitted(true);
nProgress.start();
try {
await axios.delete(`/api/category/remove/${category.id}`);
router.push("/");
setSubmitted(true);
} catch (error) {
setError(HandleAxiosError(error));
setSubmitted(false);
} finally {
nProgress.done();
}
};
const handleSubmit = async (event) => {
event.preventDefault();
setSuccess(null);
setError(null);
setCanSubmit(false);
nProgress.start();
try {
const { data }: AxiosResponse<any> = await axios.delete(`/api/category/remove/${category.id}`);
setSuccess(data?.success || 'Categorie supprimée avec succès');
setCanSubmit(false);
} catch (error) {
setError(HandleAxiosError(error));
setCanSubmit(true);
} finally {
nProgress.done();
}
}
return (<>
<FormLayout
title='Supprimer une catégorie'
errorMessage={error}
successMessage={success}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
classBtnConfirm='red-btn'
textBtnConfirm='Supprimer'
>
<TextBox
name='name'
label='Nom'
value={category.name}
fieldClass={styles['input-field']}
disabled={true}
/>
</FormLayout>
</>);
return (
<>
<FormLayout
title="Supprimer une catégorie"
categoryId={category.id.toString()}
errorMessage={error}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
classBtnConfirm="red-btn"
textBtnConfirm="Supprimer"
>
<TextBox
name="name"
label="Nom"
value={category.name}
fieldClass={styles["input-field"]}
disabled={true}
/>
<Checkbox
name="confirm-delete"
label="Confirmer la suppression ?"
isChecked={confirmDelete}
disabled={!!error}
onChangeCallback={(checked) => setConfirmDelete(checked)}
/>
</FormLayout>
</>
);
}
RemoveCategory.authRequired = true;
export default RemoveCategory;
export async function getServerSideProps({ query }) {
const { cid } = query;
const categoryDB = await prisma.category.findFirst({
where: { id: Number(cid) },
include: { links: true }
});
const { cid } = query;
const categoryDB = await prisma.category.findFirst({
where: { id: Number(cid) },
include: { links: true },
});
if (!categoryDB) {
return {
redirect: {
destination: '/'
}
}
}
const category = BuildCategory(categoryDB);
if (!categoryDB) {
return {
props: {
category: JSON.parse(JSON.stringify(category))
}
}
}
redirect: {
destination: "/",
},
};
}
const category = BuildCategory(categoryDB);
return {
props: {
category: JSON.parse(JSON.stringify(category)),
},
};
}

View File

@@ -6,22 +6,28 @@ import SideMenu from "../components/SideMenu/SideMenu";
import { Category, Link } from "../types";
import { useRouter } from "next/router";
import { prisma } from "../utils/back";
import { BuildCategory } from "../utils/front";
interface HomeProps {
categories: Category[];
favorites: Link[];
currentCategory: Category | undefined;
}
function Home({ categories, favorites }: HomeProps) {
function Home({ categories, favorites, currentCategory }: HomeProps) {
const router = useRouter();
const { data } = useSession({ required: true });
const [categoryActive, setCategoryActive] = useState<Category | null>(
categories?.[0]
currentCategory || categories?.[0]
);
const handleSelectCategory = (category: Category) =>
const handleSelectCategory = (category: Category) => {
setCategoryActive(category);
router.push(`/?categoryId=${category.id}`);
};
return (
<div className="App">
@@ -37,7 +43,9 @@ function Home({ categories, favorites }: HomeProps) {
);
}
export async function getServerSideProps() {
export async function getServerSideProps({ query }) {
const queryCategoryId = (query?.categoryId as string) || "";
const categoriesDB = await prisma.category.findMany({
include: { links: true },
});
@@ -57,10 +65,17 @@ export async function getServerSideProps() {
};
}
const currentCategory = categories.find(
(c) => c.id === Number(queryCategoryId)
);
return {
props: {
categories: JSON.parse(JSON.stringify(categories)),
favorites: JSON.parse(JSON.stringify(favorites)),
currentCategory: currentCategory
? JSON.parse(JSON.stringify(currentCategory))
: null,
},
};
}

View File

@@ -1,108 +1,132 @@
import axios, { AxiosResponse } from 'axios';
import { useRouter } from 'next/router';
import nProgress from 'nprogress';
import { useMemo, useState } from 'react';
import axios from "axios";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import { useMemo, useState } from "react";
import Checkbox from '../../components/Checkbox';
import FormLayout from '../../components/FormLayout';
import Selector from '../../components/Selector';
import TextBox from '../../components/TextBox';
import Checkbox from "../../components/Checkbox";
import FormLayout from "../../components/FormLayout";
import Selector from "../../components/Selector";
import TextBox from "../../components/TextBox";
import { Category, Link } from '../../types';
import { BuildCategory, HandleAxiosError, IsValidURL } from '../../utils/front';
import useAutoFocus from "../../hooks/useAutoFocus";
import { prisma } from '../../utils/back';
import { Category, Link } from "../../types";
import { prisma } from "../../utils/back";
import { BuildCategory, HandleAxiosError, IsValidURL } from "../../utils/front";
import styles from '../../styles/create.module.scss';
import styles from "../../styles/create.module.scss";
function CreateLink({ categories }: { categories: Category[]; }) {
const { query } = useRouter();
const categoryIdQuery = Number(query.categoryId?.[0]);
function CreateLink({ categories }: { categories: Category[] }) {
const autoFocusRef = useAutoFocus();
const router = useRouter();
const categoryIdQuery = router.query?.categoryId as string;
const [name, setName] = useState<Link['name']>('');
const [url, setUrl] = useState<Link['url']>('');
const [favorite, setFavorite] = useState<Link['favorite']>(false);
const [categoryId, setCategoryId] = useState<Link['category']['id']>(categoryIdQuery || categories?.[0].id || null);
const [name, setName] = useState<Link["name"]>("");
const [url, setUrl] = useState<Link["url"]>("");
const [favorite, setFavorite] = useState<Link["favorite"]>(false);
const [categoryId, setCategoryId] = useState<Link["category"]["id"]>(
Number(categoryIdQuery) || categories?.[0].id || null
);
const [error, setError] = useState<string>(null);
const [success, setSuccess] = useState<string>(null);
const [submitted, setSubmitted] = useState<boolean>(false);
const [error, setError] = useState<string>(null);
const [submitted, setSubmitted] = useState<boolean>(false);
const canSubmit = useMemo<boolean>(
() => name !== '' && IsValidURL(url) && favorite !== null && categoryId !== null && !submitted,
[name, url, favorite, categoryId, submitted]
);
const canSubmit = useMemo<boolean>(
() =>
name !== "" &&
IsValidURL(url) &&
favorite !== null &&
categoryId !== null &&
!submitted,
[name, url, favorite, categoryId, submitted]
);
const handleSubmit = async (event) => {
event.preventDefault();
setSuccess(null);
setError(null);
setSubmitted(false);
nProgress.start();
const handleSubmit = async (event) => {
event.preventDefault();
setError(null);
setSubmitted(true);
nProgress.start();
try {
const payload = { name, url, favorite, categoryId };
const { data }: AxiosResponse<any> = await axios.post('/api/link/create', payload);
setSuccess(data?.success || 'Lien modifié avec succès');
} catch (error) {
setError(HandleAxiosError(error));
} finally {
setSubmitted(true);
nProgress.done();
}
try {
const payload = { name, url, favorite, categoryId };
const { data } = await axios.post("/api/link/create", payload);
router.push(`/?categoryId=${data?.categoryId}`);
setSubmitted(true);
} catch (error) {
setError(HandleAxiosError(error));
} finally {
setSubmitted(true);
nProgress.done();
}
};
return (<>
<FormLayout
title='Créer un lien'
errorMessage={error}
successMessage={success}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name='name'
label='Nom'
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles['input-field']}
placeholder='Nom du lien'
/>
<TextBox
name='url'
label='URL'
onChangeCallback={(value) => setUrl(value)}
value={url}
fieldClass={styles['input-field']}
placeholder='https://www.example.org/'
/>
<Selector
name='category'
label='Catégorie'
value={categoryId}
onChangeCallback={(value: number) => setCategoryId(value)}
options={categories.map(({ id, name }) => ({ label: name, value: id }))}
/>
<Checkbox
name='favorite'
isChecked={favorite}
onChangeCallback={(value) => setFavorite(value)}
label='Favoris'
/>
</FormLayout>
</>);
return (
<>
<FormLayout
title="Créer un lien"
categoryId={categoryIdQuery}
errorMessage={error}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name="name"
label="Nom"
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles["input-field"]}
placeholder="Nom du lien"
innerRef={autoFocusRef}
/>
<TextBox
name="url"
label="URL"
onChangeCallback={(value) => setUrl(value)}
value={url}
fieldClass={styles["input-field"]}
placeholder="https://www.example.org/"
/>
<Selector
name="category"
label="Catégorie"
value={categoryId}
onChangeCallback={(value: number) => setCategoryId(value)}
options={categories.map(({ id, name }) => ({
label: name,
value: id,
}))}
/>
<Checkbox
name="favorite"
isChecked={favorite}
onChangeCallback={(value) => setFavorite(value)}
label="Favoris"
/>
</FormLayout>
</>
);
}
CreateLink.authRequired = true;
export default CreateLink;
export async function getServerSideProps() {
const categoriesDB = await prisma.category.findMany();
const categories = categoriesDB.map((categoryDB) => BuildCategory(categoryDB));
const categoriesDB = await prisma.category.findMany();
const categories = categoriesDB.map((categoryDB) =>
BuildCategory(categoryDB)
);
if (categories.length === 0) {
return {
props: {
categories: JSON.parse(JSON.stringify(categories))
}
}
}
redirect: {
destination: "/",
},
};
}
return {
props: {
categories: JSON.parse(JSON.stringify(categories)),
},
};
}

View File

@@ -1,129 +1,167 @@
import { useEffect, useState } from 'react';
import axios from "axios";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import { useMemo, useState } from "react";
import axios, { AxiosResponse } from 'axios';
import nProgress from 'nprogress';
import Checkbox from "../../../components/Checkbox";
import FormLayout from "../../../components/FormLayout";
import Selector from "../../../components/Selector";
import TextBox from "../../../components/TextBox";
import FormLayout from '../../../components/FormLayout';
import TextBox from '../../../components/TextBox';
import Selector from '../../../components/Selector';
import Checkbox from '../../../components/Checkbox';
import useAutoFocus from "../../../hooks/useAutoFocus";
import styles from '../../../styles/create.module.scss';
import { Category, Link } from "../../../types";
import { prisma } from "../../../utils/back";
import {
BuildCategory,
BuildLink,
HandleAxiosError,
IsValidURL,
} from "../../../utils/front";
import { Category, Link } from '../../../types';
import { BuildCategory, BuildLink, HandleAxiosError, IsValidURL } from '../../../utils/front';
import styles from "../../../styles/create.module.scss";
import { prisma } from '../../../utils/back';
function EditLink({
link,
categories,
}: {
link: Link;
categories: Category[];
}) {
const autoFocusRef = useAutoFocus();
const router = useRouter();
function EditLink({ link, categories }: { link: Link; categories: Category[]; }) {
const [name, setName] = useState<string>(link.name);
const [url, setUrl] = useState<string>(link.url);
const [favorite, setFavorite] = useState<boolean>(link.favorite);
const [categoryId, setCategoryId] = useState<number | null>(link.category?.id || null);
const [name, setName] = useState<string>(link.name);
const [url, setUrl] = useState<string>(link.url);
const [favorite, setFavorite] = useState<boolean>(link.favorite);
const [categoryId, setCategoryId] = useState<number | null>(
link.category?.id || null
);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const [canSubmit, setCanSubmit] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [submitted, setSubmitted] = useState<boolean>(false);
useEffect(() => {
if (name !== link.name || url !== link.url || favorite !== link.favorite || categoryId !== link.category.id) {
if (name !== '' && IsValidURL(url) && favorite !== null && categoryId !== null) {
setCanSubmit(true);
} else {
setCanSubmit(false);
}
} else {
setCanSubmit(false);
}
}, [name, url, favorite, categoryId, link]);
const canSubmit = useMemo<boolean>(() => {
const isFormEdited =
name !== link.name ||
url !== link.url ||
favorite !== link.favorite ||
categoryId !== link.category.id;
const isFormValid =
name !== "" &&
IsValidURL(url) &&
favorite !== null &&
categoryId !== null;
return isFormEdited && isFormValid && !submitted;
}, [
categoryId,
favorite,
link.category.id,
link.favorite,
link.name,
link.url,
name,
submitted,
url,
]);
const handleSubmit = async (event) => {
event.preventDefault();
setSuccess(null);
setError(null);
setCanSubmit(false);
nProgress.start();
const handleSubmit = async (event) => {
event.preventDefault();
setError(null);
setSubmitted(true);
nProgress.start();
try {
const payload = { name, url, favorite, categoryId };
const { data }: AxiosResponse<any> = await axios.put(`/api/link/edit/${link.id}`, payload);
setSuccess(data?.success || 'Lien modifié avec succès');
} catch (error) {
setError(HandleAxiosError(error));
} finally {
setCanSubmit(true);
nProgress.done();
}
try {
const payload = { name, url, favorite, categoryId };
const { data } = await axios.put(`/api/link/edit/${link.id}`, payload);
router.push(`/?categoryId=${data?.categoryId}`);
setSubmitted(true);
} catch (error) {
setError(HandleAxiosError(error));
setSubmitted(false);
} finally {
nProgress.done();
}
};
return (<>
<FormLayout
title='Modifier un lien'
errorMessage={error}
successMessage={success}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name='name'
label='Nom'
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles['input-field']}
placeholder={`Nom original : ${link.name}`}
/>
<TextBox
name='url'
label='URL'
onChangeCallback={(value) => setUrl(value)}
value={url}
fieldClass={styles['input-field']}
placeholder={`URL original : ${link.url}`}
/>
<Selector
name='category'
label='Catégorie'
value={categoryId}
onChangeCallback={(value: number) => setCategoryId(value)}
options={categories.map(({ id, name }) => ({ label: name, value: id }))}
/>
<Checkbox
name='favorite'
isChecked={favorite}
onChangeCallback={(value) => setFavorite(value)}
label='Favoris'
/>
</FormLayout>
</>);
return (
<>
<FormLayout
title="Modifier un lien"
errorMessage={error}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
>
<TextBox
name="name"
label="Nom"
onChangeCallback={(value) => setName(value)}
value={name}
fieldClass={styles["input-field"]}
placeholder={`Nom original : ${link.name}`}
innerRef={autoFocusRef}
/>
<TextBox
name="url"
label="URL"
onChangeCallback={(value) => setUrl(value)}
value={url}
fieldClass={styles["input-field"]}
placeholder={`URL original : ${link.url}`}
/>
<Selector
name="category"
label="Catégorie"
value={categoryId}
onChangeCallback={(value: number) => setCategoryId(value)}
options={categories.map(({ id, name }) => ({
label: name,
value: id,
}))}
/>
<Checkbox
name="favorite"
isChecked={favorite}
onChangeCallback={(value) => setFavorite(value)}
label="Favoris"
/>
</FormLayout>
</>
);
}
EditLink.authRequired = true;
export default EditLink;
export async function getServerSideProps({ query }) {
const { lid } = query;
const { lid } = query;
const categoriesDB = await prisma.category.findMany();
const categories = categoriesDB.map((categoryDB) => BuildCategory(categoryDB));
const categoriesDB = await prisma.category.findMany();
const categories = categoriesDB.map((categoryDB) =>
BuildCategory(categoryDB)
);
const linkDB = await prisma.link.findFirst({
where: { id: Number(lid) },
include: { category: true }
});
const linkDB = await prisma.link.findFirst({
where: { id: Number(lid) },
include: { category: true },
});
if (!linkDB) {
return {
redirect: {
destination: '/'
}
}
}
const link = BuildLink(linkDB, { categoryId: linkDB.categoryId, categoryName: linkDB.category.name });
if (!linkDB) {
return {
props: {
link: JSON.parse(JSON.stringify(link)),
categories: JSON.parse(JSON.stringify(categories))
}
}
}
redirect: {
destination: "/",
},
};
}
const link = BuildLink(linkDB, {
categoryId: linkDB.categoryId,
categoryName: linkDB.category.name,
});
return {
props: {
link: JSON.parse(JSON.stringify(link)),
categories: JSON.parse(JSON.stringify(categories)),
},
};
}

View File

@@ -1,104 +1,120 @@
import axios, { AxiosResponse } from 'axios';
import nProgress from 'nprogress';
import { useState } from 'react';
import axios from "axios";
import { useRouter } from "next/router";
import nProgress from "nprogress";
import { useMemo, useState } from "react";
import Checkbox from '../../../components/Checkbox';
import FormLayout from '../../../components/FormLayout';
import TextBox from '../../../components/TextBox';
import Checkbox from "../../../components/Checkbox";
import FormLayout from "../../../components/FormLayout";
import TextBox from "../../../components/TextBox";
import { Link } from '../../../types';
import { BuildLink, HandleAxiosError } from '../../../utils/front';
import { prisma } from '../../../utils/back';
import { Link } from "../../../types";
import { prisma } from "../../../utils/back";
import { BuildLink, HandleAxiosError } from "../../../utils/front";
import styles from '../../../styles/create.module.scss';
import styles from "../../../styles/create.module.scss";
function RemoveLink({ link }: { link: Link; }) {
const [canSubmit, setCanSubmit] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
function RemoveLink({ link }: { link: Link }) {
const router = useRouter();
const handleSubmit = async (event) => {
event.preventDefault();
setSuccess(null);
setError(null);
setCanSubmit(false);
nProgress.start();
const [error, setError] = useState<string | null>(null);
const [confirmDelete, setConfirmDelete] = useState<boolean>(false);
const [submitted, setSubmitted] = useState<boolean>(false);
try {
const { data }: AxiosResponse<any> = await axios.delete(`/api/link/remove/${link.id}`);
setSuccess(data?.success || 'Lien supprimé avec succès');
setCanSubmit(false);
} catch (error) {
setError(HandleAxiosError(error));
setCanSubmit(true);
} finally {
nProgress.done();
}
const canSubmit = useMemo<boolean>(
() => confirmDelete && !submitted,
[confirmDelete, submitted]
);
const handleSubmit = async (event) => {
event.preventDefault();
setError(null);
nProgress.start();
try {
const { data } = await axios.delete(`/api/link/remove/${link.id}`);
router.push(`/?categoryId=${data?.categoryId}`);
setSubmitted(true);
} catch (error) {
setError(HandleAxiosError(error));
} finally {
nProgress.done();
}
};
return (<>
<FormLayout
title='Supprimer un lien'
errorMessage={error}
successMessage={success}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
classBtnConfirm='red-btn'
textBtnConfirm='Supprimer'
>
<TextBox
name='name'
label='Nom'
value={link.name}
fieldClass={styles['input-field']}
disabled={true}
/>
<TextBox
name='url'
label='URL'
value={link.url}
fieldClass={styles['input-field']}
disabled={true}
/>
<TextBox
name='category'
label='Catégorie'
value={link.category.name}
fieldClass={styles['input-field']}
disabled={true}
/>
<Checkbox
name='favorite'
label='Favoris'
isChecked={link.favorite}
disabled={true}
/>
</FormLayout>
</>);
return (
<>
<FormLayout
title="Supprimer un lien"
categoryId={link.category.id.toString()}
errorMessage={error}
canSubmit={canSubmit}
handleSubmit={handleSubmit}
classBtnConfirm="red-btn"
textBtnConfirm="Supprimer"
>
<TextBox
name="name"
label="Nom"
value={link.name}
fieldClass={styles["input-field"]}
disabled={true}
/>
<TextBox
name="url"
label="URL"
value={link.url}
fieldClass={styles["input-field"]}
disabled={true}
/>
<TextBox
name="category"
label="Catégorie"
value={link.category.name}
fieldClass={styles["input-field"]}
disabled={true}
/>
<Checkbox
name="favorite"
label="Favoris"
isChecked={link.favorite}
disabled={true}
/>
<Checkbox
name="confirm-delete"
label="Confirmer la suppression ?"
isChecked={confirmDelete}
onChangeCallback={(checked) => setConfirmDelete(checked)}
/>
</FormLayout>
</>
);
}
RemoveLink.authRequired = true;
export default RemoveLink;
export async function getServerSideProps({ query }) {
const { lid } = query;
const linkDB = await prisma.link.findFirst({
where: { id: Number(lid) },
include: { category: true }
});
const { lid } = query;
const linkDB = await prisma.link.findFirst({
where: { id: Number(lid) },
include: { category: true },
});
if (!linkDB) {
return {
redirect: {
destination: '/'
}
}
}
const link = BuildLink(linkDB, { categoryId: linkDB.categoryId, categoryName: linkDB.category.name });
if (!linkDB) {
return {
props: {
link: JSON.parse(JSON.stringify(link))
}
}
}
redirect: {
destination: "/",
},
};
}
const link = BuildLink(linkDB, {
categoryId: linkDB.categoryId,
categoryName: linkDB.category.name,
});
return {
props: {
link: JSON.parse(JSON.stringify(link)),
},
};
}