Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47ebd3ad73 | ||
|
|
d544e43abc | ||
|
|
809eeee931 | ||
|
|
f39466936f | ||
|
|
5d418830b9 | ||
|
|
d58ad58ef8 | ||
|
|
b62c762cf9 | ||
|
|
4e665cca76 | ||
|
|
74c0ebc5dd | ||
|
|
eadd554a32 | ||
|
|
ecdaa4a82e | ||
|
|
747e75e684 | ||
|
|
76637dd051 | ||
|
|
3dda8149ba | ||
|
|
cc21735468 | ||
|
|
a6bc6de72e | ||
|
|
340bd8c4bf | ||
|
|
1594be9d46 | ||
|
|
7eecf2621d | ||
|
|
088a0fc41f | ||
|
|
9178c65c4a | ||
|
|
f93d741cef | ||
|
|
81fa73935f | ||
|
|
88c71ccdfa | ||
|
|
5b3b9e44fa | ||
|
|
ac2ff2ec92 | ||
|
|
79e0d39280 | ||
|
|
fd9113109c | ||
|
|
da7e1067d2 | ||
|
|
75779b0373 | ||
|
|
a3887519f9 | ||
|
|
2ec6ad5b5c | ||
|
|
2d541c71ba | ||
|
|
46f3bd6014 | ||
|
|
c374c97e66 | ||
|
|
a4ca53004f | ||
|
|
2d9473ea7c | ||
|
|
6bfa48bed8 | ||
|
|
43167fe7a1 | ||
|
|
39391e5522 | ||
|
|
ec509a7b43 | ||
|
|
a76bd89ec1 | ||
|
|
cafdb29024 | ||
|
|
d79d0d3d09 | ||
|
|
7606c5b4e2 | ||
|
|
bc9a0fd740 | ||
|
|
7f9a106fca | ||
|
|
0bcc8f7f6a | ||
|
|
31b7587571 | ||
|
|
82fb1ea2bc | ||
|
|
bd5e46e712 | ||
|
|
542469bc67 | ||
|
|
631684cb05 | ||
|
|
54f4572533 | ||
|
|
ca933f624a | ||
|
|
90ab974e03 | ||
|
|
daeeb09798 | ||
|
|
c8935b885e |
@@ -0,0 +1,30 @@
|
||||
## Issue
|
||||
|
||||
<!-- #GitHubIssueNumber / Issue Link / Describe the problem here (if the issue hasn't been defined) -->
|
||||
|
||||
## What did you do?
|
||||
|
||||
<!--
|
||||
- Describe what did you do in this code
|
||||
- Example:
|
||||
- [CORRECT] Add auth feature / Modify auth feature.
|
||||
- [WRONG] Create file /controllers/auth.ts, /models/user.ts, modify /routes/web.ts
|
||||
-->
|
||||
|
||||
## Screenshot / Video
|
||||
|
||||
<!--
|
||||
Attach pictures of this work / video explanation about this work
|
||||
-->
|
||||
|
||||
## Note for Deployment
|
||||
|
||||
<!--
|
||||
- Tell to DevOps what should he do to deploy this change
|
||||
- example:
|
||||
- Please run migration when deploy this
|
||||
- Please upload these files into folder xxx
|
||||
- Please create folder `xxx` and give permission `777`
|
||||
- Please run this SQL after deploy ```sql INSERT INTO xxx (...) ```
|
||||
- etc...
|
||||
-->
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 511 KiB |
@@ -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,168 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { FC, ReactElement, useState } from 'react';
|
||||
import {
|
||||
FilterOutlined,
|
||||
SearchOutlined,
|
||||
EditOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { DataTable, Filter } 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 Indah',
|
||||
}));
|
||||
|
||||
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 [showFilter, setShowFilter] = useState(false);
|
||||
|
||||
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-lg 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>
|
||||
|
||||
<div className="relative">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
className="flex items-center gap-3"
|
||||
disabled
|
||||
onClick={() => setShowFilter(!showFilter)}
|
||||
>
|
||||
<FilterOutlined />
|
||||
Filters
|
||||
</Button>
|
||||
{showFilter && (
|
||||
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg">
|
||||
<Filter onClose={() => setShowFilter(false)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<DataTable data={mockData} columns={columns} table={table} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
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,145 @@
|
||||
import {
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
UsergroupAddOutlined,
|
||||
UsergroupDeleteOutlined,
|
||||
UserSwitchOutlined,
|
||||
} 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-lg 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">
|
||||
{/* Participants */}
|
||||
<div 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">
|
||||
<UsergroupAddOutlined 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">Participants</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Roll and Reroll */}
|
||||
<div 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">
|
||||
<ReloadOutlined 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">
|
||||
Roll and Reroll
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Redeem */}
|
||||
<div 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">
|
||||
<UserSwitchOutlined 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">Redeem</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Inactive Users */}
|
||||
<div 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">
|
||||
<UsergroupDeleteOutlined 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">Inactive Users</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-red-500 p-0 font-normal hover:bg-transparent hover:text-red-700"
|
||||
>
|
||||
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.webp"
|
||||
alt="Lanyard IMPHNEN"
|
||||
className="h-full"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Right-side illustration */}
|
||||
<img
|
||||
src="gacha.webp"
|
||||
alt=""
|
||||
className="rounded-lg hidden xl:block xl:min-w-[436px] h-auto object-cover"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
@@ -1,11 +1,9 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Navbar } from '@imphnen-frontend-service/ui/organisms';
|
||||
|
||||
export const AppLayout: FC = (): ReactElement => {
|
||||
return (
|
||||
<main className="bg-primary-50 min-h-screen">
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,36 @@
|
||||
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 => {
|
||||
return <>Hallo</>;
|
||||
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;
|
||||
|
||||
@@ -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,232 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { FC, ReactElement, useState } from 'react';
|
||||
import {
|
||||
FilterOutlined,
|
||||
SearchOutlined,
|
||||
AuditOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms';
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
PaginationState,
|
||||
useReactTable,
|
||||
RowSelectionState,
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
type Status = 'valid' | 'invalid' | 'unchecked';
|
||||
|
||||
interface Prize {
|
||||
id: number;
|
||||
name: string;
|
||||
orderValid: Status;
|
||||
items: string;
|
||||
address: string;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
const items = [
|
||||
'Sertifikat + Laminating',
|
||||
'Lanyard + ID Card',
|
||||
'Pin',
|
||||
'Sticker Isi 3',
|
||||
'Sticker Isi 5',
|
||||
'Gelang Karet',
|
||||
];
|
||||
|
||||
// Mock data for transactions
|
||||
const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
name: 'Nama Lengkap',
|
||||
orderValid: (i % 3 === 0
|
||||
? 'invalid'
|
||||
: i % 5 === 0
|
||||
? 'unchecked'
|
||||
: 'valid') as Status,
|
||||
items: items[i % items.length],
|
||||
address: 'Jl. Pantai Cibaduyut Indah',
|
||||
status: (i % 3 === 0
|
||||
? 'unchecked'
|
||||
: i % 5 === 0
|
||||
? 'invalid'
|
||||
: 'valid') as Status,
|
||||
}));
|
||||
|
||||
const columns: ColumnDef<Prize>[] = [
|
||||
{
|
||||
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: 'Order Valid?',
|
||||
accessorKey: 'orderValid',
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.orderValid;
|
||||
const statusColors: Record<Status, string> = {
|
||||
valid: 'bg-success-200 text-success-500',
|
||||
invalid: 'bg-danger-200 text-danger-500',
|
||||
unchecked: 'bg-warning-200 text-warning-900',
|
||||
};
|
||||
const statusText: Record<Status, string> = {
|
||||
valid: 'Valid',
|
||||
invalid: 'Invalid',
|
||||
unchecked: 'Unchecked',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
|
||||
>
|
||||
{statusText[status]}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Items',
|
||||
accessorKey: 'items',
|
||||
},
|
||||
{
|
||||
header: 'Alamat Pengiriman',
|
||||
accessorKey: 'address',
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
accessorKey: 'status',
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.status;
|
||||
const statusColors: Record<Status, string> = {
|
||||
valid: 'bg-success-200 text-success-500',
|
||||
invalid: 'bg-danger-200 text-danger-500',
|
||||
unchecked: 'bg-warning-200 text-warning-900',
|
||||
};
|
||||
const statusText: Record<Status, string> = {
|
||||
valid: 'Valid',
|
||||
invalid: 'Invalid',
|
||||
unchecked: 'Unchecked',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`py-2 px-4 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]" /> Process
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 9,
|
||||
});
|
||||
|
||||
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||
const [showFilter, setShowFilter] = useState(false);
|
||||
|
||||
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-lg shadow p-4">
|
||||
<h1 className="text-p2 font-semibold">Data Pengiriman Hadiah</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>
|
||||
|
||||
<div className="relative">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
className="flex items-center gap-3"
|
||||
onClick={() => setShowFilter(!showFilter)}
|
||||
>
|
||||
<FilterOutlined />
|
||||
Filters
|
||||
</Button>
|
||||
{showFilter && (
|
||||
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg">
|
||||
<Filter onClose={() => setShowFilter(false)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<DataTable data={mockData} columns={columns} table={table} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
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,186 @@
|
||||
import * as React from 'react';
|
||||
import { FC, ReactElement, useState } from 'react';
|
||||
import {
|
||||
FilterOutlined,
|
||||
SearchOutlined,
|
||||
AuditOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { DataTable, Filter } 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<Transaction>[] = [
|
||||
{
|
||||
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-200 text-success-500',
|
||||
invalid: 'bg-danger-200 text-danger-500',
|
||||
unchecked: 'bg-warning-200 text-warning-900',
|
||||
};
|
||||
const statusText: Record<TransactionStatus, string> = {
|
||||
valid: 'Valid',
|
||||
invalid: 'Invalid',
|
||||
unchecked: 'Unchecked',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`py-2 px-4 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 [showFilter, setShowFilter] = useState(false);
|
||||
|
||||
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-lg 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>
|
||||
|
||||
<div className="relative">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
className="flex items-center gap-3"
|
||||
onClick={() => setShowFilter(!showFilter)}
|
||||
>
|
||||
<FilterOutlined />
|
||||
Filters
|
||||
</Button>
|
||||
{showFilter && (
|
||||
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg">
|
||||
<Filter onClose={() => setShowFilter(false)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<DataTable data={mockTransactions} columns={columns} table={table} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
@@ -17,59 +17,55 @@
|
||||
|
||||
--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-200: #d1d1d1;
|
||||
--color-neutral-300: #b0b0b0;
|
||||
--color-neutral-400: #888888;
|
||||
--color-neutral-500: #6d6d6d;
|
||||
--color-neutral-600: #5d5d5d;
|
||||
--color-neutral-700: #4a4a4a;
|
||||
--color-neutral-800: #3a3a3a;
|
||||
--color-neutral-900: #2f2f2f;
|
||||
--color-neutral-700: #4f4f4f;
|
||||
--color-neutral-800: #454545;
|
||||
--color-neutral-900: #3d3d3d;
|
||||
--color-neutral-950: #2b2b2b;
|
||||
|
||||
--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-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-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-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-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-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-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;
|
||||
--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;
|
||||
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 8.6 MiB |
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms";
|
||||
import { ForgotStep, OtpForm } from "@imphnen-frontend-service/ui/molecules";
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]'>
|
||||
<div className='bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6'>
|
||||
<RegisterResetBanner />
|
||||
<div className='border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[48px] flex flex-col justify-center'>
|
||||
<ForgotStep step={2} />
|
||||
<h3 className='mt-5 text-3xl font-semibold text-primary-500'>Forgot Password</h3>
|
||||
<h5 className='mt-2 text-xl font-medium text-primary-500'>Yeay~! Pesan dari dunia lain (a.k.a email-mu) sudah tiba!</h5>
|
||||
<OtpForm />
|
||||
<Button>Summon Password Baru!</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms";
|
||||
import { ForgotStep } from "@imphnen-frontend-service/ui/molecules";
|
||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]'>
|
||||
<div className='bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6'>
|
||||
<RegisterResetBanner />
|
||||
<div className='border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[48px] flex flex-col justify-center'>
|
||||
<ForgotStep step={1} />
|
||||
<h3 className='mt-5 text-3xl font-semibold text-primary-500'>Forgot Password</h3>
|
||||
<h5 className='mt-2 text-xl font-medium text-primary-500'>Masukkan email-mu, dan biarkan kami memanggil password-mu kembali dari dunia lain!</h5>
|
||||
<h6 className='text-gray-600 mt-8'>Email</h6>
|
||||
<Input type='email' size='lg' placeholder='Contoh: yourname@mail.com'></Input>
|
||||
<Button variant='primary' className='mt-7'>Kirim Kode OTP ^^</Button>
|
||||
</div>
|
||||
</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;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms";
|
||||
import { ForgotStep } from "@imphnen-frontend-service/ui/molecules";
|
||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]'>
|
||||
<div className='bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6'>
|
||||
<RegisterResetBanner />
|
||||
<div className='border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[48px] flex flex-col justify-center'>
|
||||
<ForgotStep step={3} />
|
||||
<h3 className='mt-5 text-3xl font-semibold text-primary-500'>Forgot Password</h3>
|
||||
<h5 className='mt-2 text-md font-medium text-primary-500'>Sekarang, buatlah password baru yang lebih kuat, seperti jurus andalanmu!<span role="img" aria-label='emoji'>🔥⚡</span></h5>
|
||||
<div className='my-4'>
|
||||
<h6 className='text-gray-700'>Password Baru</h6>
|
||||
<Input className='w-full' type="password" placeholder='Buat password sekokoh armor legendary!'/>
|
||||
<h6 className='text-gray-700 mt-5'>Ulang Password Baru</h6>
|
||||
<Input className='w-full' type="password" placeholder='Pastikan Cocok! Jangan sampai ada typo, Senpai~!'/>
|
||||
</div>
|
||||
<div className='mb-4 text-gray-700 flex flex-col gap-3 text-sm'>
|
||||
<h6><span role='img' aria-label='emoji'>💡</span> Tips dari kami:</h6>
|
||||
<h6><span role='img' aria-label='emoji'>✅ </span> Buat kombinasi huruf besar, kecil, angka, dan simbol untuk kekuatan maksimal!</h6>
|
||||
<h6><span role='img' aria-label='emoji'>✅</span> Pastikan kamu ingat password-mu atau simpan di tempat aman~</h6>
|
||||
<h6>Masukkan password baru dan bersiaplah untuk kembali bertualang!</h6>
|
||||
</div>
|
||||
<Button className='mt-5'>Summon Password Baru!</Button>
|
||||
</div>
|
||||
</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;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { LoginBanner } from "@imphnen-frontend-service/ui/organisms"
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { InputForm } from '@imphnen-frontend-service/ui/molecules';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const error = searchParams.get("error"); // Ambil nilai ?error=
|
||||
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]'>
|
||||
<div className='bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6'>
|
||||
<LoginBanner />
|
||||
<div className='border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[96px] flex justify-center'>
|
||||
<div className='w-[404px]'>
|
||||
<h2 className='text-4xl font-semibold text-primary-500 text-center mb-2'>Hallo Minna-san</h2>
|
||||
<h5 className='text-xl font-medium text-primary-500 text-center'>Welcome to Dimentorin by IMPHNEN</h5>
|
||||
<InputForm label='Email' error={error ? error : undefined} size='lg' className='w-full' placeholder='Masukkan email-mu, Senpai~! ✨ (Pastikan tidak typo, ya~ 😆)' />
|
||||
<InputForm label="password" error={error ? error : undefined} className='w-full' size='lg' type='password' placeholder='Masukkan password rahasiamu!' />
|
||||
<div className='flex justify-end my-5'>
|
||||
<a href="/auth/forgot" className='text-primary-500 font-medium'>Lupa Password ?</a>
|
||||
</div>
|
||||
<Button className='w-full'>Enter Isekai</Button>
|
||||
<div className='flex my-3 gap-3 justify-center'>
|
||||
<h5>Belum Punya akun ?</h5>
|
||||
<a href="/auth/register" className='text-primary-500 font-medium'>Daftar Disini</a>
|
||||
</div>
|
||||
<div className="flex items-center w-full">
|
||||
<div className="flex-grow border-t border-blue-400 opacity-50"></div>
|
||||
<span className="px-3 text-blue-400">Or</span>
|
||||
<div className="flex-grow border-t border-blue-400 opacity-50"></div>
|
||||
</div>
|
||||
<Button className='w-full my-3 text-gray-500 gap-2' variant='secondary'>
|
||||
<p>Log In With Google</p>
|
||||
<img src="/image/33978ce5bed2da9bf9d73acad802182a.webp" alt="Google Icon" width={24}/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</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;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms";
|
||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]'>
|
||||
<div className='bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6'>
|
||||
<RegisterResetBanner />
|
||||
<div className='border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[96px] flex justify-center'>
|
||||
<div className='w-[404px]'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import {
|
||||
InputForm,
|
||||
Modal,
|
||||
Stepper,
|
||||
} from '@imphnen-frontend-service/ui/molecules';
|
||||
import { useQueryState } from '../../../hooks/use-query-state';
|
||||
|
||||
interface IModalFormForgotPasswordProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ModalFormForgotPassword = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: IModalFormForgotPasswordProps) => {
|
||||
const {
|
||||
step: currentStep,
|
||||
nextStep,
|
||||
prevStep,
|
||||
resetStep,
|
||||
} = useQueryState('step', {
|
||||
defaultValue: 1,
|
||||
maxValue: 3,
|
||||
minValue: 1,
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="py-[45px] min-w-[400px] lg:min-w-[455px] px-7"
|
||||
isOpen={isOpen}
|
||||
onClose={() => {
|
||||
onClose();
|
||||
resetStep();
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
Forgot Password
|
||||
</h1>
|
||||
<Stepper currentStep={currentStep} totalSteps={3} />
|
||||
</Modal.Header>
|
||||
<Modal.Content className="space-y-6">
|
||||
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||
{currentStep === 2 && (
|
||||
<StepTwo nextStep={nextStep} prevStep={prevStep} />
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<StepThree onClose={onClose} resetStep={resetStep} />
|
||||
)}
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
interface IStepOneProps {
|
||||
nextStep: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
|
||||
<>
|
||||
<InputForm
|
||||
label="Email"
|
||||
placeholder="Masukkan Email yang Terdaftar"
|
||||
type="email"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="flex gap-6">
|
||||
<Button variant="bordered" size="md" className="w-full" onClick={onClose}>
|
||||
Back To Login
|
||||
</Button>
|
||||
<Button size="md" className="w-full" onClick={nextStep}>
|
||||
Kirim OTP
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
interface IStepTwoProps {
|
||||
nextStep: () => void;
|
||||
prevStep: () => void;
|
||||
}
|
||||
|
||||
const StepTwo = ({ nextStep, prevStep }: IStepTwoProps) => (
|
||||
<>
|
||||
{/* TODO: Change component using OTP Input */}
|
||||
<InputForm
|
||||
label="Kode OTP"
|
||||
placeholder="Masukkan Kode OTP"
|
||||
type="text"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="flex gap-6">
|
||||
<Button
|
||||
size="md"
|
||||
variant="bordered"
|
||||
className="w-full"
|
||||
onClick={prevStep}
|
||||
>
|
||||
Change Email
|
||||
</Button>
|
||||
<Button size="md" className="w-full" onClick={nextStep}>
|
||||
Reset Password
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
interface IStepThreeProps {
|
||||
onClose: () => void;
|
||||
resetStep: () => void;
|
||||
}
|
||||
|
||||
const StepThree = ({ onClose, resetStep }: IStepThreeProps) => (
|
||||
<>
|
||||
<InputForm
|
||||
label="Password Baru"
|
||||
placeholder="Masukkan Password Baru"
|
||||
type="password"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<InputForm
|
||||
label="Ulang Password"
|
||||
placeholder="Masukkan Ulang Password"
|
||||
type="password"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<Button
|
||||
size="md"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
console.log('Password reset submitted');
|
||||
onClose();
|
||||
resetStep();
|
||||
}}
|
||||
>
|
||||
Buat Password Baru
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
export default ModalFormForgotPassword;
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface IModalFormLogin {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onForgotPassword: () => void;
|
||||
}
|
||||
|
||||
const ModalFormLogin = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onForgotPassword,
|
||||
}: IModalFormLogin) => {
|
||||
return (
|
||||
<Modal
|
||||
className="py-[45px] min-w-[400px] lg:min-w-[455px] px-7"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
<h1 className="text-end text-primary-500 text-xl font-medium">
|
||||
<Button variant="text" onClick={onForgotPassword}>
|
||||
Lupa Password?
|
||||
</Button>
|
||||
</h1>
|
||||
<Button size="md" className="w-full">
|
||||
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
|
||||
</Link>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalFormLogin;
|
||||
@@ -0,0 +1,123 @@
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
|
||||
import { useQueryState } from '../../../hooks/use-query-state';
|
||||
|
||||
interface IModalFormRegisterProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
|
||||
const {
|
||||
step: currentStep,
|
||||
nextStep,
|
||||
prevStep,
|
||||
resetStep,
|
||||
} = useQueryState('registerStep', {
|
||||
defaultValue: 1,
|
||||
maxValue: 2,
|
||||
minValue: 1,
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="py-[45px] min-w-[400px] lg:min-w-[455px] px-7"
|
||||
isOpen={isOpen}
|
||||
onClose={() => {
|
||||
onClose();
|
||||
resetStep();
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
Register
|
||||
</h1>
|
||||
<h2 className="text-neutral-500 text-lg text-center">
|
||||
{currentStep === 1 ? 'Info Akun' : 'Informasi Pengiriman Hadiah'}
|
||||
</h2>
|
||||
</Modal.Header>
|
||||
<Modal.Content className="space-y-6">
|
||||
{currentStep === 1 && <StepOne nextStep={nextStep} onClose={onClose} />}
|
||||
{currentStep === 2 && (
|
||||
<StepTwo nextStep={nextStep} prevStep={prevStep} />
|
||||
)}
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
interface IStepOneProps {
|
||||
nextStep: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
|
||||
<>
|
||||
<InputForm
|
||||
label="Nama Lengkap"
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
type="text"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<InputForm
|
||||
label="Email"
|
||||
placeholder="Masukkan Email Anda"
|
||||
type="email"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<InputForm
|
||||
label="Password"
|
||||
placeholder="Masukkan Password"
|
||||
type="password"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<InputForm
|
||||
label="Ulangi Password"
|
||||
placeholder="Masukkan Ulang Password"
|
||||
type="password"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<Button size="md" className="w-full" onClick={nextStep}>
|
||||
Selanjutnya
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
interface IStepTwoProps {
|
||||
nextStep: () => void;
|
||||
prevStep: () => void;
|
||||
}
|
||||
|
||||
const StepTwo = ({ nextStep, prevStep }: IStepTwoProps) => (
|
||||
<>
|
||||
<InputForm
|
||||
label="Nomor Telepon"
|
||||
placeholder="Masukkan Nomor Telepon Aktif"
|
||||
type="text"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<InputForm
|
||||
label="Alamat Pengiriman"
|
||||
placeholder="Masukkan Alamat Pengiriman"
|
||||
type="text"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="flex gap-6">
|
||||
<Button size="md" variant="text" className="w-[40%]" onClick={prevStep}>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button size="md" className="w-full" onClick={nextStep}>
|
||||
Gacha Sekarang
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export default ModalFormRegister;
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ArrowDownOutlined } from '@ant-design/icons';
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { ModalsGacha } from '@imphnen-frontend-service/ui/organisms';
|
||||
import { cn } from '@imphnen-frontend-service/utils';
|
||||
import { FC, Fragment, ReactElement, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import ModalFormForgotPassword from './_components/form/modal-form-forgot-password';
|
||||
import ModalFormLogin from './_components/form/modal-form-login';
|
||||
import ModalFormRegister from './_components/form/modal-form-register';
|
||||
|
||||
interface GachaItemProps {
|
||||
src: string;
|
||||
@@ -12,9 +13,9 @@ interface GachaItemProps {
|
||||
}
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
// Pinjem
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
//
|
||||
const [showModalForgotPassword, setShowModalForgotPassword] = useState(false);
|
||||
const [showModalLogin, setShowModalLogin] = useState(false);
|
||||
const [showModalRegister, setShowModalRegister] = useState(false);
|
||||
|
||||
const scrollToRoulette = () => {
|
||||
const rouletteSection = document.getElementById('roulette');
|
||||
@@ -66,6 +67,11 @@ export const Components: FC = (): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleForgotPasswordClick = () => {
|
||||
setShowModalLogin(false);
|
||||
setShowModalForgotPassword(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{/* Landing Page */}
|
||||
@@ -217,22 +223,48 @@ export const Components: FC = (): ReactElement => {
|
||||
Spin Now
|
||||
</Button>
|
||||
</div>
|
||||
{/* TODO: Change using real button trigger */}
|
||||
<div className="flex gap-4 col-span-4 md:col-span-8 lg:col-span-6 justify-center items-center">
|
||||
<Button onClick={() => setShowModalLogin(true)}>
|
||||
Open Modal Login
|
||||
</Button>
|
||||
<Button onClick={() => setShowModalForgotPassword(true)}>
|
||||
Open Modal Forgot Password
|
||||
</Button>
|
||||
<Button onClick={() => setShowModalRegister(true)}>
|
||||
Open Modal Register
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
{/* 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 */}
|
||||
<div>
|
||||
<p>Debug : Test Modals Gacha</p>
|
||||
<Button onClick={
|
||||
() => setShowModal(true)
|
||||
}>Tekan</Button>
|
||||
</div>
|
||||
{showModal && createPortal(
|
||||
<ModalsGacha></ModalsGacha>,
|
||||
document.body
|
||||
)}
|
||||
{/**Izin pake untuk debug modals gacha */}
|
||||
{/* Login Modal */}
|
||||
<ModalFormLogin
|
||||
isOpen={showModalLogin}
|
||||
onClose={() => setShowModalLogin(false)}
|
||||
onForgotPassword={handleForgotPasswordClick}
|
||||
key="login"
|
||||
/>
|
||||
|
||||
{/* Register Modal */}
|
||||
<ModalFormRegister
|
||||
isOpen={showModalRegister}
|
||||
onClose={() => {
|
||||
setShowModalRegister(false);
|
||||
}}
|
||||
key="register"
|
||||
/>
|
||||
|
||||
{/* Forgot Password Modal */}
|
||||
<ModalFormForgotPassword
|
||||
isOpen={showModalForgotPassword}
|
||||
onClose={() => {
|
||||
setShowModalForgotPassword(false);
|
||||
setShowModalLogin(true);
|
||||
}}
|
||||
key="forgot-password"
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
interface UseQueryStateOptions {
|
||||
defaultValue: number;
|
||||
maxValue?: number;
|
||||
minValue?: number;
|
||||
}
|
||||
|
||||
export const useQueryState = (key: string, options: UseQueryStateOptions) => {
|
||||
const { defaultValue, maxValue = Infinity, minValue = 1 } = options;
|
||||
|
||||
const getQueryParam = (param: string): string | null => {
|
||||
if (typeof window === 'undefined') return null;
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
return searchParams.get(param);
|
||||
};
|
||||
|
||||
const setQueryParam = (param: string, value: string) => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
searchParams.set(param, value);
|
||||
const newUrl = `${window.location.pathname}?${searchParams.toString()}`;
|
||||
window.history.replaceState(null, '', newUrl);
|
||||
};
|
||||
|
||||
const initialValue = () => {
|
||||
const queryValue = getQueryParam(key);
|
||||
const parsedValue = queryValue ? parseInt(queryValue, 10) : defaultValue;
|
||||
return Math.max(minValue, Math.min(maxValue, parsedValue));
|
||||
};
|
||||
|
||||
const [value, setValue] = useState<number>(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
const queryValue = getQueryParam(key);
|
||||
const newValue = queryValue ? parseInt(queryValue, 10) : defaultValue;
|
||||
setValue(Math.max(minValue, Math.min(maxValue, newValue)));
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, [key, defaultValue, minValue, maxValue]);
|
||||
|
||||
const updateValue = useCallback(
|
||||
(newValue: number) => {
|
||||
const constrainedValue = Math.max(minValue, Math.min(maxValue, newValue));
|
||||
setValue(constrainedValue);
|
||||
setQueryParam(key, constrainedValue.toString());
|
||||
},
|
||||
[key, minValue, maxValue]
|
||||
);
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
updateValue(value + 1);
|
||||
}, [value, updateValue]);
|
||||
|
||||
const prevStep = useCallback(() => {
|
||||
updateValue(value - 1);
|
||||
}, [value, updateValue]);
|
||||
|
||||
const resetStep = useCallback(() => {
|
||||
updateValue(defaultValue);
|
||||
}, [defaultValue, updateValue]);
|
||||
|
||||
return {
|
||||
step: value,
|
||||
nextStep,
|
||||
prevStep,
|
||||
resetStep,
|
||||
};
|
||||
};
|
||||
@@ -17,59 +17,55 @@
|
||||
|
||||
--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-200: #d1d1d1;
|
||||
--color-neutral-300: #b0b0b0;
|
||||
--color-neutral-400: #888888;
|
||||
--color-neutral-500: #6d6d6d;
|
||||
--color-neutral-600: #5d5d5d;
|
||||
--color-neutral-700: #4a4a4a;
|
||||
--color-neutral-800: #3a3a3a;
|
||||
--color-neutral-900: #2f2f2f;
|
||||
--color-neutral-700: #4f4f4f;
|
||||
--color-neutral-800: #454545;
|
||||
--color-neutral-900: #3d3d3d;
|
||||
--color-neutral-950: #2b2b2b;
|
||||
|
||||
--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-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-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-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-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-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-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;
|
||||
--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;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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/).
|
||||
@@ -0,0 +1,12 @@
|
||||
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: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@imphnen-frontend-service/service",
|
||||
"version": "0.0.1",
|
||||
"main": "./index.js",
|
||||
"types": "./index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.mjs",
|
||||
"require": "./index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "service",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/service/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"nx-release-publish": {
|
||||
"options": {
|
||||
"packageRoot": "dist/{projectRoot}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"version": {
|
||||
"generatorOptions": {
|
||||
"packageRoot": "dist/{projectRoot}",
|
||||
"currentVersionResolver": "git-tag",
|
||||
"fallbackCurrentVersionResolver": "disk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { api } from '@imphnen-frontend-service/utils';
|
||||
import {
|
||||
TLoginRequest,
|
||||
TLoginResponse,
|
||||
TRegisterRequest,
|
||||
TVerifyEmailRequest,
|
||||
} from '../../types/auth';
|
||||
import { TResponseMessage } from '../../types/common';
|
||||
|
||||
export const postLogin = async (
|
||||
payload: TLoginRequest
|
||||
): Promise<TLoginResponse> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/login',
|
||||
data: payload,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postRegister = async (
|
||||
payload: TRegisterRequest
|
||||
): Promise<TResponseMessage> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/register',
|
||||
data: payload,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postVerifyEmail = async (
|
||||
payload: TVerifyEmailRequest
|
||||
): Promise<TResponseMessage> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/verify',
|
||||
data: payload,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './gacha';
|
||||
export * from './users';
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMutation, UseMutationResult } from '@tanstack/react-query';
|
||||
import { postLogin, postRegister, postVerifyEmail } from '../../api/auth';
|
||||
import {
|
||||
TLoginRequest,
|
||||
TLoginResponse,
|
||||
TRegisterRequest,
|
||||
TVerifyEmailRequest,
|
||||
} from '../../types/auth';
|
||||
import { TResponseError, TResponseMessage } from '../../types/common';
|
||||
|
||||
export const usePostLogin = (): UseMutationResult<
|
||||
TLoginResponse,
|
||||
TResponseError,
|
||||
TLoginRequest,
|
||||
unknown
|
||||
> => {
|
||||
return useMutation({
|
||||
mutationKey: ['post-login'],
|
||||
mutationFn: async (payload) => await postLogin(payload),
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostRegister = (): UseMutationResult<
|
||||
TResponseMessage,
|
||||
TResponseError,
|
||||
TRegisterRequest,
|
||||
unknown
|
||||
> => {
|
||||
return useMutation({
|
||||
mutationKey: ['post-register'],
|
||||
mutationFn: async (payload) => await postRegister(payload),
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostVerifyEmail = (): UseMutationResult<
|
||||
TResponseMessage,
|
||||
TResponseError,
|
||||
TVerifyEmailRequest,
|
||||
unknown
|
||||
> => {
|
||||
return useMutation({
|
||||
mutationKey: ['post-verify-email'],
|
||||
mutationFn: async (payload) => await postVerifyEmail(payload),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './gacha';
|
||||
export * from './users';
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './api';
|
||||
export * from './hooks';
|
||||
export * from './types';
|
||||