From 62f45ed45492bc50974c9aaf1ed10d1d3ecf1f37 Mon Sep 17 00:00:00 2001 From: Asep Haryana Saputra <90584806+MythEclipse@users.noreply.github.com> Date: Fri, 22 May 2026 12:41:55 +0000 Subject: [PATCH] Add React web scaffold --- apps/web/index.html | 12 ++++ apps/web/moon.yml | 35 +++++++++++ apps/web/package.json | 33 ++++++++++ apps/web/postcss.config.js | 6 ++ apps/web/src/app.tsx | 25 ++++++++ apps/web/src/components/ui/button.tsx | 36 +++++++++++ apps/web/src/components/ui/card.tsx | 22 +++++++ apps/web/src/index.css | 27 +++++++++ apps/web/src/lib/utils.ts | 6 ++ apps/web/src/main.tsx | 10 +++ apps/web/src/pages/dashboard-page.tsx | 72 ++++++++++++++++++++++ apps/web/src/pages/landing-page.tsx | 87 +++++++++++++++++++++++++++ apps/web/src/store/ui-store.ts | 12 ++++ apps/web/tailwind.config.ts | 33 ++++++++++ apps/web/tsconfig.json | 14 +++++ apps/web/tsconfig.node.json | 10 +++ apps/web/vite.config.ts | 12 ++++ 17 files changed, 452 insertions(+) create mode 100644 apps/web/index.html create mode 100644 apps/web/moon.yml create mode 100644 apps/web/package.json create mode 100644 apps/web/postcss.config.js create mode 100644 apps/web/src/app.tsx create mode 100644 apps/web/src/components/ui/button.tsx create mode 100644 apps/web/src/components/ui/card.tsx create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/lib/utils.ts create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/src/pages/dashboard-page.tsx create mode 100644 apps/web/src/pages/landing-page.tsx create mode 100644 apps/web/src/store/ui-store.ts create mode 100644 apps/web/tailwind.config.ts create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsconfig.node.json create mode 100644 apps/web/vite.config.ts diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..2d65458 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,12 @@ + + + + + + ZeaVis Edu + + +
+ + + diff --git a/apps/web/moon.yml b/apps/web/moon.yml new file mode 100644 index 0000000..d8bc7f8 --- /dev/null +++ b/apps/web/moon.yml @@ -0,0 +1,35 @@ +type: application +language: typescript + +tasks: + dev: + command: bun run dev + local: true + build: + command: bun run build + deps: + - shared:build + inputs: + - src/**/* + - index.html + - package.json + - tsconfig.json + - tsconfig.node.json + - vite.config.ts + - tailwind.config.ts + - postcss.config.js + - ../../packages/shared/dist/**/* + outputs: + - dist + typecheck: + command: bun run typecheck + deps: + - shared:typecheck + inputs: + - src/**/* + - package.json + - tsconfig.json + - tsconfig.node.json + - vite.config.ts + - tailwind.config.ts + - ../../packages/shared/dist/**/* diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..a4b0feb --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,33 @@ +{ + "name": "@zeavis/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc --project tsconfig.json && vite build", + "preview": "vite preview --host 0.0.0.0", + "typecheck": "tsc --project tsconfig.json" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-query": "^5.59.16", + "@vitejs/plugin-react": "^4.3.3", + "@zeavis/shared": "workspace:*", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "tailwind-merge": "^2.5.4", + "zustand": "^5.0.1" + }, + "devDependencies": { + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^6.0.1" + } +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx new file mode 100644 index 0000000..e66e83a --- /dev/null +++ b/apps/web/src/app.tsx @@ -0,0 +1,25 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { DashboardPage } from '@/pages/dashboard-page'; +import { LandingPage } from '@/pages/landing-page'; + +const queryClient = new QueryClient(); + +const router = createBrowserRouter([ + { + path: '/', + element: , + }, + { + path: '/dashboard', + element: , + }, +]); + +export function App() { + return ( + + + + ); +} diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx new file mode 100644 index 0000000..b0ac053 --- /dev/null +++ b/apps/web/src/components/ui/button.tsx @@ -0,0 +1,36 @@ +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import type { ButtonHTMLAttributes } from 'react'; +import { cn } from '@/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + outline: 'border border-border bg-transparent hover:bg-muted', + ghost: 'hover:bg-muted', + }, + size: { + default: 'h-10 px-4 py-2', + lg: 'h-12 rounded-lg px-6', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +type ButtonProps = ButtonHTMLAttributes & + VariantProps & { + asChild?: boolean; + }; + +export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { + const Comp = asChild ? Slot : 'button'; + + return ; +} diff --git a/apps/web/src/components/ui/card.tsx b/apps/web/src/components/ui/card.tsx new file mode 100644 index 0000000..dae5948 --- /dev/null +++ b/apps/web/src/components/ui/card.tsx @@ -0,0 +1,22 @@ +import type { HTMLAttributes } from 'react'; +import { cn } from '@/lib/utils'; + +export function Card({ className, ...props }: HTMLAttributes) { + return
; +} + +export function CardHeader({ className, ...props }: HTMLAttributes) { + return
; +} + +export function CardTitle({ className, ...props }: HTMLAttributes) { + return

; +} + +export function CardDescription({ className, ...props }: HTMLAttributes) { + return

; +} + +export function CardContent({ className, ...props }: HTMLAttributes) { + return

; +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..21285dc --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,27 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 48 60% 97%; + --foreground: 156 26% 12%; + --card: 0 0% 100%; + --card-foreground: 156 26% 12%; + --primary: 142 60% 32%; + --primary-foreground: 0 0% 100%; + --muted: 84 35% 90%; + --muted-foreground: 156 12% 35%; + --border: 84 24% 82%; + --radius: 1rem; + } + + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground antialiased; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + } +} diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..2819a83 --- /dev/null +++ b/apps/web/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..81819a4 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { App } from './app'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/apps/web/src/pages/dashboard-page.tsx b/apps/web/src/pages/dashboard-page.tsx new file mode 100644 index 0000000..273f696 --- /dev/null +++ b/apps/web/src/pages/dashboard-page.tsx @@ -0,0 +1,72 @@ +import { BookOpen, History, Leaf, LayoutDashboard } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useUiStore } from '@/store/ui-store'; + +const dashboardCards = [ + { + icon: Leaf, + title: 'Deteksi Penyakit', + description: 'Area ini akan menjadi pintu masuk analisis gambar daun jagung.', + }, + { + icon: History, + title: 'Riwayat Analisis', + description: 'Hasil deteksi sebelumnya akan ditampilkan saat fitur data tersedia.', + }, + { + icon: BookOpen, + title: 'Materi Edukasi', + description: 'Konten edukasi penyakit jagung akan terhubung ke modul pembelajaran.', + }, +]; + +export function DashboardPage() { + const { dashboardCompact, toggleDashboardCompact } = useUiStore(); + + return ( +
+
+
+
+
+ Dashboard +
+

ZeaVis Edu Workspace

+

+ Placeholder awal untuk fitur deteksi, riwayat analisis, dan edukasi. +

+
+
+ + +
+
+ +
+ {dashboardCards.map((item) => ( + + +
+ +
+ {item.title} + {item.description} +
+ +
+ Belum ada data. Fitur akan dihubungkan pada iterasi berikutnya. +
+
+
+ ))} +
+
+
+ ); +} diff --git a/apps/web/src/pages/landing-page.tsx b/apps/web/src/pages/landing-page.tsx new file mode 100644 index 0000000..faba059 --- /dev/null +++ b/apps/web/src/pages/landing-page.tsx @@ -0,0 +1,87 @@ +import { ArrowRight, Leaf, ShieldCheck, Sprout } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { Button } from '@/components/ui/button'; +import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; + +const features = [ + { + icon: Leaf, + title: 'Deteksi penyakit daun jagung', + description: 'Fondasi aplikasi siap untuk integrasi model klasifikasi ZeaVis Edu.', + }, + { + icon: ShieldCheck, + title: 'Edukasi berbasis data', + description: 'Materi dan hasil analisis dapat dikembangkan di atas dashboard awal.', + }, + { + icon: Sprout, + title: 'Siap tumbuh bersama produk', + description: 'Monorepo memisahkan frontend, backend, dan shared types dengan jelas.', + }, +]; + +export function LandingPage() { + return ( +
+
+ + +
+
+
+ Platform edukasi kesehatan tanaman jagung +
+
+

+ Belajar mengenali penyakit daun jagung dengan alur digital yang rapi. +

+

+ Scaffold ini menyiapkan fondasi aplikasi ZeaVis Edu untuk antarmuka edukasi, + API, dan integrasi model machine learning berikutnya. +

+
+
+ + +
+
+ +
+ {features.map((feature) => ( + + +
+ +
+
+ {feature.title} + {feature.description} +
+
+
+ ))} +
+
+
+
+ ); +} diff --git a/apps/web/src/store/ui-store.ts b/apps/web/src/store/ui-store.ts new file mode 100644 index 0000000..8f0fcfa --- /dev/null +++ b/apps/web/src/store/ui-store.ts @@ -0,0 +1,12 @@ +import { create } from 'zustand'; + +type UiState = { + dashboardCompact: boolean; + toggleDashboardCompact: () => void; +}; + +export const useUiStore = create((set) => ({ + dashboardCompact: false, + toggleDashboardCompact: () => + set((state) => ({ dashboardCompact: !state.dashboardCompact })), +})); diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..88cb33f --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -0,0 +1,33 @@ +import type { Config } from 'tailwindcss'; + +export default { + darkMode: ['class'], + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + }, + }, + plugins: [], +} satisfies Config; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..14cbdd2 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@zeavis/shared": ["../../packages/shared/src/index.ts"], + "@zeavis/shared/*": ["../../packages/shared/src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..3710a09 --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + }, + "include": ["vite.config.ts", "tailwind.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..93cad99 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,12 @@ +import react from '@vitejs/plugin-react'; +import path from 'node:path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +});