Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
@@ -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,23 +2,27 @@ 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, isLoading } = 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,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isLoading
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import {
|
|
||||||
TVerifyOtpRequest,
|
|
||||||
verifyEmailSchema,
|
|
||||||
} from '@imphnen-frontend-service/service';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { useOtp } from '@imphnen-frontend-service/utils';
|
|
||||||
|
|
||||||
export const useOtpHook = () => {
|
|
||||||
const form = useForm<TVerifyOtpRequest>({
|
|
||||||
resolver: zodResolver(verifyEmailSchema),
|
|
||||||
mode: 'all',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { otp, isLoading } = useOtp();
|
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => otp(data));
|
|
||||||
|
|
||||||
return {
|
|
||||||
form,
|
|
||||||
onSubmit,
|
|
||||||
isLoading
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import {
|
|
||||||
authRegisterSchema,
|
|
||||||
TRegisterRequest,
|
|
||||||
} from '@imphnen-frontend-service/service';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { useRegister } from '@imphnen-frontend-service/utils';
|
|
||||||
|
|
||||||
export const useRegisterHook = () => {
|
|
||||||
const form = useForm<TRegisterRequest>({
|
|
||||||
resolver: zodResolver(authRegisterSchema),
|
|
||||||
mode: 'all',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { register, isLoading } = useRegister();
|
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => register(data));
|
|
||||||
|
|
||||||
return {
|
|
||||||
form,
|
|
||||||
onSubmit,
|
|
||||||
isLoading
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { useSendOTP } from '@imphnen-frontend-service/utils';
|
|
||||||
|
|
||||||
export const useResendOtpHook = () => {
|
|
||||||
|
|
||||||
const { resendOTP, isLoading } = useSendOTP();
|
|
||||||
|
|
||||||
return {
|
|
||||||
resendOTP
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -5,7 +5,7 @@ import { useLogin } from '../../_hooks/use-login';
|
|||||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
const { form, onSubmit, isLoading } = useLogin();
|
const { form, onSubmit } = useLogin();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
||||||
@@ -42,7 +42,7 @@ export const Components: FC = (): ReactElement => {
|
|||||||
Lupa Password ?
|
Lupa Password ?
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<Button className="w-full" type='submit' disabled={(!form.formState.isValid || isLoading)}>Enter Isekai</Button>
|
<Button className="w-full" type='submit'>Enter Isekai</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="flex my-3 gap-3 justify-center">
|
<div className="flex my-3 gap-3 justify-center">
|
||||||
@@ -74,13 +74,6 @@ export const Components: FC = (): ReactElement => {
|
|||||||
<ArrowLeftOutlined />
|
<ArrowLeftOutlined />
|
||||||
Kembali Ke Homepage
|
Kembali Ke Homepage
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
className='xl:hidden w-full gap-3'
|
|
||||||
variant='secondary'
|
|
||||||
>
|
|
||||||
<ArrowLeftOutlined />
|
|
||||||
Kembali Ke Homepage
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { FC, ReactElement } from 'react';
|
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const AppLayout: FC = (): ReactElement => {
|
|
||||||
return (
|
|
||||||
<main className="bg-primary-50 min-h-screen">
|
|
||||||
<Outlet />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppLayout;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { FC, ReactElement, useEffect, useState } from 'react';
|
|
||||||
import { ControlledInputField, RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms";
|
|
||||||
import { useOtpHook } from '../../../_hooks/use-otp';
|
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import { useResendOtpHook } from '../../../_hooks/use-resend-otp';
|
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
|
||||||
const { form, onSubmit, isLoading } = useOtpHook()
|
|
||||||
const [ searchParams ] = useSearchParams()
|
|
||||||
const [ disabled, setDisabled] = useState(true);
|
|
||||||
const [ timeLeft, setTimeLeft ] = useState(5 * 60);
|
|
||||||
const { resendOTP } = useResendOtpHook()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (disabled) {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setTimeLeft(prev => {
|
|
||||||
if (prev <= 1) {
|
|
||||||
clearInterval(interval);
|
|
||||||
setDisabled(false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return prev - 1;
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
|
||||||
}, [disabled]);
|
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
|
||||||
const m = Math.floor(seconds / 60);
|
|
||||||
const s = seconds % 60;
|
|
||||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]'>
|
|
||||||
<div className='bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6'>
|
|
||||||
<RegisterResetBanner />
|
|
||||||
<div className='border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[48px] flex flex-col justify-center'>
|
|
||||||
<h3 className='mt-5 text-3xl font-semibold text-primary-500'>Verifikasi Email</h3>
|
|
||||||
<h5 className='mt-2 text-xl font-medium text-primary-500'>Yeay~! Pesan dari dunia lain sudah dikirimkan ke {searchParams.get("email")}</h5>
|
|
||||||
<form onSubmit={onSubmit} className='mt-7'>
|
|
||||||
<ControlledInputField
|
|
||||||
label="OTP"
|
|
||||||
size="lg"
|
|
||||||
className="w-full"
|
|
||||||
placeholder="XXXXXX"
|
|
||||||
max={6}
|
|
||||||
name={'otp'}
|
|
||||||
maxLength={6}
|
|
||||||
control={form.control}
|
|
||||||
/>
|
|
||||||
<Button type="button" className="mt-2" disabled={disabled} onClick={
|
|
||||||
() => {
|
|
||||||
resendOTP({email: searchParams.get("email") || ""})
|
|
||||||
setDisabled(true)
|
|
||||||
setTimeLeft(5 * 60)
|
|
||||||
}
|
|
||||||
}>Kirim ulang otp {disabled? `(${formatTime(timeLeft)})`: ""}</Button>
|
|
||||||
<Button className="xl:w-full mt-2" disabled={(!form.formState.isValid || isLoading)}>Linked Start !!!</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Components;
|
|
||||||
@@ -1,108 +1,96 @@
|
|||||||
import { FC, ReactElement } from 'react';
|
import { FC, ReactElement } from 'react';
|
||||||
import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
import { InputField } from '@imphnen-frontend-service/ui/molecules';
|
||||||
import { useRegisterHook } from '../../_hooks/use-register';
|
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
const { form, onSubmit, isLoading } = useRegisterHook();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
||||||
<div className="bg-white xl:min-w-[1130px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6">
|
<div className="bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6">
|
||||||
<RegisterResetBanner />
|
<RegisterResetBanner />
|
||||||
<div className="xl:border-2 xl:border-primary-500/50 xl:w-[726px] rounded-lg py-[32px] px-[48px] flex justify-center">
|
<div className="border-2 border-primary-500/50 w-[696px] rounded-lg py-[32px] px-[48px] flex justify-center">
|
||||||
<div className="xl:w-[714px]">
|
<div className="w-[704px]">
|
||||||
<Button
|
|
||||||
className='xl:hidden gap-3'
|
|
||||||
variant='secondary'
|
|
||||||
>
|
|
||||||
<ArrowLeftOutlined />
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
<h3 className="mt-5 text-3xl font-semibold text-primary-500">
|
<h3 className="mt-5 text-3xl font-semibold text-primary-500">
|
||||||
Register
|
Register
|
||||||
</h3>
|
</h3>
|
||||||
<h5 className="text-primary-500 font-medium">
|
<h5 className="text-primary-500 font-medium">
|
||||||
Yosha~! Saatnya Bergabung dengan Dimentorin
|
Yosha~! Saatnya Bergabung dengan Dimentorin
|
||||||
</h5>
|
</h5>
|
||||||
<form onSubmit={onSubmit}>
|
<div className="grid grid-flow-row-dense my-7 grid-cols-2 gap-2">
|
||||||
<div className='my-7'>
|
<InputField
|
||||||
<ControlledInputField
|
label="First Name"
|
||||||
label="Full name"
|
|
||||||
size="lg"
|
|
||||||
className="w-full"
|
|
||||||
placeholder="Nama Lengkap"
|
|
||||||
name={'fullname'}
|
|
||||||
control={form.control}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-row-dense my-7 lg:grid-cols-2 gap-2">
|
|
||||||
<ControlledInputField
|
|
||||||
label="Email"
|
|
||||||
size="lg"
|
|
||||||
className="w-full"
|
|
||||||
placeholder="Contoh : yourname@mail.com"
|
|
||||||
type='email'
|
|
||||||
control={form.control}
|
|
||||||
name={'email'}
|
|
||||||
/>
|
|
||||||
<ControlledInputField
|
|
||||||
label="No Hp"
|
|
||||||
size="lg"
|
|
||||||
className="w-full"
|
|
||||||
placeholder="Contoh : 088877665544"
|
|
||||||
control={form.control}
|
|
||||||
name={'phone_number'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ControlledInputField
|
|
||||||
label="Password"
|
|
||||||
className="w-full"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
type="password"
|
placeholder="Nama Depan"
|
||||||
placeholder="Buat password sekeren jurus ultimate-mu!"
|
|
||||||
control={form.control}
|
|
||||||
name={'password'}
|
|
||||||
/>
|
/>
|
||||||
<h6 className="text-sm text-gray-500 mt-1">
|
<InputField
|
||||||
"Senpai~! Pastikan password-mu sekuat pertahanan kastil!"
|
label="Last Name"
|
||||||
</h6>
|
|
||||||
<h6 className="text-sm text-gray-500 mt-2">
|
|
||||||
Tips membuat password yang OP:
|
|
||||||
</h6>
|
|
||||||
<h6 className="text-sm text-gray-500">
|
|
||||||
- Minimal 8 karakter (semakin panjang, semakin power-up!{' '}
|
|
||||||
<span role="img" aria-label="emoji">
|
|
||||||
⚡
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
</h6>
|
|
||||||
<h6 className="text-sm text-gray-500">
|
|
||||||
- Campur huruf besar, kecil, angka, dan simbol untuk kombinasi
|
|
||||||
ultimate!
|
|
||||||
<span role="img" aria-label="emoji">
|
|
||||||
🔥
|
|
||||||
</span>
|
|
||||||
</h6>
|
|
||||||
<h6 className="text-sm text-gray-500 mb-2">
|
|
||||||
- Jangan pakai password yang gampang ditebak, nanti ketahuan
|
|
||||||
musuh!
|
|
||||||
<span role="img" aria-label="emoji">
|
|
||||||
🚨
|
|
||||||
</span>
|
|
||||||
</h6>
|
|
||||||
<ControlledInputField
|
|
||||||
label="Repeat Password"
|
|
||||||
className="w-full"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
type="password"
|
className="w-full"
|
||||||
placeholder="Ulangi password-mu, Senpai~!"
|
placeholder="Nama Belakang"
|
||||||
control={form.control}
|
|
||||||
name={"confirm_password"}
|
|
||||||
/>
|
/>
|
||||||
<Button className="xl:w-full mt-2" disabled={(!form.formState.isValid || isLoading)}>Linked Start !!!</Button>
|
<InputField
|
||||||
</form>
|
label="Email"
|
||||||
|
size="lg"
|
||||||
|
placeholder="Contoh : yourname@mail.com"
|
||||||
|
/>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h4 className="font-medium">OTP Code</h4>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
size="lg"
|
||||||
|
placeholder="Kode OTP"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Button size="sm" className="mt-[30px]">
|
||||||
|
Kirim OTP
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<InputField
|
||||||
|
label="Password"
|
||||||
|
className="w-full"
|
||||||
|
size="lg"
|
||||||
|
type="password"
|
||||||
|
placeholder="Buat password sekeren jurus ultimate-mu!"
|
||||||
|
/>
|
||||||
|
<h6 className="text-sm text-gray-500 mt-1">
|
||||||
|
"Senpai~! Pastikan password-mu sekuat pertahanan kastil!"
|
||||||
|
</h6>
|
||||||
|
<h6 className="text-sm text-gray-500 mt-2">
|
||||||
|
Tips membuat password yang OP:
|
||||||
|
</h6>
|
||||||
|
<h6 className="text-sm text-gray-500">
|
||||||
|
- Minimal 8 karakter (semakin panjang, semakin power-up!{' '}
|
||||||
|
<span role="img" aria-label="emoji">
|
||||||
|
⚡
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</h6>
|
||||||
|
<h6 className="text-sm text-gray-500">
|
||||||
|
- Campur huruf besar, kecil, angka, dan simbol untuk kombinasi
|
||||||
|
ultimate!
|
||||||
|
<span role="img" aria-label="emoji">
|
||||||
|
🔥
|
||||||
|
</span>
|
||||||
|
</h6>
|
||||||
|
<h6 className="text-sm text-gray-500 mb-2">
|
||||||
|
- Jangan pakai password yang gampang ditebak, nanti ketahuan
|
||||||
|
musuh!
|
||||||
|
<span role="img" aria-label="emoji">
|
||||||
|
🚨
|
||||||
|
</span>
|
||||||
|
</h6>
|
||||||
|
<InputField
|
||||||
|
label="Repeat Password"
|
||||||
|
className="w-full"
|
||||||
|
size="lg"
|
||||||
|
type="password"
|
||||||
|
placeholder="Ulangi password-mu, Senpai~!"
|
||||||
|
/>
|
||||||
|
<Button className="w-full mt-2">Linked Start !!!</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
import { FC, ReactElement, useEffect } from 'react';
|
import { FC, ReactElement } from 'react';
|
||||||
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
document.location.href = "/auth/login"
|
|
||||||
}, 10000)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
||||||
<div className="bg-white xl:min-w-[1220px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6">
|
<div className="bg-white min-w-[1220px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6">
|
||||||
<RegisterResetBanner />
|
<RegisterResetBanner />
|
||||||
<div className="border-2 border-primary-500/50 xl:w-[696px] rounded-lg py-[32px] px-[48px] flex justify-center bg-primary-50">
|
<div className="border-2 border-primary-500/50 w-[696px] rounded-lg py-[32px] px-[48px] flex justify-center bg-primary-50">
|
||||||
<div className="xl:w-[704px] flex flex-col justify-center items-center">
|
<div className="w-[704px] flex flex-col justify-center items-center">
|
||||||
<h2 className="text-4xl text-primary-500 font-semibold">
|
<h2 className="text-4xl text-primary-500 font-semibold">
|
||||||
Register Successfull
|
Register Successfull
|
||||||
</h2>
|
</h2>
|
||||||
@@ -47,12 +41,10 @@ export const Components: FC = (): ReactElement => {
|
|||||||
<h4 className="mt-10 text-primary-500 font-medium text-xl">
|
<h4 className="mt-10 text-primary-500 font-medium text-xl">
|
||||||
Kamu akan memasuki isekai dalam 10 dtk
|
Kamu akan memasuki isekai dalam 10 dtk
|
||||||
</h4>
|
</h4>
|
||||||
<a href="/auth/login">
|
<Button className="gap-3 mt-10" size="lg">
|
||||||
<Button className="gap-3 mt-10" size="lg">
|
Masuk Isekai
|
||||||
Masuk Isekai
|
<ArrowRightOutlined />
|
||||||
<ArrowRightOutlined />
|
</Button>
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user