diff --git a/dashboard/README.md b/dashboard/README.md index 965a122..5e8cfab 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -1,5 +1,3 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - ## Getting Started First, run the development server: @@ -14,25 +12,15 @@ pnpm dev Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - ## Learn More To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +- [@reduxjs/toolkit](https://redux-toolkit.js.org/) - The official, opinionated, batteries-included toolset for efficient Redux development +- [axios](https://axios-http.com/docs/intro) - Promise based HTTP client for the browser and node.js +- [formik](https://formik.org/) - Build forms in React, without the tears +- [yup](https://www.npmjs.com/package/yup) - Yup is a schema builder for runtime value parsing and validation. +- [tailwindcss](https://tailwindcss.com/) - Rapidly build modern websites without ever leaving your HTML. +- [jwt-decode](jwt-decode) diff --git a/dashboard/src/app/layout.tsx b/dashboard/src/app/layout.tsx index c086ad9..79c32ed 100644 --- a/dashboard/src/app/layout.tsx +++ b/dashboard/src/app/layout.tsx @@ -1,12 +1,12 @@ -import "@/styles/globals.css"; -import { Metadata } from "next"; +import Providers from '@/lib/Providers'; +import '@/styles/globals.css'; +import { Metadata } from 'next'; export const metadata: Metadata = { - title: "ES Dashboard", - description: "English Sphere Dashboard", - authors: [{ name: "Fahim Montasir", url: "https://moontasir.web.app/" }], - keywords: - "ES Dashboard, English Sphere Dashboard, English Sphere content center", + title: 'ES Dashboard', + description: 'English Sphere Dashboard', + authors: [{ name: 'Fahim Montasir', url: 'https://moontasir.web.app/' }], + keywords: 'ES Dashboard, English Sphere Dashboard, English Sphere content center', }; export default function RootLayout({ @@ -18,7 +18,9 @@ export default function RootLayout({ }) { return ( - {children} + + {children} + ); } diff --git a/dashboard/src/lib/Providers.tsx b/dashboard/src/lib/Providers.tsx new file mode 100644 index 0000000..8cc5435 --- /dev/null +++ b/dashboard/src/lib/Providers.tsx @@ -0,0 +1,9 @@ +'use client'; +import { store } from '@/redux/store'; +import { Provider } from 'react-redux'; + +const Providers = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export default Providers; diff --git a/dashboard/src/redux/api/authApi.ts b/dashboard/src/redux/api/authApi.ts new file mode 100644 index 0000000..d5de7ff --- /dev/null +++ b/dashboard/src/redux/api/authApi.ts @@ -0,0 +1,18 @@ +import { tagTypes } from '../tag-types'; +import { baseApi } from './baseApi'; +const AUTH_URL = '/auth'; + +export const authApi = baseApi.injectEndpoints({ + endpoints: build => ({ + userLogin: build.mutation({ + query: loginData => ({ + url: `${AUTH_URL}/login`, + method: 'POST', + data: loginData, + }), + invalidatesTags: [tagTypes.user], + }), + }), +}); + +export const { useUserLoginMutation } = authApi; diff --git a/dashboard/src/redux/api/baseApi.ts b/dashboard/src/redux/api/baseApi.ts new file mode 100644 index 0000000..bd148d1 --- /dev/null +++ b/dashboard/src/redux/api/baseApi.ts @@ -0,0 +1,12 @@ +import { axiosBaseQuery } from '@/helpers/axios/axiosBaseQuery'; +import { getBaseUrl } from '@/helpers/config/envConfig'; +import { createApi } from '@reduxjs/toolkit/query/react'; +import { tagTypesList } from '../tag-types'; + +// Define a service using a base URL and expected endpoints +export const baseApi = createApi({ + reducerPath: 'api', + baseQuery: axiosBaseQuery({ baseUrl: getBaseUrl() }), + endpoints: () => ({}), + tagTypes: tagTypesList, +}); diff --git a/dashboard/src/redux/api/courseApi.ts b/dashboard/src/redux/api/courseApi.ts new file mode 100644 index 0000000..bc705ea --- /dev/null +++ b/dashboard/src/redux/api/courseApi.ts @@ -0,0 +1,69 @@ +import { ICourse, IMeta } from '@/types'; +import { baseApi } from './baseApi'; +import { tagTypes } from '../tag-types'; + +const COURSE_URL = '/courses'; + +export const courseApi = baseApi.injectEndpoints({ + endpoints: build => ({ + // get all + courses: build.query({ + query: (arg: Record) => { + return { + url: COURSE_URL, + method: 'GET', + params: arg, + }; + }, + transformResponse: (response: ICourse[], meta: IMeta) => { + return { + courses: response, + meta, + }; + }, + providesTags: [tagTypes.course], + }), + // get single + course: build.query({ + query: (id: string) => ({ + url: `${COURSE_URL}/${id}`, + method: 'GET', + }), + providesTags: [tagTypes.course], + }), + // create + addCourse: build.mutation({ + query: data => ({ + url: COURSE_URL, + method: 'POST', + data, + }), + invalidatesTags: [tagTypes.course], + }), + // update + updateCourse: build.mutation({ + query: data => ({ + url: `${COURSE_URL}/${data.id}`, + method: 'PATCH', + data: data.body, + }), + invalidatesTags: [tagTypes.course], + }), + // delete + deleteCourse: build.mutation({ + query: id => ({ + url: `${COURSE_URL}/${id}`, + method: 'DELETE', + }), + invalidatesTags: [tagTypes.course], + }), + }), +}); + +export const { + useCoursesQuery, + useCourseQuery, + useAddCourseMutation, + useDeleteCourseMutation, + useUpdateCourseMutation, +} = courseApi; diff --git a/dashboard/src/redux/hooks.ts b/dashboard/src/redux/hooks.ts new file mode 100644 index 0000000..0f1e227 --- /dev/null +++ b/dashboard/src/redux/hooks.ts @@ -0,0 +1,28 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; +import type { RootState, AppDispatch } from "./store"; +import { useEffect, useState } from "react"; + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppSelector: TypedUseSelectorHook = useSelector; + +interface IDebounced { + searchQuery: string; + delay: number; +} + +export const useDebounced = ({ searchQuery, delay }: IDebounced) => { + const [debouncedValue, setDebouncedValue] = useState(searchQuery); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(searchQuery); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [searchQuery, delay]); + + return debouncedValue; +}; diff --git a/dashboard/src/redux/rootReducer.ts b/dashboard/src/redux/rootReducer.ts new file mode 100644 index 0000000..5934473 --- /dev/null +++ b/dashboard/src/redux/rootReducer.ts @@ -0,0 +1,6 @@ +import { baseApi } from "./api/baseApi"; + +export const reducer = { + [baseApi.reducerPath]: baseApi.reducer, +} + diff --git a/dashboard/src/redux/store.ts b/dashboard/src/redux/store.ts new file mode 100644 index 0000000..a000d94 --- /dev/null +++ b/dashboard/src/redux/store.ts @@ -0,0 +1,16 @@ +import { baseApi } from './api/baseApi'; +import { reducer } from './rootReducer'; +import { configureStore } from '@reduxjs/toolkit' + +export const store = configureStore({ + reducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat(baseApi.middleware), +}) + + + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch \ No newline at end of file diff --git a/dashboard/src/redux/tag-types.ts b/dashboard/src/redux/tag-types.ts new file mode 100644 index 0000000..de4079f --- /dev/null +++ b/dashboard/src/redux/tag-types.ts @@ -0,0 +1,6 @@ +export enum tagTypes { + course = 'course', + user = 'user', +} + +export const tagTypesList = [tagTypes.course, tagTypes.user]; diff --git a/dashboard/src/schemas/login.ts b/dashboard/src/schemas/login.ts new file mode 100644 index 0000000..5623148 --- /dev/null +++ b/dashboard/src/schemas/login.ts @@ -0,0 +1,6 @@ +import * as yup from "yup"; + +export const loginSchema = yup.object().shape({ + id: yup.string().required("UserId is required"), + password: yup.string().min(6).max(32).required(), +});