From 515b78c57f64fe0f8579970a1af1d3239ab5fdfc Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Mon, 17 Mar 2025 09:39:04 +0700 Subject: [PATCH] feat: add backoffice apps --- apps/backoffice/eslint.config.mjs | 12 ++ apps/backoffice/index.html | 16 +++ apps/backoffice/postcss.config.mjs | 9 ++ apps/backoffice/project.json | 9 ++ apps/backoffice/public/favicon.ico | Bin 0 -> 15086 bytes apps/backoffice/src/app/layout.tsx | 14 ++ apps/backoffice/src/app/page.tsx | 221 +++++++++++++++++++++++++++++ apps/backoffice/src/index.css | 140 ++++++++++++++++++ apps/backoffice/src/main.tsx | 40 ++++++ apps/backoffice/src/middleware.ts | 11 ++ apps/backoffice/tsconfig.app.json | 27 ++++ apps/backoffice/tsconfig.json | 21 +++ apps/backoffice/tsconfig.spec.json | 30 ++++ apps/backoffice/vite.config.ts | 42 ++++++ package.json | 3 + 15 files changed, 595 insertions(+) create mode 100644 apps/backoffice/eslint.config.mjs create mode 100644 apps/backoffice/index.html create mode 100644 apps/backoffice/postcss.config.mjs create mode 100644 apps/backoffice/project.json create mode 100644 apps/backoffice/public/favicon.ico create mode 100644 apps/backoffice/src/app/layout.tsx create mode 100644 apps/backoffice/src/app/page.tsx create mode 100644 apps/backoffice/src/index.css create mode 100644 apps/backoffice/src/main.tsx create mode 100644 apps/backoffice/src/middleware.ts create mode 100644 apps/backoffice/tsconfig.app.json create mode 100644 apps/backoffice/tsconfig.json create mode 100644 apps/backoffice/tsconfig.spec.json create mode 100644 apps/backoffice/vite.config.ts diff --git a/apps/backoffice/eslint.config.mjs b/apps/backoffice/eslint.config.mjs new file mode 100644 index 0000000..e8e4590 --- /dev/null +++ b/apps/backoffice/eslint.config.mjs @@ -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: {}, + }, +]; diff --git a/apps/backoffice/index.html b/apps/backoffice/index.html new file mode 100644 index 0000000..3905403 --- /dev/null +++ b/apps/backoffice/index.html @@ -0,0 +1,16 @@ + + + + + Backoffice + + + + + + + +
+ + + diff --git a/apps/backoffice/postcss.config.mjs b/apps/backoffice/postcss.config.mjs new file mode 100644 index 0000000..63252e5 --- /dev/null +++ b/apps/backoffice/postcss.config.mjs @@ -0,0 +1,9 @@ +import { join } from 'path'; + +export default { + plugins: { + '@tailwindcss/postcss': { + base: join(import.meta.dirname, '../../'), + }, + }, +}; diff --git a/apps/backoffice/project.json b/apps/backoffice/project.json new file mode 100644 index 0000000..6ee852a --- /dev/null +++ b/apps/backoffice/project.json @@ -0,0 +1,9 @@ +{ + "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": {} +} diff --git a/apps/backoffice/public/favicon.ico b/apps/backoffice/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 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 { + return ( +
+ + +
+ ); +}; + +export default AppLayout; diff --git a/apps/backoffice/src/app/page.tsx b/apps/backoffice/src/app/page.tsx new file mode 100644 index 0000000..63b8712 --- /dev/null +++ b/apps/backoffice/src/app/page.tsx @@ -0,0 +1,221 @@ +import { ArrowDownOutlined } from '@ant-design/icons'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { cn } from '@imphnen-frontend-service/utils'; +import { FC, Fragment, ReactElement, useEffect } from 'react'; + +interface GachaItemProps { + src: string; + label: string; + className?: string; +} + +export const Components: FC = (): ReactElement => { + 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 = ({ + src, + label, + className, + }): ReactElement => { + return ( +
+
+ +
+

{label}

+
+ ); + }; + + return ( + + {/* Landing Page */} +
+
+ IMPHNEN Logo + +
+
+

New Merchandise

+

Coming UP

+
+
+ Periode Gacha: 1 - 31 Maret 2025 +
+
+ +
+

Let's Go Checkout Our Merch &

+

Gacha Your Prize Here

+
+ +
+ +
+
+ Merch 1 + + +
+
+ Official Merch IMPHNEN +
+
+ ~ 175k ~ +
+
+
+
+ Merch 2 + + +
+
+ IMPHNEN Mini Merch +
+
+ ~ 90k ~ +
+
+
+ {/* Roulette Page */} +
+
+
+

Gacha Roulette

+
+

+ How to participate +

+

+ Kamu harus melakukan pembelian merch batch 2 di shopee IMPHNEN +

+

+ How to gacha +

+
    +
  1. First, press the "Spin Now" button
  2. +
  3. + If the merch does not match you can reroll by paying IDR5,000 +
  4. +
  5. If it is appropriate you can submit
  6. +
  7. Then fill in the data for your merch delivery process
  8. +
+
+ +
+
+ Here Take Your Prize +
+
+ + + + + + +
+ +
+
+ {/* Diffuser & Cloud */} +
+
+ ); +}; + +export default Components; diff --git a/apps/backoffice/src/index.css b/apps/backoffice/src/index.css new file mode 100644 index 0000000..e9bf12a --- /dev/null +++ b/apps/backoffice/src/index.css @@ -0,0 +1,140 @@ +@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-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-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-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-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; + --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; + } +} diff --git a/apps/backoffice/src/main.tsx b/apps/backoffice/src/main.tsx new file mode 100644 index 0000000..7201952 --- /dev/null +++ b/apps/backoffice/src/main.tsx @@ -0,0 +1,40 @@ +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( + + + + + +); diff --git a/apps/backoffice/src/middleware.ts b/apps/backoffice/src/middleware.ts new file mode 100644 index 0000000..f32b107 --- /dev/null +++ b/apps/backoffice/src/middleware.ts @@ -0,0 +1,11 @@ +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; +}; diff --git a/apps/backoffice/tsconfig.app.json b/apps/backoffice/tsconfig.app.json new file mode 100644 index 0000000..6d35def --- /dev/null +++ b/apps/backoffice/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "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"] +} diff --git a/apps/backoffice/tsconfig.json b/apps/backoffice/tsconfig.json new file mode 100644 index 0000000..fdfab6c --- /dev/null +++ b/apps/backoffice/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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" +} diff --git a/apps/backoffice/tsconfig.spec.json b/apps/backoffice/tsconfig.spec.json new file mode 100644 index 0000000..9d3bb72 --- /dev/null +++ b/apps/backoffice/tsconfig.spec.json @@ -0,0 +1,30 @@ +{ + "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" + ] +} diff --git a/apps/backoffice/vite.config.ts b/apps/backoffice/vite.config.ts new file mode 100644 index 0000000..936ed86 --- /dev/null +++ b/apps/backoffice/vite.config.ts @@ -0,0 +1,42 @@ +/// +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, + }, + }, +})); diff --git a/package.json b/package.json index adfbc0e..4c65385 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "gacha:dev": "nx serve gacha", "gacha:build": "nx build gacha", "gacha:prod": "serve ./dist/apps/gacha", + "backoffice:dev": "nx serve backoffice", + "backoffice:build": "nx build backoffice", + "backoffice:prod": "serve ./dist/apps/gacha", "dimentorin:dev": "nx serve dimentorin", "dimentorin:build": "nx build dimentorin", "dimentorin:prod": "serve ./dist/apps/dimentorin",