feat: add delete collection form and controller method

This commit is contained in:
Sonny
2024-05-19 16:23:09 +02:00
committed by Sonny
parent 32133be8b0
commit 50030df9a6
10 changed files with 128 additions and 29 deletions

View File

@@ -2,6 +2,7 @@ import Collection from '#models/collection';
import User from '#models/user'; import User from '#models/user';
import { import {
createCollectionValidator, createCollectionValidator,
deleteCollectionValidator,
updateCollectionValidator, updateCollectionValidator,
} from '#validators/collection'; } from '#validators/collection';
import type { HttpContext } from '@adonisjs/core/http'; import type { HttpContext } from '@adonisjs/core/http';
@@ -82,6 +83,28 @@ export default class CollectionsController {
return this.redirectToCollectionId(response, params.id); return this.redirectToCollectionId(response, params.id);
} }
async showDeletePage({ auth, request, inertia, response }: HttpContext) {
const collectionId = request.qs()?.collectionId;
if (!collectionId) {
return response.redirectToNamedRoute('dashboard');
}
const collection = await this.getCollectionById(
collectionId,
auth.user!.id
);
return inertia.render('collections/delete', {
collection,
});
}
async delete({ request, auth, response }: HttpContext) {
const { params } = await request.validateUsing(deleteCollectionValidator);
const collection = await this.getCollectionById(params.id, auth.user!.id);
await collection.delete();
return response.redirectToNamedRoute('dashboard');
}
/** /**
* Get collection by id. * Get collection by id.
* *

View File

@@ -1,6 +1,10 @@
import vine, { SimpleMessagesProvider } from '@vinejs/vine'; import vine, { SimpleMessagesProvider } from '@vinejs/vine';
import { Visibility } from '../enums/visibility.js'; import { Visibility } from '../enums/visibility.js';
const params = vine.object({
id: vine.string().trim(),
});
export const createCollectionValidator = vine.compile( export const createCollectionValidator = vine.compile(
vine.object({ vine.object({
name: vine.string().trim().minLength(1).maxLength(254), name: vine.string().trim().minLength(1).maxLength(254),
@@ -17,9 +21,13 @@ export const updateCollectionValidator = vine.compile(
visibility: vine.enum(Visibility), visibility: vine.enum(Visibility),
nextId: vine.string().optional(), nextId: vine.string().optional(),
params: vine.object({ params,
id: vine.string().trim(), })
}), );
export const deleteCollectionValidator = vine.compile(
vine.object({
params,
}) })
); );

View File

@@ -1,27 +1,31 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
const Button = styled.button(({ theme }) => ({ const Button = styled.button<{ danger?: boolean }>(({ theme, danger }) => {
cursor: 'pointer', const btnColor = !danger ? theme.colors.primary : theme.colors.lightRed;
width: '100%', const btnDarkColor = !danger ? theme.colors.darkBlue : theme.colors.lightRed;
textTransform: 'uppercase', return {
fontSize: '14px', cursor: 'pointer',
color: theme.colors.white, width: '100%',
background: theme.colors.primary, textTransform: 'uppercase',
padding: '0.75em', fontSize: '14px',
border: `1px solid ${theme.colors.primary}`,
borderRadius: theme.border.radius,
transition: theme.transition.delay,
'&:disabled': {
cursor: 'not-allowed',
opacity: '0.5',
},
'&:not(:disabled):hover': {
boxShadow: `${theme.colors.darkBlue} 0 0 3px 1px`,
background: theme.colors.darkBlue,
color: theme.colors.white, color: theme.colors.white,
}, background: btnColor,
})); padding: '0.75em',
border: `1px solid ${btnColor}`,
borderRadius: theme.border.radius,
transition: theme.transition.delay,
'&:disabled': {
cursor: 'not-allowed',
opacity: '0.5',
},
'&:not(:disabled):hover': {
boxShadow: `${btnDarkColor} 0 0 3px 1px`,
background: btnDarkColor,
color: theme.colors.white,
},
};
});
export default Button; export default Button;

View File

@@ -14,6 +14,10 @@ const Input = styled.input(({ theme }) => ({
borderBottom: `2px solid ${theme.colors.primary}`, borderBottom: `2px solid ${theme.colors.primary}`,
}, },
'&:disabled': {
opacity: 0.5,
},
'&::placeholder': { '&::placeholder': {
fontStyle: 'italic', fontStyle: 'italic',
color: theme.colors.grey, color: theme.colors.grey,

View File

@@ -18,6 +18,8 @@ interface FormCollectionProps {
disableHomeLink?: boolean; disableHomeLink?: boolean;
data: FormCollectionData; data: FormCollectionData;
errors?: Record<string, Array<string>>; errors?: Record<string, Array<string>>;
disableInputs?: boolean;
submitBtnDanger?: boolean;
setData: (name: string, value: any) => void; setData: (name: string, value: any) => void;
handleSubmit: () => void; handleSubmit: () => void;
@@ -29,6 +31,8 @@ export default function FormCollection({
disableHomeLink, disableHomeLink,
data, data,
errors, errors,
disableInputs = false,
submitBtnDanger = false,
setData, setData,
handleSubmit, handleSubmit,
@@ -48,6 +52,7 @@ export default function FormCollection({
handleSubmit={onSubmit} handleSubmit={onSubmit}
canSubmit={canSubmit} canSubmit={canSubmit}
disableHomeLink={disableHomeLink} disableHomeLink={disableHomeLink}
submitBtnDanger={submitBtnDanger}
> >
<BackToDashboard> <BackToDashboard>
<TextBox <TextBox
@@ -59,6 +64,7 @@ export default function FormCollection({
errors={errors?.name} errors={errors?.name}
required required
autoFocus autoFocus
disabled={disableInputs}
/> />
<TextBox <TextBox
label={t('collection.description')} label={t('collection.description')}
@@ -67,12 +73,14 @@ export default function FormCollection({
onChange={setData} onChange={setData}
value={data.description ?? undefined} value={data.description ?? undefined}
errors={errors?.description} errors={errors?.description}
disabled={disableInputs}
/> />
<Checkbox <Checkbox
label="Public" label="Public"
name="visibility" name="visibility"
onChange={handleOnCheck} onChange={handleOnCheck}
checked={data.visibility === Visibility.PUBLIC} checked={data.visibility === Visibility.PUBLIC}
disabled={disableInputs}
/> />
</BackToDashboard> </BackToDashboard>
</FormLayout> </FormLayout>

View File

@@ -30,16 +30,20 @@ interface FormLayoutProps {
textSubmitButton?: string; textSubmitButton?: string;
disableHomeLink?: boolean; disableHomeLink?: boolean;
submitBtnDanger?: boolean;
collectionId?: string; collectionId?: string;
} }
export default function FormLayout({ export default function FormLayout({
title, title,
children, children,
canSubmit, canSubmit,
handleSubmit, handleSubmit,
textSubmitButton = i18n.t('common:confirm'), textSubmitButton = i18n.t('common:confirm'),
disableHomeLink = false, disableHomeLink = false,
submitBtnDanger = false,
collectionId, collectionId,
}: FormLayoutProps) { }: FormLayoutProps) {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
@@ -50,7 +54,7 @@ export default function FormLayout({
<h2>{title}</h2> <h2>{title}</h2>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
{children} {children}
<Button type="submit" disabled={!canSubmit}> <Button type="submit" disabled={!canSubmit} danger={submitBtnDanger}>
{textSubmitButton} {textSubmitButton}
</Button> </Button>
</Form> </Form>

View File

@@ -0,0 +1,41 @@
import type Collection from '#models/collection';
import { useForm } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
import { useTranslation } from 'react-i18next';
import FormCollection, {
FormCollectionData,
} from '~/components/form/form_collection';
export default function DeleteCollectionPage({
collection,
}: {
collection: Collection;
}) {
const { t } = useTranslation('common');
const { data, setData, submit, processing, errors } =
useForm<FormCollectionData>({
name: collection.name,
description: collection.description,
visibility: collection.visibility,
});
const handleSubmit = () => {
const { method, url } = route('collection.delete', {
params: { id: collection.id },
});
submit(method, url);
};
return (
<FormCollection
title={t('collection.delete')}
canSubmit={!processing}
data={data}
setData={setData}
handleSubmit={handleSubmit}
errors={errors as any}
disableInputs={true}
submitBtnDanger
/>
);
}

View File

@@ -2,6 +2,7 @@ import type Collection from '#models/collection';
import { useForm } from '@inertiajs/react'; import { useForm } from '@inertiajs/react';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import FormCollection, { import FormCollection, {
FormCollectionData, FormCollectionData,
} from '~/components/form/form_collection'; } from '~/components/form/form_collection';
@@ -11,6 +12,7 @@ export default function EditCollectionPage({
}: { }: {
collection: Collection; collection: Collection;
}) { }) {
const { t } = useTranslation('common');
const { data, setData, submit, processing, errors } = const { data, setData, submit, processing, errors } =
useForm<FormCollectionData>({ useForm<FormCollectionData>({
name: collection.name, name: collection.name,
@@ -35,7 +37,7 @@ export default function EditCollectionPage({
return ( return (
<FormCollection <FormCollection
title="Edit a collection" title={t('collection.edit')}
canSubmit={canSubmit} canSubmit={canSubmit}
data={data} data={data}
setData={setData} setData={setData}

View File

@@ -46,7 +46,7 @@ export default function EditLinkPage({
return ( return (
<FormLink <FormLink
title={t('link.create')} title={t('link.edit')}
canSubmit={canSubmit} canSubmit={canSubmit}
data={data} data={data}
setData={setData} setData={setData}

View File

@@ -26,7 +26,12 @@ router
.put('/:id', [CollectionsController, 'update']) .put('/:id', [CollectionsController, 'update'])
.as('collection.edit'); .as('collection.edit');
router.get('/delete', () => 'delete').as('collection.delete-form'); router
.get('/delete', [CollectionsController, 'showDeletePage'])
.as('collection.delete-form');
router
.delete('/:id', [CollectionsController, 'delete'])
.as('collection.delete');
}) })
.prefix('/collections'); .prefix('/collections');
}) })