Merge branch 'develop' into feat/backoffice-login
This commit is contained in:
@@ -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
|
||||
npx nx serve dimentorin
|
||||
```
|
||||
## How to run
|
||||
|
||||
To create a production bundle:
|
||||
### Development
|
||||
|
||||
```sh
|
||||
npx nx build dimentorin
|
||||
```
|
||||
Use the following commands to run in development mode:
|
||||
|
||||
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
|
||||
npx nx show project dimentorin
|
||||
```
|
||||
### Build
|
||||
|
||||
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
|
||||
npx nx g @nx/react:app demo
|
||||
```
|
||||
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:
|
||||
|
||||
To generate a new library, use:
|
||||
- **Run Storybook**
|
||||
|
||||
```sh
|
||||
npx nx g @nx/react:lib mylib
|
||||
```
|
||||
This command starts Storybook in development mode, allowing you to view and test UI components interactively.
|
||||
|
||||
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.
|
||||
```sh
|
||||
npm run ui:storybook
|
||||
```
|
||||
|
||||
[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)
|
||||
|
||||
## Set up CI!
|
||||
- **Run Unit Test**
|
||||
|
||||
### Step 1
|
||||
This command runs unit tests for the UI components to ensure they function as expected.
|
||||
|
||||
To connect to Nx Cloud, run the following command:
|
||||
```sh
|
||||
npm run ui:test
|
||||
```
|
||||
|
||||
```sh
|
||||
npx nx connect
|
||||
```
|
||||
- **Build Components**
|
||||
|
||||
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:
|
||||
This command generates a static build of Storybook, which can be deployed for sharing and documentation purposes.
|
||||
|
||||
- [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
```sh
|
||||
npm run ui:build
|
||||
```
|
||||
|
||||
### Step 2
|
||||
## How to contribute
|
||||
|
||||
Use the following command to configure a CI workflow for your workspace:
|
||||
1. Fork the repository and clone it locally.
|
||||
2. Create a new branch for a new feature or fix:
|
||||
```sh
|
||||
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.
|
||||
|
||||
```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)
|
||||
If you encounter any issues or problems, feel free to create a new Issue.
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useLogin } from '../../_hooks/use-login';
|
||||
|
||||
interface IModalFormLogin {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onForgotPassword: () => void;
|
||||
setIsOpenRegisterModal: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const ModalFormLogin = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onForgotPassword,
|
||||
setIsOpenRegisterModal,
|
||||
}: IModalFormLogin) => {
|
||||
const { form, onSubmit } = useLogin();
|
||||
|
||||
@@ -59,12 +60,13 @@ const ModalFormLogin = ({
|
||||
</Button>
|
||||
<div className="flex justify-center gap-2 pt-2.5 font-medium">
|
||||
<p className="text-neutral-500">Belum punya akun?</p>
|
||||
<Link
|
||||
className="text-primary-500 hover:text-primary-600"
|
||||
to="/register"
|
||||
<Button
|
||||
variant="text"
|
||||
className="text-primary-500 hover:text-primary-600 m-0 p-0"
|
||||
onClick={() => setIsOpenRegisterModal(true)}
|
||||
>
|
||||
Daftar
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal.Content>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Navbar } from '@imphnen-frontend-service/ui/organisms';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { ModalLoginProvider } from '@imphnen-frontend-service/utils';
|
||||
|
||||
export const AppLayout: FC = (): ReactElement => {
|
||||
return (
|
||||
<ModalLoginProvider>
|
||||
<main className="bg-primary-50 min-h-screen">
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
</main>
|
||||
</ModalLoginProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppLayout;
|
||||
|
||||
@@ -5,10 +5,11 @@ import ModalFormForgotPassword from './_components/form/modal-form-forgot-passwo
|
||||
import ModalFormLogin from './_components/form/modal-form-login';
|
||||
import ModalFormRegister from './_components/form/modal-form-register';
|
||||
import { GachaItem } from './_components/item/gacha-item';
|
||||
import { useModalLogin } from '@imphnen-frontend-service/utils';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const { showModalLogin, setShowModalLogin } = useModalLogin();
|
||||
const [showModalForgotPassword, setShowModalForgotPassword] = useState(false);
|
||||
const [showModalLogin, setShowModalLogin] = useState(false);
|
||||
const [showModalRegister, setShowModalRegister] = useState(false);
|
||||
|
||||
const scrollToRoulette = () => {
|
||||
@@ -176,18 +177,6 @@ export const Components: FC = (): ReactElement => {
|
||||
Spin Now
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 col-span-4 md:col-span-8 lg:col-span-6 justify-center items-center">
|
||||
<Button onClick={() => setShowModalLogin(true)}>
|
||||
Open Modal Login
|
||||
</Button>
|
||||
<Button onClick={() => setShowModalForgotPassword(true)}>
|
||||
Open Modal Forgot Password
|
||||
</Button>
|
||||
<Button onClick={() => setShowModalRegister(true)}>
|
||||
Open Modal Register
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
<div className="sticky bottom-0 h-[86px] md:h-[200px] bg-gradient-to-b from-primary-500/0 to-primary-500/50 to-80%"></div>
|
||||
|
||||
@@ -195,6 +184,7 @@ export const Components: FC = (): ReactElement => {
|
||||
isOpen={showModalLogin}
|
||||
onClose={() => setShowModalLogin(false)}
|
||||
onForgotPassword={handleForgotPasswordClick}
|
||||
setIsOpenRegisterModal={setShowModalRegister}
|
||||
key="login"
|
||||
/>
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 351 KiB |
@@ -2,8 +2,16 @@ import { z } from 'zod';
|
||||
|
||||
export const authLoginSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, 'Email cannot be empty')
|
||||
.email('Email must be valid'),
|
||||
password: z.string().min(1, 'Password cannot be empty'),
|
||||
.string({
|
||||
required_error: 'Email tidak boleh kosong',
|
||||
invalid_type_error: 'Email harus berupa string',
|
||||
})
|
||||
.min(1, 'Email tidak boleh kosong')
|
||||
.email('Email harus valid'),
|
||||
password: z
|
||||
.string({
|
||||
required_error: 'Password tidak boleh kosong',
|
||||
invalid_type_error: 'Password harus berupa string',
|
||||
})
|
||||
.min(1, 'Password tidak boleh kosong'),
|
||||
});
|
||||
|
||||
@@ -75,12 +75,12 @@ export const InputField: FC<TInputFieldProps> = ({
|
||||
{...rest}
|
||||
/>
|
||||
{error ? (
|
||||
<p className="text-danger-500 text-xs mt-1 text-left">{error}</p>
|
||||
<p className="text-danger-500 text-label2 text-left">{error}</p>
|
||||
) : (
|
||||
helperText && (
|
||||
<p
|
||||
className={cn(
|
||||
'text-cs mt-1 text-left',
|
||||
'text-label2 text-left',
|
||||
sizeClasses[size].helperText
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -2,11 +2,12 @@ import { MenuOutlined } from '@ant-design/icons';
|
||||
import { FC, ReactElement, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '../../atoms/button';
|
||||
import { useSession } from '@imphnen-frontend-service/utils';
|
||||
import { useModalLogin, useSession } from '@imphnen-frontend-service/utils';
|
||||
|
||||
export const Navbar: FC = (): ReactElement => {
|
||||
const { session, signOut, isAuthenticated } = useSession();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const { setShowModalLogin } = useModalLogin();
|
||||
|
||||
return (
|
||||
<div className="bg-primary-50 w-full px-[32px] pt-[32px] md:px-[60px] md:pt-[60px] lg:px-[80px] sticky top-0 z-50">
|
||||
@@ -44,7 +45,7 @@ export const Navbar: FC = (): ReactElement => {
|
||||
</li>
|
||||
{!isAuthenticated ? (
|
||||
<li>
|
||||
<Button>Login</Button>
|
||||
<Button onClick={() => setShowModalLogin(true)}>Login</Button>
|
||||
</li>
|
||||
) : (
|
||||
<li className="flex gap-x-4">
|
||||
@@ -88,12 +89,12 @@ export const Navbar: FC = (): ReactElement => {
|
||||
</li>
|
||||
{!isAuthenticated ? (
|
||||
<li>
|
||||
<Link
|
||||
to="#"
|
||||
className="block text-gray-600 transition-colors px-4 py-2 text-center font-semibold"
|
||||
<Button
|
||||
onClick={() => setShowModalLogin(true)}
|
||||
className="block w-full text-gray-100 transition-colors px-4 py-2 text-center font-semibold"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
</Button>
|
||||
</li>
|
||||
) : (
|
||||
<li>{session.user?.fullname}</li>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './use-query-state';
|
||||
export * from './use-session';
|
||||
export * from './use-modal-login';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createContext, ReactNode, useContext, useState } from 'react';
|
||||
|
||||
interface ModalLoginContextType {
|
||||
showModalLogin: boolean;
|
||||
setShowModalLogin: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const ModalLoginContext = createContext<ModalLoginContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export const ModalLoginProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [showModalLogin, setShowModalLogin] = useState(false);
|
||||
|
||||
return (
|
||||
<ModalLoginContext.Provider value={{ showModalLogin, setShowModalLogin }}>
|
||||
{children}
|
||||
</ModalLoginContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useModalLogin = () => {
|
||||
const context = useContext(ModalLoginContext);
|
||||
if (!context) {
|
||||
throw new Error('useModalLogin must be used within an ModalLoginProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Generated
+11
@@ -21,6 +21,7 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
@@ -24244,6 +24245,16 @@
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sonner": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
|
||||
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-keys": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user