Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9108ddb742 | ||
|
|
628e168342 | ||
|
|
9aff9e2dc8 | ||
|
|
585ad482f2 | ||
|
|
988476b409 | ||
|
|
38e6d4643b | ||
|
|
79cb9d81fc | ||
|
|
64e702df5d | ||
|
|
33c04707f8 | ||
|
|
f87dc4c782 | ||
|
|
b5428d8552 | ||
|
|
0a846b0512 | ||
|
|
3a950ea867 | ||
|
|
4693284b37 | ||
|
|
fe1928e8ef | ||
|
|
a9cc728ade | ||
|
|
2602657201 |
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_URL=https://api.example.com
|
||||||
@@ -1,101 +1,117 @@
|
|||||||
# ImphnenFrontendService
|
# IMPHNEN Frontend Service
|
||||||
|
|
||||||
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
|
<p align="center">
|
||||||
|
<img src="docs/logo.svg" alt="IMPHNEN">
|
||||||
|
</p>
|
||||||
|
|
||||||
✨ Your new, shiny [Nx workspace](https://nx.dev) is ready ✨.
|
This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications:
|
||||||
|
|
||||||
[Learn more about this workspace setup and its capabilities](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed!
|
1. **Gacha** - Application for <a href="https://gacha.imphnen.dev/" target="_blank">gacha website</a>.
|
||||||
|
2. **Backoffice** - Application for internal management.
|
||||||
|
3. **Dimentorin** - Application for mentoring services.
|
||||||
|
|
||||||
## Run tasks
|
## How to install
|
||||||
|
|
||||||
To run the dev server for your app, use:
|
1. Clone this repository:
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/IMPHNEN/imphnen-frontend-service.git
|
||||||
|
cd imphnen-frontend-service
|
||||||
|
```
|
||||||
|
2. Install all dependencies:
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
```sh
|
## How to run
|
||||||
npx nx serve dimentorin
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a production bundle:
|
### Development
|
||||||
|
|
||||||
```sh
|
Use the following commands to run in development mode:
|
||||||
npx nx build dimentorin
|
|
||||||
```
|
|
||||||
|
|
||||||
To see all available targets to run for a project, run:
|
- **Gacha**:
|
||||||
|
```sh
|
||||||
|
npm run gacha:dev
|
||||||
|
```
|
||||||
|
- **Backoffice**:
|
||||||
|
```sh
|
||||||
|
npm run backoffice:dev
|
||||||
|
```
|
||||||
|
- **Dimentorin**:
|
||||||
|
```sh
|
||||||
|
npm run dimentorin:dev
|
||||||
|
```
|
||||||
|
|
||||||
```sh
|
### Build
|
||||||
npx nx show project dimentorin
|
|
||||||
```
|
|
||||||
|
|
||||||
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files.
|
Use the following commands to build the applications:
|
||||||
|
|
||||||
[More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
- **Gacha**:
|
||||||
|
```sh
|
||||||
|
npm run gacha:build
|
||||||
|
```
|
||||||
|
- **Backoffice**:
|
||||||
|
```sh
|
||||||
|
npm run backoffice:build
|
||||||
|
```
|
||||||
|
- **Dimentorin**:
|
||||||
|
```sh
|
||||||
|
npm run dimentorin:build
|
||||||
|
```
|
||||||
|
|
||||||
## Add new projects
|
### Production
|
||||||
|
|
||||||
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
|
Use the following commands to run the applications in production mode:
|
||||||
|
|
||||||
Use the plugin's generator to create new projects.
|
- **Gacha**:
|
||||||
|
```sh
|
||||||
|
npm run gacha:prod
|
||||||
|
```
|
||||||
|
- **Backoffice**:
|
||||||
|
```sh
|
||||||
|
npm run backoffice:prod
|
||||||
|
```
|
||||||
|
- **Dimentorin**:
|
||||||
|
```sh
|
||||||
|
npm run dimentorin:prod
|
||||||
|
```
|
||||||
|
|
||||||
To generate a new application, use:
|
### Storybook
|
||||||
|
|
||||||
```sh
|
This repository uses Storybook to develop, test, and document UI components in an isolated and interactive environment. Below are the commands to work with Storybook:
|
||||||
npx nx g @nx/react:app demo
|
|
||||||
```
|
|
||||||
|
|
||||||
To generate a new library, use:
|
- **Run Storybook**
|
||||||
|
|
||||||
|
This command starts Storybook in development mode, allowing you to view and test UI components interactively.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx nx g @nx/react:lib mylib
|
npm run ui:storybook
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list <plugin-name>` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
|
- **Run Unit Test**
|
||||||
|
|
||||||
[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
This command runs unit tests for the UI components to ensure they function as expected.
|
||||||
|
|
||||||
## Set up CI!
|
```sh
|
||||||
|
npm run ui:test
|
||||||
|
```
|
||||||
|
|
||||||
### Step 1
|
- **Build Components**
|
||||||
|
|
||||||
To connect to Nx Cloud, run the following command:
|
This command generates a static build of Storybook, which can be deployed for sharing and documentation purposes.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx nx connect
|
npm run ui:build
|
||||||
```
|
```
|
||||||
|
|
||||||
Connecting to Nx Cloud ensures a [fast and scalable CI](https://nx.dev/ci/intro/why-nx-cloud?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) pipeline. It includes features such as:
|
## How to contribute
|
||||||
|
|
||||||
- [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
1. Fork the repository and clone it locally.
|
||||||
- [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
2. Create a new branch for a new feature or fix:
|
||||||
- [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
```sh
|
||||||
- [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
git checkout -b feat/feature-name
|
||||||
|
```
|
||||||
|
3. Make changes, commit, and push to your forked repository.
|
||||||
|
4. Create a pull request to this repository `develop` branch.
|
||||||
|
|
||||||
### Step 2
|
If you encounter any issues or problems, feel free to create a new Issue.
|
||||||
|
|
||||||
Use the following command to configure a CI workflow for your workspace:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npx nx g ci-workflow
|
|
||||||
```
|
|
||||||
|
|
||||||
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
|
|
||||||
## Install Nx Console
|
|
||||||
|
|
||||||
Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ.
|
|
||||||
|
|
||||||
[Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
|
|
||||||
## Useful links
|
|
||||||
|
|
||||||
Learn more:
|
|
||||||
|
|
||||||
- [Learn more about this workspace setup](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
|
|
||||||
And join the Nx community:
|
|
||||||
- [Discord](https://go.nx.dev/community)
|
|
||||||
- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl)
|
|
||||||
- [Our Youtube channel](https://www.youtube.com/@nxdevtools)
|
|
||||||
- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/logos/simple.svg" />
|
||||||
<link rel="stylesheet" href="/src/styles.css" />
|
<link rel="stylesheet" href="/src/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
authLoginSchema,
|
||||||
|
TLoginRequest,
|
||||||
|
usePostLogin,
|
||||||
|
} from '@imphnen-frontend-service/service';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const useLogin = () => {
|
||||||
|
const postLogin = usePostLogin();
|
||||||
|
const form = useForm<TLoginRequest>({
|
||||||
|
resolver: zodResolver(authLoginSchema),
|
||||||
|
mode: 'all',
|
||||||
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
|
postLogin.mutate(data, {
|
||||||
|
onSuccess: () => {
|
||||||
|
console.log('Success Login');
|
||||||
|
navigate('/dashboard');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { FC, ReactElement } from 'react';
|
import { FC, ReactElement } from 'react';
|
||||||
import { useNavigate } from 'react-router';
|
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { InputField } from '@imphnen-frontend-service/ui/molecules';
|
import { useLogin } from './_hooks/use-login';
|
||||||
|
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
const navigate = useNavigate();
|
const { form, onSubmit } = useLogin();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen">
|
<div className="flex justify-center items-center min-h-screen">
|
||||||
@@ -13,21 +13,29 @@ export const Components: FC = (): ReactElement => {
|
|||||||
<h1 className="text-primary-500 text-p1 font-semibold">
|
<h1 className="text-primary-500 text-p1 font-semibold">
|
||||||
Welcome to IMPHNEN Backoffice
|
Welcome to IMPHNEN Backoffice
|
||||||
</h1>
|
</h1>
|
||||||
<InputField
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
label="Email"
|
<ControlledInputField
|
||||||
placeholder="Masukkan Email"
|
control={form.control}
|
||||||
type="email"
|
label="Email"
|
||||||
size="lg"
|
placeholder="Masukkan Email"
|
||||||
className="w-full"
|
type="email"
|
||||||
/>
|
name="email"
|
||||||
<InputField
|
size="lg"
|
||||||
label="Password"
|
className="w-full"
|
||||||
placeholder="Masukkan password"
|
/>
|
||||||
type="password"
|
<ControlledInputField
|
||||||
size="lg"
|
control={form.control}
|
||||||
className="w-full"
|
label="Password"
|
||||||
/>
|
placeholder="Masukkan Password"
|
||||||
<Button onClick={() => navigate('/dashboard')}>Login</Button>
|
type="password"
|
||||||
|
name="password"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<Button type="submit" size="md" className="w-full">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ export default defineConfig(() => ({
|
|||||||
root: __dirname,
|
root: __dirname,
|
||||||
cacheDir: '../../node_modules/.vite/apps/backoffice',
|
cacheDir: '../../node_modules/.vite/apps/backoffice',
|
||||||
server: {
|
server: {
|
||||||
port: 4200,
|
port: 3000,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
port: 4300,
|
port: 3001,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},
|
},
|
||||||
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules';
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
import { useQueryState } from '@imphnen-frontend-service/utils';
|
import { useQueryState } from '@imphnen-frontend-service/utils';
|
||||||
|
import { useRegister } from '../../_hooks/use-register';
|
||||||
|
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
|
import { TRegisterRequest } from '@imphnen-frontend-service/service';
|
||||||
|
|
||||||
interface IModalFormRegisterProps {
|
interface IModalFormRegisterProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -8,6 +12,8 @@ interface IModalFormRegisterProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
|
const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
|
||||||
|
const { form, onSubmit } = useRegister();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
step: currentStep,
|
step: currentStep,
|
||||||
nextStep,
|
nextStep,
|
||||||
@@ -37,45 +43,56 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
|
|||||||
{currentStep === 1 ? 'Info Akun' : 'Informasi Pengiriman Hadiah'}
|
{currentStep === 1 ? 'Info Akun' : 'Informasi Pengiriman Hadiah'}
|
||||||
</h2>
|
</h2>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content className="space-y-6">
|
<Modal.Content>
|
||||||
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
{currentStep === 2 && (
|
{currentStep === 1 && (
|
||||||
<StepTwo nextStep={nextStep} prevStep={prevStep} />
|
<StepOne form={form} nextStep={nextStep} onClose={onClose} />
|
||||||
)}
|
)}
|
||||||
|
{currentStep === 2 && <StepTwo form={form} prevStep={prevStep} />}
|
||||||
|
</form>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IStepOneProps {
|
interface IStepOneProps {
|
||||||
|
form: UseFormReturn<TRegisterRequest, any, TRegisterRequest>;
|
||||||
nextStep: () => void;
|
nextStep: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
|
const StepOne = ({ form, nextStep, onClose }: IStepOneProps) => (
|
||||||
<>
|
<>
|
||||||
<InputField
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
name="fullname"
|
||||||
label="Nama Lengkap"
|
label="Nama Lengkap"
|
||||||
placeholder="Masukkan Nama Lengkap"
|
placeholder="Masukkan Nama Lengkap"
|
||||||
type="text"
|
type="text"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<InputField
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
label="Email"
|
label="Email"
|
||||||
placeholder="Masukkan Email Anda"
|
placeholder="Masukkan Email Anda"
|
||||||
type="email"
|
type="email"
|
||||||
|
name="email"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<InputField
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
placeholder="Masukkan Password"
|
placeholder="Masukkan Password"
|
||||||
type="password"
|
type="password"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<InputField
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
name="confirm_password"
|
||||||
label="Ulangi Password"
|
label="Ulangi Password"
|
||||||
placeholder="Masukkan Ulang Password"
|
placeholder="Masukkan Ulang Password"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -89,22 +106,44 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
interface IStepTwoProps {
|
interface IStepTwoProps {
|
||||||
nextStep: () => void;
|
form: UseFormReturn<TRegisterRequest, any, TRegisterRequest>;
|
||||||
prevStep: () => void;
|
prevStep: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StepTwo = ({ nextStep, prevStep }: IStepTwoProps) => (
|
const StepTwo = ({ form, prevStep }: IStepTwoProps) => (
|
||||||
<>
|
<>
|
||||||
<InputField
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
name="phone_number"
|
||||||
label="Nomor Telepon"
|
label="Nomor Telepon"
|
||||||
placeholder="Masukkan Nomor Telepon Aktif"
|
placeholder="Masukkan Nomor Telepon Aktif"
|
||||||
type="text"
|
type="text"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<InputField
|
<ControlledInputField
|
||||||
label="Alamat Pengiriman"
|
control={form.control}
|
||||||
placeholder="Masukkan Alamat Pengiriman"
|
name="referral_code"
|
||||||
|
label="Kode Referral"
|
||||||
|
placeholder="Masukkan Kode Referral"
|
||||||
|
type="text"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
name="referred_by"
|
||||||
|
label="Referral By"
|
||||||
|
placeholder="Masukkan Referral By"
|
||||||
|
type="text"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
name="student_type"
|
||||||
|
label="Tipe Pelajar"
|
||||||
|
placeholder="Masukkan Tipe Pelajar"
|
||||||
type="text"
|
type="text"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -113,7 +152,7 @@ const StepTwo = ({ nextStep, prevStep }: IStepTwoProps) => (
|
|||||||
<Button size="md" variant="text" className="w-[40%]" onClick={prevStep}>
|
<Button size="md" variant="text" className="w-[40%]" onClick={prevStep}>
|
||||||
Kembali
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="md" className="w-full" onClick={nextStep}>
|
<Button size="md" className="w-full" type="submit">
|
||||||
Gacha Sekarang
|
Gacha Sekarang
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
authRegisterSchema,
|
||||||
|
TRegisterRequest,
|
||||||
|
usePostRegister,
|
||||||
|
} from '@imphnen-frontend-service/service';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export const useRegister = () => {
|
||||||
|
const postRegister = usePostRegister();
|
||||||
|
const form = useForm<TRegisterRequest>({
|
||||||
|
resolver: zodResolver(authRegisterSchema),
|
||||||
|
mode: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
|
postRegister.mutate(data, {
|
||||||
|
onSuccess: (data) => toast.success(data.message),
|
||||||
|
onError: (error) => toast.error(error.message),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
convertPagesToRoute,
|
convertPagesToRoute,
|
||||||
QueryProvider,
|
QueryProvider,
|
||||||
} from '@imphnen-frontend-service/utils';
|
} from '@imphnen-frontend-service/utils';
|
||||||
|
import { Toaster } from 'sonner';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
||||||
@@ -34,6 +35,7 @@ if (!rootElement) throw new Error('Failed to find the root element');
|
|||||||
createRoot(rootElement).render(
|
createRoot(rootElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
|
<Toaster />
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 351 KiB |
@@ -15,3 +15,62 @@ export const authLoginSchema = z.object({
|
|||||||
})
|
})
|
||||||
.min(1, 'Password tidak boleh kosong'),
|
.min(1, 'Password tidak boleh kosong'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const authRegisterSchema = z
|
||||||
|
.object({
|
||||||
|
email: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Email tidak boleh kosong',
|
||||||
|
invalid_type_error: 'Email harus berupa string',
|
||||||
|
})
|
||||||
|
.min(1, 'Email tidak boleh kosong')
|
||||||
|
.email('Email harus valid'),
|
||||||
|
fullname: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Nama tidak boleh kosong',
|
||||||
|
invalid_type_error: 'Nama harus berupa string',
|
||||||
|
})
|
||||||
|
.min(1, 'Nama tidak boleh kosong')
|
||||||
|
.max(50, 'Nama tidak boleh lebih dari 50 karakter'),
|
||||||
|
password: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Password tidak boleh kosong',
|
||||||
|
invalid_type_error: 'Password harus berupa string',
|
||||||
|
})
|
||||||
|
.min(1, 'Password tidak boleh kosong')
|
||||||
|
.min(8, 'Password harus lebih dari 8 karakter')
|
||||||
|
.max(50, 'Password tidak boleh lebih dari 50 karakter'),
|
||||||
|
phone_number: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Nomor telepon tidak boleh kosong',
|
||||||
|
invalid_type_error: 'Nomor telepon harus berupa string',
|
||||||
|
})
|
||||||
|
.min(1, 'Nomor telepon tidak boleh kosong')
|
||||||
|
.max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter'),
|
||||||
|
referral_code: z
|
||||||
|
.string()
|
||||||
|
.min(1, 'Kode referral tidak boleh kosong')
|
||||||
|
.max(4, 'Kode referral tidak boleh lebih dari 4 karakter')
|
||||||
|
.optional(),
|
||||||
|
referred_by: z
|
||||||
|
.string()
|
||||||
|
.min(1, 'Kode referral tidak boleh kosong')
|
||||||
|
.max(50, 'Kode referral tidak boleh lebih dari 50 karakter')
|
||||||
|
.optional(),
|
||||||
|
student_type: z.string(),
|
||||||
|
confirm_password: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Konfirmasi password tidak boleh kosong',
|
||||||
|
})
|
||||||
|
.min(1, 'Konfirmasi password tidak boleh kosong')
|
||||||
|
.max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'),
|
||||||
|
})
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (val.password !== val.confirm_password) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ['confirm_password'],
|
||||||
|
message: `No duplicates allowed.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -16,9 +16,14 @@ export type TLoginResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TRegisterRequest = {
|
export type TRegisterRequest = {
|
||||||
fullname: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
|
fullname: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
phone_number: string;
|
||||||
|
referral_code?: string;
|
||||||
|
referred_by?: string;
|
||||||
|
student_type: string;
|
||||||
|
confirm_password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TVerifyEmailRequest = {
|
export type TVerifyEmailRequest = {
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export const Input: FC<TInputProps> = ({
|
|||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
const [showPassword, setShowPassword] = useState(false); // State for password visibility
|
const [showPassword, setShowPassword] = useState(false); // State for password visibility
|
||||||
|
|
||||||
const togglePasswordVisibility = () => {
|
const togglePasswordVisibility = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
if (!disabled) setShowPassword((prev) => !prev);
|
if (!disabled) setShowPassword((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ export const Input: FC<TInputProps> = ({
|
|||||||
{type === 'password' && (
|
{type === 'password' && (
|
||||||
<div className="absolute end-0 px-[12px] h-full flex items-center">
|
<div className="absolute end-0 px-[12px] h-full flex items-center">
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
variant="text"
|
variant="text"
|
||||||
size={size}
|
size={size}
|
||||||
onClick={togglePasswordVisibility}
|
onClick={togglePasswordVisibility}
|
||||||
|
|||||||
@@ -75,10 +75,15 @@ export const InputField: FC<TInputFieldProps> = ({
|
|||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{error ? (
|
{error ? (
|
||||||
<p className="text-danger-500 text-xl mt-1">{error}</p>
|
<p className="text-danger-500 text-label1 text-left">{error}</p>
|
||||||
) : (
|
) : (
|
||||||
helperText && (
|
helperText && (
|
||||||
<p className={cn('text-cs mt-1', sizeClasses[size].helperText)}>
|
<p
|
||||||
|
className={cn(
|
||||||
|
'text-label2 text-left',
|
||||||
|
sizeClasses[size].helperText
|
||||||
|
)}
|
||||||
|
>
|
||||||
{helperText}
|
{helperText}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,20 +7,19 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button } from '../../atoms';
|
import { Button } from '../../atoms';
|
||||||
import { FC, ReactElement } from 'react';
|
import { FC, ReactElement } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { useSession } from '@imphnen-frontend-service/utils';
|
||||||
|
|
||||||
export const BackofficeSidebar: FC = (): ReactElement => {
|
export const BackofficeSidebar: FC = (): ReactElement => {
|
||||||
|
const { signOut } = useSession();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
|
||||||
const isActive = (path: string) => location.pathname.includes(path);
|
const isActive = (path: string) => location.pathname.includes(path);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sticky top-0 left-0 w-[280px] bg-white min-h-screen py-[60px] px-[28px] shadow-xl flex flex-col justify-between">
|
<aside className="sticky top-0 left-0 w-[280px] bg-white min-h-screen py-[60px] px-[28px] shadow-xl flex flex-col justify-between">
|
||||||
<div className="flex flex-col gap-20 justify-between items-center">
|
<div className="flex flex-col gap-20 justify-between items-center">
|
||||||
{/* Logo */}
|
|
||||||
<img src="/logos/simple.svg" alt="IMPHNEN Logo" className="w-[150px]" />
|
<img src="/logos/simple.svg" alt="IMPHNEN Logo" className="w-[150px]" />
|
||||||
|
|
||||||
{/* Navigation Menu */}
|
|
||||||
<nav className="flex flex-col gap-4 w-full">
|
<nav className="flex flex-col gap-4 w-full">
|
||||||
<Link
|
<Link
|
||||||
to="/dashboard"
|
to="/dashboard"
|
||||||
@@ -72,14 +71,10 @@ export const BackofficeSidebar: FC = (): ReactElement => {
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Log Out Button */}
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<hr className="mb-5 border-primary-200" />
|
<hr className="mb-5 border-primary-200" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={signOut}
|
||||||
navigate('/');
|
|
||||||
}}
|
|
||||||
variant="text"
|
variant="text"
|
||||||
className="items-start justify-start gap-3 px-[8px] py-[10px] text-gray-700 hover:text-red-500 transition-colors w-full"
|
className="items-start justify-start gap-3 px-[8px] py-[10px] text-gray-700 hover:text-red-500 transition-colors w-full"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user