feat: add optionnal category description

This commit is contained in:
Sonny
2024-04-10 18:26:23 +02:00
parent 883b36c93e
commit 42a5dabec1
12 changed files with 73 additions and 34 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `category` ADD COLUMN `description` VARCHAR(255) NULL;

View File

@@ -31,6 +31,7 @@ model User {
model Category { model Category {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String @db.VarChar(255) name String @db.VarChar(255)
description String? @db.VarChar(255)
links Link[] links Link[]
author User @relation(fields: [authorId], references: [id]) author User @relation(fields: [authorId], references: [id])
@@ -47,7 +48,7 @@ model Category {
model Link { model Link {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String @db.VarChar(255) name String @db.VarChar(255)
description String? @db.VarChar(255) description String @db.VarChar(255)
url String @db.Text url String @db.Text
category Category @relation(fields: [categoryId], references: [id]) category Category @relation(fields: [categoryId], references: [id])

View File

@@ -18,6 +18,7 @@
"categories": "Categories", "categories": "Categories",
"category": "Category", "category": "Category",
"name": "Category name", "name": "Category name",
"description": "Category description",
"create": "Create a category", "create": "Create a category",
"edit": "Edit a category", "edit": "Edit a category",
"remove": "Delete a category", "remove": "Delete a category",

View File

@@ -18,6 +18,7 @@
"categories": "Catégories", "categories": "Catégories",
"category": "Catégorie", "category": "Catégorie",
"name": "Nom de la catégorie", "name": "Nom de la catégorie",
"description": "Description de la catégorie",
"create": "Créer une catégorie", "create": "Créer une catégorie",
"edit": "Modifier une catégorie", "edit": "Modifier une catégorie",
"remove": "Supprimer une catégorie", "remove": "Supprimer une catégorie",

View File

@@ -66,6 +66,11 @@ export default function Links({
/> />
</span> </span>
</h2> </h2>
{activeCategory.description && (
<p className={styles['category-description']}>
{activeCategory.description}
</p>
)}
{links.length !== 0 ? ( {links.length !== 0 ? (
<ul className={clsx(styles['links'], 'reset')}> <ul className={clsx(styles['links'], 'reset')}>
{links.map((link, index) => ( {links.map((link, index) => (

View File

@@ -29,24 +29,15 @@
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
& h2 { & .category-description {
color: $blue; font-size: 0.85em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
font-weight: 500;
& svg {
display: flex;
}
& .links-count {
color: $grey;
font-weight: 300;
font-size: 0.8em;
}
} }
} }
.category-header { .category-header {
color: $blue;
font-weight: 500;
display: flex; display: flex;
gap: 0.4em; gap: 0.4em;
align-items: center; align-items: center;
@@ -65,6 +56,16 @@
gap: 0.5em; gap: 0.5em;
align-items: center; align-items: center;
} }
& svg {
display: flex;
}
& .links-count {
color: $grey;
font-weight: 300;
font-size: 0.8em;
}
} }
.links { .links {

View File

@@ -5,6 +5,7 @@ const CategoryBodySchema = object({
.trim() .trim()
.required('Category name is required') .required('Category name is required')
.max(128, 'Category name is too long'), .max(128, 'Category name is too long'),
description: string().trim().max(255, 'Category description is too long'),
nextId: number().required().nullable(), nextId: number().required().nullable(),
}).typeError('Missing request Body'); }).typeError('Missing request Body');

View File

@@ -16,7 +16,9 @@ export default apiHandler({
async function editCategory({ req, res, user }) { async function editCategory({ req, res, user }) {
const { cid } = await CategoryQuerySchema.validate(req.query); const { cid } = await CategoryQuerySchema.validate(req.query);
const { name, nextId } = await CategoryBodySchema.validate(req.body); const { name, description, nextId } = await CategoryBodySchema.validate(
req.body,
);
const userId = user.id as User['id']; const userId = user.id as User['id'];
const category = await getUserCategory(user, cid); const category = await getUserCategory(user, cid);
@@ -104,6 +106,7 @@ async function editCategory({ req, res, user }) {
}, },
data: { data: {
name, name,
description,
nextId: category.nextId, nextId: category.nextId,
}, },
}); });

View File

@@ -18,7 +18,7 @@ async function getCategories({ res, user }) {
} }
async function createCategory({ req, res, user }) { async function createCategory({ req, res, user }) {
const { name } = await CategoryBodySchema.validate(req.body); const { name, description } = await CategoryBodySchema.validate(req.body);
const category = await getUserCategoryByName(user, name); const category = await getUserCategoryByName(user, name);
if (category) { if (category) {
@@ -36,7 +36,7 @@ async function createCategory({ req, res, user }) {
}); });
const categoryCreated = await prisma.category.create({ const categoryCreated = await prisma.category.create({
data: { name, authorId: user.id }, data: { name, description, authorId: user.id },
}); });
if (lastCategory) { if (lastCategory) {

View File

@@ -23,6 +23,7 @@ export default function PageCreateCategory({
const info = useRouter().query?.info as string; const info = useRouter().query?.info as string;
const [name, setName] = useState<string>(''); const [name, setName] = useState<string>('');
const [description, setDescription] = useState<string>('');
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [submitted, setSubmitted] = useState<boolean>(false); const [submitted, setSubmitted] = useState<boolean>(false);
@@ -40,7 +41,7 @@ export default function PageCreateCategory({
makeRequest({ makeRequest({
url: PATHS.API.CATEGORY, url: PATHS.API.CATEGORY,
method: 'POST', method: 'POST',
body: { name, nextId: null }, body: { name, description, nextId: null },
}) })
.then((data) => .then((data) =>
router.push(`${PATHS.HOME}?categoryId=${data?.categoryId}`), router.push(`${PATHS.HOME}?categoryId=${data?.categoryId}`),
@@ -62,11 +63,20 @@ export default function PageCreateCategory({
<TextBox <TextBox
name='name' name='name'
label={t('common:category.name')} label={t('common:category.name')}
onChangeCallback={(value) => setName(value)} onChangeCallback={setName}
value={name} value={name}
fieldClass={styles['input-field']} fieldClass={styles['input-field']}
placeholder={t('common:category.name')} placeholder={t('common:category.name')}
innerRef={autoFocusRef} innerRef={autoFocusRef}
required
/>
<TextBox
name='description'
label={t('common:category.description')}
onChangeCallback={setDescription}
value={description}
fieldClass={styles['input-field']}
placeholder={t('common:category.description')}
/> />
</FormLayout> </FormLayout>
</PageTransition> </PageTransition>

View File

@@ -23,14 +23,17 @@ export default function PageEditCategory({
const autoFocusRef = useAutoFocus(); const autoFocusRef = useAutoFocus();
const [name, setName] = useState<string>(category.name); const [name, setName] = useState<string>(category.name);
const [description, setDescription] = useState<string>(category.description);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [submitted, setSubmitted] = useState<boolean>(false); const [submitted, setSubmitted] = useState<boolean>(false);
const canSubmit = useMemo<boolean>( const canSubmit = useMemo<boolean>(() => {
() => name !== category.name && name !== '' && !submitted, const isFormEdited =
[category.name, name, submitted], name !== category.name || description !== category.description;
); const isFormValid = name !== '';
return isFormEdited && isFormValid && !submitted;
}, [category.description, category.name, description, name, submitted]);
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
@@ -40,7 +43,7 @@ export default function PageEditCategory({
makeRequest({ makeRequest({
url: `${PATHS.API.CATEGORY}/${category.id}`, url: `${PATHS.API.CATEGORY}/${category.id}`,
method: 'PUT', method: 'PUT',
body: { name, nextId: category.nextId }, body: { name, description, nextId: category.nextId },
}) })
.then((data) => .then((data) =>
router.push(`${PATHS.HOME}?categoryId=${data?.categoryId}`), router.push(`${PATHS.HOME}?categoryId=${data?.categoryId}`),
@@ -60,11 +63,20 @@ export default function PageEditCategory({
<TextBox <TextBox
name='name' name='name'
label={t('common:category.name')} label={t('common:category.name')}
onChangeCallback={(value) => setName(value)} onChangeCallback={setName}
value={name} value={name}
fieldClass={styles['input-field']} fieldClass={styles['input-field']}
placeholder={`${t('common:category.name')} : ${category.name}`} placeholder={`${t('common:category.name')} : ${category.name}`}
innerRef={autoFocusRef} innerRef={autoFocusRef}
required
/>
<TextBox
name='description'
label={t('common:category.description')}
onChangeCallback={setDescription}
value={description}
fieldClass={styles['input-field']}
placeholder={t('common:category.description')}
/> />
</FormLayout> </FormLayout>
</PageTransition> </PageTransition>

View File

@@ -104,7 +104,9 @@ export default function PageEditLink({
onChangeCallback={setDescription} onChangeCallback={setDescription}
value={description} value={description}
fieldClass={styles['input-field']} fieldClass={styles['input-field']}
placeholder={`${t('common:link.description')} : ${link.description}`} placeholder={`${t('common:link.description')}${
` : ${link.description}` ?? ''
}`}
/> />
<Selector <Selector
name='category' name='category'