chore: add all the code from original repo

This commit is contained in:
Riccardo
2024-05-23 16:55:29 +02:00
parent d8f9a215eb
commit 85d66215a7
66 changed files with 16668 additions and 122 deletions

View File

@@ -0,0 +1,101 @@
import {
Box,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField,
Typography
} from '@mui/material';
import { nanoid } from 'nanoid';
import { Dispatch, useEffect, useState } from 'react';
import { useFormState } from 'react-dom';
import { Profiles } from '../../../../data/types';
import { CreateProfileAction } from './actions/CreateProfileAction';
import { ProfilesAction } from './actions/ProfilesAction';
import ProfilesTableRow from './ProfilesTableRow';
interface ProfilesTableProps {
selectedProfile: string | undefined;
setSelectedProfile: Dispatch<string | undefined>;
}
export default function ProfilesTable({
selectedProfile,
setSelectedProfile
}: ProfilesTableProps) {
const [profiles, setProfiles] = useState<Profiles>([]);
const [formKey, setFormKey] = useState(() => nanoid());
const [refreshKey, setRefreshKey] = useState(0);
const [formState, formAction] = useFormState(CreateProfileAction, {
clear: false
});
useEffect(() => {
const fetchProfiles = async () => {
try {
const profiles = await ProfilesAction();
setProfiles(profiles);
} catch (error) {
console.error("Couldn't fetch profiles.");
}
};
if (formState.clear) {
setFormKey(nanoid());
}
fetchProfiles();
}, [formState, refreshKey]);
return (
<Stack
sx={{
spacing: 2,
padding: 2
}}
>
<Typography variant="h6">Profiles</Typography>
<Box>
<form key={formKey} action={formAction}>
<TextField fullWidth name="name" placeholder="Add new profile" />
</form>
<Box
sx={{
maxHeight: 'calc(100vh - 200px)',
overflow: 'auto'
}}
>
<Table
stickyHeader
sx={{
width: '100%'
}}
>
<TableHead>
<TableRow sx={{ '& th': { textAlign: 'center' } }}>
<TableCell>Name</TableCell>
<TableCell>Delete</TableCell>
</TableRow>
</TableHead>
<TableBody>
{profiles &&
profiles.map((profile) => (
<ProfilesTableRow
key={profile.id}
profile={profile}
selected={selectedProfile}
setSelected={setSelectedProfile}
onDelete={() => setRefreshKey((prevKey) => prevKey + 1)}
/>
))}
</TableBody>
</Table>
</Box>
</Box>
</Stack>
);
}

View File

@@ -0,0 +1,81 @@
import { Button, TableCell, TableRow, TextField } from '@mui/material';
import { Dispatch, useState } from 'react';
import { Profile } from '../../../../data/types';
import { DeleteProfileAction } from './actions/DeleteProfileAction';
import { UpdateProfileAction } from './actions/UpdateProfileAction';
interface ProfilesTableRowProps {
profile: Profile;
selected: string | undefined;
setSelected: Dispatch<string | undefined>;
onDelete: () => void;
}
export default function ProfilesTableRow({
profile,
selected,
setSelected,
onDelete
}: ProfilesTableRowProps) {
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(profile.name);
const handleClick = () => {
setSelected(profile.id);
};
const handleDoubleClick = () => {
setIsEditing(true);
};
const handleBlur = async () => {
setIsEditing(false);
try {
await UpdateProfileAction({
id: profile.id,
name
});
} catch (error) {
console.error("Couldn't update profile.");
}
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
};
const handleDelete = async () => {
try {
await DeleteProfileAction(profile.id);
onDelete();
} catch (error) {
console.error("Couldn't delete profile.");
}
};
return (
<TableRow key={profile.id}>
<TableCell
align="center"
onClick={handleClick}
onDoubleClick={handleDoubleClick}
sx={{ bgcolor: profile.id === selected ? 'lightblue' : 'inherit' }}
>
{isEditing ? (
<TextField
value={name}
onChange={handleChange}
onBlur={handleBlur}
autoFocus
/>
) : (
name
)}
</TableCell>
<TableCell align="center">
<Button onClick={handleDelete}>Delete</Button>
</TableCell>
</TableRow>
);
}

View File

@@ -0,0 +1,53 @@
'use server';
import { initializeUser } from '../../../../../data/initializeUser';
import { CreateProfileFormSchema } from '../../../../../data/types';
import prisma from '../../../../../prisma/prisma';
interface CreateItemActionProps {
clear: boolean;
error?: string;
}
export async function CreateProfileAction(
_: CreateItemActionProps,
formData: FormData
) {
const formDataObj = Object.fromEntries(formData.entries());
const validatedBody = CreateProfileFormSchema.safeParse(formDataObj);
if (!validatedBody.success) {
throw new Error('Bad request');
}
const user = await initializeUser();
try {
const existingProfile = await prisma.profile.findFirst({
where: {
userId: user.id,
name: validatedBody.data.name
}
});
if (existingProfile) {
return { clear: false };
}
} catch (error) {
throw new Error(`Failed to find profile`);
}
try {
await prisma.profile.create({
data: {
userId: user.id,
name: validatedBody.data.name
}
});
return { clear: true };
} catch (error) {
throw new Error(`Failed to create profile`);
}
}

View File

@@ -0,0 +1,22 @@
'use server';
import { z } from 'zod';
import prisma from '../../../../../prisma/prisma';
export async function DeleteProfileAction(id: string) {
const validatedBody = z.string().safeParse(id);
if (!validatedBody.success) {
throw new Error('Bad request');
}
try {
await prisma.profile.delete({
where: {
id
}
});
} catch (error) {
throw new Error(`Failed to delete profile`);
}
}

View File

@@ -0,0 +1,23 @@
'use server';
import { initializeUser } from '../../../../../data/initializeUser';
import prisma from '../../../../../prisma/prisma';
export async function ProfilesAction() {
const user = await initializeUser();
try {
const profiles = await prisma.profile.findMany({
where: {
userId: user.id
},
orderBy: {
createdAt: 'desc'
}
});
return profiles;
} catch (error) {
throw new Error('Failed to find profiles');
}
}

View File

@@ -0,0 +1,31 @@
'use server';
import { z } from 'zod';
import prisma from '../../../../../prisma/prisma';
interface ProfileActionProps {
id: string;
name: string;
error?: string;
}
export async function UpdateProfileAction({ id, name }: ProfileActionProps) {
const validatedBody = z.string().safeParse(name);
if (!validatedBody.success) {
throw new Error('Bad request');
}
try {
await prisma.profile.update({
where: {
id
},
data: {
name: validatedBody.data
}
});
} catch (error) {
throw new Error(`Failed to update profile`);
}
}

View File

@@ -0,0 +1,33 @@
import { ProfileContext } from '@/contexts/ProfileContext';
import { Card, Paper, Stack } from '@mui/material';
import { nanoid } from 'nanoid';
import { useContext, useEffect, useState } from 'react';
import ProfilesTable from './Profiles/ProfilesTable';
import TagsTable from './Tags/TagsTable';
export default function Settings() {
const { profile, setProfile } = useContext(ProfileContext);
const [formKey, setFormKey] = useState(() => nanoid());
useEffect(() => {
if (profile) {
setFormKey(nanoid());
}
}, [profile]);
return (
<Paper sx={{ width: '100%', height: 'calc(100vh - 50px)' }}>
<Stack direction="row" spacing={2} padding={2}>
<Card sx={{ width: '50%', height: '100%' }}>
<ProfilesTable
selectedProfile={profile}
setSelectedProfile={setProfile}
/>
</Card>
<Card sx={{ width: '50%', height: '100%' }}>
<TagsTable key={formKey} selectedProfile={profile} />
</Card>
</Stack>
</Paper>
);
}

View File

@@ -0,0 +1,103 @@
import {
Box,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField,
Typography
} from '@mui/material';
import { nanoid } from 'nanoid';
import { useEffect, useState } from 'react';
import { useFormState } from 'react-dom';
import { Tags } from '../../../../data/types';
import { CreateTagAction } from './actions/CreateTagAction';
import { TagsAction } from './actions/TagsAction';
import TagsTableRow from './TagsTableRow';
interface TagsTableProps {
selectedProfile: string | undefined;
}
export default function TagsTable({ selectedProfile }: TagsTableProps) {
const [tags, setTags] = useState<Tags>([]);
const [formKey, setFormKey] = useState(() => nanoid());
const [refreshKey, setRefreshKey] = useState(0);
const [formState, formAction] = useFormState(CreateTagAction, {
clear: false,
profileId: selectedProfile
});
useEffect(() => {
const fetchTags = async () => {
if (selectedProfile) {
try {
const tags = await TagsAction({ selectedProfile });
setTags(tags);
} catch (error) {
console.error("Couldn't fetch tags.");
}
}
};
if (formState.clear) {
setFormKey(nanoid());
}
fetchTags();
}, [formState, selectedProfile, refreshKey]);
return (
<Stack
sx={{
spacing: 2,
padding: 2
}}
>
<Typography variant="h6">Tags</Typography>
<Box>
<form key={formKey} action={formAction}>
<TextField
fullWidth
disabled={!selectedProfile}
name="name"
placeholder="Add new tag"
/>
</form>
<Box
sx={{
maxHeight: 'calc(100vh - 200px)',
overflow: 'auto'
}}
>
<Table
stickyHeader
sx={{
width: '100%'
}}
>
<TableHead>
<TableRow sx={{ '& th': { textAlign: 'center' } }}>
<TableCell>Name</TableCell>
<TableCell>Delete</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tags &&
tags.map((tag) => (
<TagsTableRow
key={tag.id}
tag={tag}
onDelete={() => setRefreshKey((prevKey) => prevKey + 1)}
/>
))}
</TableBody>
</Table>
</Box>
</Box>
</Stack>
);
}

View File

@@ -0,0 +1,65 @@
import { Button, TableCell, TableRow, TextField } from '@mui/material';
import { useState } from 'react';
import { Tag } from '../../../../data/types';
import { DeleteTagAction } from './actions/DeleteTagAction';
import { UpdateTagAction } from './actions/UpdateTagAction';
interface TagsTableRowProps {
tag: Tag;
onDelete: () => void;
}
export default function TagsTableRow({ tag, onDelete }: TagsTableRowProps) {
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(tag.name);
const handleDoubleClick = () => {
setIsEditing(true);
};
const handleBlur = async () => {
setIsEditing(false);
try {
await UpdateTagAction({
id: tag.id,
name
});
} catch (error) {
console.error("Couldn't update tag.");
}
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
};
const handleDelete = async () => {
try {
await DeleteTagAction(tag.id);
onDelete();
} catch (error) {
console.error("Couldn't delete tag.");
}
};
return (
<TableRow sx={{ '& td': { textAlign: 'center' } }} key={tag.id}>
<TableCell onDoubleClick={handleDoubleClick}>
{isEditing ? (
<TextField
value={name}
onChange={handleChange}
onBlur={handleBlur}
autoFocus
/>
) : (
name
)}
</TableCell>
<TableCell>
<Button onClick={handleDelete}>Delete</Button>
</TableCell>
</TableRow>
);
}

View File

@@ -0,0 +1,53 @@
'use server';
import { CreateProfileFormSchema } from '../../../../../data/types';
import prisma from '../../../../../prisma/prisma';
interface CreateItemActionProps {
clear: boolean;
profileId: string | undefined;
error?: string;
}
export async function CreateTagAction(
{ profileId }: CreateItemActionProps,
formData: FormData
) {
const formDataObj = Object.fromEntries(formData.entries());
const validatedBody = CreateProfileFormSchema.safeParse(formDataObj);
if (!validatedBody.success || !profileId) {
throw new Error('Bad request');
}
const { name } = validatedBody.data;
try {
const existingTag = await prisma.tag.findFirst({
where: {
profileId,
name
}
});
if (existingTag) {
return { clear: false, profileId };
}
} catch (error) {
throw new Error(`Failed to find tag`);
}
try {
await prisma.tag.create({
data: {
profileId,
name
}
});
return { clear: true, profileId };
} catch (error) {
throw new Error(`Failed to create tag`);
}
}

View File

@@ -0,0 +1,22 @@
'use server';
import { z } from 'zod';
import prisma from '../../../../../prisma/prisma';
export async function DeleteTagAction(id: string) {
const validatedBody = z.string().safeParse(id);
if (!validatedBody.success) {
throw new Error('Bad request');
}
try {
await prisma.tag.delete({
where: {
id
}
});
} catch (error) {
throw new Error(`Failed to delete tag`);
}
}

View File

@@ -0,0 +1,40 @@
'use server';
import { z } from 'zod';
import { initializeUser } from '../../../../../data/initializeUser';
import { Profiles } from '../../../../../data/types';
import prisma from '../../../../../prisma/prisma';
interface TagsActionProps {
selectedProfile: string | undefined;
}
export async function TagsAction({ selectedProfile }: TagsActionProps) {
const validatedBody = z.string().safeParse(selectedProfile);
if (!validatedBody.success) {
throw new Error('Bad request');
}
const user = await initializeUser();
try {
const profiles = await prisma.tag.findMany({
where: {
Profile: {
User: {
id: user.id
},
id: selectedProfile
}
},
orderBy: {
createdAt: 'desc'
}
});
return profiles as Profiles;
} catch (error) {
throw new Error('Failed to find tags');
}
}

View File

@@ -0,0 +1,31 @@
'use server';
import { z } from 'zod';
import prisma from '../../../../../prisma/prisma';
interface TagActionProps {
id: string;
name: string;
error?: string;
}
export async function UpdateTagAction({ id, name }: TagActionProps) {
const validatedBody = z.string().safeParse(name);
if (!validatedBody.success) {
throw new Error('Bad request');
}
try {
await prisma.tag.update({
where: {
id
},
data: {
name: validatedBody.data
}
});
} catch (error) {
throw new Error(`Failed to update tag`);
}
}