Skip to content

Commit

Permalink
Merge pull request #275 from dump-hr/speakers-admin
Browse files Browse the repository at this point in the history
Speakers admin
  • Loading branch information
bdeak4 authored Mar 24, 2024
2 parents 6876ef9 + ddb09a4 commit 6b7b6b5
Show file tree
Hide file tree
Showing 23 changed files with 630 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
.turbo
.env.local
dist
dist
apps/api/db/migrations/meta/_journal.json
2 changes: 2 additions & 0 deletions apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CompanyPage } from './pages/CompanyPage';
import EventPage from './pages/EventPage';
import { HomePage } from './pages/HomePage';
import { InterestPage } from './pages/InterestPage';
import SpeakerPage from './pages/SpeakerPage';

export const App = () => {
useMsalAuthentication(InteractionType.Redirect);
Expand All @@ -23,6 +24,7 @@ export const App = () => {
<Route path={Path.Company} component={CompanyPage} />
<Route path={Path.Interest} component={InterestPage} />
<Route path={Path.Event} component={EventPage} />
<Route path={Path.Speaker} component={SpeakerPage} />
</Switch>
</Layout>
<Toaster />
Expand Down
23 changes: 23 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SpeakerDto, SpeakerModifyDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const speakerCreate = async (dto: SpeakerModifyDto) => {
return await api.post<SpeakerModifyDto, SpeakerDto>('/speaker', dto);
};

export const useSpeakerCreate = () => {
const queryClient = useQueryClient();

return useMutation(speakerCreate, {
onSuccess: () => {
queryClient.invalidateQueries(['speaker']);
toast.success('Speaker uspješno dodan!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
12 changes: 12 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerGetAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SpeakerDto } from '@ddays-app/types';
import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const speakerGetAll = () => {
return api.get<never, SpeakerDto[]>('speaker');
};

export const useSpeakerGetAll = (options?: QueryOptions<SpeakerDto[]>) => {
return useQuery(['speaker'], speakerGetAll, options);
};
18 changes: 18 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerGetOne.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SpeakerDto } from '@ddays-app/types';
import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const speakerGetOne = async (id: number) => {
return await api.get<never, SpeakerDto>(`/speaker/${id}`);
};

export const useSpeakerGetOne = (
id?: number,
options?: QueryOptions<SpeakerDto>,
) => {
return useQuery(['speaker', id], () => speakerGetOne(id!), {
enabled: !!id,
...options,
});
};
23 changes: 23 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerRemove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SpeakerDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const speakerRemove = async (id: number) => {
return await api.delete<never, SpeakerDto>(`/speaker/${id}`);
};

export const useSpeakerRemove = () => {
const queryClient = useQueryClient();

return useMutation(speakerRemove, {
onSuccess: () => {
queryClient.invalidateQueries(['speaker']);
toast.success('Speaker uspješno uklonjen!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
22 changes: 22 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerRemovePhoto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const speakerRemovePhoto = async (id: number | undefined) => {
return await api.delete(`/speaker/photo/${id}`);
};

export const useSpeakerRemovePhoto = () => {
const queryClient = useQueryClient();

return useMutation(speakerRemovePhoto, {
onSuccess: () => {
queryClient.invalidateQueries(['speaker']);
toast.success('Slika uspješno izbrisana');
},
onError: (error: string) => {
toast.error(error);
},
});
};
27 changes: 27 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SpeakerDto, SpeakerModifyDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const speakerUpdate = async (dto: SpeakerModifyDto & { id: number }) => {
return await api.patch<SpeakerModifyDto, SpeakerDto>(`/speaker/${dto.id}`, {
...dto,
id: undefined,
});
};

export const useSpeakerUpdate = () => {
const queryClient = useQueryClient();

return useMutation(speakerUpdate, {
onSuccess: (updatedSpeaker) => {
queryClient.invalidateQueries(['speaker']);
queryClient.invalidateQueries(['speaker', updatedSpeaker.id]);
toast.success('Uređivanje speakera uspješno izvršeno!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
28 changes: 28 additions & 0 deletions apps/admin/src/api/speaker/useSpeakerUpdatePhoto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const speakerUpdatePhoto = async (speakerFile: {
id: number | undefined;
file: File;
}) => {
const data = new FormData();
data.append('file', speakerFile.file);

return await api.patchForm(`/speaker/photo/${speakerFile.id}`, data);
};

export const useSpeakerUpdatePhoto = () => {
const queryClient = useQueryClient();

return useMutation(speakerUpdatePhoto, {
onSuccess: () => {
queryClient.invalidateQueries(['speaker']);
toast.success('Slika uspješno uploadana');
},
onError: (error: string) => {
toast.error(error);
},
});
};
24 changes: 8 additions & 16 deletions apps/admin/src/components/FileUpload/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,26 @@ import { Button } from '../Button';
import c from './FileUpload.module.scss';

type FileUploadProps = {
src: string | ArrayBuffer | null;
src: string | ArrayBuffer | null | undefined;
label?: string;
accept: string | undefined;
setSrc: (result: string | ArrayBuffer | null) => void;
accept?: string | undefined;
handleUpload: (files: File[]) => void;
handleRemove: () => void;
};

export const FileUpload: React.FC<FileUploadProps> = ({
src,
label = null,
accept = 'image/*',
setSrc,
handleUpload,
handleRemove,
}) => {
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop: (acceptedFiles: File[]) => {
const file = new FileReader();

file.onload = () => {
setSrc(file.result);
};

file.readAsDataURL(acceptedFiles[0]);
handleUpload(acceptedFiles);
},
});

const handleFileRemove = () => {
setSrc(null);
};

return (
<div className={c.dragNDropWrapper}>
{label && <p>{label}</p>}
Expand All @@ -51,7 +43,7 @@ export const FileUpload: React.FC<FileUploadProps> = ({
{src && (
<div className={c.previewWrapper}>
<img className={c.img} src={src as string} alt='preview' />
<Button onClick={handleFileRemove}>Remove</Button>
<Button onClick={() => handleRemove()}>Remove</Button>
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions apps/admin/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const navLinks = [
{ href: Path.Company, text: 'Company' },
{ href: Path.Interest, text: 'Interest' },
{ href: Path.Event, text: 'Event' },
{ href: Path.Speaker, text: 'Speaker' },
];

export const Layout: React.FC<LayoutProps> = ({ children }) => {
Expand Down
1 change: 1 addition & 0 deletions apps/admin/src/constants/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export enum Path {
Interest = '/admin/interest',
CatchAll = '/admin/:path*',
Event = '/admin/event',
Speaker = '/admin/speaker',
}
77 changes: 77 additions & 0 deletions apps/admin/src/forms/SpeakerForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { SpeakerModifyDto } from '@ddays-app/types';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { useForm } from 'react-hook-form';

import { useSpeakerCreate } from '../api/speaker/useSpeakerCreate';
import { useSpeakerGetOne } from '../api/speaker/useSpeakerGetOne';
import { useSpeakerUpdate } from '../api/speaker/useSpeakerUpdate';
import { Button } from '../components/Button';
import { InputHandler } from '../components/InputHandler';
import { Question, QuestionType } from '../types/question';

type SpeakerFormProps = {
id?: number;
onSuccess: () => void;
};

export const SpeakerForm: React.FC<SpeakerFormProps> = ({ id, onSuccess }) => {
const { data: speaker, isLoading } = useSpeakerGetOne(id);

const createSpeaker = useSpeakerCreate();
const updateSpeaker = useSpeakerUpdate();

const questions: Question[] = [
{
id: 'firstName',
type: QuestionType.Field,
title: 'Ime',
defaultValue: speaker?.firstName,
},
{
id: 'lastName',
type: QuestionType.Field,
title: 'Prezime',
defaultValue: speaker?.lastName,
},
{
id: 'title',
type: QuestionType.Field,
title: 'Titula',
defaultValue: speaker?.title,
},
{
id: 'companyId',
type: QuestionType.Number,
title: 'CompanyId (0 for null)',
defaultValue: speaker?.companyId,
},
];

const form = useForm<SpeakerModifyDto>({
resolver: classValidatorResolver(SpeakerModifyDto),
});

if (id && isLoading) {
return <div>Loading...</div>;
}

return (
<>
{questions.map((q) => {
return <InputHandler question={q} form={form} key={q.id} />;
})}

<Button
onClick={form.handleSubmit(async (formData) => {
if (id) {
await updateSpeaker.mutateAsync({ ...formData, id });
} else {
await createSpeaker.mutateAsync(formData);
}
onSuccess();
})}>
Submit
</Button>
</>
);
};
Loading

0 comments on commit 6b7b6b5

Please sign in to comment.