Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aa66540ba | ||
|
|
01da8339f4 | ||
|
|
e13e5b4de2 | ||
|
|
08b4c39c03 | ||
|
|
610a1b7556 | ||
|
|
f5f715175c | ||
|
|
1ddb2f72c6 | ||
|
|
b04f687199 | ||
|
|
50cc24e46f | ||
|
|
4c7bb18a4f | ||
|
|
0bc7d084d5 | ||
|
|
795bcb6ab1 | ||
|
|
2de5fd56d8 | ||
|
|
d078999eec | ||
|
|
a8dd867e1a | ||
|
|
ff65424ff9 | ||
|
|
2fc625e454 | ||
|
|
d1bc9e10d7 | ||
|
|
017b5309a5 | ||
|
|
e5844327a5 | ||
|
|
7874800d5e | ||
|
|
32d482ba4c | ||
|
|
502e45b4ff | ||
|
|
7411fd2a13 | ||
|
|
9df5bf8bbf | ||
|
|
9548272340 | ||
|
|
d1abc3c7d4 |
@@ -0,0 +1,14 @@
|
|||||||
|
# API Endpoint
|
||||||
|
VITE_API_URL=
|
||||||
|
|
||||||
|
# Payload CMS Secret Key (minimum 32 characters)
|
||||||
|
CMS_SECRET=your-very-long-secret-key
|
||||||
|
|
||||||
|
# PostgreSQL Connection String
|
||||||
|
CMS_POSTGRES_URL=postgres://user:password@host:port/database
|
||||||
|
|
||||||
|
# S3 Storage Configuration
|
||||||
|
CMS_STORAGE_ENDPOINT=https://your-s3-endpoint.com
|
||||||
|
CMS_STORAGE_ACCESS_KEY_ID=your-access-key-id
|
||||||
|
CMS_STORAGE_SECRET_ACCESS_KEY=your-secret-access-key
|
||||||
|
CMS_STORAGE_REGION=ap-southeast-1
|
||||||
@@ -62,3 +62,9 @@ storybook-static
|
|||||||
# Next.js
|
# Next.js
|
||||||
.next
|
.next
|
||||||
out
|
out
|
||||||
|
|
||||||
|
.cursor/rules/nx-rules.mdc
|
||||||
|
.github/instructions/nx.instructions.md
|
||||||
|
|
||||||
|
.cursor/rules/nx-rules.mdc
|
||||||
|
.github/instructions/nx.instructions.md
|
||||||
|
|||||||
@@ -6,10 +6,9 @@
|
|||||||
|
|
||||||
This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications:
|
This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications:
|
||||||
|
|
||||||
1. **Gacha** - Application for <a href="https://gacha.imphnen.dev/" target="_blank">Gacha Website</a>.
|
1. **Gacha** - Application for <a href="https://gacha.imphnen.dev/" target="_blank">gacha website</a>.
|
||||||
2. **Backoffice** - Application for <a href="https://gacha.imphnen.dev/" target="_blank">Internal Management Website</a>.
|
2. **Backoffice** - Application for internal management.
|
||||||
3. **Dimentorin** - Application for <a href="https://dimentorin.imphnen.dev/" target="_blank">Mentoring Service</a>.
|
3. **Dimentorin** - Application for mentoring services.
|
||||||
4. **Landing Page** - Application for <a href="https://imphnen.dev/" target="_blank">Landing Page</a>.
|
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
@@ -25,15 +24,6 @@ This repository is a **monorepo** for all frontend services of IMPHNEN. The mono
|
|||||||
|
|
||||||
## How to run
|
## How to run
|
||||||
|
|
||||||
### Setup Environment Variables
|
|
||||||
|
|
||||||
Before running the applications, you need to set up the environment variables. You can do this by copying the `.env.example` file to `.env` and modifying the values according to your needs.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd apps/{appname}
|
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
Use the following commands to run in development mode:
|
Use the following commands to run in development mode:
|
||||||
@@ -50,10 +40,6 @@ Use the following commands to run in development mode:
|
|||||||
```sh
|
```sh
|
||||||
npm run dimentorin:dev
|
npm run dimentorin:dev
|
||||||
```
|
```
|
||||||
- **Landing Page**:
|
|
||||||
```sh
|
|
||||||
npm run landing:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
@@ -71,10 +57,6 @@ Use the following commands to build the applications:
|
|||||||
```sh
|
```sh
|
||||||
npm run dimentorin:build
|
npm run dimentorin:build
|
||||||
```
|
```
|
||||||
- **Landing Page**:
|
|
||||||
```sh
|
|
||||||
npm run landing:build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production
|
### Production
|
||||||
|
|
||||||
@@ -92,10 +74,6 @@ Use the following commands to run the applications in production mode:
|
|||||||
```sh
|
```sh
|
||||||
npm run dimentorin:prod
|
npm run dimentorin:prod
|
||||||
```
|
```
|
||||||
- **Landing Page**:
|
|
||||||
```sh
|
|
||||||
npm run landing:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
### Storybook
|
### Storybook
|
||||||
|
|
||||||
@@ -109,6 +87,7 @@ This repository uses Storybook to develop, test, and document UI components in a
|
|||||||
npm run ui:storybook
|
npm run ui:storybook
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
- **Run Unit Test**
|
- **Run Unit Test**
|
||||||
|
|
||||||
This command runs unit tests for the UI components to ensure they function as expected.
|
This command runs unit tests for the UI components to ensure they function as expected.
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
VITE_API_URL=
|
|
||||||
+9
-4
@@ -2,19 +2,24 @@ import { useForm } from 'react-hook-form';
|
|||||||
import {
|
import {
|
||||||
authLoginSchema,
|
authLoginSchema,
|
||||||
TLoginRequest,
|
TLoginRequest,
|
||||||
|
usePostLogin,
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useSession } from '@imphnen-frontend-service/utils';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export const useLogin = () => {
|
export const useLogin = () => {
|
||||||
|
const postLogin = usePostLogin();
|
||||||
const form = useForm<TLoginRequest>({
|
const form = useForm<TLoginRequest>({
|
||||||
resolver: zodResolver(authLoginSchema),
|
resolver: zodResolver(authLoginSchema),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { signIn } = useSession();
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
|
postLogin.mutate(data, {
|
||||||
const onSubmit = form.handleSubmit((data) => signIn(data));
|
onSuccess: () => toast.success("Login sukses"),
|
||||||
|
onError: (error) => toast.error(error.message),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
+2
-2
@@ -18,7 +18,7 @@ import {
|
|||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import ModalEditAccount from './_components/modal-edit-account';
|
import ModalEditAccount from './_components/modal-edit-account';
|
||||||
import { useQueryState } from '@imphnen-frontend-service/utils';
|
import { useQueryState } from '../../hook/use-query-state';
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -166,7 +166,7 @@ export const Components: FC = (): ReactElement => {
|
|||||||
</Button>
|
</Button>
|
||||||
{showFilter && (
|
{showFilter && (
|
||||||
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg">
|
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg">
|
||||||
<Filter onClose={() => setShowFilter(false)} options={[]} />
|
<Filter onClose={() => setShowFilter(false)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-50 min-h-screen flex justify-center">
|
||||||
|
<div className="bg-primary-50 min-h-screen w-full flex">
|
||||||
|
<BackofficeSidebar />
|
||||||
|
<div className="flex-1 overflow-auto lg:max-w-[1000px] 2xl:max-w-[1280px] mx-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
+1
-1
@@ -10,7 +10,7 @@ import { FC, Fragment, ReactElement, useState } from 'react';
|
|||||||
import ModalAddItem from './_components/modal-add-item';
|
import ModalAddItem from './_components/modal-add-item';
|
||||||
import ModalEditItem from './_components/modal-edit-item';
|
import ModalEditItem from './_components/modal-edit-item';
|
||||||
import ModalDeleteItem from './_components/modal-delete-item';
|
import ModalDeleteItem from './_components/modal-delete-item';
|
||||||
import { useQueryState } from '@imphnen-frontend-service/utils';
|
import { useQueryState } from '../../hook/use-query-state';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
const [showModalAddItem, setShowModalAddItem] = useState(false);
|
const [showModalAddItem, setShowModalAddItem] = useState(false);
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-50 min-h-screen flex justify-center">
|
||||||
|
<div className="bg-primary-50 min-h-screen w-full flex">
|
||||||
|
<BackofficeSidebar />
|
||||||
|
<div className="flex-1 overflow-auto lg:max-w-[1000px] 2xl:max-w-[1280px] mx-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
+1
-10
@@ -32,16 +32,7 @@ export const Components: FC = (): ReactElement => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button type="submit" size="md" className="w-full">
|
||||||
disabled={
|
|
||||||
form.formState.isSubmitting ||
|
|
||||||
form.formState.isValidating ||
|
|
||||||
!form.formState.isValid
|
|
||||||
}
|
|
||||||
type="submit"
|
|
||||||
size="md"
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-50 min-h-screen flex justify-center">
|
||||||
|
<div className="bg-primary-50 min-h-screen w-full flex">
|
||||||
|
<BackofficeSidebar />
|
||||||
|
<div className="flex-1 overflow-auto lg:max-w-[1000px] 2xl:max-w-[1280px] mx-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-50 min-h-screen flex justify-center">
|
||||||
|
<div className="bg-primary-50 min-h-screen w-full flex">
|
||||||
|
<BackofficeSidebar />
|
||||||
|
<div className="flex-1 overflow-auto lg:max-w-[1000px] 2xl:max-w-[1280px] mx-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-50 min-h-screen flex justify-center">
|
||||||
|
<div className="bg-primary-50 min-h-screen w-full flex">
|
||||||
|
<BackofficeSidebar />
|
||||||
|
<div className="flex-1 overflow-auto lg:max-w-[1000px] 2xl:max-w-[1280px] mx-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-50 min-h-screen flex justify-center">
|
||||||
|
<div className="bg-primary-50 min-h-screen w-full flex">
|
||||||
|
<BackofficeSidebar />
|
||||||
|
<div className="flex-1 overflow-auto lg:max-w-[1000px] 2xl:max-w-[1280px] mx-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
@@ -1,108 +1,11 @@
|
|||||||
import {
|
import { LoaderFunctionArgs } from 'react-router-dom';
|
||||||
PERMISSIONS,
|
|
||||||
SessionToken,
|
|
||||||
SessionUser,
|
|
||||||
} from '@imphnen-frontend-service/utils';
|
|
||||||
import { LoaderFunctionArgs, redirect } from 'react-router';
|
|
||||||
|
|
||||||
const mappingPublicRoutes = [
|
|
||||||
'/auth/login',
|
|
||||||
'/auth/forgot',
|
|
||||||
'/auth/new-password',
|
|
||||||
];
|
|
||||||
|
|
||||||
const mappingRoutePermissions = [
|
|
||||||
{
|
|
||||||
path: '/dashboard',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users',
|
|
||||||
permissions: [PERMISSIONS.USERS.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/create',
|
|
||||||
permissions: [PERMISSIONS.USERS.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/update',
|
|
||||||
permissions: [PERMISSIONS.USERS.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/detail',
|
|
||||||
permissions: [PERMISSIONS.USERS.READ_DETAIL],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles',
|
|
||||||
permissions: [PERMISSIONS.ROLES.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/create',
|
|
||||||
permissions: [PERMISSIONS.ROLES.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/update',
|
|
||||||
permissions: [PERMISSIONS.ROLES.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/detail',
|
|
||||||
permissions: [PERMISSIONS.ROLES.READ_DETAIL],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/create',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/update',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/detail',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
//TODO : Fix this later
|
|
||||||
// const redirectToFirstAccessibleRoute = (userPermissions: string[]) => {
|
|
||||||
// const fallback = mappingRoutePermissions.find((route) =>
|
|
||||||
// route.permissions.some((perm) => userPermissions.includes(perm))
|
|
||||||
// );
|
|
||||||
// return redirect(fallback?.path ?? '/auth/login');
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
const pathname = url.pathname;
|
const pathname = url.pathname;
|
||||||
const session = SessionUser.get();
|
|
||||||
const session_token = SessionToken.get();
|
|
||||||
const token = session_token?.token?.access_token;
|
|
||||||
const userPermissions =
|
|
||||||
session?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
|
|
||||||
|
|
||||||
if (mappingPublicRoutes.includes(pathname)) {
|
if (pathname) return null;
|
||||||
if (token) return redirect('/dashboard');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) return redirect('/auth/login');
|
|
||||||
|
|
||||||
const matchedRoute = mappingRoutePermissions.find(
|
|
||||||
(route) => route.path === pathname
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchedRoute) {
|
|
||||||
const hasPermission =
|
|
||||||
!matchedRoute.permissions ||
|
|
||||||
matchedRoute.permissions.some((perm) => userPermissions.includes(perm));
|
|
||||||
|
|
||||||
if (!hasPermission) {
|
|
||||||
return '/dashboard';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
VITE_API_URL=
|
|
||||||
@@ -2,19 +2,24 @@ import { useForm } from 'react-hook-form';
|
|||||||
import {
|
import {
|
||||||
authLoginSchema,
|
authLoginSchema,
|
||||||
TLoginRequest,
|
TLoginRequest,
|
||||||
|
usePostLogin,
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useSession } from '@imphnen-frontend-service/utils';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export const useLogin = () => {
|
export const useLogin = () => {
|
||||||
|
const postLogin = usePostLogin();
|
||||||
const form = useForm<TLoginRequest>({
|
const form = useForm<TLoginRequest>({
|
||||||
resolver: zodResolver(authLoginSchema),
|
resolver: zodResolver(authLoginSchema),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { signIn } = useSession();
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
|
postLogin.mutate(data, {
|
||||||
const onSubmit = form.handleSubmit((data) => signIn(data));
|
onSuccess: () => toast.success("Login sukses"),
|
||||||
|
onError: (error) => toast.error(error.message),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
|
|||||||
@@ -1,109 +1,11 @@
|
|||||||
import {
|
import { LoaderFunctionArgs } from 'react-router-dom';
|
||||||
PERMISSIONS,
|
|
||||||
SessionToken,
|
|
||||||
SessionUser,
|
|
||||||
} from '@imphnen-frontend-service/utils';
|
|
||||||
import { LoaderFunctionArgs, redirect } from 'react-router';
|
|
||||||
|
|
||||||
const mappingPublicRoutes = [
|
|
||||||
'/',
|
|
||||||
'/auth/login',
|
|
||||||
'/auth/forgot',
|
|
||||||
'/auth/new-password',
|
|
||||||
];
|
|
||||||
|
|
||||||
const mappingRoutePermissions = [
|
|
||||||
{
|
|
||||||
path: '/dashboard',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users',
|
|
||||||
permissions: [PERMISSIONS.USERS.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/create',
|
|
||||||
permissions: [PERMISSIONS.USERS.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/update',
|
|
||||||
permissions: [PERMISSIONS.USERS.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/detail',
|
|
||||||
permissions: [PERMISSIONS.USERS.READ_DETAIL],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles',
|
|
||||||
permissions: [PERMISSIONS.ROLES.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/create',
|
|
||||||
permissions: [PERMISSIONS.ROLES.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/update',
|
|
||||||
permissions: [PERMISSIONS.ROLES.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/detail',
|
|
||||||
permissions: [PERMISSIONS.ROLES.READ_DETAIL],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/create',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/update',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/detail',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
//TODO : Fix this later
|
|
||||||
// const redirectToFirstAccessibleRoute = (userPermissions: string[]) => {
|
|
||||||
// const fallback = mappingRoutePermissions.find((route) =>
|
|
||||||
// route.permissions.some((perm) => userPermissions.includes(perm))
|
|
||||||
// );
|
|
||||||
// return redirect(fallback?.path ?? '/auth/login');
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
const pathname = url.pathname;
|
const pathname = url.pathname;
|
||||||
const session = SessionUser.get();
|
|
||||||
const session_token = SessionToken.get();
|
|
||||||
const token = session_token?.token?.access_token;
|
|
||||||
const userPermissions =
|
|
||||||
session?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
|
|
||||||
|
|
||||||
if (mappingPublicRoutes.includes(pathname)) {
|
if (pathname) return null;
|
||||||
if (token) return redirect('/dashboard');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) return redirect('/auth/login');
|
|
||||||
|
|
||||||
const matchedRoute = mappingRoutePermissions.find(
|
|
||||||
(route) => route.path === pathname
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchedRoute) {
|
|
||||||
const hasPermission =
|
|
||||||
!matchedRoute.permissions ||
|
|
||||||
matchedRoute.permissions.some((perm) => userPermissions.includes(perm));
|
|
||||||
|
|
||||||
if (!hasPermission) {
|
|
||||||
return '/dashboard';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
VITE_API_URL=
|
|
||||||
@@ -2,19 +2,22 @@ import { useForm } from 'react-hook-form';
|
|||||||
import {
|
import {
|
||||||
authLoginSchema,
|
authLoginSchema,
|
||||||
TLoginRequest,
|
TLoginRequest,
|
||||||
|
usePostLogin,
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useSession } from '@imphnen-frontend-service/utils';
|
|
||||||
|
|
||||||
export const useLogin = () => {
|
export const useLogin = () => {
|
||||||
|
const postLogin = usePostLogin();
|
||||||
const form = useForm<TLoginRequest>({
|
const form = useForm<TLoginRequest>({
|
||||||
resolver: zodResolver(authLoginSchema),
|
resolver: zodResolver(authLoginSchema),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { signIn } = useSession();
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
|
postLogin.mutate(data, {
|
||||||
const onSubmit = form.handleSubmit((data) => signIn(data));
|
onSuccess: () => console.log('Success Login'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import {
|
import {
|
||||||
authRegisterSchema,
|
authRegisterSchema,
|
||||||
|
stepOneRegisterSchema,
|
||||||
TRegisterRequest,
|
TRegisterRequest,
|
||||||
usePostRegister,
|
usePostRegister,
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
export const useRegister = () => {
|
export const useRegister = () => {
|
||||||
const postRegister = usePostRegister();
|
const postRegister = usePostRegister();
|
||||||
@@ -18,12 +21,31 @@ export const useRegister = () => {
|
|||||||
password: '',
|
password: '',
|
||||||
confirm_password: '',
|
confirm_password: '',
|
||||||
phone_number: '',
|
phone_number: '',
|
||||||
|
referral_code: '',
|
||||||
|
referred_by: '',
|
||||||
|
student_type: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [stepValid, setStepValid] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = form.watch(() => {
|
||||||
|
const { fullname, email, password, confirm_password } = form.getValues();
|
||||||
|
const valid = stepOneRegisterSchema.safeParse({
|
||||||
|
fullname,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
}).success;
|
||||||
|
setStepValid(valid);
|
||||||
|
});
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => {
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
postRegister.mutate(data, {
|
postRegister.mutate(data, {
|
||||||
onSuccess: () => toast.success('Registrasi sukses'),
|
onSuccess: (data) => toast.success(data.message),
|
||||||
onError: (error) => toast.error(error.message),
|
onError: (error) => toast.error(error.message),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -31,5 +53,6 @@ export const useRegister = () => {
|
|||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
isStepOneValid: stepValid,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,109 +1,11 @@
|
|||||||
import {
|
import { LoaderFunctionArgs } from 'react-router-dom';
|
||||||
PERMISSIONS,
|
|
||||||
SessionToken,
|
|
||||||
SessionUser,
|
|
||||||
} from '@imphnen-frontend-service/utils';
|
|
||||||
import { LoaderFunctionArgs, redirect } from 'react-router';
|
|
||||||
|
|
||||||
const mappingPublicRoutes = [
|
|
||||||
'/',
|
|
||||||
'/auth/login',
|
|
||||||
'/auth/forgot',
|
|
||||||
'/auth/new-password',
|
|
||||||
];
|
|
||||||
|
|
||||||
const mappingRoutePermissions = [
|
|
||||||
{
|
|
||||||
path: '/dashboard',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users',
|
|
||||||
permissions: [PERMISSIONS.USERS.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/create',
|
|
||||||
permissions: [PERMISSIONS.USERS.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/update',
|
|
||||||
permissions: [PERMISSIONS.USERS.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/detail',
|
|
||||||
permissions: [PERMISSIONS.USERS.READ_DETAIL],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles',
|
|
||||||
permissions: [PERMISSIONS.ROLES.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/create',
|
|
||||||
permissions: [PERMISSIONS.ROLES.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/update',
|
|
||||||
permissions: [PERMISSIONS.ROLES.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/roles/detail',
|
|
||||||
permissions: [PERMISSIONS.ROLES.READ_DETAIL],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.READ_LIST],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/create',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/update',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.UPDATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/permissions/detail',
|
|
||||||
permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
//TODO : Fix this later
|
|
||||||
// const redirectToFirstAccessibleRoute = (userPermissions: string[]) => {
|
|
||||||
// const fallback = mappingRoutePermissions.find((route) =>
|
|
||||||
// route.permissions.some((perm) => userPermissions.includes(perm))
|
|
||||||
// );
|
|
||||||
// return redirect(fallback?.path ?? '/auth/login');
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
const pathname = url.pathname;
|
const pathname = url.pathname;
|
||||||
const session = SessionUser.get();
|
|
||||||
const session_token = SessionToken.get();
|
|
||||||
const token = session_token?.token?.access_token;
|
|
||||||
const userPermissions =
|
|
||||||
session?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
|
|
||||||
|
|
||||||
if (mappingPublicRoutes.includes(pathname)) {
|
if (pathname) return null;
|
||||||
if (token) return redirect('/dashboard');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) return redirect('/auth/login');
|
|
||||||
|
|
||||||
const matchedRoute = mappingRoutePermissions.find(
|
|
||||||
(route) => route.path === pathname
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchedRoute) {
|
|
||||||
const hasPermission =
|
|
||||||
!matchedRoute.permissions ||
|
|
||||||
matchedRoute.permissions.some((perm) => userPermissions.includes(perm));
|
|
||||||
|
|
||||||
if (!hasPermission) {
|
|
||||||
return '/dashboard';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
NEXT_PUBLIC_API_URL=
|
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
const { composePlugins, withNx } = require('@nx/next');
|
const { composePlugins, withNx } = require('@nx/next');
|
||||||
|
const { withPayload } = require('@payloadcms/next/withPayload');
|
||||||
|
|
||||||
/** @type {import('@nx/next/plugins/with-nx').WithNxOptions} */
|
/** @type {import('@nx/next/plugins/with-nx').WithNxOptions} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
nx: { svgr: false },
|
nx: { svgr: false },
|
||||||
|
sassOptions: {
|
||||||
|
quietDeps: true,
|
||||||
|
logger: {
|
||||||
|
warn: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = composePlugins(withNx)(nextConfig);
|
module.exports = composePlugins(withNx, withPayload)(nextConfig);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user