Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72dbe3c261 | ||
|
|
da7f30659a | ||
|
|
c3da21d642 | ||
|
|
81894bf0cb | ||
|
|
9851c3b9e4 | ||
|
|
f4baae0749 | ||
|
|
bc03ca23e9 | ||
|
|
7321d28a76 | ||
|
|
890afa98db | ||
|
|
62e18e5f7e | ||
|
|
b266aa8796 | ||
|
|
57222b655c | ||
|
|
72888bd090 | ||
|
|
a5bde58bc8 |
No files matched your search
@@ -0,0 +1,33 @@
|
|||||||
|
name: Deploy Landing
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'apps/landing/**' # INI BASED ON PATH CHANGES JADI NTAR KALAU ADA PUSH DIISNI OTOMATIS DEPLOY
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SSH_HOST }}
|
||||||
|
username: ${{ secrets.SSH_USER }}
|
||||||
|
key: ${{ secrets.SSH_KEY }}
|
||||||
|
port: ${{ secrets.SSH_PORT }}
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
if [ ! -d ~/imphnen-frontend-service ]; then
|
||||||
|
git clone https://github.com/IMPHNEN/imphnen-frontend-service.git ~/imphnen-frontend-service
|
||||||
|
else
|
||||||
|
cd ~/imphnen-frontend-service && git pull
|
||||||
|
fi
|
||||||
|
echo "NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}" > ~/imphnen-frontend-service/apps/landing/.env
|
||||||
|
docker compose -f ~/imphnen-frontend-service/docker-compose-landing.yml down || true
|
||||||
|
docker compose -f ~/imphnen-frontend-service/docker-compose-landing.yml build
|
||||||
|
docker compose -f ~/imphnen-frontend-service/docker-compose-landing.yml up -d
|
||||||
|
docker image prune -af
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
name: Nix Build & Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ['develop']
|
|
||||||
pull_request:
|
|
||||||
branches: ['develop']
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
detect:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
affected: ${{ steps.affected.outputs.apps }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci --legacy-peer-deps
|
|
||||||
|
|
||||||
- name: Detect affected apps
|
|
||||||
id: affected
|
|
||||||
run: |
|
|
||||||
AFFECTED=$(npx nx show projects --affected --base=HEAD~1 --type=app 2>/dev/null | tr '\n' ' ')
|
|
||||||
echo "Affected apps: $AFFECTED"
|
|
||||||
APPS="[]"
|
|
||||||
for app in landing backoffice gacha dimentorin hackathon infra qrcampaign; do
|
|
||||||
if echo "$AFFECTED" | grep -qw "$app"; then
|
|
||||||
APPS=$(echo "$APPS" | jq -c ". + [\"$app\"]")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "apps=$APPS" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Matrix: $APPS"
|
|
||||||
|
|
||||||
build:
|
|
||||||
needs: detect
|
|
||||||
if: needs.detect.outputs.affected != '[]'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
app: ${{ fromJson(needs.detect.outputs.affected) }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
|
|
||||||
- name: Setup Cachix
|
|
||||||
uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: msdqn
|
|
||||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
|
||||||
|
|
||||||
- name: Build ${{ matrix.app }}
|
|
||||||
run: nix build .#${{ matrix.app }} -o result-${{ matrix.app }}
|
|
||||||
|
|
||||||
- name: Push to Cachix
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
|
|
||||||
run: cachix push msdqn result-${{ matrix.app }}
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
needs: build
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Install Nix
|
|
||||||
uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
|
|
||||||
- name: Setup SSH
|
|
||||||
env:
|
|
||||||
INFRA_DEPLOY_KEY: ${{ secrets.INFRA_DEPLOY_KEY }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "${INFRA_DEPLOY_KEY}" > ~/.ssh/deploy_key
|
|
||||||
chmod 600 ~/.ssh/deploy_key
|
|
||||||
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
|
|
||||||
ssh-keyscan 167.235.70.37 >> ~/.ssh/known_hosts 2>/dev/null
|
|
||||||
|
|
||||||
- name: Update infra flake.lock
|
|
||||||
run: |
|
|
||||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy_key -o IdentitiesOnly=yes"
|
|
||||||
git clone git@github.com:IMPHNEN/imphnen-infrastructure.git /tmp/infra
|
|
||||||
cd /tmp/infra
|
|
||||||
nix flake update imphnen-frontend
|
|
||||||
if git diff --quiet flake.lock; then
|
|
||||||
echo "flake.lock unchanged, skipping"
|
|
||||||
else
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add flake.lock
|
|
||||||
git commit -m "chore: update imphnen-frontend-service to ${GITHUB_SHA::7}"
|
|
||||||
git push
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Deploy to server
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
ssh -i ~/.ssh/deploy_key -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new root@167.235.70.37 \
|
|
||||||
'nixos-rebuild switch --flake github:IMPHNEN/imphnen-infrastructure#hetzner --refresh 2>&1 | tail -30'
|
|
||||||
|
|
||||||
- name: Cleanup
|
|
||||||
if: always()
|
|
||||||
run: rm -f ~/.ssh/deploy_key
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Test and Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['develop']
|
||||||
|
pull_request:
|
||||||
|
branches: ['develop']
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: 22.14.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Set env vars
|
||||||
|
run: |
|
||||||
|
echo "CMS_SECRET=${{ secrets.CMS_SECRET }}" >> $GITHUB_ENV
|
||||||
|
echo "CMS_STORAGE_REGION=${{ secrets.CMS_STORAGE_REGION }}" >> $GITHUB_ENV
|
||||||
|
echo "CMS_STORAGE_ENDPOINT=${{ secrets.CMS_STORAGE_ENDPOINT }}" >> $GITHUB_ENV
|
||||||
|
echo "CMS_STORAGE_ACCESS_KEY_ID=${{ secrets.CMS_STORAGE_ACCESS_KEY_ID }}" >> $GITHUB_ENV
|
||||||
|
echo "CMS_STORAGE_SECRET_ACCESS_KEY=${{ secrets.CMS_STORAGE_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV
|
||||||
|
echo "CMS_POSTGRES_URL=${{ secrets.CMS_POSTGRES_URL }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Nx Build
|
||||||
|
run: npx nx run-many --target=build --all
|
||||||
-11
@@ -62,14 +62,3 @@ storybook-static
|
|||||||
# Next.js
|
# Next.js
|
||||||
.next
|
.next
|
||||||
out
|
out
|
||||||
.cursor/rules/nx-rules.mdc
|
|
||||||
.github/instructions/nx.instructions.md
|
|
||||||
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# Nix
|
|
||||||
.direnv
|
|
||||||
result
|
|
||||||
|
|
||||||
.claude/worktrees
|
|
||||||
.claude/settings.local.json
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# path to a directory with all packages
|
||||||
|
storage: ../tmp/local-registry/storage
|
||||||
|
|
||||||
|
# a list of other known repositories we can talk to
|
||||||
|
uplinks:
|
||||||
|
npmjs:
|
||||||
|
url: https://registry.npmjs.org/
|
||||||
|
maxage: 60m
|
||||||
|
|
||||||
|
packages:
|
||||||
|
'**':
|
||||||
|
# give all users (including non-authenticated users) full access
|
||||||
|
# because it is a local registry
|
||||||
|
access: $all
|
||||||
|
publish: $all
|
||||||
|
unpublish: $all
|
||||||
|
|
||||||
|
# if package is not available locally, proxy requests to npm registry
|
||||||
|
proxy: npmjs
|
||||||
|
|
||||||
|
# log settings
|
||||||
|
log:
|
||||||
|
type: stdout
|
||||||
|
format: pretty
|
||||||
|
level: warn
|
||||||
|
|
||||||
|
publish:
|
||||||
|
allow_offline: true # set offline to true to allow publish offline
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"nrwl.angular-console",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-playwright.playwright"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# IMPHNEN Frontend Service
|
|
||||||
|
|
||||||
Nx monorepo for IMPHNEN (Ingin Menjadi Programmer Handal Namun Enggan Ngoding) — Indonesia's largest programmer community.
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
- **Monorepo**: Nx 22.6
|
|
||||||
- **Framework**: Next.js 16 (landing), Vite + React 19 (all other apps)
|
|
||||||
- **Routing**: react-router v7 (Vite apps), Next.js App Router (landing)
|
|
||||||
- **Styling**: Tailwind CSS v4, class-variance-authority (CVA)
|
|
||||||
- **State**: Zustand, TanStack React Query
|
|
||||||
- **Forms**: react-hook-form + zod
|
|
||||||
- **Build/Deploy**: Nix flakes, Cachix binary cache, NixOS modules
|
|
||||||
- **CI/CD**: GitHub Actions with `nx affected` + matrix strategy
|
|
||||||
- **Node**: v22
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
### Apps
|
|
||||||
| App | Framework | Description |
|
|
||||||
|-----|-----------|-------------|
|
|
||||||
| `landing` | Next.js 16 | Public website |
|
|
||||||
| `backoffice` | Vite + React | Admin dashboard |
|
|
||||||
| `hackathon` | Vite + React | Hackathon platform |
|
|
||||||
| `dimentorin` | Vite + React | Mentoring platform |
|
|
||||||
| `gacha` | Vite + React | Merch gacha system |
|
|
||||||
| `qrcampaign` | Vite + React | QR campaign tool |
|
|
||||||
| `infra` | Vite + React | Infrastructure dashboard |
|
|
||||||
|
|
||||||
### Shared Libraries
|
|
||||||
| Lib | Purpose | Depends on |
|
|
||||||
|-----|---------|------------|
|
|
||||||
| `utils` | Pure utilities only: `cn`, `For`, `Show`, `useQueryState`, `useModalLogin`, react-query helpers, react-router file-based routing | nothing |
|
|
||||||
| `service` | Business logic: API clients, auth hooks, storage (SessionToken/SessionUser), constants (PERMISSIONS, cities) | `utils` |
|
|
||||||
| `ui` | UI components: atoms (Button, Input, Card, Dialog, Drawer, Form, Label), molecules, organisms (Navbar, Sidebar, Datatable) | `utils`, `service` |
|
|
||||||
|
|
||||||
**Dependency rule**: `ui` → `service` → `utils` (never the reverse)
|
|
||||||
|
|
||||||
## Common Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Dev
|
|
||||||
nx dev <app> # Start dev server
|
|
||||||
nx build <app> # Build single app
|
|
||||||
nx run-many -t build --all # Build everything
|
|
||||||
|
|
||||||
# Test
|
|
||||||
nx test <lib> # Run unit tests (vitest)
|
|
||||||
nx e2e <app>-e2e # Run e2e tests (playwright)
|
|
||||||
nx lint <project> # Lint
|
|
||||||
|
|
||||||
# Build all affected
|
|
||||||
nx affected -t build # Only build what changed
|
|
||||||
nx affected -t test # Only test what changed
|
|
||||||
|
|
||||||
# Nix
|
|
||||||
nix build .#<app> # Build Nix package for an app
|
|
||||||
nix develop # Enter dev shell (node 22, bun, git, jq)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nix / Deployment
|
|
||||||
|
|
||||||
All Nix config is in a single `flake.nix`:
|
|
||||||
- `mkViteApp`: Builds Vite apps as Nix packages
|
|
||||||
- `mkLandingApp`: Builds the Next.js landing app
|
|
||||||
- `mkLandingModule`: NixOS module (systemd service for Next.js)
|
|
||||||
- `mkStaticAppModule`: NixOS module (nginx for static Vite apps)
|
|
||||||
- `npmDepsHash`: Must be updated when `package-lock.json` changes. Use `lib.fakeHash` to get the new hash from a failed build.
|
|
||||||
|
|
||||||
## CI/CD Pipeline (.github/workflows/nix-build.yml)
|
|
||||||
|
|
||||||
1. **detect**: Uses `nx affected` to find changed apps
|
|
||||||
2. **build**: Matrix strategy builds only affected apps with Nix, pushes to Cachix
|
|
||||||
3. **deploy**: Clones `imphnen-infrastructure`, updates `flake.lock`, pushes, then runs `clan machines update hetzner` to deploy to the Hetzner server
|
|
||||||
|
|
||||||
Required GitHub secrets: `CACHIX_AUTH_TOKEN`, `INFRA_DEPLOY_KEY` (SSH key for both GitHub and server access)
|
|
||||||
|
|
||||||
The server (167.235.70.37) pulls pre-built packages from Cachix during `nixos-rebuild`.
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
- Vite apps: `VITE_API_URL`, `VITE_GITHUB_CLIENT_ID`
|
|
||||||
- Next.js (landing): `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_GITHUB_CLIENT_ID`
|
|
||||||
- The `getBaseURL()` function in `libs/service/src/api/index.ts` handles both environments
|
|
||||||
|
|
||||||
## Key Conventions
|
|
||||||
|
|
||||||
- Atomic design: atoms → molecules → organisms
|
|
||||||
- Components have their own folder with `component.tsx`, `index.ts`, `spec.tsx`, `stories.tsx`
|
|
||||||
- Use `cn()` from `@imphnen-frontend-service/utils` for className merging
|
|
||||||
- Use `'use client'` directive on any component using React hooks (for Next.js compatibility)
|
|
||||||
- Button variants via CVA: `primary`, `secondary`, `text`, `bordered`, `success`, `danger`
|
|
||||||
- Landing app uses `container` class — Tailwind v4 requires explicit `margin-inline: auto` (defined in globals.css)
|
|
||||||
@@ -4,117 +4,135 @@
|
|||||||
<img src="docs/logo.svg" alt="IMPHNEN">
|
<img src="docs/logo.svg" alt="IMPHNEN">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Monorepo for all frontend services of [IMPHNEN](https://imphnen.dev) (Ingin Menjadi Programmer Handal Namun Enggan Ngoding) — Indonesia's largest programmer community.
|
This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications:
|
||||||
|
|
||||||
## Apps
|
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>.
|
||||||
|
3. **Dimentorin** - Application for <a href="https://dimentorin.imphnen.dev/" target="_blank">Mentoring Service</a>.
|
||||||
|
4. **Landing Page** - Application for <a href="https://imphnen.dev/" target="_blank">Landing Page</a>.
|
||||||
|
|
||||||
| App | Framework | URL |
|
## How to install
|
||||||
|-----|-----------|-----|
|
|
||||||
| **Landing** | Next.js 16 | [imphnen.dev](https://imphnen.dev) |
|
|
||||||
| **Backoffice** | Vite + React | [backoffice.imphnen.dev](https://backoffice.imphnen.dev) |
|
|
||||||
| **Hackathon** | Vite + React | [hackathon.imphnen.dev](https://dimentorin.imphnen.dev) |
|
|
||||||
| **Dimentorin** | Vite + React | [dimentorin.imphnen.dev](https://dimentorin.imphnen.dev) |
|
|
||||||
| **Gacha** | Vite + React | [gacha.imphnen.dev](https://gacha.imphnen.dev) |
|
|
||||||
| **QR Campaign** | Vite + React | [qr.imphnen.dev](https://qr.imphnen.dev) |
|
|
||||||
| **Infra** | Vite + React | [infra.imphnen.dev](https://infra.imphnen.dev) |
|
|
||||||
|
|
||||||
## Shared Libraries
|
|
||||||
|
|
||||||
| Lib | Purpose |
|
|
||||||
|-----|---------|
|
|
||||||
| `utils` | Pure utilities — `cn()`, `For`, `Show`, `useQueryState`, `useModalLogin` |
|
|
||||||
| `service` | Business logic — API clients, auth hooks, storage, constants |
|
|
||||||
| `ui` | UI components — atoms, molecules, organisms (atomic design) |
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Node.js 22
|
|
||||||
- [Nix](https://nixos.org/download/) (optional, for reproducible builds)
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
|
1. Clone this repository:
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/IMPHNEN/imphnen-frontend-service.git
|
git clone https://github.com/IMPHNEN/imphnen-frontend-service.git
|
||||||
cd imphnen-frontend-service
|
cd imphnen-frontend-service
|
||||||
|
```
|
||||||
|
2. Install all dependencies:
|
||||||
|
```sh
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Or with Nix:
|
## 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
|
```sh
|
||||||
nix develop # enters dev shell with node 22, bun, git, jq
|
cd apps/{appname}
|
||||||
```
|
cp .env.example .env
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
Copy `.env.example` to `.env` in the app directory:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp apps/<app>/.env.example apps/<app>/.env
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
|
Use the following commands to run in development mode:
|
||||||
|
|
||||||
|
- **Gacha**:
|
||||||
```sh
|
```sh
|
||||||
nx dev <app> # e.g. nx dev landing, nx dev backoffice
|
npm run gacha:dev
|
||||||
|
```
|
||||||
|
- **Backoffice**:
|
||||||
|
```sh
|
||||||
|
npm run backoffice:dev
|
||||||
|
```
|
||||||
|
- **Dimentorin**:
|
||||||
|
```sh
|
||||||
|
npm run dimentorin:dev
|
||||||
|
```
|
||||||
|
- **Landing Page**:
|
||||||
|
```sh
|
||||||
|
npm run landing:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
|
Use the following commands to build the applications:
|
||||||
|
|
||||||
|
- **Gacha**:
|
||||||
```sh
|
```sh
|
||||||
nx build <app> # build single app
|
npm run gacha:build
|
||||||
nx run-many -t build --all # build everything
|
```
|
||||||
nx affected -t build # build only what changed
|
- **Backoffice**:
|
||||||
|
```sh
|
||||||
|
npm run backoffice:build
|
||||||
|
```
|
||||||
|
- **Dimentorin**:
|
||||||
|
```sh
|
||||||
|
npm run dimentorin:build
|
||||||
|
```
|
||||||
|
- **Landing Page**:
|
||||||
|
```sh
|
||||||
|
npm run landing:build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nix Build
|
### Production
|
||||||
|
|
||||||
|
Use the following commands to run the applications in production mode:
|
||||||
|
|
||||||
|
- **Gacha**:
|
||||||
```sh
|
```sh
|
||||||
nix build .#<app> # e.g. nix build .#landing, .#dimentorin
|
npm run gacha:prod
|
||||||
```
|
```
|
||||||
|
- **Backoffice**:
|
||||||
All Nix config lives in `flake.nix`. When `package-lock.json` changes, update `npmDepsHash` using `lib.fakeHash`.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nx test <project> # unit tests (vitest)
|
npm run backoffice:prod
|
||||||
nx e2e <app>-e2e # e2e tests (playwright)
|
```
|
||||||
nx lint <project> # eslint
|
- **Dimentorin**:
|
||||||
|
```sh
|
||||||
|
npm run dimentorin:prod
|
||||||
|
```
|
||||||
|
- **Landing Page**:
|
||||||
|
```sh
|
||||||
|
npm run landing:prod
|
||||||
```
|
```
|
||||||
|
|
||||||
### Storybook
|
### Storybook
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
- **Run Storybook**
|
||||||
|
|
||||||
|
This command starts Storybook in development mode, allowing you to view and test UI components interactively.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nx storybook ui # run storybook for ui lib
|
npm run ui:storybook
|
||||||
nx build-storybook ui # build static storybook
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## CI/CD
|
- **Run Unit Test**
|
||||||
|
|
||||||
GitHub Actions pipeline (`.github/workflows/nix-build.yml`):
|
This command runs unit tests for the UI components to ensure they function as expected.
|
||||||
|
|
||||||
1. **detect** — uses `nx affected` to find changed apps
|
```sh
|
||||||
2. **build** — matrix strategy builds only affected apps with Nix, pushes to [Cachix](https://app.cachix.org/cache/msdqn)
|
npm run ui:test
|
||||||
3. **deploy** — updates `flake.lock` in [imphnen-infrastructure](https://github.com/IMPHNEN/imphnen-infrastructure) and deploys to the server using [clan](https://clan.lol)
|
```
|
||||||
|
|
||||||
## Tech Stack
|
- **Build Components**
|
||||||
|
|
||||||
- **Monorepo**: Nx 22.6
|
This command generates a static build of Storybook, which can be deployed for sharing and documentation purposes.
|
||||||
- **Frontend**: React 19, TypeScript
|
|
||||||
- **Styling**: Tailwind CSS v4, CVA
|
|
||||||
- **State**: Zustand, TanStack React Query
|
|
||||||
- **Forms**: react-hook-form + zod
|
|
||||||
- **Build**: Nix flakes, Cachix
|
|
||||||
- **CI**: GitHub Actions
|
|
||||||
|
|
||||||
## Contributing
|
```sh
|
||||||
|
npm run ui:build
|
||||||
|
```
|
||||||
|
|
||||||
1. Fork and clone the repository
|
## How to contribute
|
||||||
2. Create a branch: `git checkout -b feat/feature-name`
|
|
||||||
3. Make changes, commit, and push
|
|
||||||
4. Open a pull request to the `develop` branch
|
|
||||||
|
|
||||||
Issues and feedback welcome via [GitHub Issues](https://github.com/IMPHNEN/imphnen-frontend-service/issues).
|
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.
|
||||||
|
|
||||||
|
If you encounter any issues or problems, feel free to create a new Issue.
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
Use this section to tell people about which versions of your project are
|
|
||||||
currently being supported with security updates.
|
|
||||||
|
|
||||||
| Version | Supported |
|
|
||||||
| ------- | ------------------ |
|
|
||||||
| 5.1.x | :white_check_mark: |
|
|
||||||
| 5.0.x | :x: |
|
|
||||||
| 4.0.x | :white_check_mark: |
|
|
||||||
| < 4.0 | :x: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Use this section to tell people how to report a vulnerability.
|
|
||||||
|
|
||||||
Tell them where to go, how often they can expect to get an update on a
|
|
||||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
|
||||||
declined, etc.
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="light">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Backoffice</title>
|
<title>Backoffice</title>
|
||||||
@@ -7,9 +7,10 @@
|
|||||||
|
|
||||||
<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="/logos/simple.svg" />
|
<link rel="icon" type="image/x-icon" href="/logos/simple.svg" />
|
||||||
|
<link rel="stylesheet" href="/src/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#f9fafb"><div style="width:40px;height:40px;border:3px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin .8s linear infinite"></div></div><style>@keyframes spin{to{transform:rotate(360deg)}}</style></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,162 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface IModalEditAccount {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleEditAccount?: () => void;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalEditAccount = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
prevStep,
|
||||||
|
resetStep,
|
||||||
|
handleEditAccount,
|
||||||
|
}: IModalEditAccount) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleEditAccount={handleEditAccount}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const [fullName, setFullName] = useState('Ahmad Wiyana');
|
||||||
|
const [email, setEmail] = useState('fullname23@gmail.com');
|
||||||
|
const [phoneNumber, setPhoneNumber] = useState('081904423804');
|
||||||
|
const [address, setAddress] = useState('Jl. Pantai Cibaduyut Indah');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Edit Data Akun
|
||||||
|
</h2>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<InputField
|
||||||
|
label="Nama Lengkap"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Nama Lengkap"
|
||||||
|
value={fullName}
|
||||||
|
onChange={(e) => setFullName(e.target.value)}
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="Email"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="Nomor Telepon"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Nomor Telepon"
|
||||||
|
value={phoneNumber}
|
||||||
|
onChange={(e) => setPhoneNumber(e.target.value)}
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="Alamat"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Alamat"
|
||||||
|
value={address}
|
||||||
|
onChange={(e) => setAddress(e.target.value)}
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={nextStep}
|
||||||
|
>
|
||||||
|
Perbarui Data
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleEditAccount?: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleEditAccount, resetStep }: IStepTwoProps) => (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-0 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Update Data
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin dengan
|
||||||
|
<br /> perubahan yang dilakukan?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
handleEditAccount && handleEditAccount();
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ModalEditAccount;
|
||||||
+77
-56
@@ -1,13 +1,14 @@
|
|||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
import * as React from 'react';
|
||||||
import * as React from 'react'
|
|
||||||
import { Fragment, useState } from 'react'
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FilterOutlined,
|
FilterOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons';
|
||||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms'
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms'
|
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
@@ -15,38 +16,50 @@ import {
|
|||||||
PaginationState,
|
PaginationState,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
} from '@tanstack/react-table'
|
} from '@tanstack/react-table';
|
||||||
import {
|
import ModalEditAccount from './_components/modal-edit-account';
|
||||||
useUserList,
|
import { useQueryState } from '@imphnen-frontend-service/utils';
|
||||||
TUsersListItem,
|
|
||||||
} from '@imphnen-frontend-service/service'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/_authenticated/accounts')({
|
interface Account {
|
||||||
component: AccountsPage,
|
id: number;
|
||||||
})
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
|
||||||
function AccountsPage() {
|
// Mock data for demonstration
|
||||||
const navigate = useNavigate()
|
const mockData: Account[] = Array.from({ length: 90 }, (_, i) => ({
|
||||||
const [search, setSearch] = useState('')
|
id: i + 1,
|
||||||
|
name: i === 0 ? 'Ahmad Wijuana' : 'Nama Lengkap',
|
||||||
|
email: 'fullname23@gmail.com',
|
||||||
|
phone: '081904423804',
|
||||||
|
address: 'Jl. Pantai Cibaduyut Indah',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const Components: FC = (): ReactElement => {
|
||||||
|
const [showModalEditAccount, setShowModalEditAccount] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
step: currentStep,
|
||||||
|
nextStep,
|
||||||
|
prevStep,
|
||||||
|
resetStep,
|
||||||
|
} = useQueryState('step', {
|
||||||
|
defaultValue: 1,
|
||||||
|
maxValue: 2,
|
||||||
|
minValue: 1,
|
||||||
|
});
|
||||||
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 9,
|
pageSize: 9,
|
||||||
})
|
});
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
const [showFilter, setShowFilter] = useState(false)
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
|
|
||||||
const { data: usersData, isLoading } = useUserList({
|
const columns: ColumnDef<Account>[] = [
|
||||||
search,
|
|
||||||
page: pagination.pageIndex + 1,
|
|
||||||
per_page: pagination.pageSize,
|
|
||||||
})
|
|
||||||
|
|
||||||
const users: TUsersListItem[] = usersData?.data ?? []
|
|
||||||
const totalItems = usersData?.meta?.total ?? users.length
|
|
||||||
|
|
||||||
const columns: ColumnDef<TUsersListItem>[] = [
|
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -72,24 +85,19 @@ function AccountsPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Nama Lengkap',
|
header: 'Nama Lengkap',
|
||||||
accessorKey: 'fullname',
|
accessorKey: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Email',
|
header: 'Email',
|
||||||
accessorKey: 'email',
|
accessorKey: 'email',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Role',
|
header: 'Nomor Telp',
|
||||||
accessorKey: 'role',
|
accessorKey: 'phone',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status',
|
header: 'Alamat Pengiriman',
|
||||||
accessorKey: 'is_active',
|
accessorKey: 'address',
|
||||||
cell: ({ row }) => (
|
|
||||||
<span className={row.original.is_active ? 'text-success-500' : 'text-danger-500'}>
|
|
||||||
{row.original.is_active ? 'Aktif' : 'Tidak Aktif'}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Action',
|
header: 'Action',
|
||||||
@@ -98,8 +106,8 @@ function AccountsPage() {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
navigate({ to: '/accounts/$id', params: { id: row.original.id } })
|
setShowModalEditAccount(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
@@ -107,10 +115,10 @@ function AccountsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: users,
|
data: mockData,
|
||||||
columns,
|
columns,
|
||||||
state: {
|
state: {
|
||||||
pagination,
|
pagination,
|
||||||
@@ -121,24 +129,25 @@ function AccountsPage() {
|
|||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
pageCount: Math.ceil(totalItems / pagination.pageSize),
|
pageCount: Math.ceil(mockData.length / pagination.pageSize),
|
||||||
manualPagination: true,
|
manualPagination: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
||||||
|
{/* Header */}
|
||||||
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
||||||
<h1 className="text-p2 font-semibold">Data Akun</h1>
|
<h1 className="text-p2 font-semibold">Data Akun</h1>
|
||||||
</header>
|
</header>
|
||||||
|
{/* Account Table Section */}
|
||||||
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
||||||
|
{/* Search and Filter */}
|
||||||
<div className="flex justify-between items-center gap-8 mb-2">
|
<div className="flex justify-between items-center gap-8 mb-2">
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Cari berdasarkan nama lengkap, email"
|
placeholder="Cari berdasarkan nama lengkap, email"
|
||||||
className="pl-12 w-full max-h-full"
|
className="pl-12 w-full max-h-full"
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
|
||||||
<SearchOutlined />
|
<SearchOutlined />
|
||||||
@@ -162,13 +171,25 @@ function AccountsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{/* Table */}
|
||||||
<div className="text-center py-8 text-neutral-400">Loading...</div>
|
<DataTable data={mockData} columns={columns} table={table} />
|
||||||
) : (
|
|
||||||
<DataTable data={users} columns={columns} table={table} />
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Modal Edit Account */}
|
||||||
|
<ModalEditAccount
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalEditAccount}
|
||||||
|
onClose={() => setShowModalEditAccount(false)}
|
||||||
|
handleEditAccount={() => {
|
||||||
|
console.log('Account updated');
|
||||||
|
}}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem, useItem } from '../_hook/use-item';
|
||||||
|
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
interface IModalAddItem {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleAddItem?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalAddItem = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
resetStep,
|
||||||
|
handleAddItem,
|
||||||
|
}: IModalAddItem) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleAddItem={handleAddItem}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const { form, onSubmit } = useItem(nextStep);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah Item Gacha
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Lengkapi detail di bawah ini untuk menambahkan item gacha
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Nama Hadiah"
|
||||||
|
name="itemName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Nama Hadiah"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Quantity"
|
||||||
|
name="quantity"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
placeholder="Masukkan Kuantitas Item"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Foto Barang"
|
||||||
|
type="file"
|
||||||
|
name="foto"
|
||||||
|
placeholder=".jpg, .jpeg, atau .png"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={nextStep}
|
||||||
|
>
|
||||||
|
Tambahkan Item
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleAddItem?: () => Promise<boolean>;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => {
|
||||||
|
const { onConfirm, onCancel } = useConfirmItem(
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleAddItem,
|
||||||
|
{
|
||||||
|
success: 'Item ditambahkan ke gacha item',
|
||||||
|
error: 'Item gagal ditambahkan ke gacha item',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-0 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah Item
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin ingin
|
||||||
|
<br /> menambahkan item ini?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Tambahkan
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalAddItem;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
|
||||||
|
interface IModalDeleteItem {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleDeleteItem?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalDeleteItem = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
handleDeleteItem,
|
||||||
|
}: IModalDeleteItem) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
closeButtonClassName="hidden"
|
||||||
|
>
|
||||||
|
<Modal.Header className="gap-8">
|
||||||
|
<img
|
||||||
|
src="/chibi-delete.webp"
|
||||||
|
alt="Delete item?"
|
||||||
|
width={148}
|
||||||
|
className="self-center"
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-danger-500 mb-3">
|
||||||
|
Delete Item
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin untuk menghapus item ini?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Batal Hapus
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteItem && handleDeleteItem();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Hapus Item
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalDeleteItem;
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem, useItem } from '../_hook/use-item';
|
||||||
|
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
interface IModalEditItem {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleEditItem?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalEditItem = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
resetStep,
|
||||||
|
handleEditItem,
|
||||||
|
}: IModalEditItem) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleEditItem={handleEditItem}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const initialValues = {
|
||||||
|
itemName: 'Hoodie IMPHNEN Official 2025',
|
||||||
|
quantity: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { form, onSubmit } = useItem(nextStep, initialValues);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Edit Item Gacha
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Silakan mengubah detail dari item yang diperlukan
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex flex-col gap-8">
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Nama Hadiah"
|
||||||
|
type="text"
|
||||||
|
name="itemName"
|
||||||
|
placeholder="Masukkan Nama Hadiah"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Quantity"
|
||||||
|
type="number"
|
||||||
|
name="quantity"
|
||||||
|
placeholder="Masukkan Kuantitas Item"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Foto Barang"
|
||||||
|
type="file"
|
||||||
|
name="foto"
|
||||||
|
placeholder=".jpg, .jpeg, atau .png"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={nextStep}
|
||||||
|
>
|
||||||
|
Perbarui Item
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleEditItem?: () => Promise<boolean>;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => {
|
||||||
|
const { onConfirm, onCancel } = useConfirmItem(
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleEditItem,
|
||||||
|
{
|
||||||
|
success: 'Perubahan item berhasil dilakukan',
|
||||||
|
error: 'Perubahan item gagal dilakukan',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-0 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Update Item
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin dengan
|
||||||
|
<br /> perubahan yang dilakukan?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalEditItem;
|
||||||
+5
-7
@@ -8,17 +8,16 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
export const useItem = (
|
export const useItem = (
|
||||||
nextStep: () => void,
|
nextStep: () => void,
|
||||||
initialValues?: Partial<TGachaItem>,
|
initialValues?: TGachaItem
|
||||||
onDataCapture?: (data: any) => void,
|
|
||||||
) => {
|
) => {
|
||||||
const form = useForm<any>({
|
const form = useForm<TGachaItem>({
|
||||||
resolver: zodResolver(gachaItemSchema),
|
resolver: zodResolver(gachaItemSchema),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
defaultValues: initialValues,
|
defaultValues: initialValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => {
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
onDataCapture?.(data);
|
console.log('Form data:', data);
|
||||||
nextStep();
|
nextStep();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,9 +38,8 @@ export const useConfirmItem = (
|
|||||||
) => {
|
) => {
|
||||||
const onConfirm = async () => {
|
const onConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
if (actionFunction) {
|
// const result = await actionFunction?.();
|
||||||
await actionFunction();
|
// result ? toast.success(messages?.success) : toast.error(messages?.error);
|
||||||
}
|
|
||||||
toast.success(messages?.success);
|
toast.success(messages?.success);
|
||||||
onClose();
|
onClose();
|
||||||
resetStep();
|
resetStep();
|
||||||
+66
-73
@@ -1,47 +1,32 @@
|
|||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
UsergroupAddOutlined,
|
UsergroupAddOutlined,
|
||||||
UsergroupDeleteOutlined,
|
UsergroupDeleteOutlined,
|
||||||
UserSwitchOutlined,
|
UserSwitchOutlined,
|
||||||
DeleteOutlined,
|
} from '@ant-design/icons';
|
||||||
} from '@ant-design/icons'
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms'
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
import { Fragment, useState } from 'react'
|
import ModalAddItem from './_components/modal-add-item';
|
||||||
import {
|
import ModalEditItem from './_components/modal-edit-item';
|
||||||
useUserList,
|
import ModalDeleteItem from './_components/modal-delete-item';
|
||||||
useGachaItemList,
|
import { useQueryState } from '@imphnen-frontend-service/utils';
|
||||||
useDeleteGachaItem,
|
|
||||||
TGachaItemDto,
|
|
||||||
} from '@imphnen-frontend-service/service'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/_authenticated/dashboard')({
|
export const Components: FC = (): ReactElement => {
|
||||||
component: DashboardPage,
|
const [showModalAddItem, setShowModalAddItem] = useState(false);
|
||||||
})
|
const [showModalEditItem, setShowModalEditItem] = useState(false);
|
||||||
|
const [showModalDeleteItem, setShowModalDeleteItem] = useState(false);
|
||||||
|
|
||||||
function DashboardPage() {
|
const {
|
||||||
const navigate = useNavigate()
|
step: currentStep,
|
||||||
const [deleteId, setDeleteId] = useState<string | null>(null)
|
nextStep,
|
||||||
|
prevStep,
|
||||||
const { data: usersData } = useUserList({ per_page: 1 })
|
resetStep,
|
||||||
const { data: gachaItemsData } = useGachaItemList({ per_page: 9 })
|
} = useQueryState('step', {
|
||||||
const deleteItem = useDeleteGachaItem()
|
defaultValue: 1,
|
||||||
|
maxValue: 2,
|
||||||
const totalUsers = usersData?.meta?.total ?? 0
|
minValue: 1,
|
||||||
const gachaItems: TGachaItemDto[] = gachaItemsData?.data ?? []
|
});
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
|
||||||
try {
|
|
||||||
await deleteItem.mutateAsync(id)
|
|
||||||
toast.success('Item berhasil dihapus')
|
|
||||||
setDeleteId(null)
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
toast.error('Item gagal dihapus')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@@ -62,7 +47,7 @@ function DashboardPage() {
|
|||||||
<UsergroupAddOutlined className="text-[20px]" />
|
<UsergroupAddOutlined className="text-[20px]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="text-p1 font-semibold">{totalUsers}</h3>
|
<h3 className="text-p1 font-semibold">1000</h3>
|
||||||
<p className="text-label1 text-neutral-500">Participants</p>
|
<p className="text-label1 text-neutral-500">Participants</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,9 +57,9 @@ function DashboardPage() {
|
|||||||
<ReloadOutlined className="text-[20px]" />
|
<ReloadOutlined className="text-[20px]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="text-p1 font-semibold">{gachaItemsData?.meta?.total ?? 0}</h3>
|
<h3 className="text-p1 font-semibold">1000</h3>
|
||||||
<p className="text-label1 text-neutral-500">
|
<p className="text-label1 text-neutral-500">
|
||||||
Gacha Items
|
Roll and Reroll
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +69,7 @@ function DashboardPage() {
|
|||||||
<UserSwitchOutlined className="text-[20px]" />
|
<UserSwitchOutlined className="text-[20px]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="text-p1 font-semibold">-</h3>
|
<h3 className="text-p1 font-semibold">1000</h3>
|
||||||
<p className="text-label1 text-neutral-500">Redeem</p>
|
<p className="text-label1 text-neutral-500">Redeem</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +79,7 @@ function DashboardPage() {
|
|||||||
<UsergroupDeleteOutlined className="text-[20px]" />
|
<UsergroupDeleteOutlined className="text-[20px]" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="text-p1 font-semibold">-</h3>
|
<h3 className="text-p1 font-semibold">1000</h3>
|
||||||
<p className="text-label1 text-neutral-500">
|
<p className="text-label1 text-neutral-500">
|
||||||
Inactive Users
|
Inactive Users
|
||||||
</p>
|
</p>
|
||||||
@@ -112,7 +97,7 @@ function DashboardPage() {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="items-end gap-3"
|
className="items-end gap-3"
|
||||||
onClick={() => navigate({ to: '/dashboard/create' })}
|
onClick={() => setShowModalAddItem(true)}
|
||||||
>
|
>
|
||||||
<span>Tambah Item</span>
|
<span>Tambah Item</span>
|
||||||
<PlusOutlined className="text-[16px]" />
|
<PlusOutlined className="text-[16px]" />
|
||||||
@@ -120,18 +105,19 @@ function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4 max-h-140 overflow-auto">
|
<div className="flex flex-col gap-4 max-h-140 overflow-auto">
|
||||||
{gachaItems.map((item) => (
|
{[1, 2, 3, 4, 5, 6].map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item}
|
||||||
className="bg-white overflow-clip rounded-lg shadow-sm flex justify-between border border-neutral-100"
|
className="bg-white overflow-clip rounded-lg shadow-sm flex justify-between border border-neutral-100"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col py-4 px-6 gap-[8px]">
|
<div className="flex flex-col py-4 px-6 gap-[8px]">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-p3 text-primary-500 font-medium">
|
<h3 className="text-p3 text-primary-500 font-medium">
|
||||||
{item.name}
|
Lanyard IMPHNEN
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center justify-start gap-10 text-label2 text-gray-500 mt-1">
|
<div className="flex items-center justify-start gap-10 text-label2 text-gray-500 mt-1">
|
||||||
<span>{item.id}</span>
|
<span>Prize {item}</span>
|
||||||
|
<span>Quantity: 10</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-start gap-2">
|
<div className="flex justify-start gap-2">
|
||||||
@@ -139,50 +125,29 @@ function DashboardPage() {
|
|||||||
variant="text"
|
variant="text"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent hover:text-primary-500"
|
className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent hover:text-primary-500"
|
||||||
onClick={() => navigate({ to: '/dashboard/$id', params: { id: item.id } })}
|
onClick={() => setShowModalEditItem(true)}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
{deleteId === item.id ? (
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<span className="text-[10px] text-neutral-400">Yakin?</span>
|
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-[10px] text-red-500 p-0 font-normal hover:bg-transparent hover:text-red-700"
|
className="text-[10px] text-red-500 p-0 font-normal hover:bg-transparent hover:text-red-700"
|
||||||
onClick={() => handleDelete(item.id)}
|
onClick={() => setShowModalDeleteItem(true)}
|
||||||
>
|
|
||||||
Ya
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
size="sm"
|
|
||||||
className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent"
|
|
||||||
onClick={() => setDeleteId(null)}
|
|
||||||
>
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
size="sm"
|
|
||||||
className="text-[10px] text-red-500 p-0 font-normal hover:bg-transparent hover:text-red-700"
|
|
||||||
onClick={() => setDeleteId(item.id)}
|
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img src="gacha-clip.webp" alt={item.name} />
|
<img src="gacha-clip.webp" alt="Lanyard IMPHNEN" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Right-side illustration */}
|
||||||
<img
|
<img
|
||||||
src="gacha.webp"
|
src="gacha.webp"
|
||||||
alt=""
|
alt=""
|
||||||
@@ -190,6 +155,34 @@ function DashboardPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Modal Add Item */}
|
||||||
|
<ModalAddItem
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalAddItem}
|
||||||
|
onClose={() => setShowModalAddItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Modal Edit Item */}
|
||||||
|
<ModalEditItem
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalEditItem}
|
||||||
|
onClose={() => setShowModalEditItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Modal Delete Item */}
|
||||||
|
<ModalDeleteItem
|
||||||
|
isOpen={showModalDeleteItem}
|
||||||
|
onClose={() => setShowModalDeleteItem(false)}
|
||||||
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
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 { useItem, useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalAddItem {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleAddItem?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalAddItem = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
resetStep,
|
||||||
|
handleAddItem,
|
||||||
|
}: IModalAddItem) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleAddItem={handleAddItem}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const { form, onSubmit } = useItem(nextStep);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah Item Roll Gacha
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Lengkapi detal di bawah ini, untuk menambahkan item gacha
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Pilih Item"
|
||||||
|
name="itemName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Pilih item yang dimasukkan ke roll"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Quantity"
|
||||||
|
name="quantity"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
placeholder="Masukkan Kuantitas Item"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Chance Rate"
|
||||||
|
name="chanceRate"
|
||||||
|
type="number"
|
||||||
|
value={0.1}
|
||||||
|
min={0.1}
|
||||||
|
step={0.1}
|
||||||
|
max={1}
|
||||||
|
placeholder="Masukkan Chance Rate (0,1 - 1)"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="primary" size="lg" className="w-full" type="submit">
|
||||||
|
Tambahkan Item
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleAddItem?: () => Promise<boolean>;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => {
|
||||||
|
const { onConfirm, onCancel } = useConfirmItem(
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleAddItem,
|
||||||
|
{
|
||||||
|
success: 'Item ditambahkan ke roll gacha',
|
||||||
|
error: 'Item gagal ditambahkan ke roll gacha',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-0 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah ke Roll Gacha
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin ingin
|
||||||
|
<br /> menambahkan item ini ke roll gacha?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Tambahkan
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalAddItem;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
|
||||||
|
interface IModalDeleteItem {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleDeleteItem?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalDeleteItem = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
handleDeleteItem,
|
||||||
|
}: IModalDeleteItem) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
closeButtonClassName="hidden"
|
||||||
|
>
|
||||||
|
<Modal.Header className="gap-8">
|
||||||
|
<img
|
||||||
|
src="/chibi-delete.webp"
|
||||||
|
alt="Delete item?"
|
||||||
|
width={148}
|
||||||
|
className="self-center"
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-danger-500 mb-3">
|
||||||
|
Delete Item
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin untuk menghapus item ini?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Batal Hapus
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteItem && handleDeleteItem();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Hapus Item
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalDeleteItem;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem, useItem } from '../_hook/use-item';
|
||||||
|
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
interface IModalUpdateItem {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleUpdateItem?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalUpdateItem = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
resetStep,
|
||||||
|
handleUpdateItem,
|
||||||
|
}: IModalUpdateItem) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleUpdateItem={handleUpdateItem}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const initialValues = {
|
||||||
|
itemName: 'Hoodie IMPHNEN Official 2025',
|
||||||
|
quantity: 10,
|
||||||
|
chanceRate: 0.1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { form, onSubmit } = useItem(nextStep, initialValues);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Update Item Roll Gacha
|
||||||
|
</h2>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Pilih Item"
|
||||||
|
name="itemName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Pilih item yang dimasukkan ke roll"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Quantity"
|
||||||
|
name="quantity"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
placeholder="Masukkan Kuantitas Item"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Chance Rate"
|
||||||
|
name="chanceRate"
|
||||||
|
type="number"
|
||||||
|
value={0.1}
|
||||||
|
min={0.1}
|
||||||
|
step={0.1}
|
||||||
|
max={1}
|
||||||
|
placeholder="Masukkan Chance Rate (0,1 - 1)"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={nextStep}
|
||||||
|
>
|
||||||
|
Perbarui Item
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleUpdateItem?: () => Promise<boolean>;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleUpdateItem, resetStep }: IStepTwoProps) => {
|
||||||
|
const { onConfirm, onCancel } = useConfirmItem(
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleUpdateItem,
|
||||||
|
{
|
||||||
|
success: 'Perubahan item roll berhasil dilakukan',
|
||||||
|
error: 'Perubahan item roll gagal dilakukan',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-0 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Update Item
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin dengan
|
||||||
|
<br /> perubahan yang dilakukan?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalUpdateItem;
|
||||||
+5
-7
@@ -8,17 +8,16 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
export const useItem = (
|
export const useItem = (
|
||||||
nextStep: () => void,
|
nextStep: () => void,
|
||||||
initialValues?: Partial<TGachaRollItem>,
|
initialValues?: TGachaRollItem
|
||||||
onDataCapture?: (data: any) => void,
|
|
||||||
) => {
|
) => {
|
||||||
const form = useForm<any>({
|
const form = useForm<TGachaRollItem>({
|
||||||
resolver: zodResolver(gachaRollItemSchema),
|
resolver: zodResolver(gachaRollItemSchema),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
defaultValues: initialValues,
|
defaultValues: initialValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => {
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
onDataCapture?.(data);
|
console.log('Form data:', data);
|
||||||
nextStep();
|
nextStep();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,9 +38,8 @@ export const useConfirmItem = (
|
|||||||
) => {
|
) => {
|
||||||
const onConfirm = async () => {
|
const onConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
if (actionFunction) {
|
// const result = await actionFunction?.();
|
||||||
await actionFunction();
|
// result ? toast.success(messages?.success) : toast.error(messages?.error);
|
||||||
}
|
|
||||||
toast.success(messages?.success);
|
toast.success(messages?.success);
|
||||||
onClose();
|
onClose();
|
||||||
resetStep();
|
resetStep();
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
|
import {
|
||||||
|
SearchOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { DataTable } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
PaginationState,
|
||||||
|
useReactTable,
|
||||||
|
RowSelectionState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import ModalAddItem from './_components/modal-add-item';
|
||||||
|
import ModalUpdateItem from './_components/modal-update-item';
|
||||||
|
import ModalDeleteItem from './_components/modal-delete-item';
|
||||||
|
import { useQueryState } from '@imphnen-frontend-service/utils';
|
||||||
|
|
||||||
|
interface GachaItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
chanceRate: number;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData: GachaItem[] = Array.from({ length: 90 }, (_, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
name: 'Hoodie IMPHNEN Official 2025',
|
||||||
|
chanceRate: 0.1,
|
||||||
|
quantity: 10,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const Components: FC = (): ReactElement => {
|
||||||
|
const [showModalAddItem, setShowModalAddItem] = useState(false);
|
||||||
|
const [showModalUpdateItem, setShowModalUpdateItem] = useState(false);
|
||||||
|
const [showModalDeleteItem, setShowModalDeleteItem] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
step: currentStep,
|
||||||
|
nextStep,
|
||||||
|
prevStep,
|
||||||
|
resetStep,
|
||||||
|
} = useQueryState('step', {
|
||||||
|
defaultValue: 1,
|
||||||
|
maxValue: 2,
|
||||||
|
minValue: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 9,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
|
|
||||||
|
const columns: ColumnDef<GachaItem>[] = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="rounded"
|
||||||
|
checked={table.getIsAllRowsSelected()}
|
||||||
|
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="rounded"
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onChange={row.getToggleSelectedHandler()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'No',
|
||||||
|
accessorKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Nama Item',
|
||||||
|
accessorKey: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Chance Rate',
|
||||||
|
accessorKey: 'chanceRate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Quantity',
|
||||||
|
accessorKey: 'quantity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Action',
|
||||||
|
cell: () => (
|
||||||
|
<div className="flex gap-[8px]">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowModalUpdateItem(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<EditOutlined /> Update
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowModalDeleteItem(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<DeleteOutlined /> Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: mockData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
pagination,
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
pageCount: Math.ceil(mockData.length / pagination.pageSize),
|
||||||
|
manualPagination: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
||||||
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
||||||
|
<h1 className="text-p2 font-semibold">Gacha Roll</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
||||||
|
<div className="flex justify-between items-center gap-8 mb-2">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Input
|
||||||
|
placeholder="Cari berdasarkan nama item"
|
||||||
|
className="pl-12 w-full max-h-full"
|
||||||
|
/>
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
|
||||||
|
<SearchOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
className="flex gap-3 text-nowrap"
|
||||||
|
onClick={() => {
|
||||||
|
setShowModalAddItem(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
Tambah Item
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable data={mockData} columns={columns} table={table} />
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<ModalAddItem
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalAddItem}
|
||||||
|
onClose={() => setShowModalAddItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
<ModalUpdateItem
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalUpdateItem}
|
||||||
|
onClose={() => setShowModalUpdateItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
<ModalDeleteItem
|
||||||
|
isOpen={showModalDeleteItem}
|
||||||
|
onClose={() => setShowModalDeleteItem(false)}
|
||||||
|
handleDeleteItem={() => {
|
||||||
|
console.log('Item deleted');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
@@ -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,132 @@
|
|||||||
|
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 { useItem, useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalAddPermission {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleAddItem?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalAddPermission = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
resetStep,
|
||||||
|
handleAddItem,
|
||||||
|
}: IModalAddPermission) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-3 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleAddItem={handleAddItem}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const { form, onSubmit } = useItem(nextStep);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500">
|
||||||
|
Tambah Permissions
|
||||||
|
</h2>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nama Permission"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button variant="primary" size="lg" className="w-full" type="submit">
|
||||||
|
Tambah Permission
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleAddItem?: () => Promise<boolean>;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => {
|
||||||
|
const { onConfirm, onCancel } = useConfirmItem(
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleAddItem,
|
||||||
|
{
|
||||||
|
success: 'Data permissions berhasil ditambahkan',
|
||||||
|
error: 'Data permissions gagal ditambahkan',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-7 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah Permissions
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-center text-neutral-400">
|
||||||
|
Apakah kamu yakin ingin
|
||||||
|
<br /> menambahkan permission ini?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex mb-0 gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Tambahkan
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalAddPermission;
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalDeletePermission {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleDelete?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalDeletePermission = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleDelete,
|
||||||
|
}: IModalDeletePermission) => {
|
||||||
|
const { onConfirm } = useConfirmItem(onClose, resetStep, handleDelete, {
|
||||||
|
success: 'Data permissions berhasil dihapus',
|
||||||
|
error: 'Data permissions gagal dihapus',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
closeButtonClassName="hidden"
|
||||||
|
>
|
||||||
|
<Modal.Header className="gap-8">
|
||||||
|
<img
|
||||||
|
src="/chibi-delete.webp"
|
||||||
|
alt="Delete?"
|
||||||
|
width={148}
|
||||||
|
className="self-center"
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-danger-500 mb-3">
|
||||||
|
Delete Permissions
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin untuk menghapus permission ini? Menghapus data ini
|
||||||
|
mungkin akan mempengaruhi fungsional sistem
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Batal Hapus
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-danger-500 hover:bg-danger-600"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Hapus Item
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalDeletePermission;
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalUpdatePermission {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleUpdate?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalUpdatePermission = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleUpdate,
|
||||||
|
}: IModalUpdatePermission) => {
|
||||||
|
const { onConfirm } = useConfirmItem(onClose, resetStep, handleUpdate, {
|
||||||
|
success: 'Perubahan permissions berhasil dilakukan',
|
||||||
|
error: 'Perubahan permissions gagal dilakukan',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-3 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500">
|
||||||
|
Update Permissions
|
||||||
|
</h2>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex flex-col gap-8">
|
||||||
|
<InputField
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nama Permission"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
type="submit"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Update Permission
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalUpdatePermission;
|
||||||
+10
-6
@@ -1,18 +1,23 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
// import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
// import {
|
||||||
|
// gachaRollItemSchema,
|
||||||
|
// TGachaRollItem
|
||||||
|
// } from '@imphnen-frontend-service/service';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export const useItem = (
|
export const useItem = (
|
||||||
nextStep: () => void,
|
nextStep: () => void,
|
||||||
initialValues?: any,
|
initialValues?: any
|
||||||
onDataCapture?: (data: any) => void,
|
|
||||||
) => {
|
) => {
|
||||||
const form = useForm<any>({
|
const form = useForm<any>({
|
||||||
|
// resolver: zodResolver(),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
defaultValues: initialValues,
|
defaultValues: initialValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => {
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
onDataCapture?.(data);
|
console.log('Form data:', data);
|
||||||
nextStep();
|
nextStep();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,9 +38,8 @@ export const useConfirmItem = (
|
|||||||
) => {
|
) => {
|
||||||
const onConfirm = async () => {
|
const onConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
if (actionFunction) {
|
// const result = await actionFunction?.();
|
||||||
await actionFunction();
|
// result ? toast.success(messages?.success) : toast.error(messages?.error);
|
||||||
}
|
|
||||||
toast.success(messages?.success);
|
toast.success(messages?.success);
|
||||||
onClose();
|
onClose();
|
||||||
resetStep();
|
resetStep();
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
|
import {
|
||||||
|
SearchOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { DataTable } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
PaginationState,
|
||||||
|
RowSelectionState,
|
||||||
|
useReactTable,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import ModalAddPermission from './_components/modal-add-permission';
|
||||||
|
import ModalUpdatePermission from './_components/modal-update-permission';
|
||||||
|
import ModalDeletePermission from './_components/modal-delete-permission';
|
||||||
|
import { useQueryState } from '@imphnen-frontend-service/utils';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Permission {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData: Permission[] = [
|
||||||
|
{ id: 1, name: 'Read' },
|
||||||
|
{ id: 2, name: 'Create' },
|
||||||
|
{ id: 3, name: 'Update' },
|
||||||
|
{ id: 4, name: 'Delete' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Components: FC = (): ReactElement => {
|
||||||
|
const [showModalAddItem, setShowModalAddItem] = useState(false);
|
||||||
|
const [showModalUpdateItem, setShowModalUpdateItem] = useState(false);
|
||||||
|
const [showModalDeleteItem, setShowModalDeleteItem] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
step: currentStep,
|
||||||
|
nextStep,
|
||||||
|
prevStep,
|
||||||
|
resetStep,
|
||||||
|
} = useQueryState('step', {
|
||||||
|
defaultValue: 1,
|
||||||
|
maxValue: 2,
|
||||||
|
minValue: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 9,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
|
|
||||||
|
const columns: ColumnDef<Permission>[] = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="rounded"
|
||||||
|
checked={table.getIsAllRowsSelected()}
|
||||||
|
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="rounded"
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onChange={row.getToggleSelectedHandler()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'No',
|
||||||
|
accessorKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Name',
|
||||||
|
accessorKey: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Action',
|
||||||
|
cell: () => (
|
||||||
|
<div className="flex gap-[8px]">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowModalUpdateItem(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<EditOutlined /> Update
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowModalDeleteItem(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<DeleteOutlined /> Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: mockData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
pagination,
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
pageCount: Math.ceil(mockData.length / pagination.pageSize),
|
||||||
|
manualPagination: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
||||||
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
||||||
|
<h1 className="text-p2 font-semibold">Permissions</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
||||||
|
<div className="flex justify-between items-center gap-8 mb-2">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Input
|
||||||
|
placeholder="Cari berdasarkan nama permissions"
|
||||||
|
className="pl-12 w-full max-h-full"
|
||||||
|
/>
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
|
||||||
|
<SearchOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
className="flex gap-3 text-nowrap"
|
||||||
|
onClick={() => {
|
||||||
|
setShowModalAddItem(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
Tambah Permissionss
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
data={mockData}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={9}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<ModalAddPermission
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalAddItem}
|
||||||
|
onClose={() => setShowModalAddItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
<ModalUpdatePermission
|
||||||
|
isOpen={showModalUpdateItem}
|
||||||
|
onClose={() => setShowModalUpdateItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
<ModalDeletePermission
|
||||||
|
isOpen={showModalDeleteItem}
|
||||||
|
onClose={() => setShowModalDeleteItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
File renamed without changes.
+50
-44
@@ -1,13 +1,14 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import * as React from 'react';
|
||||||
import * as React from 'react'
|
|
||||||
import { FC, Fragment, ReactElement, useState } from 'react'
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FilterOutlined,
|
FilterOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
AuditOutlined,
|
AuditOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons';
|
||||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms'
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms'
|
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
@@ -15,19 +16,19 @@ import {
|
|||||||
PaginationState,
|
PaginationState,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
} from '@tanstack/react-table'
|
} from '@tanstack/react-table';
|
||||||
import ModalProcessDelivery from './_components/prizes/modal-process-item'
|
import ModalProcessDelivery from './_components/modal-process-item';
|
||||||
|
|
||||||
type OrderValid = 'valid' | 'invalid' | 'unchecked'
|
type OrderValid = 'valid' | 'invalid' | 'unchecked';
|
||||||
type Status = 'undelivered' | 'delivered'
|
type Status = 'undelivered' | 'delivered';
|
||||||
|
|
||||||
interface Prize {
|
interface Prize {
|
||||||
id: number
|
id: number;
|
||||||
name: string
|
name: string;
|
||||||
orderValid: OrderValid
|
orderValid: OrderValid;
|
||||||
items: string
|
items: string;
|
||||||
address: string
|
address: string;
|
||||||
status: Status
|
status: Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
@@ -37,8 +38,9 @@ const items = [
|
|||||||
'Sticker Isi 3',
|
'Sticker Isi 3',
|
||||||
'Sticker Isi 5',
|
'Sticker Isi 5',
|
||||||
'Gelang Karet',
|
'Gelang Karet',
|
||||||
]
|
];
|
||||||
|
|
||||||
|
// Mock data for transactions
|
||||||
const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({
|
const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
name: 'Nama Lengkap',
|
name: 'Nama Lengkap',
|
||||||
@@ -50,28 +52,24 @@ const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({
|
|||||||
items: items[i % items.length],
|
items: items[i % items.length],
|
||||||
address: 'Jl. Pantai Cibaduyut Indah',
|
address: 'Jl. Pantai Cibaduyut Indah',
|
||||||
status: (i % 3 === 0 ? 'undelivered' : 'delivered') as Status,
|
status: (i % 3 === 0 ? 'undelivered' : 'delivered') as Status,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
export const Route = createFileRoute('/_authenticated/prizes')({
|
export const Components: FC = (): ReactElement => {
|
||||||
component: PrizesPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
function PrizesPage() {
|
|
||||||
const [showModalProcessDelivery, setShowModalProcessDelivery] =
|
const [showModalProcessDelivery, setShowModalProcessDelivery] =
|
||||||
useState(false)
|
useState(false);
|
||||||
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 9,
|
pageSize: 9,
|
||||||
})
|
});
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
const [showFilter, setShowFilter] = useState(false)
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
|
|
||||||
const deliveryOptions = [
|
const deliveryOptions = [
|
||||||
{ id: 'option1', value: 'undelivered', label: 'Undelivered' },
|
{ id: 'option1', value: 'undelivered', label: 'Undelivered' },
|
||||||
{ id: 'option1', value: 'delivered', label: 'Delivered' },
|
{ id: 'option1', value: 'delivered', label: 'Delivered' },
|
||||||
]
|
];
|
||||||
|
|
||||||
const columns: ColumnDef<Prize>[] = [
|
const columns: ColumnDef<Prize>[] = [
|
||||||
{
|
{
|
||||||
@@ -105,24 +103,24 @@ function PrizesPage() {
|
|||||||
header: 'Order Valid?',
|
header: 'Order Valid?',
|
||||||
accessorKey: 'orderValid',
|
accessorKey: 'orderValid',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = row.original.orderValid
|
const status = row.original.orderValid;
|
||||||
const statusColors: Record<OrderValid, string> = {
|
const statusColors: Record<OrderValid, string> = {
|
||||||
valid: 'bg-success-200 text-success-500',
|
valid: 'bg-success-200 text-success-500',
|
||||||
invalid: 'bg-danger-200 text-danger-500',
|
invalid: 'bg-danger-200 text-danger-500',
|
||||||
unchecked: 'bg-warning-200 text-warning-900',
|
unchecked: 'bg-warning-200 text-warning-900',
|
||||||
}
|
};
|
||||||
const statusText: Record<OrderValid, string> = {
|
const statusText: Record<OrderValid, string> = {
|
||||||
valid: 'Valid',
|
valid: 'Valid',
|
||||||
invalid: 'Invalid',
|
invalid: 'Invalid',
|
||||||
unchecked: 'Unchecked',
|
unchecked: 'Unchecked',
|
||||||
}
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
||||||
>
|
>
|
||||||
{statusText[status]}
|
{statusText[status]}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,22 +135,22 @@ function PrizesPage() {
|
|||||||
header: 'Status',
|
header: 'Status',
|
||||||
accessorKey: 'status',
|
accessorKey: 'status',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = row.original.status
|
const status = row.original.status;
|
||||||
const statusColors: Record<Status, string> = {
|
const statusColors: Record<Status, string> = {
|
||||||
delivered: 'bg-success-200 text-success-500',
|
delivered: 'bg-success-200 text-success-500',
|
||||||
undelivered: 'bg-danger-200 text-danger-500',
|
undelivered: 'bg-danger-200 text-danger-500',
|
||||||
}
|
};
|
||||||
const statusText: Record<Status, string> = {
|
const statusText: Record<Status, string> = {
|
||||||
delivered: 'Delivered',
|
delivered: 'Delivered',
|
||||||
undelivered: 'Undelivered',
|
undelivered: 'Undelivered',
|
||||||
}
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
||||||
>
|
>
|
||||||
{statusText[status]}
|
{statusText[status]}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -162,8 +160,8 @@ function PrizesPage() {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
setShowModalProcessDelivery(true)
|
setShowModalProcessDelivery(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2 w-full"
|
className="flex items-center gap-2 w-full"
|
||||||
>
|
>
|
||||||
@@ -171,7 +169,7 @@ function PrizesPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: mockData,
|
data: mockData,
|
||||||
@@ -187,15 +185,18 @@ function PrizesPage() {
|
|||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
pageCount: Math.ceil(mockData.length / pagination.pageSize),
|
pageCount: Math.ceil(mockData.length / pagination.pageSize),
|
||||||
manualPagination: false,
|
manualPagination: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
||||||
|
{/* Header */}
|
||||||
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
||||||
<h1 className="text-p2 font-semibold">Data Pengiriman Hadiah</h1>
|
<h1 className="text-p2 font-semibold">Data Pengiriman Hadiah</h1>
|
||||||
</header>
|
</header>
|
||||||
|
{/* Account Table Section */}
|
||||||
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
||||||
|
{/* Search and Filter */}
|
||||||
<div className="flex justify-between items-center gap-8 mb-2">
|
<div className="flex justify-between items-center gap-8 mb-2">
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Input
|
<Input
|
||||||
@@ -222,24 +223,29 @@ function PrizesPage() {
|
|||||||
options={deliveryOptions}
|
options={deliveryOptions}
|
||||||
onClose={() => setShowFilter(false)}
|
onClose={() => setShowFilter(false)}
|
||||||
onFilterChange={(value) => {
|
onFilterChange={(value) => {
|
||||||
console.log('Selected filter:', value)
|
console.log('Selected filter:', value);
|
||||||
|
// Filter logic di sini
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Table */}
|
||||||
<DataTable data={mockData} columns={columns} table={table} />
|
<DataTable data={mockData} columns={columns} table={table} />
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Modal Process Delivery */}
|
||||||
<ModalProcessDelivery
|
<ModalProcessDelivery
|
||||||
isOpen={showModalProcessDelivery}
|
isOpen={showModalProcessDelivery}
|
||||||
onClose={() => setShowModalProcessDelivery(false)}
|
onClose={() => setShowModalProcessDelivery(false)}
|
||||||
handleProcessDelivery={() => {
|
handleProcessDelivery={() => {
|
||||||
console.log('Action ketika user menekan tombol Proses Pengiriman')
|
console.log('Action ketika user menekan tombol Proses Pengiriman');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
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 { useItem, useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalAddRole {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleAdd?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalAddRole = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
currentStep,
|
||||||
|
nextStep,
|
||||||
|
resetStep,
|
||||||
|
handleAdd,
|
||||||
|
}: IModalAddRole) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-0 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
resetStep();
|
||||||
|
}}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<StepTwo
|
||||||
|
onClose={onClose}
|
||||||
|
handleAdd={handleAdd}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepOneProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepOne = ({ nextStep }: IStepOneProps) => {
|
||||||
|
const { form, onSubmit } = useItem(nextStep);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah Roles
|
||||||
|
</h2>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Nama Role"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Nama Role"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 items-start overflow-auto">
|
||||||
|
<span className="text-p3 font-medium text-neutral-800 sticky left-0">
|
||||||
|
Permissions
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-x-6 overflow-x-scroll">
|
||||||
|
{[
|
||||||
|
'Gacha Items',
|
||||||
|
'Gacha Roll',
|
||||||
|
'Roll',
|
||||||
|
'Users',
|
||||||
|
'Gacha Claim',
|
||||||
|
].map((title) => (
|
||||||
|
<div
|
||||||
|
key={title}
|
||||||
|
className="flex flex-col gap-4 select-none text-label2 font-medium text-neutral-900 "
|
||||||
|
>
|
||||||
|
<span className="text-nowrap text-label1">{title}</span>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`${title}-all`}
|
||||||
|
className="rounded"
|
||||||
|
/>
|
||||||
|
<label htmlFor={`${title}-all`} className="text-nowrap">
|
||||||
|
Check All
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<hr className="border-blue-200" />
|
||||||
|
<div className="flex flex-col items-start gap-4 mb-4">
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-read`} />
|
||||||
|
<label htmlFor={`${title}-read`}>Read</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-create`} />
|
||||||
|
<label htmlFor={`${title}-create`}>Create</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-update`} />
|
||||||
|
<label htmlFor={`${title}-update`}>Update</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-delete`} />
|
||||||
|
<label htmlFor={`${title}-delete`}>Delete</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="primary" size="lg" className="w-full" type="submit">
|
||||||
|
Tambah Role
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IStepTwoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
handleAdd?: () => Promise<boolean>;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepTwo = ({ onClose, handleAdd, resetStep }: IStepTwoProps) => {
|
||||||
|
const { onConfirm, onCancel } = useConfirmItem(
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleAdd,
|
||||||
|
{
|
||||||
|
success: 'Data role berhasil ditambahkan',
|
||||||
|
error: 'Data role gagal ditambahkan',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal.Header className="mb-10 text-center items-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Tambah Roles
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-center text-neutral-400">
|
||||||
|
Apakah kamu yakin ingin
|
||||||
|
<br /> menambahkan role ini?
|
||||||
|
</p>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex mb-0 gap-4">
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Tambahkan
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalAddRole;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalDeletePermission {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleDelete?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalDeletePermission = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleDelete,
|
||||||
|
}: IModalDeletePermission) => {
|
||||||
|
const { onConfirm } = useConfirmItem(onClose, resetStep, handleDelete, {
|
||||||
|
success: 'Data roles berhasil dihapus',
|
||||||
|
error: 'Data roles gagal dihapus',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
closeButtonClassName="hidden"
|
||||||
|
>
|
||||||
|
<Modal.Header className="gap-8">
|
||||||
|
<img
|
||||||
|
src="/chibi-delete.webp"
|
||||||
|
alt="Delete?"
|
||||||
|
width={148}
|
||||||
|
className="self-center"
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-p1 font-semibold text-danger-500 mb-3">
|
||||||
|
Delete Roles
|
||||||
|
</h2>
|
||||||
|
<p className="text-p3 text-neutral-400">
|
||||||
|
Apakah kamu yakin untuk menghapus role ini? Menghapus data ini
|
||||||
|
mungkin akan mempengaruhi fungsional sistem
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Batal Hapus
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-danger-500 hover:bg-danger-600"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Hapus Role
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalDeletePermission;
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||||
|
import { useConfirmItem } from '../_hook/use-item';
|
||||||
|
|
||||||
|
interface IModalUpdatePermission {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
handleUpdate?: () => Promise<boolean>;
|
||||||
|
currentStep?: number;
|
||||||
|
nextStep: () => void;
|
||||||
|
prevStep: () => void;
|
||||||
|
resetStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalUpdatePermission = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
resetStep,
|
||||||
|
handleUpdate,
|
||||||
|
}: IModalUpdatePermission) => {
|
||||||
|
const { onConfirm } = useConfirmItem(onClose, resetStep, handleUpdate, {
|
||||||
|
success: 'Perubahan roles berhasil dilakukan',
|
||||||
|
error: 'Perubahan roles gagal dilakukan',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-0 text-center"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
disableEscapeKeyDown={true}
|
||||||
|
>
|
||||||
|
<Modal.Header>
|
||||||
|
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
|
||||||
|
Update Roles
|
||||||
|
</h2>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Content className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<InputField
|
||||||
|
label="Nama Role"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan Nama Role"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 items-start overflow-auto">
|
||||||
|
<span className="text-p3 font-medium text-neutral-800 sticky left-0">
|
||||||
|
Permissions
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-x-6 overflow-x-scroll">
|
||||||
|
{['Gacha Items', 'Gacha Roll', 'Roll', 'Users', 'Gacha Claim'].map(
|
||||||
|
(title) => (
|
||||||
|
<div
|
||||||
|
key={title}
|
||||||
|
className="flex flex-col gap-4 select-none text-label2 font-medium text-neutral-900 "
|
||||||
|
>
|
||||||
|
<span className="text-nowrap text-label1">{title}</span>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`${title}-all`}
|
||||||
|
className="rounded"
|
||||||
|
/>
|
||||||
|
<label htmlFor={`${title}-all`} className="text-nowrap">
|
||||||
|
Check All
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<hr className="border-blue-200" />
|
||||||
|
<div className="flex flex-col items-start gap-4 mb-4">
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-read`} />
|
||||||
|
<label htmlFor={`${title}-read`}>Read</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-create`} />
|
||||||
|
<label htmlFor={`${title}-create`}>Create</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-update`} />
|
||||||
|
<label htmlFor={`${title}-update`}>Update</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[8px] items-center">
|
||||||
|
<input type="checkbox" id={`${title}-delete`} />
|
||||||
|
<label htmlFor={`${title}-delete`}>Delete</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
type="submit"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Update Role
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalUpdatePermission;
|
||||||
+10
-6
@@ -1,18 +1,23 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
// import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
// import {
|
||||||
|
// gachaRollItemSchema,
|
||||||
|
// TGachaRollItem
|
||||||
|
// } from '@imphnen-frontend-service/service';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export const useItem = (
|
export const useItem = (
|
||||||
nextStep: () => void,
|
nextStep: () => void,
|
||||||
initialValues?: any,
|
initialValues?: any
|
||||||
onDataCapture?: (data: any) => void,
|
|
||||||
) => {
|
) => {
|
||||||
const form = useForm<any>({
|
const form = useForm<any>({
|
||||||
|
// resolver: zodResolver(),
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
defaultValues: initialValues,
|
defaultValues: initialValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => {
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
onDataCapture?.(data);
|
console.log('Form data:', data);
|
||||||
nextStep();
|
nextStep();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,9 +38,8 @@ export const useConfirmItem = (
|
|||||||
) => {
|
) => {
|
||||||
const onConfirm = async () => {
|
const onConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
if (actionFunction) {
|
// const result = await actionFunction?.();
|
||||||
await actionFunction();
|
// result ? toast.success(messages?.success) : toast.error(messages?.error);
|
||||||
}
|
|
||||||
toast.success(messages?.success);
|
toast.success(messages?.success);
|
||||||
onClose();
|
onClose();
|
||||||
resetStep();
|
resetStep();
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
|
import {
|
||||||
|
SearchOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { DataTable } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
PaginationState,
|
||||||
|
RowSelectionState,
|
||||||
|
useReactTable,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import ModalAddRole from './_components/modal-add-role';
|
||||||
|
import ModalUpdateRole from './_components/modal-update-role';
|
||||||
|
import ModalDeleteRole from './_components/modal-delete-role';
|
||||||
|
import { useQueryState } from '@imphnen-frontend-service/utils';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Role {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData: Role[] = [
|
||||||
|
{ id: 1, name: 'Admin' },
|
||||||
|
{ id: 2, name: 'Admin Pembayaran' },
|
||||||
|
{ id: 3, name: 'Staff' },
|
||||||
|
{ id: 4, name: 'Staff Aktivasi User' },
|
||||||
|
{ id: 5, name: 'User' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Components: FC = (): ReactElement => {
|
||||||
|
const [showModalAddItem, setShowModalAddItem] = useState(false);
|
||||||
|
const [showModalUpdateItem, setShowModalUpdateItem] = useState(false);
|
||||||
|
const [showModalDeleteItem, setShowModalDeleteItem] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
step: currentStep,
|
||||||
|
nextStep,
|
||||||
|
prevStep,
|
||||||
|
resetStep,
|
||||||
|
} = useQueryState('step', {
|
||||||
|
defaultValue: 1,
|
||||||
|
maxValue: 2,
|
||||||
|
minValue: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 9,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
|
|
||||||
|
const columns: ColumnDef<Role>[] = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="rounded"
|
||||||
|
checked={table.getIsAllRowsSelected()}
|
||||||
|
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="rounded"
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onChange={row.getToggleSelectedHandler()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'ID',
|
||||||
|
accessorKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Roles Name',
|
||||||
|
accessorKey: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Action',
|
||||||
|
cell: () => (
|
||||||
|
<div className="flex gap-[8px]">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowModalUpdateItem(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<EditOutlined /> Update
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowModalDeleteItem(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<DeleteOutlined /> Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: mockData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
pagination,
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
pageCount: Math.ceil(mockData.length / pagination.pageSize),
|
||||||
|
manualPagination: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
||||||
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
||||||
|
<h1 className="text-p2 font-semibold">Roles</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
||||||
|
<div className="flex justify-between items-center gap-8 mb-2">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Input
|
||||||
|
placeholder="Cari berdasarkan nama roles"
|
||||||
|
className="pl-12 w-full max-h-full"
|
||||||
|
/>
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
|
||||||
|
<SearchOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
className="flex gap-3 text-nowrap"
|
||||||
|
onClick={() => {
|
||||||
|
setShowModalAddItem(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
Tambah Role
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
data={mockData}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={9}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<ModalAddRole
|
||||||
|
currentStep={currentStep}
|
||||||
|
isOpen={showModalAddItem}
|
||||||
|
onClose={() => setShowModalAddItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
<ModalUpdateRole
|
||||||
|
isOpen={showModalUpdateItem}
|
||||||
|
onClose={() => setShowModalUpdateItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
<ModalDeleteRole
|
||||||
|
isOpen={showModalDeleteItem}
|
||||||
|
onClose={() => setShowModalDeleteItem(false)}
|
||||||
|
nextStep={nextStep}
|
||||||
|
prevStep={prevStep}
|
||||||
|
resetStep={resetStep}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
File renamed without changes.
+41
-37
@@ -1,13 +1,13 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import * as React from 'react';
|
||||||
import * as React from 'react'
|
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||||
import { FC, Fragment, ReactElement, useState } from 'react'
|
|
||||||
import {
|
import {
|
||||||
FilterOutlined,
|
FilterOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
AuditOutlined,
|
AuditOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons';
|
||||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms'
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms'
|
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
@@ -15,18 +15,19 @@ import {
|
|||||||
PaginationState,
|
PaginationState,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
} from '@tanstack/react-table'
|
} from '@tanstack/react-table';
|
||||||
import ModalValidate from './_components/transactions/modal-validate'
|
import ModalValidate from './_components/modal-validate';
|
||||||
|
|
||||||
type TransactionStatus = 'valid' | 'invalid' | 'unchecked'
|
type TransactionStatus = 'valid' | 'invalid' | 'unchecked';
|
||||||
|
|
||||||
interface Transaction {
|
interface Transaction {
|
||||||
id: number
|
id: number;
|
||||||
name: string
|
name: string;
|
||||||
transactionNumber: string
|
transactionNumber: string;
|
||||||
status: TransactionStatus
|
status: TransactionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mock data for transactions
|
||||||
const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
|
const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
name: i === 0 ? 'Ahmad Wijuana' : 'Nama Lengkap',
|
name: i === 0 ? 'Ahmad Wijuana' : 'Nama Lengkap',
|
||||||
@@ -36,28 +37,24 @@ const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
|
|||||||
: i % 5 === 0
|
: i % 5 === 0
|
||||||
? 'unchecked'
|
? 'unchecked'
|
||||||
: 'valid') as TransactionStatus,
|
: 'valid') as TransactionStatus,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
export const Route = createFileRoute('/_authenticated/transactions')({
|
export const Components: FC = (): ReactElement => {
|
||||||
component: TransactionsPage,
|
const [showModalValidate, setShowModalValidate] = useState(false);
|
||||||
})
|
|
||||||
|
|
||||||
function TransactionsPage() {
|
|
||||||
const [showModalValidate, setShowModalValidate] = useState(false)
|
|
||||||
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 9,
|
pageSize: 9,
|
||||||
})
|
});
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
const [showFilter, setShowFilter] = useState(false)
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
|
|
||||||
const validationOptions = [
|
const validationOptions = [
|
||||||
{ id: 'option1', value: 'unchecked', label: 'Unchecked' },
|
{ id: 'option1', value: 'unchecked', label: 'Unchecked' },
|
||||||
{ id: 'option2', value: 'valid', label: 'Valid' },
|
{ id: 'option2', value: 'valid', label: 'Valid' },
|
||||||
{ id: 'option3', value: 'invalid', label: 'Invalid' },
|
{ id: 'option3', value: 'invalid', label: 'Invalid' },
|
||||||
]
|
];
|
||||||
|
|
||||||
const columns: ColumnDef<Transaction>[] = [
|
const columns: ColumnDef<Transaction>[] = [
|
||||||
{
|
{
|
||||||
@@ -95,24 +92,24 @@ function TransactionsPage() {
|
|||||||
header: 'Order Valid?',
|
header: 'Order Valid?',
|
||||||
accessorKey: 'status',
|
accessorKey: 'status',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = row.original.status
|
const status = row.original.status;
|
||||||
const statusColors: Record<TransactionStatus, string> = {
|
const statusColors: Record<TransactionStatus, string> = {
|
||||||
valid: 'bg-success-200 text-success-500',
|
valid: 'bg-success-200 text-success-500',
|
||||||
invalid: 'bg-danger-200 text-danger-500',
|
invalid: 'bg-danger-200 text-danger-500',
|
||||||
unchecked: 'bg-warning-200 text-warning-900',
|
unchecked: 'bg-warning-200 text-warning-900',
|
||||||
}
|
};
|
||||||
const statusText: Record<TransactionStatus, string> = {
|
const statusText: Record<TransactionStatus, string> = {
|
||||||
valid: 'Valid',
|
valid: 'Valid',
|
||||||
invalid: 'Invalid',
|
invalid: 'Invalid',
|
||||||
unchecked: 'Unchecked',
|
unchecked: 'Unchecked',
|
||||||
}
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
||||||
>
|
>
|
||||||
{statusText[status]}
|
{statusText[status]}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -122,8 +119,8 @@ function TransactionsPage() {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
setShowModalValidate(true)
|
setShowModalValidate(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2 w-full"
|
className="flex items-center gap-2 w-full"
|
||||||
>
|
>
|
||||||
@@ -131,7 +128,7 @@ function TransactionsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: mockTransactions,
|
data: mockTransactions,
|
||||||
@@ -147,16 +144,19 @@ function TransactionsPage() {
|
|||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
pageCount: Math.ceil(mockTransactions.length / pagination.pageSize),
|
pageCount: Math.ceil(mockTransactions.length / pagination.pageSize),
|
||||||
manualPagination: false,
|
manualPagination: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
||||||
|
{/* Header */}
|
||||||
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
||||||
<h1 className="text-p2 font-semibold">Validasi Transaksi</h1>
|
<h1 className="text-p2 font-semibold">Validasi Transaksi</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{/* Account Table Section */}
|
||||||
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
||||||
|
{/* Search and Filter */}
|
||||||
<div className="flex justify-between items-center gap-8 mb-2">
|
<div className="flex justify-between items-center gap-8 mb-2">
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Input
|
<Input
|
||||||
@@ -184,7 +184,8 @@ function TransactionsPage() {
|
|||||||
options={validationOptions}
|
options={validationOptions}
|
||||||
onClose={() => setShowFilter(false)}
|
onClose={() => setShowFilter(false)}
|
||||||
onFilterChange={(value) => {
|
onFilterChange={(value) => {
|
||||||
console.log('Selected filter:', value)
|
console.log('Selected filter:', value);
|
||||||
|
// Filter logic di sini
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,6 +193,7 @@ function TransactionsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
<DataTable data={mockTransactions} columns={columns} table={table} />
|
<DataTable data={mockTransactions} columns={columns} table={table} />
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@@ -199,12 +201,14 @@ function TransactionsPage() {
|
|||||||
isOpen={showModalValidate}
|
isOpen={showModalValidate}
|
||||||
onClose={() => setShowModalValidate(false)}
|
onClose={() => setShowModalValidate(false)}
|
||||||
handleValid={() => {
|
handleValid={() => {
|
||||||
console.log('Action ketika user klik Valid')
|
console.log('Action ketika user klik Valid');
|
||||||
}}
|
}}
|
||||||
handleInvalid={() => {
|
handleInvalid={() => {
|
||||||
console.log('Action ketika user klik Tidak Valid')
|
console.log('Action ketika user klik Tidak Valid');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
+3
-4
@@ -2,9 +2,9 @@ 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 form = useForm<TLoginRequest>({
|
const form = useForm<TLoginRequest>({
|
||||||
@@ -12,13 +12,12 @@ export const useLogin = () => {
|
|||||||
mode: 'all',
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginMutation = usePostLogin();
|
const { signIn } = useSession();
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit((data) => loginMutation.mutate(data));
|
const onSubmit = form.handleSubmit((data) => signIn(data));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isLoading: loginMutation.isPending,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { useLogin } from './_hooks/use-login';
|
||||||
|
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const Components: FC = (): ReactElement => {
|
||||||
|
const { form, onSubmit } = useLogin();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-screen">
|
||||||
|
<div className="bg-white border border-primary-200 shadow-lg p-[60px] text-center flex flex-col justify-items-stretch gap-8 rounded-2xl">
|
||||||
|
<img src="/logos/logo.svg" alt="" className="h-[70px] w-auto" />
|
||||||
|
<h1 className="text-primary-500 text-p1 font-semibold">
|
||||||
|
Welcome to IMPHNEN Backoffice
|
||||||
|
</h1>
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Email"
|
||||||
|
placeholder="Masukkan Email"
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ControlledInputField
|
||||||
|
control={form.control}
|
||||||
|
label="Password"
|
||||||
|
placeholder="Masukkan Password"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
form.formState.isSubmitting ||
|
||||||
|
form.formState.isValidating ||
|
||||||
|
!form.formState.isValid
|
||||||
|
}
|
||||||
|
type="submit"
|
||||||
|
size="md"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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,144 +0,0 @@
|
|||||||
import { FC, useState, useRef, useEffect } from 'react';
|
|
||||||
import { FilterOutlined } from '@ant-design/icons';
|
|
||||||
import INDONESIAN_CITIES from '../constants/cities';
|
|
||||||
|
|
||||||
interface CityFilterSelectProps {
|
|
||||||
value: string;
|
|
||||||
onChange: (value: string) => void;
|
|
||||||
className?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
allOptionLabel?: string;
|
|
||||||
filterIcon?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CityFilterSelect: FC<CityFilterSelectProps> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
className = '',
|
|
||||||
placeholder = 'Search cities...',
|
|
||||||
allOptionLabel = 'All Cities',
|
|
||||||
filterIcon = true,
|
|
||||||
}) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const filteredCities = INDONESIAN_CITIES.filter((city) =>
|
|
||||||
city.toLowerCase().includes(searchQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
|
||||||
if (
|
|
||||||
dropdownRef.current &&
|
|
||||||
!dropdownRef.current.contains(event.target as Node)
|
|
||||||
) {
|
|
||||||
setIsOpen(false);
|
|
||||||
setSearchQuery('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSelectCity = (city: string) => {
|
|
||||||
onChange(city);
|
|
||||||
setSearchQuery('');
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputClick = () => {
|
|
||||||
setIsOpen(true);
|
|
||||||
setSearchQuery('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearSelection = () => {
|
|
||||||
onChange('all');
|
|
||||||
setSearchQuery('');
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayValue = value === 'all' ? allOptionLabel : value;
|
|
||||||
const showClearButton = value !== 'all' && !isOpen;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`relative ${className}`} ref={dropdownRef}>
|
|
||||||
<div className="relative">
|
|
||||||
{filterIcon && (
|
|
||||||
<FilterOutlined className="absolute left-3 top-1/2 transform -translate-y-1/2 text-neutral-400 text-sm pointer-events-none z-10" />
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
value={isOpen ? searchQuery : displayValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchQuery(e.target.value);
|
|
||||||
if (!isOpen) setIsOpen(true);
|
|
||||||
}}
|
|
||||||
onClick={handleInputClick}
|
|
||||||
onFocus={handleInputClick}
|
|
||||||
placeholder={isOpen ? placeholder : displayValue}
|
|
||||||
className={`border border-neutral-200 rounded-lg pr-10 py-2.5 text-sm w-full focus:border-primary-500 focus:outline-none appearance-none bg-white cursor-pointer ${
|
|
||||||
filterIcon ? ' pl-10' : 'pl-3'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
{showClearButton && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleClearSelection();
|
|
||||||
}}
|
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600 text-xs cursor-pointer z-20"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<div className="absolute z-50 w-full mt-1 bg-white border border-neutral-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
onClick={() => handleSelectCity('all')}
|
|
||||||
className={`px-3 py-2 cursor-pointer hover:bg-neutral-50 border-b border-neutral-100 ${
|
|
||||||
value === 'all'
|
|
||||||
? 'bg-primary-50 text-primary-700 font-medium'
|
|
||||||
: 'text-neutral-900'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{allOptionLabel}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredCities.length > 0 ? (
|
|
||||||
<div className="py-1">
|
|
||||||
{filteredCities.slice(0, 100).map((city) => (
|
|
||||||
<div
|
|
||||||
key={city}
|
|
||||||
onClick={() => handleSelectCity(city)}
|
|
||||||
className={`px-3 py-2 cursor-pointer hover:bg-neutral-50 text-sm ${
|
|
||||||
value === city
|
|
||||||
? 'bg-primary-50 text-primary-700 font-medium'
|
|
||||||
: 'text-neutral-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{city}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{filteredCities.length > 100 && (
|
|
||||||
<div className="px-3 py-2 text-xs text-neutral-500 border-t border-neutral-100">
|
|
||||||
Showing first 100 results. Continue typing to refine...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : searchQuery ? (
|
|
||||||
<div className="px-3 py-2 text-neutral-500 text-sm">
|
|
||||||
No cities found matching "{searchQuery}"
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,518 +0,0 @@
|
|||||||
const INDONESIAN_CITIES: string[] = [
|
|
||||||
'Aceh Selatan',
|
|
||||||
'Aceh Tenggara',
|
|
||||||
'Aceh Timur',
|
|
||||||
'Aceh Tengah',
|
|
||||||
'Aceh Barat',
|
|
||||||
'Aceh Besar',
|
|
||||||
'Pidie',
|
|
||||||
'Aceh Utara',
|
|
||||||
'Simeulue',
|
|
||||||
'Aceh Singkil',
|
|
||||||
'Bireuen',
|
|
||||||
'Aceh Barat Daya',
|
|
||||||
'Gayo Lues',
|
|
||||||
'Aceh Jaya',
|
|
||||||
'Nagan Raya',
|
|
||||||
'Aceh Tamiang',
|
|
||||||
'Bener Meriah',
|
|
||||||
'Pidie Jaya',
|
|
||||||
'Kota Banda Aceh',
|
|
||||||
'Kota Sabang',
|
|
||||||
'Kota Lhokseumawe',
|
|
||||||
'Kota Langsa',
|
|
||||||
'Kota Subulussalam',
|
|
||||||
'Tapanuli Tengah',
|
|
||||||
'Tapanuli Utara',
|
|
||||||
'Tapanuli Selatan',
|
|
||||||
'Nias',
|
|
||||||
'Langkat',
|
|
||||||
'Karo',
|
|
||||||
'Deli Serdang',
|
|
||||||
'Simalungun',
|
|
||||||
'Asahan',
|
|
||||||
'Labuhanbatu',
|
|
||||||
'Dairi',
|
|
||||||
'Toba',
|
|
||||||
'Mandailing Natal',
|
|
||||||
'Nias Selatan',
|
|
||||||
'Pakpak Bharat',
|
|
||||||
'Humbang Hasundutan',
|
|
||||||
'Samosir',
|
|
||||||
'Serdang Bedagai',
|
|
||||||
'Batu Bara',
|
|
||||||
'Padang Lawas Utara',
|
|
||||||
'Padang Lawas',
|
|
||||||
'Labuhanbatu Selatan',
|
|
||||||
'Labuhanbatu Utara',
|
|
||||||
'Nias Utara',
|
|
||||||
'Nias Barat',
|
|
||||||
'Kota Medan',
|
|
||||||
'Kota Pematangsiantar',
|
|
||||||
'Kota Sibolga',
|
|
||||||
'Kota Tanjung Balai',
|
|
||||||
'Kota Binjai',
|
|
||||||
'Kota Tebing Tinggi',
|
|
||||||
'Kota Padangsidimpuan',
|
|
||||||
'Kota Gunungsitoli',
|
|
||||||
'Pesisir Selatan',
|
|
||||||
'Solok',
|
|
||||||
'Sijunjung',
|
|
||||||
'Tanah Datar',
|
|
||||||
'Padang Pariaman',
|
|
||||||
'Agam',
|
|
||||||
'Lima Puluh Kota',
|
|
||||||
'Pasaman',
|
|
||||||
'Kepulauan Mentawai',
|
|
||||||
'Dharmasraya',
|
|
||||||
'Solok Selatan',
|
|
||||||
'Pasaman Barat',
|
|
||||||
'Kota Padang',
|
|
||||||
'Kota Solok',
|
|
||||||
'Kota Sawahlunto',
|
|
||||||
'Kota Padang Panjang',
|
|
||||||
'Kota Bukittinggi',
|
|
||||||
'Kota Payakumbuh',
|
|
||||||
'Kota Pariaman',
|
|
||||||
'Kampar',
|
|
||||||
'Indragiri Hulu',
|
|
||||||
'Bengkalis',
|
|
||||||
'Indragiri Hilir',
|
|
||||||
'Pelalawan',
|
|
||||||
'Rokan Hulu',
|
|
||||||
'Rokan Hilir',
|
|
||||||
'Siak',
|
|
||||||
'Kuantan Singingi',
|
|
||||||
'Kepulauan Meranti',
|
|
||||||
'Kota Pekanbaru',
|
|
||||||
'Kota Dumai',
|
|
||||||
'Kerinci',
|
|
||||||
'Merangin',
|
|
||||||
'Sarolangun',
|
|
||||||
'Batanghari',
|
|
||||||
'Muaro Jambi',
|
|
||||||
'Tanjung Jabung Barat',
|
|
||||||
'Tanjung Jabung Timur',
|
|
||||||
'Bungo',
|
|
||||||
'Tebo',
|
|
||||||
'Kota Jambi',
|
|
||||||
'Kota Sungai Penuh',
|
|
||||||
'Ogan Komering Ulu',
|
|
||||||
'Ogan Komering Ilir',
|
|
||||||
'Muara Enim',
|
|
||||||
'Lahat',
|
|
||||||
'Musi Rawas',
|
|
||||||
'Musi Banyuasin',
|
|
||||||
'Banyuasin',
|
|
||||||
'Ogan Komering Ulu Timur',
|
|
||||||
'Ogan Komering Ulu Selatan',
|
|
||||||
'Ogan Ilir',
|
|
||||||
'Empat Lawang',
|
|
||||||
'Penukal Abab Lematang Ilir',
|
|
||||||
'Musi Rawas Utara',
|
|
||||||
'Kota Palembang',
|
|
||||||
'Kota Pagar Alam',
|
|
||||||
'Kota Lubuk Linggau',
|
|
||||||
'Kota Prabumulih',
|
|
||||||
'Bengkulu Selatan',
|
|
||||||
'Rejang Lebong',
|
|
||||||
'Bengkulu Utara',
|
|
||||||
'Kaur',
|
|
||||||
'Seluma',
|
|
||||||
'Muko Muko',
|
|
||||||
'Lebong',
|
|
||||||
'Kepahiang',
|
|
||||||
'Bengkulu Tengah',
|
|
||||||
'Kota Bengkulu',
|
|
||||||
'Lampung Selatan',
|
|
||||||
'Lampung Tengah',
|
|
||||||
'Lampung Utara',
|
|
||||||
'Lampung Barat',
|
|
||||||
'Tulang Bawang',
|
|
||||||
'Tanggamus',
|
|
||||||
'Lampung Timur',
|
|
||||||
'Way Kanan',
|
|
||||||
'Pesawaran',
|
|
||||||
'Pringsewu',
|
|
||||||
'Mesuji',
|
|
||||||
'Tulang Bawang Barat',
|
|
||||||
'Pesisir Barat',
|
|
||||||
'Kota Bandar Lampung',
|
|
||||||
'Kota Metro',
|
|
||||||
'Bangka',
|
|
||||||
'Belitung',
|
|
||||||
'Bangka Selatan',
|
|
||||||
'Bangka Tengah',
|
|
||||||
'Bangka Barat',
|
|
||||||
'Belitung Timur',
|
|
||||||
'Kota Pangkal Pinang',
|
|
||||||
'Bintan',
|
|
||||||
'Karimun',
|
|
||||||
'Natuna',
|
|
||||||
'Lingga',
|
|
||||||
'Kepulauan Anambas',
|
|
||||||
'Kota Batam',
|
|
||||||
'Kota Tanjung Pinang',
|
|
||||||
'Kepulauan Seribu',
|
|
||||||
'Kota Jakarta Pusat',
|
|
||||||
'Kota Jakarta Utara',
|
|
||||||
'Kota Jakarta Barat',
|
|
||||||
'Kota Jakarta Selatan',
|
|
||||||
'Kota Jakarta Timur',
|
|
||||||
'Bogor',
|
|
||||||
'Sukabumi',
|
|
||||||
'Cianjur',
|
|
||||||
'Bandung',
|
|
||||||
'Garut',
|
|
||||||
'Tasikmalaya',
|
|
||||||
'Ciamis',
|
|
||||||
'Kuningan',
|
|
||||||
'Cirebon',
|
|
||||||
'Majalengka',
|
|
||||||
'Sumedang',
|
|
||||||
'Indramayu',
|
|
||||||
'Subang',
|
|
||||||
'Purwakarta',
|
|
||||||
'Karawang',
|
|
||||||
'Bekasi',
|
|
||||||
'Bandung Barat',
|
|
||||||
'Pangandaran',
|
|
||||||
'Kota Bogor',
|
|
||||||
'Kota Sukabumi',
|
|
||||||
'Kota Bandung',
|
|
||||||
'Kota Cirebon',
|
|
||||||
'Kota Bekasi',
|
|
||||||
'Kota Depok',
|
|
||||||
'Kota Cimahi',
|
|
||||||
'Kota Tasikmalaya',
|
|
||||||
'Kota Banjar',
|
|
||||||
'Cilacap',
|
|
||||||
'Banyumas',
|
|
||||||
'Purbalingga',
|
|
||||||
'Banjarnegara',
|
|
||||||
'Kebumen',
|
|
||||||
'Purworejo',
|
|
||||||
'Wonosobo',
|
|
||||||
'Magelang',
|
|
||||||
'Boyolali',
|
|
||||||
'Klaten',
|
|
||||||
'Sukoharjo',
|
|
||||||
'Wonogiri',
|
|
||||||
'Karanganyar',
|
|
||||||
'Sragen',
|
|
||||||
'Grobogan',
|
|
||||||
'Blora',
|
|
||||||
'Rembang',
|
|
||||||
'Pati',
|
|
||||||
'Kudus',
|
|
||||||
'Jepara',
|
|
||||||
'Demak',
|
|
||||||
'Semarang',
|
|
||||||
'Temanggung',
|
|
||||||
'Kendal',
|
|
||||||
'Batang',
|
|
||||||
'Pekalongan',
|
|
||||||
'Pemalang',
|
|
||||||
'Tegal',
|
|
||||||
'Brebes',
|
|
||||||
'Kota Magelang',
|
|
||||||
'Kota Surakarta',
|
|
||||||
'Kota Salatiga',
|
|
||||||
'Kota Semarang',
|
|
||||||
'Kota Pekalongan',
|
|
||||||
'Kota Tegal',
|
|
||||||
'Kulon Progo',
|
|
||||||
'Bantul',
|
|
||||||
'Gunungkidul',
|
|
||||||
'Sleman',
|
|
||||||
'Kota Yogyakarta',
|
|
||||||
'Pacitan',
|
|
||||||
'Ponorogo',
|
|
||||||
'Trenggalek',
|
|
||||||
'Tulungagung',
|
|
||||||
'Blitar',
|
|
||||||
'Kediri',
|
|
||||||
'Malang',
|
|
||||||
'Lumajang',
|
|
||||||
'Jember',
|
|
||||||
'Banyuwangi',
|
|
||||||
'Bondowoso',
|
|
||||||
'Situbondo',
|
|
||||||
'Probolinggo',
|
|
||||||
'Pasuruan',
|
|
||||||
'Sidoarjo',
|
|
||||||
'Mojokerto',
|
|
||||||
'Jombang',
|
|
||||||
'Nganjuk',
|
|
||||||
'Madiun',
|
|
||||||
'Magetan',
|
|
||||||
'Ngawi',
|
|
||||||
'Bojonegoro',
|
|
||||||
'Tuban',
|
|
||||||
'Lamongan',
|
|
||||||
'Gresik',
|
|
||||||
'Bangkalan',
|
|
||||||
'Sampang',
|
|
||||||
'Pamekasan',
|
|
||||||
'Sumenep',
|
|
||||||
'Kota Kediri',
|
|
||||||
'Kota Blitar',
|
|
||||||
'Kota Malang',
|
|
||||||
'Kota Probolinggo',
|
|
||||||
'Kota Pasuruan',
|
|
||||||
'Kota Mojokerto',
|
|
||||||
'Kota Madiun',
|
|
||||||
'Kota Surabaya',
|
|
||||||
'Kota Batu',
|
|
||||||
'Pandeglang',
|
|
||||||
'Lebak',
|
|
||||||
'Tangerang',
|
|
||||||
'Serang',
|
|
||||||
'Kota Tangerang',
|
|
||||||
'Kota Cilegon',
|
|
||||||
'Kota Serang',
|
|
||||||
'Kota Tangerang Selatan',
|
|
||||||
'Jembrana',
|
|
||||||
'Tabanan',
|
|
||||||
'Badung',
|
|
||||||
'Gianyar',
|
|
||||||
'Klungkung',
|
|
||||||
'Bangli',
|
|
||||||
'Karangasem',
|
|
||||||
'Buleleng',
|
|
||||||
'Kota Denpasar',
|
|
||||||
'Lombok Barat',
|
|
||||||
'Lombok Tengah',
|
|
||||||
'Lombok Timur',
|
|
||||||
'Sumbawa',
|
|
||||||
'Dompu',
|
|
||||||
'Bima',
|
|
||||||
'Sumbawa Barat',
|
|
||||||
'Lombok Utara',
|
|
||||||
'Kota Mataram',
|
|
||||||
'Kota Bima',
|
|
||||||
'Kupang',
|
|
||||||
'Timor Tengah Selatan',
|
|
||||||
'Timor Tengah Utara',
|
|
||||||
'Belu',
|
|
||||||
'Alor',
|
|
||||||
'Flores Timur',
|
|
||||||
'Sikka',
|
|
||||||
'Ende',
|
|
||||||
'Ngada',
|
|
||||||
'Manggarai',
|
|
||||||
'Sumba Timur',
|
|
||||||
'Sumba Barat',
|
|
||||||
'Lembata',
|
|
||||||
'Rote Ndao',
|
|
||||||
'Manggarai Barat',
|
|
||||||
'Nagekeo',
|
|
||||||
'Sumba Tengah',
|
|
||||||
'Sumba Barat Daya',
|
|
||||||
'Manggarai Timur',
|
|
||||||
'Sabu Raijua',
|
|
||||||
'Malaka',
|
|
||||||
'Kota Kupang',
|
|
||||||
'Sambas',
|
|
||||||
'Mempawah',
|
|
||||||
'Sanggau',
|
|
||||||
'Ketapang',
|
|
||||||
'Sintang',
|
|
||||||
'Kapuas Hulu',
|
|
||||||
'Bengkayang',
|
|
||||||
'Landak',
|
|
||||||
'Sekadau',
|
|
||||||
'Melawi',
|
|
||||||
'Kayong Utara',
|
|
||||||
'Kubu Raya',
|
|
||||||
'Kota Pontianak',
|
|
||||||
'Kota Singkawang',
|
|
||||||
'Kotawaringin Barat',
|
|
||||||
'Kotawaringin Timur',
|
|
||||||
'Kapuas',
|
|
||||||
'Barito Selatan',
|
|
||||||
'Barito Utara',
|
|
||||||
'Katingan',
|
|
||||||
'Seruyan',
|
|
||||||
'Sukamara',
|
|
||||||
'Lamandau',
|
|
||||||
'Gunung Mas',
|
|
||||||
'Pulang Pisau',
|
|
||||||
'Murung Raya',
|
|
||||||
'Barito Timur',
|
|
||||||
'Kota Palangkaraya',
|
|
||||||
'Tanah Laut',
|
|
||||||
'Kotabaru',
|
|
||||||
'Banjar',
|
|
||||||
'Barito Kuala',
|
|
||||||
'Tapin',
|
|
||||||
'Hulu Sungai Selatan',
|
|
||||||
'Hulu Sungai Tengah',
|
|
||||||
'Hulu Sungai Utara',
|
|
||||||
'Tabalong',
|
|
||||||
'Tanah Bumbu',
|
|
||||||
'Balangan',
|
|
||||||
'Kota Banjarmasin',
|
|
||||||
'Kota Banjarbaru',
|
|
||||||
'Paser',
|
|
||||||
'Kutai Kartanegara',
|
|
||||||
'Berau',
|
|
||||||
'Kutai Barat',
|
|
||||||
'Kutai Timur',
|
|
||||||
'Penajam Paser Utara',
|
|
||||||
'Mahakam Ulu',
|
|
||||||
'Kota Balikpapan',
|
|
||||||
'Kota Samarinda',
|
|
||||||
'Kota Bontang',
|
|
||||||
'Bulungan',
|
|
||||||
'Malinau',
|
|
||||||
'Nunukan',
|
|
||||||
'Tana Tidung',
|
|
||||||
'Kota Tarakan',
|
|
||||||
'Bolaang Mongondow',
|
|
||||||
'Minahasa',
|
|
||||||
'Kepulauan Sangihe',
|
|
||||||
'Kepulauan Talaud',
|
|
||||||
'Minahasa Selatan',
|
|
||||||
'Minahasa Utara',
|
|
||||||
'Minahasa Tenggara',
|
|
||||||
'Bolaang Mongondow Utara',
|
|
||||||
'Kepulauan Siau Tagulandang Biaro (Sitaro)',
|
|
||||||
'Bolaang Mongondow Timur',
|
|
||||||
'Bolaang Mongondow Selatan',
|
|
||||||
'Kota Manado',
|
|
||||||
'Kota Bitung',
|
|
||||||
'Kota Tomohon',
|
|
||||||
'Kota Kotamobagu',
|
|
||||||
'Banggai',
|
|
||||||
'Poso',
|
|
||||||
'Donggala',
|
|
||||||
'Toli Toli',
|
|
||||||
'Buol',
|
|
||||||
'Morowali',
|
|
||||||
'Banggai Kepulauan',
|
|
||||||
'Parigi Moutong',
|
|
||||||
'Tojo Una Una',
|
|
||||||
'Sigi',
|
|
||||||
'Banggai Laut',
|
|
||||||
'Morowali Utara',
|
|
||||||
'Kota Palu',
|
|
||||||
'Kepulauan Selayar',
|
|
||||||
'Bulukumba',
|
|
||||||
'Bantaeng',
|
|
||||||
'Jeneponto',
|
|
||||||
'Takalar',
|
|
||||||
'Gowa',
|
|
||||||
'Sinjai',
|
|
||||||
'Bone',
|
|
||||||
'Maros',
|
|
||||||
'Pangkajene Kepulauan',
|
|
||||||
'Barru',
|
|
||||||
'Soppeng',
|
|
||||||
'Wajo',
|
|
||||||
'Sidenreng Rappang',
|
|
||||||
'Pinrang',
|
|
||||||
'Enrekang',
|
|
||||||
'Luwu',
|
|
||||||
'Tana Toraja',
|
|
||||||
'Luwu Utara',
|
|
||||||
'Luwu Timur',
|
|
||||||
'Toraja Utara',
|
|
||||||
'Kota Makassar',
|
|
||||||
'Kota Pare Pare',
|
|
||||||
'Kota Palopo',
|
|
||||||
'Kolaka',
|
|
||||||
'Konawe',
|
|
||||||
'Muna',
|
|
||||||
'Buton',
|
|
||||||
'Konawe Selatan',
|
|
||||||
'Bombana',
|
|
||||||
'Wakatobi',
|
|
||||||
'Kolaka Utara',
|
|
||||||
'Konawe Utara',
|
|
||||||
'Buton Utara',
|
|
||||||
'Kolaka Timur',
|
|
||||||
'Konawe Kepulauan',
|
|
||||||
'Muna Barat',
|
|
||||||
'Buton Tengah',
|
|
||||||
'Buton Selatan',
|
|
||||||
'Kota Kendari',
|
|
||||||
'Kota Bau Bau',
|
|
||||||
'Gorontalo',
|
|
||||||
'Boalemo',
|
|
||||||
'Bone Bolango',
|
|
||||||
'Pahuwato',
|
|
||||||
'Gorontalo Utara',
|
|
||||||
'Kota Gorontalo',
|
|
||||||
'Pasangkayu (Mamuju Utara)',
|
|
||||||
'Mamuju',
|
|
||||||
'Mamasa',
|
|
||||||
'Polewali Mandar',
|
|
||||||
'Majene',
|
|
||||||
'Mamuju Tengah',
|
|
||||||
'Maluku Tengah',
|
|
||||||
'Maluku Tenggara',
|
|
||||||
'Kepulauan Tanimbar (Maluku Tenggara Barat)',
|
|
||||||
'Buru',
|
|
||||||
'Seram Bagian Timur',
|
|
||||||
'Seram Bagian Barat',
|
|
||||||
'Kepulauan Aru',
|
|
||||||
'Maluku Barat Daya',
|
|
||||||
'Buru Selatan',
|
|
||||||
'Kota Ambon',
|
|
||||||
'Kota Tual',
|
|
||||||
'Halmahera Barat',
|
|
||||||
'Halmahera Tengah',
|
|
||||||
'Halmahera Utara',
|
|
||||||
'Halmahera Selatan',
|
|
||||||
'Kepulauan Sula',
|
|
||||||
'Halmahera Timur',
|
|
||||||
'Pulau Morotai',
|
|
||||||
'Pulau Taliabu',
|
|
||||||
'Kota Ternate',
|
|
||||||
'Kota Tidore Kepulauan',
|
|
||||||
'Jayapura',
|
|
||||||
'Kepulauan Yapen',
|
|
||||||
'Biak Numfor',
|
|
||||||
'Sarmi',
|
|
||||||
'Keerom',
|
|
||||||
'Waropen',
|
|
||||||
'Supiori',
|
|
||||||
'Mamberamo Raya',
|
|
||||||
'Kota Jayapura',
|
|
||||||
'Manokwari',
|
|
||||||
'Fak Fak',
|
|
||||||
'Teluk Bintuni',
|
|
||||||
'Teluk Wondama',
|
|
||||||
'Kaimana',
|
|
||||||
'Manokwari Selatan',
|
|
||||||
'Pegunungan Arfak',
|
|
||||||
'Merauke',
|
|
||||||
'Boven Digoel',
|
|
||||||
'Mappi',
|
|
||||||
'Asmat',
|
|
||||||
'Nabire',
|
|
||||||
'Puncak Jaya',
|
|
||||||
'Paniai',
|
|
||||||
'Mimika',
|
|
||||||
'Puncak',
|
|
||||||
'Dogiyai',
|
|
||||||
'Intan Jaya',
|
|
||||||
'Deiyai',
|
|
||||||
'Jayawijaya',
|
|
||||||
'Pegunungan Bintang',
|
|
||||||
'Yahukimo',
|
|
||||||
'Tolikara',
|
|
||||||
'Mamberamo Tengah',
|
|
||||||
'Yalimo',
|
|
||||||
'Lanny Jaya',
|
|
||||||
'Nduga',
|
|
||||||
'Sorong',
|
|
||||||
'Sorong Selatan',
|
|
||||||
'Raja Ampat',
|
|
||||||
'Tambrauw',
|
|
||||||
'Maybrat',
|
|
||||||
'Kota Sorong',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default INDONESIAN_CITIES;
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
html {
|
html {
|
||||||
font-family: 'Bai Jamjuree', sans-serif;
|
font-family: 'Bai Jamjuree', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,42 @@
|
|||||||
import { StrictMode } from 'react'
|
import { createRoot } from 'react-dom/client';
|
||||||
import { createRoot } from 'react-dom/client'
|
import { middleware } from './middleware';
|
||||||
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
import { StrictMode } from 'react';
|
||||||
import { QueryProvider } from '@imphnen-frontend-service/utils'
|
import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router';
|
||||||
import { Toaster } from 'sonner'
|
import {
|
||||||
import { routeTree } from './routeTree.gen'
|
add404PageToRoutesChildren,
|
||||||
import './index.css'
|
addErrorElementToRoutes,
|
||||||
|
convertPagesToRoute,
|
||||||
|
QueryProvider,
|
||||||
|
} from '@imphnen-frontend-service/utils';
|
||||||
|
import { Toaster } from 'sonner';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
const router = createRouter({ routeTree })
|
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
||||||
|
const errorFiles = import.meta.glob('./app/**/*error.tsx');
|
||||||
|
const notFoundFiles = import.meta.glob('./app/**/*404.tsx');
|
||||||
|
const loadingFiles = import.meta.glob('./app/**/*loading.tsx');
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
const routes = convertPagesToRoute(files, loadingFiles) as RouteObject;
|
||||||
interface Register {
|
addErrorElementToRoutes(errorFiles, routes);
|
||||||
router: typeof router
|
add404PageToRoutesChildren(notFoundFiles, routes);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootElement = document.getElementById('root')
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
...routes,
|
||||||
|
loader: middleware,
|
||||||
|
shouldRevalidate: () => true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
if (!rootElement) throw new Error('Failed to find the root element')
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
|
if (!rootElement) throw new Error('Failed to find the root element');
|
||||||
|
|
||||||
createRoot(rootElement).render(
|
createRoot(rootElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
<RouterProvider router={router} />
|
|
||||||
<Toaster position="top-right" />
|
<Toaster position="top-right" />
|
||||||
|
<RouterProvider router={router} />
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
)
|
);
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import {
|
||||||
|
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) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
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 (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;
|
||||||
|
};
|
||||||
@@ -1,923 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
|
|
||||||
// This file was automatically generated by TanStack Router.
|
|
||||||
// You should NOT make any changes in this file as it will be overwritten.
|
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
|
||||||
import { Route as PublicRouteImport } from './routes/_public'
|
|
||||||
import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
|
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
|
||||||
import { Route as AuthenticatedUsersDimentorinRouteImport } from './routes/_authenticated/users-dimentorin'
|
|
||||||
import { Route as AuthenticatedTransactionsRouteImport } from './routes/_authenticated/transactions'
|
|
||||||
import { Route as AuthenticatedSettingsDimentorinRouteImport } from './routes/_authenticated/settings-dimentorin'
|
|
||||||
import { Route as AuthenticatedSessionDimentorinRouteImport } from './routes/_authenticated/session-dimentorin'
|
|
||||||
import { Route as AuthenticatedRolesRouteImport } from './routes/_authenticated/roles'
|
|
||||||
import { Route as AuthenticatedRoadmapDimentorinRouteImport } from './routes/_authenticated/roadmap-dimentorin'
|
|
||||||
import { Route as AuthenticatedPrizesRouteImport } from './routes/_authenticated/prizes'
|
|
||||||
import { Route as AuthenticatedPermissionsRouteImport } from './routes/_authenticated/permissions'
|
|
||||||
import { Route as AuthenticatedHackathonUsersRouteImport } from './routes/_authenticated/hackathon-users'
|
|
||||||
import { Route as AuthenticatedHackathonTeamsRouteImport } from './routes/_authenticated/hackathon-teams'
|
|
||||||
import { Route as AuthenticatedHackathonSubmissionsRouteImport } from './routes/_authenticated/hackathon-submissions'
|
|
||||||
import { Route as AuthenticatedHackathonDashboardRouteImport } from './routes/_authenticated/hackathon-dashboard'
|
|
||||||
import { Route as AuthenticatedGachaRollRouteImport } from './routes/_authenticated/gacha-roll'
|
|
||||||
import { Route as AuthenticatedFeedbackReviewDimentorinRouteImport } from './routes/_authenticated/feedback-review-dimentorin'
|
|
||||||
import { Route as AuthenticatedDashboardDimentorinRouteImport } from './routes/_authenticated/dashboard-dimentorin'
|
|
||||||
import { Route as AuthenticatedDashboardRouteImport } from './routes/_authenticated/dashboard'
|
|
||||||
import { Route as AuthenticatedCmsTestimonialsRouteImport } from './routes/_authenticated/cms-testimonials'
|
|
||||||
import { Route as AuthenticatedCmsEventsRouteImport } from './routes/_authenticated/cms-events'
|
|
||||||
import { Route as AuthenticatedAccountsRouteImport } from './routes/_authenticated/accounts'
|
|
||||||
import { Route as PublicAuthLoginRouteImport } from './routes/_public/auth/login'
|
|
||||||
import { Route as AuthenticatedUsersDimentorinIdRouteImport } from './routes/_authenticated/users-dimentorin_/$id'
|
|
||||||
import { Route as AuthenticatedSessionDimentorinIdRouteImport } from './routes/_authenticated/session-dimentorin_/$id'
|
|
||||||
import { Route as AuthenticatedRolesCreateRouteImport } from './routes/_authenticated/roles_/create'
|
|
||||||
import { Route as AuthenticatedRolesIdRouteImport } from './routes/_authenticated/roles_/$id'
|
|
||||||
import { Route as AuthenticatedRoadmapDimentorinCreateRouteImport } from './routes/_authenticated/roadmap-dimentorin_/create'
|
|
||||||
import { Route as AuthenticatedRoadmapDimentorinIdRouteImport } from './routes/_authenticated/roadmap-dimentorin_/$id'
|
|
||||||
import { Route as AuthenticatedPermissionsCreateRouteImport } from './routes/_authenticated/permissions_/create'
|
|
||||||
import { Route as AuthenticatedPermissionsIdRouteImport } from './routes/_authenticated/permissions_/$id'
|
|
||||||
import { Route as AuthenticatedGachaRollCreateRouteImport } from './routes/_authenticated/gacha-roll_/create'
|
|
||||||
import { Route as AuthenticatedGachaRollIdRouteImport } from './routes/_authenticated/gacha-roll_/$id'
|
|
||||||
import { Route as AuthenticatedDashboardCreateRouteImport } from './routes/_authenticated/dashboard_/create'
|
|
||||||
import { Route as AuthenticatedDashboardIdRouteImport } from './routes/_authenticated/dashboard_/$id'
|
|
||||||
import { Route as AuthenticatedCmsTestimonialsCreateRouteImport } from './routes/_authenticated/cms-testimonials_/create'
|
|
||||||
import { Route as AuthenticatedCmsTestimonialsIdRouteImport } from './routes/_authenticated/cms-testimonials_/$id'
|
|
||||||
import { Route as AuthenticatedCmsEventsCreateRouteImport } from './routes/_authenticated/cms-events_/create'
|
|
||||||
import { Route as AuthenticatedCmsEventsIdRouteImport } from './routes/_authenticated/cms-events_/$id'
|
|
||||||
import { Route as AuthenticatedAccountsIdRouteImport } from './routes/_authenticated/accounts_/$id'
|
|
||||||
|
|
||||||
const PublicRoute = PublicRouteImport.update({
|
|
||||||
id: '/_public',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRoute = AuthenticatedRouteImport.update({
|
|
||||||
id: '/_authenticated',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const IndexRoute = IndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedUsersDimentorinRoute =
|
|
||||||
AuthenticatedUsersDimentorinRouteImport.update({
|
|
||||||
id: '/users-dimentorin',
|
|
||||||
path: '/users-dimentorin',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedTransactionsRoute =
|
|
||||||
AuthenticatedTransactionsRouteImport.update({
|
|
||||||
id: '/transactions',
|
|
||||||
path: '/transactions',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedSettingsDimentorinRoute =
|
|
||||||
AuthenticatedSettingsDimentorinRouteImport.update({
|
|
||||||
id: '/settings-dimentorin',
|
|
||||||
path: '/settings-dimentorin',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedSessionDimentorinRoute =
|
|
||||||
AuthenticatedSessionDimentorinRouteImport.update({
|
|
||||||
id: '/session-dimentorin',
|
|
||||||
path: '/session-dimentorin',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRolesRoute = AuthenticatedRolesRouteImport.update({
|
|
||||||
id: '/roles',
|
|
||||||
path: '/roles',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRoadmapDimentorinRoute =
|
|
||||||
AuthenticatedRoadmapDimentorinRouteImport.update({
|
|
||||||
id: '/roadmap-dimentorin',
|
|
||||||
path: '/roadmap-dimentorin',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedPrizesRoute = AuthenticatedPrizesRouteImport.update({
|
|
||||||
id: '/prizes',
|
|
||||||
path: '/prizes',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedPermissionsRoute =
|
|
||||||
AuthenticatedPermissionsRouteImport.update({
|
|
||||||
id: '/permissions',
|
|
||||||
path: '/permissions',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedHackathonUsersRoute =
|
|
||||||
AuthenticatedHackathonUsersRouteImport.update({
|
|
||||||
id: '/hackathon-users',
|
|
||||||
path: '/hackathon-users',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedHackathonTeamsRoute =
|
|
||||||
AuthenticatedHackathonTeamsRouteImport.update({
|
|
||||||
id: '/hackathon-teams',
|
|
||||||
path: '/hackathon-teams',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedHackathonSubmissionsRoute =
|
|
||||||
AuthenticatedHackathonSubmissionsRouteImport.update({
|
|
||||||
id: '/hackathon-submissions',
|
|
||||||
path: '/hackathon-submissions',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedHackathonDashboardRoute =
|
|
||||||
AuthenticatedHackathonDashboardRouteImport.update({
|
|
||||||
id: '/hackathon-dashboard',
|
|
||||||
path: '/hackathon-dashboard',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedGachaRollRoute = AuthenticatedGachaRollRouteImport.update({
|
|
||||||
id: '/gacha-roll',
|
|
||||||
path: '/gacha-roll',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedFeedbackReviewDimentorinRoute =
|
|
||||||
AuthenticatedFeedbackReviewDimentorinRouteImport.update({
|
|
||||||
id: '/feedback-review-dimentorin',
|
|
||||||
path: '/feedback-review-dimentorin',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedDashboardDimentorinRoute =
|
|
||||||
AuthenticatedDashboardDimentorinRouteImport.update({
|
|
||||||
id: '/dashboard-dimentorin',
|
|
||||||
path: '/dashboard-dimentorin',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedDashboardRoute = AuthenticatedDashboardRouteImport.update({
|
|
||||||
id: '/dashboard',
|
|
||||||
path: '/dashboard',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedCmsTestimonialsRoute =
|
|
||||||
AuthenticatedCmsTestimonialsRouteImport.update({
|
|
||||||
id: '/cms-testimonials',
|
|
||||||
path: '/cms-testimonials',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedCmsEventsRoute = AuthenticatedCmsEventsRouteImport.update({
|
|
||||||
id: '/cms-events',
|
|
||||||
path: '/cms-events',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsRoute = AuthenticatedAccountsRouteImport.update({
|
|
||||||
id: '/accounts',
|
|
||||||
path: '/accounts',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const PublicAuthLoginRoute = PublicAuthLoginRouteImport.update({
|
|
||||||
id: '/auth/login',
|
|
||||||
path: '/auth/login',
|
|
||||||
getParentRoute: () => PublicRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedUsersDimentorinIdRoute =
|
|
||||||
AuthenticatedUsersDimentorinIdRouteImport.update({
|
|
||||||
id: '/users-dimentorin_/$id',
|
|
||||||
path: '/users-dimentorin/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedSessionDimentorinIdRoute =
|
|
||||||
AuthenticatedSessionDimentorinIdRouteImport.update({
|
|
||||||
id: '/session-dimentorin_/$id',
|
|
||||||
path: '/session-dimentorin/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRolesCreateRoute =
|
|
||||||
AuthenticatedRolesCreateRouteImport.update({
|
|
||||||
id: '/roles_/create',
|
|
||||||
path: '/roles/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRolesIdRoute = AuthenticatedRolesIdRouteImport.update({
|
|
||||||
id: '/roles_/$id',
|
|
||||||
path: '/roles/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRoadmapDimentorinCreateRoute =
|
|
||||||
AuthenticatedRoadmapDimentorinCreateRouteImport.update({
|
|
||||||
id: '/roadmap-dimentorin_/create',
|
|
||||||
path: '/roadmap-dimentorin/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRoadmapDimentorinIdRoute =
|
|
||||||
AuthenticatedRoadmapDimentorinIdRouteImport.update({
|
|
||||||
id: '/roadmap-dimentorin_/$id',
|
|
||||||
path: '/roadmap-dimentorin/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedPermissionsCreateRoute =
|
|
||||||
AuthenticatedPermissionsCreateRouteImport.update({
|
|
||||||
id: '/permissions_/create',
|
|
||||||
path: '/permissions/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedPermissionsIdRoute =
|
|
||||||
AuthenticatedPermissionsIdRouteImport.update({
|
|
||||||
id: '/permissions_/$id',
|
|
||||||
path: '/permissions/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedGachaRollCreateRoute =
|
|
||||||
AuthenticatedGachaRollCreateRouteImport.update({
|
|
||||||
id: '/gacha-roll_/create',
|
|
||||||
path: '/gacha-roll/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedGachaRollIdRoute =
|
|
||||||
AuthenticatedGachaRollIdRouteImport.update({
|
|
||||||
id: '/gacha-roll_/$id',
|
|
||||||
path: '/gacha-roll/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedDashboardCreateRoute =
|
|
||||||
AuthenticatedDashboardCreateRouteImport.update({
|
|
||||||
id: '/dashboard_/create',
|
|
||||||
path: '/dashboard/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedDashboardIdRoute =
|
|
||||||
AuthenticatedDashboardIdRouteImport.update({
|
|
||||||
id: '/dashboard_/$id',
|
|
||||||
path: '/dashboard/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedCmsTestimonialsCreateRoute =
|
|
||||||
AuthenticatedCmsTestimonialsCreateRouteImport.update({
|
|
||||||
id: '/cms-testimonials_/create',
|
|
||||||
path: '/cms-testimonials/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedCmsTestimonialsIdRoute =
|
|
||||||
AuthenticatedCmsTestimonialsIdRouteImport.update({
|
|
||||||
id: '/cms-testimonials_/$id',
|
|
||||||
path: '/cms-testimonials/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedCmsEventsCreateRoute =
|
|
||||||
AuthenticatedCmsEventsCreateRouteImport.update({
|
|
||||||
id: '/cms-events_/create',
|
|
||||||
path: '/cms-events/create',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedCmsEventsIdRoute =
|
|
||||||
AuthenticatedCmsEventsIdRouteImport.update({
|
|
||||||
id: '/cms-events_/$id',
|
|
||||||
path: '/cms-events/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsIdRoute = AuthenticatedAccountsIdRouteImport.update({
|
|
||||||
id: '/accounts_/$id',
|
|
||||||
path: '/accounts/$id',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
'/accounts': typeof AuthenticatedAccountsRoute
|
|
||||||
'/cms-events': typeof AuthenticatedCmsEventsRoute
|
|
||||||
'/cms-testimonials': typeof AuthenticatedCmsTestimonialsRoute
|
|
||||||
'/dashboard': typeof AuthenticatedDashboardRoute
|
|
||||||
'/dashboard-dimentorin': typeof AuthenticatedDashboardDimentorinRoute
|
|
||||||
'/feedback-review-dimentorin': typeof AuthenticatedFeedbackReviewDimentorinRoute
|
|
||||||
'/gacha-roll': typeof AuthenticatedGachaRollRoute
|
|
||||||
'/hackathon-dashboard': typeof AuthenticatedHackathonDashboardRoute
|
|
||||||
'/hackathon-submissions': typeof AuthenticatedHackathonSubmissionsRoute
|
|
||||||
'/hackathon-teams': typeof AuthenticatedHackathonTeamsRoute
|
|
||||||
'/hackathon-users': typeof AuthenticatedHackathonUsersRoute
|
|
||||||
'/permissions': typeof AuthenticatedPermissionsRoute
|
|
||||||
'/prizes': typeof AuthenticatedPrizesRoute
|
|
||||||
'/roadmap-dimentorin': typeof AuthenticatedRoadmapDimentorinRoute
|
|
||||||
'/roles': typeof AuthenticatedRolesRoute
|
|
||||||
'/session-dimentorin': typeof AuthenticatedSessionDimentorinRoute
|
|
||||||
'/settings-dimentorin': typeof AuthenticatedSettingsDimentorinRoute
|
|
||||||
'/transactions': typeof AuthenticatedTransactionsRoute
|
|
||||||
'/users-dimentorin': typeof AuthenticatedUsersDimentorinRoute
|
|
||||||
'/accounts/$id': typeof AuthenticatedAccountsIdRoute
|
|
||||||
'/cms-events/$id': typeof AuthenticatedCmsEventsIdRoute
|
|
||||||
'/cms-events/create': typeof AuthenticatedCmsEventsCreateRoute
|
|
||||||
'/cms-testimonials/$id': typeof AuthenticatedCmsTestimonialsIdRoute
|
|
||||||
'/cms-testimonials/create': typeof AuthenticatedCmsTestimonialsCreateRoute
|
|
||||||
'/dashboard/$id': typeof AuthenticatedDashboardIdRoute
|
|
||||||
'/dashboard/create': typeof AuthenticatedDashboardCreateRoute
|
|
||||||
'/gacha-roll/$id': typeof AuthenticatedGachaRollIdRoute
|
|
||||||
'/gacha-roll/create': typeof AuthenticatedGachaRollCreateRoute
|
|
||||||
'/permissions/$id': typeof AuthenticatedPermissionsIdRoute
|
|
||||||
'/permissions/create': typeof AuthenticatedPermissionsCreateRoute
|
|
||||||
'/roadmap-dimentorin/$id': typeof AuthenticatedRoadmapDimentorinIdRoute
|
|
||||||
'/roadmap-dimentorin/create': typeof AuthenticatedRoadmapDimentorinCreateRoute
|
|
||||||
'/roles/$id': typeof AuthenticatedRolesIdRoute
|
|
||||||
'/roles/create': typeof AuthenticatedRolesCreateRoute
|
|
||||||
'/session-dimentorin/$id': typeof AuthenticatedSessionDimentorinIdRoute
|
|
||||||
'/users-dimentorin/$id': typeof AuthenticatedUsersDimentorinIdRoute
|
|
||||||
'/auth/login': typeof PublicAuthLoginRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesByTo {
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
'/accounts': typeof AuthenticatedAccountsRoute
|
|
||||||
'/cms-events': typeof AuthenticatedCmsEventsRoute
|
|
||||||
'/cms-testimonials': typeof AuthenticatedCmsTestimonialsRoute
|
|
||||||
'/dashboard': typeof AuthenticatedDashboardRoute
|
|
||||||
'/dashboard-dimentorin': typeof AuthenticatedDashboardDimentorinRoute
|
|
||||||
'/feedback-review-dimentorin': typeof AuthenticatedFeedbackReviewDimentorinRoute
|
|
||||||
'/gacha-roll': typeof AuthenticatedGachaRollRoute
|
|
||||||
'/hackathon-dashboard': typeof AuthenticatedHackathonDashboardRoute
|
|
||||||
'/hackathon-submissions': typeof AuthenticatedHackathonSubmissionsRoute
|
|
||||||
'/hackathon-teams': typeof AuthenticatedHackathonTeamsRoute
|
|
||||||
'/hackathon-users': typeof AuthenticatedHackathonUsersRoute
|
|
||||||
'/permissions': typeof AuthenticatedPermissionsRoute
|
|
||||||
'/prizes': typeof AuthenticatedPrizesRoute
|
|
||||||
'/roadmap-dimentorin': typeof AuthenticatedRoadmapDimentorinRoute
|
|
||||||
'/roles': typeof AuthenticatedRolesRoute
|
|
||||||
'/session-dimentorin': typeof AuthenticatedSessionDimentorinRoute
|
|
||||||
'/settings-dimentorin': typeof AuthenticatedSettingsDimentorinRoute
|
|
||||||
'/transactions': typeof AuthenticatedTransactionsRoute
|
|
||||||
'/users-dimentorin': typeof AuthenticatedUsersDimentorinRoute
|
|
||||||
'/accounts/$id': typeof AuthenticatedAccountsIdRoute
|
|
||||||
'/cms-events/$id': typeof AuthenticatedCmsEventsIdRoute
|
|
||||||
'/cms-events/create': typeof AuthenticatedCmsEventsCreateRoute
|
|
||||||
'/cms-testimonials/$id': typeof AuthenticatedCmsTestimonialsIdRoute
|
|
||||||
'/cms-testimonials/create': typeof AuthenticatedCmsTestimonialsCreateRoute
|
|
||||||
'/dashboard/$id': typeof AuthenticatedDashboardIdRoute
|
|
||||||
'/dashboard/create': typeof AuthenticatedDashboardCreateRoute
|
|
||||||
'/gacha-roll/$id': typeof AuthenticatedGachaRollIdRoute
|
|
||||||
'/gacha-roll/create': typeof AuthenticatedGachaRollCreateRoute
|
|
||||||
'/permissions/$id': typeof AuthenticatedPermissionsIdRoute
|
|
||||||
'/permissions/create': typeof AuthenticatedPermissionsCreateRoute
|
|
||||||
'/roadmap-dimentorin/$id': typeof AuthenticatedRoadmapDimentorinIdRoute
|
|
||||||
'/roadmap-dimentorin/create': typeof AuthenticatedRoadmapDimentorinCreateRoute
|
|
||||||
'/roles/$id': typeof AuthenticatedRolesIdRoute
|
|
||||||
'/roles/create': typeof AuthenticatedRolesCreateRoute
|
|
||||||
'/session-dimentorin/$id': typeof AuthenticatedSessionDimentorinIdRoute
|
|
||||||
'/users-dimentorin/$id': typeof AuthenticatedUsersDimentorinIdRoute
|
|
||||||
'/auth/login': typeof PublicAuthLoginRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesById {
|
|
||||||
__root__: typeof rootRouteImport
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
'/_authenticated': typeof AuthenticatedRouteWithChildren
|
|
||||||
'/_public': typeof PublicRouteWithChildren
|
|
||||||
'/_authenticated/accounts': typeof AuthenticatedAccountsRoute
|
|
||||||
'/_authenticated/cms-events': typeof AuthenticatedCmsEventsRoute
|
|
||||||
'/_authenticated/cms-testimonials': typeof AuthenticatedCmsTestimonialsRoute
|
|
||||||
'/_authenticated/dashboard': typeof AuthenticatedDashboardRoute
|
|
||||||
'/_authenticated/dashboard-dimentorin': typeof AuthenticatedDashboardDimentorinRoute
|
|
||||||
'/_authenticated/feedback-review-dimentorin': typeof AuthenticatedFeedbackReviewDimentorinRoute
|
|
||||||
'/_authenticated/gacha-roll': typeof AuthenticatedGachaRollRoute
|
|
||||||
'/_authenticated/hackathon-dashboard': typeof AuthenticatedHackathonDashboardRoute
|
|
||||||
'/_authenticated/hackathon-submissions': typeof AuthenticatedHackathonSubmissionsRoute
|
|
||||||
'/_authenticated/hackathon-teams': typeof AuthenticatedHackathonTeamsRoute
|
|
||||||
'/_authenticated/hackathon-users': typeof AuthenticatedHackathonUsersRoute
|
|
||||||
'/_authenticated/permissions': typeof AuthenticatedPermissionsRoute
|
|
||||||
'/_authenticated/prizes': typeof AuthenticatedPrizesRoute
|
|
||||||
'/_authenticated/roadmap-dimentorin': typeof AuthenticatedRoadmapDimentorinRoute
|
|
||||||
'/_authenticated/roles': typeof AuthenticatedRolesRoute
|
|
||||||
'/_authenticated/session-dimentorin': typeof AuthenticatedSessionDimentorinRoute
|
|
||||||
'/_authenticated/settings-dimentorin': typeof AuthenticatedSettingsDimentorinRoute
|
|
||||||
'/_authenticated/transactions': typeof AuthenticatedTransactionsRoute
|
|
||||||
'/_authenticated/users-dimentorin': typeof AuthenticatedUsersDimentorinRoute
|
|
||||||
'/_authenticated/accounts_/$id': typeof AuthenticatedAccountsIdRoute
|
|
||||||
'/_authenticated/cms-events_/$id': typeof AuthenticatedCmsEventsIdRoute
|
|
||||||
'/_authenticated/cms-events_/create': typeof AuthenticatedCmsEventsCreateRoute
|
|
||||||
'/_authenticated/cms-testimonials_/$id': typeof AuthenticatedCmsTestimonialsIdRoute
|
|
||||||
'/_authenticated/cms-testimonials_/create': typeof AuthenticatedCmsTestimonialsCreateRoute
|
|
||||||
'/_authenticated/dashboard_/$id': typeof AuthenticatedDashboardIdRoute
|
|
||||||
'/_authenticated/dashboard_/create': typeof AuthenticatedDashboardCreateRoute
|
|
||||||
'/_authenticated/gacha-roll_/$id': typeof AuthenticatedGachaRollIdRoute
|
|
||||||
'/_authenticated/gacha-roll_/create': typeof AuthenticatedGachaRollCreateRoute
|
|
||||||
'/_authenticated/permissions_/$id': typeof AuthenticatedPermissionsIdRoute
|
|
||||||
'/_authenticated/permissions_/create': typeof AuthenticatedPermissionsCreateRoute
|
|
||||||
'/_authenticated/roadmap-dimentorin_/$id': typeof AuthenticatedRoadmapDimentorinIdRoute
|
|
||||||
'/_authenticated/roadmap-dimentorin_/create': typeof AuthenticatedRoadmapDimentorinCreateRoute
|
|
||||||
'/_authenticated/roles_/$id': typeof AuthenticatedRolesIdRoute
|
|
||||||
'/_authenticated/roles_/create': typeof AuthenticatedRolesCreateRoute
|
|
||||||
'/_authenticated/session-dimentorin_/$id': typeof AuthenticatedSessionDimentorinIdRoute
|
|
||||||
'/_authenticated/users-dimentorin_/$id': typeof AuthenticatedUsersDimentorinIdRoute
|
|
||||||
'/_public/auth/login': typeof PublicAuthLoginRoute
|
|
||||||
}
|
|
||||||
export interface FileRouteTypes {
|
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
|
||||||
fullPaths:
|
|
||||||
| '/'
|
|
||||||
| '/accounts'
|
|
||||||
| '/cms-events'
|
|
||||||
| '/cms-testimonials'
|
|
||||||
| '/dashboard'
|
|
||||||
| '/dashboard-dimentorin'
|
|
||||||
| '/feedback-review-dimentorin'
|
|
||||||
| '/gacha-roll'
|
|
||||||
| '/hackathon-dashboard'
|
|
||||||
| '/hackathon-submissions'
|
|
||||||
| '/hackathon-teams'
|
|
||||||
| '/hackathon-users'
|
|
||||||
| '/permissions'
|
|
||||||
| '/prizes'
|
|
||||||
| '/roadmap-dimentorin'
|
|
||||||
| '/roles'
|
|
||||||
| '/session-dimentorin'
|
|
||||||
| '/settings-dimentorin'
|
|
||||||
| '/transactions'
|
|
||||||
| '/users-dimentorin'
|
|
||||||
| '/accounts/$id'
|
|
||||||
| '/cms-events/$id'
|
|
||||||
| '/cms-events/create'
|
|
||||||
| '/cms-testimonials/$id'
|
|
||||||
| '/cms-testimonials/create'
|
|
||||||
| '/dashboard/$id'
|
|
||||||
| '/dashboard/create'
|
|
||||||
| '/gacha-roll/$id'
|
|
||||||
| '/gacha-roll/create'
|
|
||||||
| '/permissions/$id'
|
|
||||||
| '/permissions/create'
|
|
||||||
| '/roadmap-dimentorin/$id'
|
|
||||||
| '/roadmap-dimentorin/create'
|
|
||||||
| '/roles/$id'
|
|
||||||
| '/roles/create'
|
|
||||||
| '/session-dimentorin/$id'
|
|
||||||
| '/users-dimentorin/$id'
|
|
||||||
| '/auth/login'
|
|
||||||
fileRoutesByTo: FileRoutesByTo
|
|
||||||
to:
|
|
||||||
| '/'
|
|
||||||
| '/accounts'
|
|
||||||
| '/cms-events'
|
|
||||||
| '/cms-testimonials'
|
|
||||||
| '/dashboard'
|
|
||||||
| '/dashboard-dimentorin'
|
|
||||||
| '/feedback-review-dimentorin'
|
|
||||||
| '/gacha-roll'
|
|
||||||
| '/hackathon-dashboard'
|
|
||||||
| '/hackathon-submissions'
|
|
||||||
| '/hackathon-teams'
|
|
||||||
| '/hackathon-users'
|
|
||||||
| '/permissions'
|
|
||||||
| '/prizes'
|
|
||||||
| '/roadmap-dimentorin'
|
|
||||||
| '/roles'
|
|
||||||
| '/session-dimentorin'
|
|
||||||
| '/settings-dimentorin'
|
|
||||||
| '/transactions'
|
|
||||||
| '/users-dimentorin'
|
|
||||||
| '/accounts/$id'
|
|
||||||
| '/cms-events/$id'
|
|
||||||
| '/cms-events/create'
|
|
||||||
| '/cms-testimonials/$id'
|
|
||||||
| '/cms-testimonials/create'
|
|
||||||
| '/dashboard/$id'
|
|
||||||
| '/dashboard/create'
|
|
||||||
| '/gacha-roll/$id'
|
|
||||||
| '/gacha-roll/create'
|
|
||||||
| '/permissions/$id'
|
|
||||||
| '/permissions/create'
|
|
||||||
| '/roadmap-dimentorin/$id'
|
|
||||||
| '/roadmap-dimentorin/create'
|
|
||||||
| '/roles/$id'
|
|
||||||
| '/roles/create'
|
|
||||||
| '/session-dimentorin/$id'
|
|
||||||
| '/users-dimentorin/$id'
|
|
||||||
| '/auth/login'
|
|
||||||
id:
|
|
||||||
| '__root__'
|
|
||||||
| '/'
|
|
||||||
| '/_authenticated'
|
|
||||||
| '/_public'
|
|
||||||
| '/_authenticated/accounts'
|
|
||||||
| '/_authenticated/cms-events'
|
|
||||||
| '/_authenticated/cms-testimonials'
|
|
||||||
| '/_authenticated/dashboard'
|
|
||||||
| '/_authenticated/dashboard-dimentorin'
|
|
||||||
| '/_authenticated/feedback-review-dimentorin'
|
|
||||||
| '/_authenticated/gacha-roll'
|
|
||||||
| '/_authenticated/hackathon-dashboard'
|
|
||||||
| '/_authenticated/hackathon-submissions'
|
|
||||||
| '/_authenticated/hackathon-teams'
|
|
||||||
| '/_authenticated/hackathon-users'
|
|
||||||
| '/_authenticated/permissions'
|
|
||||||
| '/_authenticated/prizes'
|
|
||||||
| '/_authenticated/roadmap-dimentorin'
|
|
||||||
| '/_authenticated/roles'
|
|
||||||
| '/_authenticated/session-dimentorin'
|
|
||||||
| '/_authenticated/settings-dimentorin'
|
|
||||||
| '/_authenticated/transactions'
|
|
||||||
| '/_authenticated/users-dimentorin'
|
|
||||||
| '/_authenticated/accounts_/$id'
|
|
||||||
| '/_authenticated/cms-events_/$id'
|
|
||||||
| '/_authenticated/cms-events_/create'
|
|
||||||
| '/_authenticated/cms-testimonials_/$id'
|
|
||||||
| '/_authenticated/cms-testimonials_/create'
|
|
||||||
| '/_authenticated/dashboard_/$id'
|
|
||||||
| '/_authenticated/dashboard_/create'
|
|
||||||
| '/_authenticated/gacha-roll_/$id'
|
|
||||||
| '/_authenticated/gacha-roll_/create'
|
|
||||||
| '/_authenticated/permissions_/$id'
|
|
||||||
| '/_authenticated/permissions_/create'
|
|
||||||
| '/_authenticated/roadmap-dimentorin_/$id'
|
|
||||||
| '/_authenticated/roadmap-dimentorin_/create'
|
|
||||||
| '/_authenticated/roles_/$id'
|
|
||||||
| '/_authenticated/roles_/create'
|
|
||||||
| '/_authenticated/session-dimentorin_/$id'
|
|
||||||
| '/_authenticated/users-dimentorin_/$id'
|
|
||||||
| '/_public/auth/login'
|
|
||||||
fileRoutesById: FileRoutesById
|
|
||||||
}
|
|
||||||
export interface RootRouteChildren {
|
|
||||||
IndexRoute: typeof IndexRoute
|
|
||||||
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren
|
|
||||||
PublicRoute: typeof PublicRouteWithChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface FileRoutesByPath {
|
|
||||||
'/_public': {
|
|
||||||
id: '/_public'
|
|
||||||
path: ''
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof PublicRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/_authenticated': {
|
|
||||||
id: '/_authenticated'
|
|
||||||
path: ''
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/': {
|
|
||||||
id: '/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof IndexRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/_authenticated/users-dimentorin': {
|
|
||||||
id: '/_authenticated/users-dimentorin'
|
|
||||||
path: '/users-dimentorin'
|
|
||||||
fullPath: '/users-dimentorin'
|
|
||||||
preLoaderRoute: typeof AuthenticatedUsersDimentorinRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/transactions': {
|
|
||||||
id: '/_authenticated/transactions'
|
|
||||||
path: '/transactions'
|
|
||||||
fullPath: '/transactions'
|
|
||||||
preLoaderRoute: typeof AuthenticatedTransactionsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/settings-dimentorin': {
|
|
||||||
id: '/_authenticated/settings-dimentorin'
|
|
||||||
path: '/settings-dimentorin'
|
|
||||||
fullPath: '/settings-dimentorin'
|
|
||||||
preLoaderRoute: typeof AuthenticatedSettingsDimentorinRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/session-dimentorin': {
|
|
||||||
id: '/_authenticated/session-dimentorin'
|
|
||||||
path: '/session-dimentorin'
|
|
||||||
fullPath: '/session-dimentorin'
|
|
||||||
preLoaderRoute: typeof AuthenticatedSessionDimentorinRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/roles': {
|
|
||||||
id: '/_authenticated/roles'
|
|
||||||
path: '/roles'
|
|
||||||
fullPath: '/roles'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRolesRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/roadmap-dimentorin': {
|
|
||||||
id: '/_authenticated/roadmap-dimentorin'
|
|
||||||
path: '/roadmap-dimentorin'
|
|
||||||
fullPath: '/roadmap-dimentorin'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRoadmapDimentorinRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/prizes': {
|
|
||||||
id: '/_authenticated/prizes'
|
|
||||||
path: '/prizes'
|
|
||||||
fullPath: '/prizes'
|
|
||||||
preLoaderRoute: typeof AuthenticatedPrizesRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/permissions': {
|
|
||||||
id: '/_authenticated/permissions'
|
|
||||||
path: '/permissions'
|
|
||||||
fullPath: '/permissions'
|
|
||||||
preLoaderRoute: typeof AuthenticatedPermissionsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/hackathon-users': {
|
|
||||||
id: '/_authenticated/hackathon-users'
|
|
||||||
path: '/hackathon-users'
|
|
||||||
fullPath: '/hackathon-users'
|
|
||||||
preLoaderRoute: typeof AuthenticatedHackathonUsersRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/hackathon-teams': {
|
|
||||||
id: '/_authenticated/hackathon-teams'
|
|
||||||
path: '/hackathon-teams'
|
|
||||||
fullPath: '/hackathon-teams'
|
|
||||||
preLoaderRoute: typeof AuthenticatedHackathonTeamsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/hackathon-submissions': {
|
|
||||||
id: '/_authenticated/hackathon-submissions'
|
|
||||||
path: '/hackathon-submissions'
|
|
||||||
fullPath: '/hackathon-submissions'
|
|
||||||
preLoaderRoute: typeof AuthenticatedHackathonSubmissionsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/hackathon-dashboard': {
|
|
||||||
id: '/_authenticated/hackathon-dashboard'
|
|
||||||
path: '/hackathon-dashboard'
|
|
||||||
fullPath: '/hackathon-dashboard'
|
|
||||||
preLoaderRoute: typeof AuthenticatedHackathonDashboardRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/gacha-roll': {
|
|
||||||
id: '/_authenticated/gacha-roll'
|
|
||||||
path: '/gacha-roll'
|
|
||||||
fullPath: '/gacha-roll'
|
|
||||||
preLoaderRoute: typeof AuthenticatedGachaRollRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/feedback-review-dimentorin': {
|
|
||||||
id: '/_authenticated/feedback-review-dimentorin'
|
|
||||||
path: '/feedback-review-dimentorin'
|
|
||||||
fullPath: '/feedback-review-dimentorin'
|
|
||||||
preLoaderRoute: typeof AuthenticatedFeedbackReviewDimentorinRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/dashboard-dimentorin': {
|
|
||||||
id: '/_authenticated/dashboard-dimentorin'
|
|
||||||
path: '/dashboard-dimentorin'
|
|
||||||
fullPath: '/dashboard-dimentorin'
|
|
||||||
preLoaderRoute: typeof AuthenticatedDashboardDimentorinRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/dashboard': {
|
|
||||||
id: '/_authenticated/dashboard'
|
|
||||||
path: '/dashboard'
|
|
||||||
fullPath: '/dashboard'
|
|
||||||
preLoaderRoute: typeof AuthenticatedDashboardRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/cms-testimonials': {
|
|
||||||
id: '/_authenticated/cms-testimonials'
|
|
||||||
path: '/cms-testimonials'
|
|
||||||
fullPath: '/cms-testimonials'
|
|
||||||
preLoaderRoute: typeof AuthenticatedCmsTestimonialsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/cms-events': {
|
|
||||||
id: '/_authenticated/cms-events'
|
|
||||||
path: '/cms-events'
|
|
||||||
fullPath: '/cms-events'
|
|
||||||
preLoaderRoute: typeof AuthenticatedCmsEventsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts': {
|
|
||||||
id: '/_authenticated/accounts'
|
|
||||||
path: '/accounts'
|
|
||||||
fullPath: '/accounts'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_public/auth/login': {
|
|
||||||
id: '/_public/auth/login'
|
|
||||||
path: '/auth/login'
|
|
||||||
fullPath: '/auth/login'
|
|
||||||
preLoaderRoute: typeof PublicAuthLoginRouteImport
|
|
||||||
parentRoute: typeof PublicRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/users-dimentorin_/$id': {
|
|
||||||
id: '/_authenticated/users-dimentorin_/$id'
|
|
||||||
path: '/users-dimentorin/$id'
|
|
||||||
fullPath: '/users-dimentorin/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedUsersDimentorinIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/session-dimentorin_/$id': {
|
|
||||||
id: '/_authenticated/session-dimentorin_/$id'
|
|
||||||
path: '/session-dimentorin/$id'
|
|
||||||
fullPath: '/session-dimentorin/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedSessionDimentorinIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/roles_/create': {
|
|
||||||
id: '/_authenticated/roles_/create'
|
|
||||||
path: '/roles/create'
|
|
||||||
fullPath: '/roles/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRolesCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/roles_/$id': {
|
|
||||||
id: '/_authenticated/roles_/$id'
|
|
||||||
path: '/roles/$id'
|
|
||||||
fullPath: '/roles/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRolesIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/roadmap-dimentorin_/create': {
|
|
||||||
id: '/_authenticated/roadmap-dimentorin_/create'
|
|
||||||
path: '/roadmap-dimentorin/create'
|
|
||||||
fullPath: '/roadmap-dimentorin/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRoadmapDimentorinCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/roadmap-dimentorin_/$id': {
|
|
||||||
id: '/_authenticated/roadmap-dimentorin_/$id'
|
|
||||||
path: '/roadmap-dimentorin/$id'
|
|
||||||
fullPath: '/roadmap-dimentorin/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRoadmapDimentorinIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/permissions_/create': {
|
|
||||||
id: '/_authenticated/permissions_/create'
|
|
||||||
path: '/permissions/create'
|
|
||||||
fullPath: '/permissions/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedPermissionsCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/permissions_/$id': {
|
|
||||||
id: '/_authenticated/permissions_/$id'
|
|
||||||
path: '/permissions/$id'
|
|
||||||
fullPath: '/permissions/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedPermissionsIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/gacha-roll_/create': {
|
|
||||||
id: '/_authenticated/gacha-roll_/create'
|
|
||||||
path: '/gacha-roll/create'
|
|
||||||
fullPath: '/gacha-roll/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedGachaRollCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/gacha-roll_/$id': {
|
|
||||||
id: '/_authenticated/gacha-roll_/$id'
|
|
||||||
path: '/gacha-roll/$id'
|
|
||||||
fullPath: '/gacha-roll/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedGachaRollIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/dashboard_/create': {
|
|
||||||
id: '/_authenticated/dashboard_/create'
|
|
||||||
path: '/dashboard/create'
|
|
||||||
fullPath: '/dashboard/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedDashboardCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/dashboard_/$id': {
|
|
||||||
id: '/_authenticated/dashboard_/$id'
|
|
||||||
path: '/dashboard/$id'
|
|
||||||
fullPath: '/dashboard/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedDashboardIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/cms-testimonials_/create': {
|
|
||||||
id: '/_authenticated/cms-testimonials_/create'
|
|
||||||
path: '/cms-testimonials/create'
|
|
||||||
fullPath: '/cms-testimonials/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedCmsTestimonialsCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/cms-testimonials_/$id': {
|
|
||||||
id: '/_authenticated/cms-testimonials_/$id'
|
|
||||||
path: '/cms-testimonials/$id'
|
|
||||||
fullPath: '/cms-testimonials/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedCmsTestimonialsIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/cms-events_/create': {
|
|
||||||
id: '/_authenticated/cms-events_/create'
|
|
||||||
path: '/cms-events/create'
|
|
||||||
fullPath: '/cms-events/create'
|
|
||||||
preLoaderRoute: typeof AuthenticatedCmsEventsCreateRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/cms-events_/$id': {
|
|
||||||
id: '/_authenticated/cms-events_/$id'
|
|
||||||
path: '/cms-events/$id'
|
|
||||||
fullPath: '/cms-events/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedCmsEventsIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts_/$id': {
|
|
||||||
id: '/_authenticated/accounts_/$id'
|
|
||||||
path: '/accounts/$id'
|
|
||||||
fullPath: '/accounts/$id'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthenticatedRouteChildren {
|
|
||||||
AuthenticatedAccountsRoute: typeof AuthenticatedAccountsRoute
|
|
||||||
AuthenticatedCmsEventsRoute: typeof AuthenticatedCmsEventsRoute
|
|
||||||
AuthenticatedCmsTestimonialsRoute: typeof AuthenticatedCmsTestimonialsRoute
|
|
||||||
AuthenticatedDashboardRoute: typeof AuthenticatedDashboardRoute
|
|
||||||
AuthenticatedDashboardDimentorinRoute: typeof AuthenticatedDashboardDimentorinRoute
|
|
||||||
AuthenticatedFeedbackReviewDimentorinRoute: typeof AuthenticatedFeedbackReviewDimentorinRoute
|
|
||||||
AuthenticatedGachaRollRoute: typeof AuthenticatedGachaRollRoute
|
|
||||||
AuthenticatedHackathonDashboardRoute: typeof AuthenticatedHackathonDashboardRoute
|
|
||||||
AuthenticatedHackathonSubmissionsRoute: typeof AuthenticatedHackathonSubmissionsRoute
|
|
||||||
AuthenticatedHackathonTeamsRoute: typeof AuthenticatedHackathonTeamsRoute
|
|
||||||
AuthenticatedHackathonUsersRoute: typeof AuthenticatedHackathonUsersRoute
|
|
||||||
AuthenticatedPermissionsRoute: typeof AuthenticatedPermissionsRoute
|
|
||||||
AuthenticatedPrizesRoute: typeof AuthenticatedPrizesRoute
|
|
||||||
AuthenticatedRoadmapDimentorinRoute: typeof AuthenticatedRoadmapDimentorinRoute
|
|
||||||
AuthenticatedRolesRoute: typeof AuthenticatedRolesRoute
|
|
||||||
AuthenticatedSessionDimentorinRoute: typeof AuthenticatedSessionDimentorinRoute
|
|
||||||
AuthenticatedSettingsDimentorinRoute: typeof AuthenticatedSettingsDimentorinRoute
|
|
||||||
AuthenticatedTransactionsRoute: typeof AuthenticatedTransactionsRoute
|
|
||||||
AuthenticatedUsersDimentorinRoute: typeof AuthenticatedUsersDimentorinRoute
|
|
||||||
AuthenticatedAccountsIdRoute: typeof AuthenticatedAccountsIdRoute
|
|
||||||
AuthenticatedCmsEventsIdRoute: typeof AuthenticatedCmsEventsIdRoute
|
|
||||||
AuthenticatedCmsEventsCreateRoute: typeof AuthenticatedCmsEventsCreateRoute
|
|
||||||
AuthenticatedCmsTestimonialsIdRoute: typeof AuthenticatedCmsTestimonialsIdRoute
|
|
||||||
AuthenticatedCmsTestimonialsCreateRoute: typeof AuthenticatedCmsTestimonialsCreateRoute
|
|
||||||
AuthenticatedDashboardIdRoute: typeof AuthenticatedDashboardIdRoute
|
|
||||||
AuthenticatedDashboardCreateRoute: typeof AuthenticatedDashboardCreateRoute
|
|
||||||
AuthenticatedGachaRollIdRoute: typeof AuthenticatedGachaRollIdRoute
|
|
||||||
AuthenticatedGachaRollCreateRoute: typeof AuthenticatedGachaRollCreateRoute
|
|
||||||
AuthenticatedPermissionsIdRoute: typeof AuthenticatedPermissionsIdRoute
|
|
||||||
AuthenticatedPermissionsCreateRoute: typeof AuthenticatedPermissionsCreateRoute
|
|
||||||
AuthenticatedRoadmapDimentorinIdRoute: typeof AuthenticatedRoadmapDimentorinIdRoute
|
|
||||||
AuthenticatedRoadmapDimentorinCreateRoute: typeof AuthenticatedRoadmapDimentorinCreateRoute
|
|
||||||
AuthenticatedRolesIdRoute: typeof AuthenticatedRolesIdRoute
|
|
||||||
AuthenticatedRolesCreateRoute: typeof AuthenticatedRolesCreateRoute
|
|
||||||
AuthenticatedSessionDimentorinIdRoute: typeof AuthenticatedSessionDimentorinIdRoute
|
|
||||||
AuthenticatedUsersDimentorinIdRoute: typeof AuthenticatedUsersDimentorinIdRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
|
||||||
AuthenticatedAccountsRoute: AuthenticatedAccountsRoute,
|
|
||||||
AuthenticatedCmsEventsRoute: AuthenticatedCmsEventsRoute,
|
|
||||||
AuthenticatedCmsTestimonialsRoute: AuthenticatedCmsTestimonialsRoute,
|
|
||||||
AuthenticatedDashboardRoute: AuthenticatedDashboardRoute,
|
|
||||||
AuthenticatedDashboardDimentorinRoute: AuthenticatedDashboardDimentorinRoute,
|
|
||||||
AuthenticatedFeedbackReviewDimentorinRoute:
|
|
||||||
AuthenticatedFeedbackReviewDimentorinRoute,
|
|
||||||
AuthenticatedGachaRollRoute: AuthenticatedGachaRollRoute,
|
|
||||||
AuthenticatedHackathonDashboardRoute: AuthenticatedHackathonDashboardRoute,
|
|
||||||
AuthenticatedHackathonSubmissionsRoute:
|
|
||||||
AuthenticatedHackathonSubmissionsRoute,
|
|
||||||
AuthenticatedHackathonTeamsRoute: AuthenticatedHackathonTeamsRoute,
|
|
||||||
AuthenticatedHackathonUsersRoute: AuthenticatedHackathonUsersRoute,
|
|
||||||
AuthenticatedPermissionsRoute: AuthenticatedPermissionsRoute,
|
|
||||||
AuthenticatedPrizesRoute: AuthenticatedPrizesRoute,
|
|
||||||
AuthenticatedRoadmapDimentorinRoute: AuthenticatedRoadmapDimentorinRoute,
|
|
||||||
AuthenticatedRolesRoute: AuthenticatedRolesRoute,
|
|
||||||
AuthenticatedSessionDimentorinRoute: AuthenticatedSessionDimentorinRoute,
|
|
||||||
AuthenticatedSettingsDimentorinRoute: AuthenticatedSettingsDimentorinRoute,
|
|
||||||
AuthenticatedTransactionsRoute: AuthenticatedTransactionsRoute,
|
|
||||||
AuthenticatedUsersDimentorinRoute: AuthenticatedUsersDimentorinRoute,
|
|
||||||
AuthenticatedAccountsIdRoute: AuthenticatedAccountsIdRoute,
|
|
||||||
AuthenticatedCmsEventsIdRoute: AuthenticatedCmsEventsIdRoute,
|
|
||||||
AuthenticatedCmsEventsCreateRoute: AuthenticatedCmsEventsCreateRoute,
|
|
||||||
AuthenticatedCmsTestimonialsIdRoute: AuthenticatedCmsTestimonialsIdRoute,
|
|
||||||
AuthenticatedCmsTestimonialsCreateRoute:
|
|
||||||
AuthenticatedCmsTestimonialsCreateRoute,
|
|
||||||
AuthenticatedDashboardIdRoute: AuthenticatedDashboardIdRoute,
|
|
||||||
AuthenticatedDashboardCreateRoute: AuthenticatedDashboardCreateRoute,
|
|
||||||
AuthenticatedGachaRollIdRoute: AuthenticatedGachaRollIdRoute,
|
|
||||||
AuthenticatedGachaRollCreateRoute: AuthenticatedGachaRollCreateRoute,
|
|
||||||
AuthenticatedPermissionsIdRoute: AuthenticatedPermissionsIdRoute,
|
|
||||||
AuthenticatedPermissionsCreateRoute: AuthenticatedPermissionsCreateRoute,
|
|
||||||
AuthenticatedRoadmapDimentorinIdRoute: AuthenticatedRoadmapDimentorinIdRoute,
|
|
||||||
AuthenticatedRoadmapDimentorinCreateRoute:
|
|
||||||
AuthenticatedRoadmapDimentorinCreateRoute,
|
|
||||||
AuthenticatedRolesIdRoute: AuthenticatedRolesIdRoute,
|
|
||||||
AuthenticatedRolesCreateRoute: AuthenticatedRolesCreateRoute,
|
|
||||||
AuthenticatedSessionDimentorinIdRoute: AuthenticatedSessionDimentorinIdRoute,
|
|
||||||
AuthenticatedUsersDimentorinIdRoute: AuthenticatedUsersDimentorinIdRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
|
|
||||||
AuthenticatedRouteChildren,
|
|
||||||
)
|
|
||||||
|
|
||||||
interface PublicRouteChildren {
|
|
||||||
PublicAuthLoginRoute: typeof PublicAuthLoginRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
const PublicRouteChildren: PublicRouteChildren = {
|
|
||||||
PublicAuthLoginRoute: PublicAuthLoginRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PublicRouteWithChildren =
|
|
||||||
PublicRoute._addFileChildren(PublicRouteChildren)
|
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
|
||||||
IndexRoute: IndexRoute,
|
|
||||||
AuthenticatedRoute: AuthenticatedRouteWithChildren,
|
|
||||||
PublicRoute: PublicRouteWithChildren,
|
|
||||||
}
|
|
||||||
export const routeTree = rootRouteImport
|
|
||||||
._addFileChildren(rootRouteChildren)
|
|
||||||
._addFileTypes<FileRouteTypes>()
|
|
||||||
Loaded 50 of 618 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user