Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Tag Page #5

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
39 changes: 39 additions & 0 deletions src/apis/tag.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
import api from '@/utils/api';
import { Tag } from '@/utils/types';

export const getTags = async () => {
const response = await api.get('/api/tag');
const data = await response.data;
return data;
};

export const createTag = async (user: any) => {
try {
const response = await api.post('/api/tag', user);
const data = await response.data;
return data;
} catch (error: any) {
throw error;
}
};

export const updateTag = async (tagId: number, tag: Tag) => {
try {
const response = await api.put(`/api/tag/${tagId}`, tag);
const data = await response.data;
return data;
} catch (error: any) {
throw error;
}
};

export const getTag = async (tagId: number) => {
try {
const response = await api.get(`/api/tag/${tagId}`);
return response;
} catch (error: unknown) {
throw error;
}
};

export const deleteTag = async (tagId: number) => {
try {
const response = await api.delete(`/api/tag/${tagId}`);
return response;
} catch (error: unknown) {
throw error;
}
};
49 changes: 49 additions & 0 deletions src/app/(auth)/tag/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import { TagIcon } from '@heroicons/react/24/outline';
import { getTag, updateTag } from '@/apis/tag';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useRouter } from 'next/navigation';
import TagForm from '@/components/Tag/TagForm';

const Page = ({ params }: { params: { id: number } }) => {
const router = useRouter();
const [tag, setTag] = useState({});

useEffect(() => {
const fetchTag = async () => {
try {
const { data } = await getTag(params.id);
setTag(data);
} catch (error: any) {
toast.error(error.response.data.message);
}
};
fetchTag();
}, []);

const onSubmit = async (values: any) => {
try {
await updateTag(params.id, values);
toast.success('Tag updated successfully');
router.back();
} catch (error: any) {
toast.error(error.response.data.message);
}
};

return (
<>
<div className="flex justify-between">
<h2 className="mb-7 flex items-end">
<TagIcon className="h-9 w-9" />
<span className="ml-1 text-3xl">Edit Tag</span>
</h2>
</div>
<TagForm onSubmit={onSubmit} tag={tag} />
</>
);
};

export default Page;
34 changes: 34 additions & 0 deletions src/app/(auth)/tag/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

import { TagIcon } from '@heroicons/react/24/outline';
import { createTag } from '@/apis/tag';
import { toast } from 'react-toastify';
import { useRouter } from 'next/navigation';
import TagForm from '@/components/Tag/TagForm';

const Page = () => {
const router = useRouter();
const onSubmit = async (values: any) => {
try {
await createTag(values);
toast.success('Tag created successfully');
router.back();
} catch (error: any) {
toast.error(error.response.data.message);
}
};

return (
<div className="flex flex-col">
<div className="flex justify-between">
<h2 className="mb-7 flex items-end">
<TagIcon className="h-9 w-9" />
<span className="ml-1 text-3xl">New Tag</span>
</h2>
</div>
<TagForm onSubmit={onSubmit} />
</div>
);
};

export default Page;
53 changes: 39 additions & 14 deletions src/app/(auth)/tag/page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
'use client';

import { getTags } from '@/apis/tag';
import Button from '@/components/Button';
import Table from '@/components/Table';
import { Tag, TableData } from '@/utils/types';
import { Tag, TableData, TagResource } from '@/utils/types';
import { TagIcon } from '@heroicons/react/24/outline';
import { PencilSquareIcon, TrashIcon } from '@heroicons/react/24/solid';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { deleteTag } from '@/apis/tag';
import { toast } from 'react-toastify';

const Page = () => {
const [tags, setTags] = useState<Tag | undefined>(undefined);
const [tags, setTags] = useState<TagResource | undefined>(undefined);
const [tableData, setTableData] = useState<TableData>({
headers: ['Name', 'Valid Subscribers', 'Invalid Subscribers'],
rows: [],
});

useEffect(() => {
const fetchTags = async () => {
setTags(await getTags());
};
fetchTags();
}, []);
const fetchTags = async () => {
setTags(await getTags());
};

useEffect(() => {
fetchTags();
const showDeleteConfirmationModal = async (id: number) => {
const result = confirm(
'Are you sure you want to delete this subscriber?'
);
if (result) {
try {
await deleteTag(id);
toast.success('Tag deleted successfully');
fetchTags();
} catch (error: any) {
toast.error(error.response.data.message);
}
}
};
if (tags) {
const rows = tags.data.map((tag: Tag) => {
return [
Expand All @@ -44,12 +58,18 @@ const Page = () => {
</>,
<>
<div className="flex justify-end">
<a href="#" className="text-gray-400 hover:text-indigo-700">
<Link
href={{ pathname: `/tag/${tag.id}/edit` }}
className="text-gray-400 hover:text-indigo-700"
>
<PencilSquareIcon className="h-5 w-5" />
</a>
<a href="#" className="ml-2 text-gray-400 hover:text-red-600">
</Link>
<span
onClick={() => showDeleteConfirmationModal(tag.id)}
className="text-gray-400 hover:text-indigo-700"
>
<TrashIcon className="h-5 w-5" />
</a>
</span>
</div>
</>,
];
Expand All @@ -74,7 +94,12 @@ const Page = () => {
)}
</h2>
<div>
<Button onClick={() => console.log('new tag')}>Create Tag</Button>
<Link
className="flex rounded-lg bg-indigo-700 px-4 py-2 text-white hover:bg-indigo-800"
href="/tag/create"
>
Create Tag
</Link>
</div>
</div>
<Table data={tableData} />
Expand Down
48 changes: 48 additions & 0 deletions src/components/Tag/TagForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Input from '@/components/Input';
import Button from '@/components/Button';
import { Formik, Form } from 'formik';
import { tagValidationSchema } from '@/validations/tag';

const TagForm = ({
onSubmit,
tag = {},
}: {
onSubmit: (value: any) => void;
tag: object;
}) => {
let initialValues = {
name: '',
};
if (Object.keys(tag).length) {
initialValues = { ...initialValues, ...tag };
}

return (
<>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={tagValidationSchema}
enableReinitialize={true}
>
{
<Form>
<div className="grid grid-cols-2 gap-2">
<Input
type="text"
name="name"
label="Name"
placeholder="Enter Tag Name"
/>
</div>
<Button type="submit">
{Object.keys(tag).length ? 'Update' : 'Create'}
</Button>
</Form>
}
</Formik>
</>
);
};

export default TagForm;
5 changes: 5 additions & 0 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface User {
}

export interface Tag {
id: number;
name: string;
}

Expand All @@ -61,3 +62,7 @@ export interface SubscriberResource extends ApiResource {
export interface UserResource extends ApiResource {
data: User[];
}

export interface TagResource extends ApiResource {
data: Tag[];
}
5 changes: 5 additions & 0 deletions src/validations/tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as yup from 'yup';

export const tagValidationSchema = yup.object({
name: yup.string().trim().required('Name is required'),
});