Skip to content

Commit

Permalink
refactor: cleanup add manga code
Browse files Browse the repository at this point in the history
  • Loading branch information
oae committed Oct 13, 2022
1 parent fb9dc13 commit 93154a5
Show file tree
Hide file tree
Showing 10 changed files with 576 additions and 535 deletions.
163 changes: 163 additions & 0 deletions src/components/addManga/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { Button, Code, createStyles, Group, LoadingOverlay, Text } from '@mantine/core';
import { useForm, zodResolver } from '@mantine/form';
import { showNotification } from '@mantine/notifications';
import { IconCheck, IconX } from '@tabler/icons';
import { useState } from 'react';
import { z } from 'zod';
import { trpc } from '../../utils/trpc';
import AddMangaSteps from './steps';

const useStyles = createStyles((theme) => ({
form: {
display: 'flex',
flexDirection: 'column',
minHeight: 300,
padding: theme.spacing.xs,
},
buttonGroup: {
position: 'fixed',
bottom: '19px',
right: '55px',
width: 'calc(100% - 55px)',
height: '50px',
background: 'white',
},
}));

const schema = z.object({
source: z.string().min(1, { message: 'You must select a source' }),
query: z.string().min(1, { message: 'Cannot be empty' }),
mangaOrder: z.number().gte(0, { message: 'Please select a manga' }),
mangaTitle: z.string().min(1, { message: 'Please select a manga' }),
interval: z.string().min(1, { message: 'Please select an interval' }),
});

export type FormType = z.TypeOf<typeof schema>;

export function AddMangaForm({ onClose }: { onClose: () => void }) {
const [active, setActive] = useState(0);
const [visible, setVisible] = useState(false);
const { classes } = useStyles();

const mutation = trpc.manga.add.useMutation();

const form = useForm({
validateInputOnBlur: ['source', 'query', 'interval'],
initialValues: {
source: '',
query: '',
mangaOrder: -1,
mangaTitle: '',
interval: '',
},
validate: zodResolver(schema),
});

const nextStep = () => {
if (active === 0) {
form.validateField('source');
if (!form.isValid('source')) {
return;
}
}
if (active === 1) {
form.validateField('mangaTitle');
form.validateField('mangaOrder');
if (!form.isValid('mangaOrder') || !form.isValid('mangaTitle')) {
return;
}
}
if (active === 2) {
form.validateField('interval');
if (!form.isValid('interval')) {
return;
}
}
form.clearErrors();
setActive((current) => (current < 3 ? current + 1 : current));
};

const prevStep = () => {
if (active === 0) {
setVisible(false);
onClose();
form.reset();
}
if (active === 1) {
form.setFieldValue('query', '');
form.setFieldValue('source', '');
}
if (active === 2) {
form.setFieldValue('query', '');
form.setFieldValue('mangaOrder', -1);
form.setFieldValue('mangaTitle', '');
}
if (active === 3) {
form.setFieldValue('interval', '');
}
setActive((current) => (current > 0 ? current - 1 : current));
};

const onSubmit = form.onSubmit(async (values) => {
setVisible((v) => !v);
const { mangaOrder, mangaTitle, query, source, interval } = values;
try {
await mutation.mutateAsync({
keyword: query,
order: mangaOrder,
title: mangaTitle,
interval,
source,
});
} catch (err) {
showNotification({
icon: <IconX size={18} />,
color: 'red',
autoClose: true,
title: 'Manga',
message: (
<Text>
Failed to create add manga. <Code color="red">{`${err}`}</Code>
</Text>
),
});

form.reset();
onClose();
setVisible((v) => !v);
return;
}
form.reset();
onClose();
setVisible((v) => !v);
showNotification({
icon: <IconCheck size={18} />,
color: 'teal',
autoClose: true,
title: 'Manga',
message: (
<Text>
<Code color="blue">{values.mangaTitle}</Code> is added to library
</Text>
),
});
});

return (
<form className={classes.form} onSubmit={onSubmit}>
<AddMangaSteps form={form} active={active} setActive={setActive} />
<LoadingOverlay visible={visible} />
<Group position="right" className={classes.buttonGroup}>
<Button variant="default" onClick={prevStep}>
{active === 0 ? 'Cancel' : 'Back'}
</Button>
<Button hidden={active !== 3} type="submit">
Add
</Button>
<Button hidden={active === 3} onClick={nextStep}>
Next step
</Button>
</Group>
</form>
);
}
55 changes: 55 additions & 0 deletions src/components/addManga/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createStyles, Paper, Tooltip } from '@mantine/core';
import { useModals } from '@mantine/modals';
import { IconPlus } from '@tabler/icons';
import { AddMangaForm } from './form';

const useStyles = createStyles((theme) => ({
card: {
height: 350,
width: 210,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: theme.colors.gray[4],
cursor: 'pointer',

transition: 'transform 150ms ease, box-shadow 150ms ease',

'&:hover': {
transform: 'scale(1.01)',
boxShadow: theme.shadows.md,
},
},
}));

export function AddManga({ onAdd }: { onAdd: () => void }) {
const { classes } = useStyles();
const modals = useModals();

const openCreateModal = () => {
const id = modals.openModal({
overflow: 'inside',
trapFocus: true,
size: 'xl',
closeOnClickOutside: false,
title: 'Add a new manga',
centered: true,
children: (
<AddMangaForm
onClose={() => {
modals.closeModal(id);
onAdd();
}}
/>
),
});
};

return (
<Tooltip label="Add a new manga" position="bottom">
<Paper shadow="lg" p="md" radius="md" className={classes.card} onClick={openCreateModal}>
<IconPlus color="darkblue" opacity={0.5} size={96} />
</Paper>
</Tooltip>
);
}
File renamed without changes.
48 changes: 48 additions & 0 deletions src/components/addManga/steps/downloadStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Box, LoadingOverlay, Select, Stack, TextInput } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form';
import { IconFolderPlus } from '@tabler/icons';
import { trpc } from '../../../utils/trpc';
import type { FormType } from '../form';

const availableIntervals = ['daily', 'hourly', 'weekly', 'minutely'];

export function DownloadStep({ form }: { form: UseFormReturnType<FormType> }) {
const libraryQuery = trpc.library.query.useQuery();

const libraryPath = libraryQuery.data?.path;

const intervalSelectData = availableIntervals.map((k) => ({ label: k, value: k }));

if (libraryQuery.isLoading) {
return <LoadingOverlay visible />;
}

const sanitizeMangaName = form.values.mangaTitle
.replaceAll(/[\\/<>:;"'|?!*{}#%&^+,~\s]/g, '_')
.replaceAll(/__+/g, '_')
.replaceAll(/^[_\-.]+|[_\-.]+$/g, '_');

const downloadPath = `${libraryPath}/${sanitizeMangaName}`;

return (
<Box>
<Stack>
<Select
data-autofocus
size="sm"
data={intervalSelectData}
label="Download Interval"
placeholder="Select an interval"
{...form.getInputProps('interval')}
/>
<TextInput
label="Location"
size="sm"
disabled
icon={<IconFolderPlus size={18} stroke={1.5} />}
value={downloadPath}
/>
</Stack>
</Box>
);
}
80 changes: 80 additions & 0 deletions src/components/addManga/steps/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { createStyles, Stepper } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form';
import { Dispatch, SetStateAction } from 'react';
import type { FormType } from '../form';
import { DownloadStep } from './downloadStep';
import { ReviewStep } from './reviewStep';
import { SearchStep } from './searchStep';
import { SourceStep } from './sourceStep';

const useStyles = createStyles((_theme) => ({
stepper: {
flexGrow: 1,
},
stepBody: {
marginTop: 30,
marginBottom: 30,
},
buttonGroup: {
position: 'fixed',
bottom: '19px',
right: '55px',
width: 'calc(100% - 55px)',
height: '50px',
background: 'white',
},
}));

export default function AddMangaSteps({
form,
active,
setActive,
}: {
form: UseFormReturnType<FormType>;
active: number;
setActive: Dispatch<SetStateAction<number>>;
}) {
const { classes } = useStyles();

return (
<Stepper
classNames={{
root: classes.stepper,
content: classes.stepBody,
}}
active={active}
onStepClick={setActive}
breakpoint="sm"
m="xl"
>
<Stepper.Step
label="Source"
description={form.values.source || 'Select a source'}
allowStepSelect={active > 0}
color={active > 0 ? 'teal' : 'blue'}
>
<SourceStep form={form} />
</Stepper.Step>
<Stepper.Step
label="Manga"
description={form.values.mangaTitle || 'Search for manga'}
allowStepSelect={active > 1}
color={active > 1 ? 'teal' : 'blue'}
>
<SearchStep form={form} />
</Stepper.Step>
<Stepper.Step
label="Download"
description={form.values.interval || 'Select an interval'}
allowStepSelect={active > 2}
color={active > 2 ? 'teal' : 'blue'}
>
<DownloadStep form={form} />
</Stepper.Step>

<Stepper.Completed>
<ReviewStep form={form} />
</Stepper.Completed>
</Stepper>
);
}
Loading

0 comments on commit 93154a5

Please sign in to comment.