feat: Initialize customer-spa project structure

Sprint 1 - Foundation Setup:

Created customer-spa/ folder structure:
```
customer-spa/
├── src/
│   ├── pages/          # Customer pages (Shop, Cart, Checkout, Account)
│   ├── components/     # Reusable components
│   ├── lib/
│   │   ├── api/        # API client
│   │   ├── cart/       # Cart state management
│   │   ├── checkout/   # Checkout logic
│   │   └── tracking/   # Analytics & pixel tracking
│   ├── hooks/          # Custom React hooks
│   ├── contexts/       # React contexts
│   └── types/          # TypeScript types
├── public/             # Static assets
├── package.json        # Dependencies
├── vite.config.ts      # Vite configuration (port 5174)
├── tsconfig.json       # TypeScript configuration
├── tailwind.config.js  # TailwindCSS configuration
├── postcss.config.js   # PostCSS configuration
└── .eslintrc.cjs       # ESLint configuration
```

Configuration:
 Vite dev server on port 5174 (admin-spa uses 5173)
 HTTPS with shared SSL cert
 TypeScript + React 18
 TailwindCSS + shadcn/ui
 React Query for data fetching
 Zustand for state management
 React Hook Form + Zod for forms
 React Router for routing

Dependencies Added:
- Core: React 18, React DOM, React Router
- UI: Radix UI components, Lucide icons
- State: Zustand, TanStack Query
- Forms: React Hook Form, Zod, @hookform/resolvers
- Styling: TailwindCSS, class-variance-authority
- Utils: clsx, tailwind-merge, sonner (toast)

Next Steps:
- Create main.tsx entry point
- Create App.tsx with routing
- Create base layout components
- Setup API client
- Implement cart state management

Ready for Sprint 1 implementation!
This commit is contained in:
dwindown
2025-11-21 13:05:04 +07:00
parent 0a6c4059c4
commit 342104eeab
6 changed files with 159 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'eslint.config.js'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
},
}

59
customer-spa/package.json Normal file
View File

@@ -0,0 +1,59 @@
{
"name": "woonoow-customer-spa",
"private": true,
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "vite --host woonoow.local --port 5174 --strictPort",
"build": "vite build",
"preview": "vite preview --port 5174",
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint . --ext ts,tsx --report-unused-disable-directives"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.547.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",
"react-router-dom": "^7.9.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"vaul": "^1.1.2",
"zod": "^3.24.1",
"zustand": "^5.0.8"
},
"devDependencies": {
"@hookform/resolvers": "^3.10.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.46.3",
"@typescript-eslint/parser": "^8.46.3",
"@vitejs/plugin-react": "^5.1.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.39.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.13",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.6.3",
"vite": "^5.4.8"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,25 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["./src/**/*.{ts,tsx,css}", "./components/**/*.{ts,tsx,css}"],
theme: {
container: { center: true, padding: "1rem" },
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))" },
muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" },
accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))" },
popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))" },
card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))" }
},
borderRadius: { lg: "12px", md: "10px", sm: "8px" }
}
},
plugins: [require("tailwindcss-animate")]
};

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"noEmit": true,
"strict": true,
"allowJs": false,
"types": [],
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] }
},
"include": ["src"]
}

View File

@@ -0,0 +1,29 @@
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import fs from 'node:fs';
import path from 'node:path';
const key = fs.readFileSync(path.resolve(__dirname, '../admin-spa/.cert/woonoow.local-key.pem'));
const cert = fs.readFileSync(path.resolve(__dirname, '../admin-spa/.cert/woonoow.local-cert.pem'));
export default defineConfig({
plugins: [react()],
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
server: {
host: 'woonoow.local',
port: 5174,
strictPort: true,
https: { key, cert },
cors: true,
origin: 'https://woonoow.local:5174',
hmr: { protocol: 'wss', host: 'woonoow.local', port: 5174 }
},
build: {
outDir: 'dist',
emptyOutDir: true,
rollupOptions: {
input: { app: 'src/main.tsx' },
output: { entryFileNames: 'app.js', assetFileNames: 'app.[ext]' }
}
}
});