mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-10 15:35:35 +00:00
feat: add optionnal category description
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `category` ADD COLUMN `description` VARCHAR(255) NULL;
|
||||||
@@ -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])
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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) => (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user