Compare commits

..
Author SHA1 Message Date
Maulana Sodiqin 28849e30b0 feat: setup 2025-03-13 22:29:56 +07:00
114 changed files with 499 additions and 10148 deletions
-2
View File
@@ -43,5 +43,3 @@ Thumbs.db
vite.config.*.timestamp*
vitest.config.*.timestamp*
storybook-static
-12
View File
@@ -1,12 +0,0 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../eslint.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/react'],
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
];
-16
View File
@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Backoffice</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
-9
View File
@@ -1,9 +0,0 @@
import { join } from 'path';
export default {
plugins: {
'@tailwindcss/postcss': {
base: join(import.meta.dirname, '../../'),
},
},
};
-9
View File
@@ -1,9 +0,0 @@
{
"name": "backoffice",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/backoffice/src",
"projectType": "application",
"tags": [],
"// targets": "to see all targets run: nx show project backoffice --web",
"targets": {}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 351 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 511 KiB

@@ -1,18 +0,0 @@
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] mx-auto">
<Outlet />
</div>
</div>
</div>
);
};
export default AppLayout;
-159
View File
@@ -1,159 +0,0 @@
import * as React from 'react';
import { FC, ReactElement } from 'react';
import {
FilterOutlined,
SearchOutlined,
EditOutlined,
} 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';
interface Account {
id: number;
name: string;
email: string;
phone: string;
address: string;
}
// Mock data for demonstration
const mockData: Account[] = Array.from({ length: 90 }, (_, i) => ({
id: i + 1,
name: i === 0 ? 'Ahmad Wijuana' : 'Nama Lengkap',
email: 'fullname23@gmail.com',
phone: '081904423804',
address: 'Jl. Pantai Cibaduyut Indonesia',
}));
const columns: ColumnDef<Account>[] = [
{
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 Lengkap',
accessorKey: 'name',
},
{
header: 'Email',
accessorKey: 'email',
},
{
header: 'Nomor Telp',
accessorKey: 'phone',
},
{
header: 'Alamat Pengiriman',
accessorKey: 'address',
},
{
header: 'Action',
cell: ({ row }) => (
<Button
variant="primary"
size="sm"
onClick={(e) => {
e.stopPropagation();
// handleEdit(row.id);
}}
className="flex items-center gap-2"
>
<EditOutlined /> Edit
</Button>
),
},
];
export const Components: FC = (): ReactElement => {
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 9,
});
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
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 (
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
{/* Header */}
<header className="bg-white py-4 px-8 rounded-md shadow p-4">
<h1 className="text-p2 font-semibold">Data Akun</h1>
</header>
{/* Account Table Section */}
<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="relative w-full">
<Input
placeholder="Cari berdasarkan nama lengkap, email"
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>
<Button
variant="primary"
disabled
size="md"
className="flex items-center gap-3"
>
<FilterOutlined />
Filters
</Button>
</div>
{/* Table */}
<DataTable data={mockData} columns={columns} table={table} />
</section>
</main>
);
};
export default Components;
@@ -1,18 +0,0 @@
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] mx-auto">
<Outlet />
</div>
</div>
</div>
);
};
export default AppLayout;
-111
View File
@@ -1,111 +0,0 @@
import { ArrowDownOutlined, PlusOutlined } from '@ant-design/icons';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { FC, ReactElement } from 'react';
export const Components: FC = (): ReactElement => {
return (
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
{/* Dashboard Header */}
<header className="bg-white py-4 px-8 rounded-md shadow p-4">
<h1 className="text-p2 font-semibold">Dashboard</h1>
</header>
<div className="flex justify-between gap-[40px] p-8 bg-white rounded-md">
<div className="w-full flex flex-col gap-[40px]">
{/* Summary Section */}
<section>
<h2 className="text-p2 font-medium text-primary-500 mb-8">
Summary
</h2>
<div className="grid grid-cols-2 gap-4">
{/* Summary Cards */}
{[1, 2, 3, 4].map((item) => (
<div
key={item}
className="bg-white rounded-lg shadow-sm py-4 px-6 flex items-center border border-neutral-100"
>
<div className="mr-4 text-primary-500 bg-primary-100 p-[8px] rounded-md">
<ArrowDownOutlined className="text-[20px]" />
</div>
<div className="flex flex-col gap-1">
<h3 className="text-p1 font-semibold">1000</h3>
<p className="text-label1 text-neutral-500">
Total Participants
</p>
</div>
</div>
))}
</div>
</section>
{/* Gacha Items Section */}
<section className="flex flex-col gap-8">
<div className="flex justify-between items-center">
<h2 className="text-p2 font-medium text-primary-500">
Gacha Items
</h2>
<Button variant="primary" size="sm" className="items-end gap-3">
<span>Tambah Item</span>
<PlusOutlined className="text-[16px]" />
</Button>
</div>
{/* Gacha Items List */}
<div className="flex flex-col gap-4 max-h-140 overflow-auto">
{[1, 2, 3, 4, 5, 6].map((item) => (
<div
key={item}
className="bg-white max-h-[80px] overflow-clip rounded-lg shadow-sm flex justify-between border border-neutral-100"
>
<div className="flex flex-col py-4 px-6 gap-4">
<div>
<h3 className="text-p3 text-primary-500 font-medium">
Lanyard IMPHNEN
</h3>
<div className="flex items-center gap-2 text-label2 text-gray-500 mt-1">
<span>Prize {item}</span>
<span>Chance Rate: (0.1%)</span>
</div>
</div>
<div className="flex justify-start gap-2">
<Button
variant="text"
size="sm"
className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent hover:text-primary-500"
>
Edit
</Button>
<Button
variant="text"
size="sm"
className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent hover:text-red-500"
>
Delete
</Button>
</div>
</div>
{/* Lebih baik gunakan gambar yang sudah di-clip dengan size height: 78px daripada hard-code object-position dan margin */}
<img
src="gacha-clip.png"
alt="Lanyard IMPHNEN"
className="h-full"
/>
</div>
))}
</div>
</section>
</div>
{/* Right-side illustration */}
<img
src="gacha.png"
alt=""
className="rounded-lg hidden xl:block xl:min-w-[436px] h-auto object-cover"
/>
</div>
</main>
);
};
export default Components;
-12
View File
@@ -1,12 +0,0 @@
import { FC, ReactElement } from 'react';
import { Outlet } from 'react-router-dom';
export const AppLayout: FC = (): ReactElement => {
return (
<main className="bg-primary-50 min-h-screen">
<Outlet />
</main>
);
};
export default AppLayout;
-36
View File
@@ -1,36 +0,0 @@
import { FC, ReactElement } from 'react';
import { useNavigate } from 'react-router';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { InputForm } from '@imphnen-frontend-service/ui/molecules';
export const Components: FC = (): ReactElement => {
const navigate = useNavigate();
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>
<InputForm
label="Email"
placeholder="Masukkan Email"
type="email"
size="lg"
className="w-full"
/>
<InputForm
label="Password"
placeholder="Masukkan password"
type="password"
size="lg"
className="w-full"
/>
<Button onClick={() => navigate('/dashboard')}>Login</Button>
</div>
</div>
);
};
export default Components;
@@ -1,18 +0,0 @@
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] mx-auto">
<Outlet />
</div>
</div>
</div>
);
};
export default AppLayout;
@@ -1,178 +0,0 @@
import * as React from 'react';
import { FC, ReactElement } from 'react';
import {
FilterOutlined,
SearchOutlined,
AuditOutlined,
} 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';
type TransactionStatus = 'valid' | 'invalid' | 'unchecked';
interface Transaction {
id: number;
name: string;
transactionNumber: string;
status: TransactionStatus;
}
// Mock data for transactions
const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
name: i === 0 ? 'Ahmad Wijuana' : 'Nama Lengkap',
transactionNumber: '25D2133Y9AFYBD',
status: (i % 3 === 0
? 'invalid'
: i % 5 === 0
? 'unchecked'
: 'valid') as TransactionStatus,
}));
const columns: ColumnDef<Account>[] = [
{
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 Lengkap',
accessorKey: 'name',
},
{
header: 'Nomor Transaksi',
accessorKey: 'transactionNumber',
},
{
header: 'Order Valid?',
accessorKey: 'status',
cell: ({ row }) => {
const status = row.original.status;
const statusColors: Record<TransactionStatus, string> = {
valid: 'bg-success-500 text-white',
invalid: 'bg-danger-500 text-white',
unchecked: 'bg-yellow-400 text-black',
};
const statusText: Record<TransactionStatus, string> = {
valid: 'Valid',
invalid: 'Invalid',
unchecked: 'Unchecked',
};
return (
<div
className={`py-1 px-3 rounded-md text-center ${statusColors[status]}`}
>
{statusText[status]}
</div>
);
},
},
{
header: 'Action',
cell: ({ row }) => (
<Button
variant="primary"
size="sm"
onClick={(e) => {
e.stopPropagation();
// handleUpdate(row.id);
}}
className="flex items-center gap-2 w-full"
>
<AuditOutlined className="text-[16px]" /> Update
</Button>
),
},
];
export const Components: FC = (): ReactElement => {
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 9,
});
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const table = useReactTable({
data: mockTransactions,
columns,
state: {
pagination,
rowSelection,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: setPagination,
pageCount: Math.ceil(mockTransactions.length / pagination.pageSize),
manualPagination: false,
});
return (
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
{/* Header */}
<header className="bg-white py-4 px-8 rounded-md shadow p-4">
<h1 className="text-p2 font-semibold">Validasi Transaksi</h1>
</header>
{/* Account Table Section */}
<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="relative w-full">
<Input
placeholder="Cari berdasarkan nama lengkap, nomor order Shopee"
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>
<Button
variant="primary"
size="md"
className="flex items-center gap-3"
>
<FilterOutlined />
Filters
</Button>
</div>
{/* Table */}
<DataTable data={mockTransactions} columns={columns} table={table} />
</section>
</main>
);
};
export default Components;
-136
View File
@@ -1,136 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
@import 'tailwindcss';
@source "../../../libs/ui/**/*.{ts,tsx}";
@theme {
--color-primary-50: #f0f8ff;
--color-primary-100: #e1f0fd;
--color-primary-200: #bce1fb;
--color-primary-300: #81cbf8;
--color-primary-400: #3eb0f2;
--color-primary-500: #23a1eb;
--color-primary-600: #0877c1;
--color-primary-700: #085f9c;
--color-primary-800: #0b5181;
--color-primary-900: #0f446b;
--color-primary-950: #0a2b47;
--color-neutral-50: #f6f6f6;
--color-neutral-100: #e7e7e7;
--color-neutral-200: #d0d0d0;
--color-neutral-300: #b7b7b7;
--color-neutral-400: #9f9f9f;
--color-neutral-500: #7e7e7e;
--color-neutral-600: #5d5d5d;
--color-neutral-700: #4a4a4a;
--color-neutral-800: #3a3a3a;
--color-neutral-900: #2f2f2f;
--color-neutral-950: #2b2b2b;
--color-success-100: #e0fbd8;
--color-success-200: #bcf8b0;
--color-success-300: #8eea85;
--color-success-400: #63d564;
--color-success-500: #35ba43;
--color-success-600: #269f3e;
--color-success-700: #1a8439;
--color-success-800: #106b32;
--color-success-900: #0b592f;
--color-info-100: #ccfcfe;
--color-info-200: #9bf3fd;
--color-info-300: #67e3fb;
--color-info-400: #42cdf8;
--color-info-500: #04acf3;
--color-info-600: #0185d0;
--color-info-700: #0264af;
--color-info-800: #01478d;
--color-info-900: #003375;
--color-warning-100: #fffcd3;
--color-warning-200: #fffaa9;
--color-warning-300: #fff67d;
--color-warning-400: #fff25d;
--color-warning-500: #ffed27;
--color-warning-600: #dbc91d;
--color-warning-700: #b7a714;
--color-warning-800: #93850b;
--color-warning-900: #7a6d07;
--color-danger-100: #ffe8da;
--color-danger-200: #ffcbb3;
--color-danger-300: #ffaa8d;
--color-danger-400: #ff8870;
--color-danger-500: #ff5242;
--color-danger-600: #da3030;
--color-danger-700: #b7212d;
--color-danger-800: #93152a;
--color-danger-900: #7a0c27;
--radius-none: 0px;
--radius-sm: 2px;
--radius-md: 4px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-3xl: 24px;
--radius-full: 50%;
--font-bai-jamjuree: 'Bai Jamjuree', sans-serif;
--text-h1: 3.833rem;
/* ~46px */
--text-h2: 3.083rem;
/* ~37px */
--text-h3: 2.417rem;
/* ~29px */
--text-p1: 1.917rem;
/* ~23px */
--text-p2: 1.583rem;
/* ~19px */
--text-p3: 1.25rem;
/* 15px */
--text-label1: 1rem;
/* 12px */
--text-label2: 0.833rem;
/* ~10px */
--text-label3: 0.677rem;
/* ~8px */
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-neutral-200, currentColor);
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--color-neutral-50);
}
::-webkit-scrollbar-thumb {
background: #cccccc;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-neutral-300);
}
html {
font-family: 'Bai Jamjuree', sans-serif;
font-weight: 400;
font-size: 12px;
line-height: 1.2;
}
}
-40
View File
@@ -1,40 +0,0 @@
import { createRoot } from 'react-dom/client';
import { middleware } from './middleware';
import { StrictMode } from 'react';
import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router';
import {
add404PageToRoutesChildren,
addErrorElementToRoutes,
convertPagesToRoute,
QueryProvider,
} from '@imphnen-frontend-service/utils';
import './index.css';
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');
const routes = convertPagesToRoute(files, loadingFiles) as RouteObject;
addErrorElementToRoutes(errorFiles, routes);
add404PageToRoutesChildren(notFoundFiles, routes);
const router = createBrowserRouter([
{
...routes,
loader: middleware,
shouldRevalidate: () => true,
},
]);
const rootElement = document.getElementById('root');
if (!rootElement) throw new Error('Failed to find the root element');
createRoot(rootElement).render(
<StrictMode>
<QueryProvider>
<RouterProvider router={router} />
</QueryProvider>
</StrictMode>
);
-11
View File
@@ -1,11 +0,0 @@
import { LoaderFunctionArgs } from 'react-router-dom';
export const middleware = async ({ request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const pathname = url.pathname;
if (pathname) return null;
return null;
};
-27
View File
@@ -1,27 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
]
},
"exclude": [
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
-21
View File
@@ -1,21 +0,0 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"types": ["vite/client", "vitest"]
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}
-30
View File
@@ -1,30 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
]
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}
-42
View File
@@ -1,42 +0,0 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/backoffice',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/apps/backoffice',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/apps/backoffice',
provider: 'v8' as const,
},
},
}));
+2 -2
View File
@@ -6,8 +6,8 @@
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/logos/simple.svg" />
<link rel="stylesheet" href="/src/index.css" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div id="root"></div>
+1 -5
View File
@@ -1,9 +1,5 @@
import { join } from 'path';
export default {
plugins: {
'@tailwindcss/postcss': {
base: join(import.meta.dirname, '../../'),
},
'@tailwindcss/postcss': {},
},
};
-1
View File
@@ -1,6 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
@import 'tailwindcss';
@source "../../../libs/ui/**/*.{ts,tsx}";
@theme {
--color-primary-50: #f0f8ff;
+1 -1
View File
@@ -6,7 +6,7 @@
<base href="/" />
<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="/favicon.ico" />
<link rel="stylesheet" href="/src/index.css" />
</head>
<body>
+1 -5
View File
@@ -1,9 +1,5 @@
import { join } from 'path';
export default {
plugins: {
'@tailwindcss/postcss': {
base: join(import.meta.dirname, '../../'),
},
'@tailwindcss/postcss': {},
},
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 351 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 511 KiB

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1017 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 941 B

+1 -1
View File
@@ -4,7 +4,7 @@ import { Navbar } from '@imphnen-frontend-service/ui/organisms';
export const AppLayout: FC = (): ReactElement => {
return (
<main className="bg-primary-50 min-h-screen">
<main className="bg-primary-50 min-h-screen px-20 py-[60px]">
<Navbar />
<Outlet />
</main>
+2 -276
View File
@@ -1,281 +1,7 @@
import { ArrowDownOutlined } from '@ant-design/icons';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
import { cn } from '@imphnen-frontend-service/utils';
import { FC, Fragment, ReactElement, useEffect, useState } from 'react';
import { Link } from 'react-router';
interface GachaItemProps {
src: string;
label: string;
className?: string;
}
import { FC, ReactElement } from 'react';
export const Components: FC = (): ReactElement => {
// Pinjem
const [showModal, setShowModal] = useState(false);
const scrollToRoulette = () => {
const rouletteSection = document.getElementById('roulette');
if (rouletteSection) {
rouletteSection.scrollIntoView({ behavior: 'smooth' });
}
};
useEffect(() => {
const gachaPlaySection = document.getElementById('gacha-play');
if (gachaPlaySection) {
let currentIndex = 0;
const items = gachaPlaySection.children;
const totalItems = items.length;
const scrollItems = () => {
if (currentIndex >= totalItems) {
currentIndex = 0;
}
items[currentIndex].scrollIntoView({
behavior: 'smooth',
inline: 'center',
});
currentIndex++;
};
const intervalId = setInterval(scrollItems, 2800);
return () => clearInterval(intervalId);
}
}, []);
const GachaItem: FC<GachaItemProps> = ({
src,
label,
className,
}): ReactElement => {
return (
<div className="snap-center flex-auto justify-center items-center flex flex-col min-w-full">
<div className="max-h-[180px] md:max-h-[270px] md:max-w-[500px] mb-2 md:mb-5 flex flex-1 justify-center items-center">
<img
src={src}
alt=""
className={cn('h-[150px] md:h-[255px] object-contain', className)}
/>
</div>
<p className="font-semibold text-center md:text-p2">{label}</p>
</div>
);
};
return (
<Fragment>
{/* Landing Page */}
<section
id="landing-page"
className="my-12 md:my-24 grid grid-cols-4 md:grid-cols-8 lg:grid-cols-12 justify-items-center max-w-[1280px] items-center justify-center mx-[32px] md:mx-[60px] lg:mx-[80px] xl:mx-auto"
>
<div className="order-none lg:order-1 flex flex-col col-span-4 md:col-span-8 lg:col-span-4 md:w-full lg:w-fit items-center gap-7 md:gap-13 text-primary-500 mb-10 md:mb-20">
<img
src="/logos/logo.svg"
alt="IMPHNEN Logo"
className="w-[170px] md:w-[317px]"
/>
<div className="font-semibold text-center">
<div className="text-p1 md:text-h2 lg:font-bold text-center">
<p>New Merchandise</p>
<p>Coming UP</p>
</div>
<div className="text-base md:text-p2 mt-3">
Periode Gacha: 1 - 31 Maret 2025
</div>
</div>
<div className="text-center font-semibold text-p3 md:text-p1">
<p>Let's Go Checkout Our Merch &</p>
<p>Gacha Your Prize Here</p>
</div>
<Button
size="sm"
variant="bordered"
className="flex gap-3 px-3 md:px-5 py-2 md:py-4 bg-primary-100 border border-primary-500 border-dashed rounded md:rounded-md animate-bounce text-label2 md:text-p2"
onClick={scrollToRoulette}
>
<ArrowDownOutlined />
<span>Scroll For Gacha</span>
<ArrowDownOutlined />
</Button>
</div>
<div className="order-1 lg:order-none flex flex-col col-span-4 justify-center items-center mb-8 relative">
<div className="relative h-[150px] md:h-[230px]">
<img
src="/merch/1.png"
alt="Merch 1"
className="relative top-[3px] md:top-[6px] left-[4px] md:left-[7px] z-10 w-[165px] md:w-[266px]"
/>
<img
className="absolute top-0 left-0 min-w-[174px] md:min-w-[280px] z-0"
src="/merch/Vector-1.svg"
alt=""
/>
<img
className="absolute hidden lg:block -bottom-[50px] -right-[50px]"
src="/landing-arrow-1.svg"
alt=""
/>
</div>
<div className="px-3 py-1 text-primary-500 font-semibold text-base md:text-p2 bg-white shadow-md rounded">
Official Merch IMPHNEN
</div>
<div className="mt-2 text-primary-500 font-bold text-p2 md:text-h3">
~ 175k ~
</div>
</div>
<div className="order-2 flex flex-col col-span-4 justify-center items-center mb-8 relative">
<div className="relative h-[130px] md:h-[230px]">
<img
src="/merch/2.png"
alt="Merch 2"
className="relative top-[5px] md:top-[8px] left-0 md:left-[5px] z-10 w-[165px] md:w-[277px]"
/>
<img
className="absolute top-0 -left-[5px] md:-left-[8px] z-0 min-w-[174px] md:min-w-[302px]"
src="/merch/Vector-2.svg"
alt=""
/>
<img
className="absolute hidden lg:block -bottom-[100px] -left-[55px]"
src="/landing-arrow-2.svg"
alt=""
/>
</div>
<div className="px-3 py-1 text-primary-500 font-semibold text-base md:text-p2 bg-white shadow-md rounded">
IMPHNEN Mini Merch
</div>
<div className="mt-2 text-primary-500 font-bold text-p2 md:text-h3">
~ 90k ~
</div>
</div>
</section>
{/* Roulette Page */}
<section
id="roulette"
className="mt-20 pb-50 lg:py-90 mx-[32px] md:mx-[60px] lg:mx-[80px] xl:mx-auto lg:max-w-[1280px] grid grid-cols-4 md:grid-cols-8 lg:grid-cols-12 justify-items-center gap-y-16 md:gap-y-28"
>
<div className="col-span-4 md:col-span-8 lg:col-span-6 text-primary-500 bg-white rounded-lg md:rounded-xl p-5 md:py-9 md:px-13 shadow md:max-w-[430px]">
<header className="mb-4">
<h3 className="font-semibold text-p3 md:text-h2">Gacha Roulette</h3>
</header>
<h4 className="font-semibold my-2 text-base md:text-p1">
How to participate
</h4>
<p className="text-base md:text-p2">
Kamu harus melakukan pembelian merch batch 2 di shopee IMPHNEN
</p>
<h4 className="font-semibold my-2 text-base md:text-p1">
How to gacha
</h4>
<ol className="text-base md:text-p2 list-decimal list-outside ml-4 md:ml-6">
<li>First, press the "Spin Now" button</li>
<li>
If the merch does not match you can reroll by paying IDR5,000
</li>
<li>If it is appropriate you can submit</li>
<li>Then fill in the data for your merch delivery process</li>
</ol>
</div>
<div
id="roulette-spin"
className="col-span-4 md:col-span-8 lg:col-span-6 flex flex-col items-center gap-4 md:gap-8 overflow-x-hidden"
>
<div className="bg-white text-primary-500 font-medium text-p3 md:text-h3 shadow py-2 px-4 md:py-4 md:px-8 max-w-fit rounded-md md:rounded-lg">
Here Take Your Prize
</div>
<section
id="gacha-play"
className="flex flex-nowrap overflow-auto w-full gap-x-8 snap-x snap-mandatory"
>
<GachaItem
src="/gacha/certificate.png"
label="Sertifikat + Laminating"
/>
<GachaItem
src="/gacha/lanyard-id-card.png"
label="Lanyard + ID Card"
/>
<GachaItem src="/gacha/pin.png" label="Pin" />
<GachaItem src="/gacha/sticker.png" label="Sticker Isi 3" />
<GachaItem src="/gacha/sticker.png" label="Sticker Isi 5" />
<GachaItem
src="/gacha/gelang-karet.png"
label="Gelang Karet"
className="h-[86px] md:h-[160px]"
/>
</section>
<Button variant="secondary" size="md">
Spin Now
</Button>
</div>
</section>
{/* Diffuser & Cloud */}
<div className="sticky bottom-0 h-[86px] md:h-[200px] bg-gradient-to-b from-primary-500/0 to-primary-500/50 to-80%"></div>
{/**Izin pake untuk debug modals gacha */}
<button onClick={() => setShowModal(true)}>Open Modal</button>
<Modal
className="py-[45px] min-w-[400px] lg:min-w-[455px] px-7"
isOpen={showModal}
onClose={() => setShowModal(false)}
>
<Modal.Header className="space-y-4">
<img src="/logos/logo.svg" alt="" className="h-[70px] w-auto" />
<h1 className="text-primary-500 text-p1 text-center font-semibold">
Login
</h1>
</Modal.Header>
<Modal.Content className="space-y-4">
<InputForm
label="Email"
placeholder="Masukkan Email"
type="email"
size="lg"
className="w-full"
/>
<InputForm
label="Password"
placeholder="Masukkan password"
type="password"
size="lg"
className="w-full"
/>
<Link to="/forgot-password">
<h1 className="text-end text-primary-500 text-xl font-medium">
Lupa Password?
</h1>
</Link>
</Modal.Content>
<Modal.Footer className="flex flex-col md:flex-col pt-4">
<Button
size="md"
className="w-full"
onClick={() => console.log('Login')}
>
Login
</Button>
<div className="flex justify-center gap-2 pt-2.5 font-medium">
<p className="text-neutral-500">Belum punya akun?</p>
<Link
className="text-primary-500 hover:text-primary-600"
to="/register"
>
Daftar disini
</Link>
</div>
</Modal.Footer>
</Modal>
{/**Izin pake untuk debug modals gacha */}
</Fragment>
);
return <>Hallo</>;
};
export default Components;
View File
+56 -72
View File
@@ -1,21 +1,19 @@
@import url('https://fonts.googleapis.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
@import 'tailwindcss';
@source "../../../libs/ui/**/*.{ts,tsx}";
@theme {
--color-primary-50: #f0f8ff;
--color-primary-100: #e1f0fd;
--color-primary-200: #bce1fb;
--color-primary-300: #81cbf8;
--color-primary-400: #3eb0f2;
--color-primary-500: #23a1eb;
--color-primary-600: #0877c1;
--color-primary-700: #085f9c;
--color-primary-800: #0b5181;
--color-primary-900: #0f446b;
--color-primary-950: #0a2b47;
--color-primary-200: #b8e1f8;
--color-primary-300: #a1d4f0;
--color-primary-400: #4aa4da;
--color-primary-500: #1a8ce6;
--color-primary-600: #1673c7;
--color-primary-700: #0f5ca5;
--color-primary-800: #0a4780;
--color-primary-900: #04305d;
--color-neutral-50: #f6f6f6;
--color-neutral-50: #f7f7f7;
--color-neutral-100: #e7e7e7;
--color-neutral-200: #d0d0d0;
--color-neutral-300: #b7b7b7;
@@ -25,47 +23,50 @@
--color-neutral-700: #4a4a4a;
--color-neutral-800: #3a3a3a;
--color-neutral-900: #2f2f2f;
--color-neutral-950: #2b2b2b;
--color-success-100: #e0fbd8;
--color-success-200: #bcf8b0;
--color-success-300: #8eea85;
--color-success-400: #63d564;
--color-success-500: #35ba43;
--color-success-600: #269f3e;
--color-success-700: #1a8439;
--color-success-800: #106b32;
--color-success-900: #0b592f;
--color-success-50: #e7f2ee;
--color-success-100: #cde6dd;
--color-success-200: #9bccbc;
--color-success-300: #78bdab;
--color-success-400: #4ca88f;
--color-success-500: #349078;
--color-success-600: #2f7c66;
--color-success-700: #26624f;
--color-success-800: #1e4a3b;
--color-success-900: #1b513b;
--color-info-100: #ccfcfe;
--color-info-200: #9bf3fd;
--color-info-300: #67e3fb;
--color-info-400: #42cdf8;
--color-info-500: #04acf3;
--color-info-600: #0185d0;
--color-info-700: #0264af;
--color-info-800: #01478d;
--color-info-900: #003375;
--color-info-50: #e7f4f5;
--color-info-100: #cce9ea;
--color-info-200: #99d3d5;
--color-info-300: #78c4c7;
--color-info-400: #3aa9ad;
--color-info-500: #279599;
--color-info-600: #207d82;
--color-info-700: #1a666b;
--color-info-800: #124c52;
--color-info-900: #093d43;
--color-warning-100: #fffcd3;
--color-warning-200: #fffaa9;
--color-warning-300: #fff67d;
--color-warning-400: #fff25d;
--color-warning-500: #ffed27;
--color-warning-600: #dbc91d;
--color-warning-700: #b7a714;
--color-warning-800: #93850b;
--color-warning-900: #7a6d07;
--color-warning-50: #fffce6;
--color-warning-100: #fff8cc;
--color-warning-200: #fef39b;
--color-warning-300: #fde967;
--color-warning-400: #fbd93d;
--color-warning-500: #f3c215;
--color-warning-600: #d7a20f;
--color-warning-700: #aa7e0c;
--color-warning-800: #805c07;
--color-warning-900: #665c00;
--color-danger-100: #ffe8da;
--color-danger-200: #ffcbb3;
--color-danger-300: #ffaa8d;
--color-danger-400: #ff8870;
--color-danger-500: #ff5242;
--color-danger-600: #da3030;
--color-danger-700: #b7212d;
--color-danger-800: #93152a;
--color-danger-900: #7a0c27;
--color-danger-50: #ffe7e7;
--color-danger-100: #ffcece;
--color-danger-200: #ff9f9f;
--color-danger-300: #ff6e6e;
--color-danger-400: #fa4646;
--color-danger-500: #ef1f1f;
--color-danger-600: #d11515;
--color-danger-700: #a51111;
--color-danger-800: #7f0c0c;
--color-danger-900: #5d2d2d;
--radius-none: 0px;
--radius-sm: 2px;
@@ -78,28 +79,16 @@
--font-bai-jamjuree: 'Bai Jamjuree', sans-serif;
--text-h1: 3.833rem;
/* ~46px */
--text-h2: 3.083rem;
/* ~37px */
--text-h3: 2.417rem;
/* ~29px */
--text-p1: 1.917rem;
/* ~23px */
--text-p2: 1.583rem;
/* ~19px */
--text-p3: 1.25rem;
/* 15px */
--text-label1: 1rem;
/* 12px */
--text-label2: 0.833rem;
/* ~10px */
--text-label3: 0.677rem;
/* ~8px */
--text-h1: 3rem; /* ~48px */
--text-h2: 2.4rem; /* ~38.4px */
--text-h3: 1.92rem; /* ~30.72px */
--text-p1: 1.54rem; /* ~24.58px */
--text-p2: 1.23rem; /* ~19.68px */
--text-label1: 0.98rem; /* ~15.74px */
--text-label2: 0.78rem; /* ~12.59px */
}
@layer base {
*,
::after,
::before,
@@ -113,16 +102,13 @@
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--color-neutral-50);
}
::-webkit-scrollbar-thumb {
background: #cccccc;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-neutral-300);
}
@@ -130,7 +116,5 @@
html {
font-family: 'Bai Jamjuree', sans-serif;
font-weight: 400;
font-size: 12px;
line-height: 1.2;
}
}
-12
View File
@@ -1,12 +0,0 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
-7
View File
@@ -1,7 +0,0 @@
# service
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test service` to execute the unit tests via [Vitest](https://vitest.dev/).
-12
View File
@@ -1,12 +0,0 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../eslint.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/react'],
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
];

Some files were not shown because too many files have changed in this diff Show More