ca-grow-ops-manager/frontend
fullsizemalt debc5d9447
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run
fix: restore missing Lucide icon imports
2025-12-18 19:32:31 -08:00
..
public chore(kiosk): update assetlinks.json with real SHA256 fingerprint 2025-12-11 23:16:52 -08:00
src fix: restore missing Lucide icon imports 2025-12-18 19:32:31 -08:00
components.json feat: Setup React Router, Tailwind, and shadcn/ui components 2025-12-09 01:19:33 -08:00
Dockerfile fix: add --legacy-peer-deps to Dockerfile for React Three Fiber compatibility 2025-12-17 01:59:53 -08:00
index.html feat(pwa): Add PWA support for installable kiosk app 2025-12-11 16:13:50 -08:00
nginx.conf feat: Linear-inspired UI redesign with Space Grotesk headlines 2025-12-12 14:29:47 -08:00
package-lock.json fix: correct 3D facility view navigation path (/facility-3d → /facility/3d) 2025-12-18 11:43:25 -08:00
package.json fix(3d-viewer): downgrade three.js/fiber/drei dependencies to stable versions and restore full viewer code 2025-12-17 07:28:51 -08:00
postcss.config.js fix: Revert to Tailwind v3 for Shadcn compatibility 2025-12-09 11:30:16 -08:00
README.md Initial commit: Spec Kit foundation complete 2025-12-08 23:54:12 -08:00
tailwind.config.js feat: Linear-inspired UI redesign with Space Grotesk headlines 2025-12-12 14:29:47 -08:00
tsconfig.json fix: Build & Runtime Fixes (TS Lax, Vite Types, Backend OpenSSL) 2025-12-09 11:18:04 -08:00
tsconfig.node.json fix: Add missing project files for backend/frontend build 2025-12-09 00:23:28 -08:00
vite.config.ts fix: Add missing project files for backend/frontend build 2025-12-09 00:23:28 -08:00
vitest.config.ts feat: Full Spec Kit compliance implementation 2025-12-11 09:53:32 -08:00

CA Grow Ops Manager — Frontend

Version: 0.1.0
Stack: TypeScript, React, Vite/Next.js, Radix UI/shadcn


Overview

The frontend is a mobile-first, accessible web application optimized for low-friction daily use in cultivation facilities. It provides intuitive interfaces for tasks, batches, labor tracking, compliance, and team communication.


Tech Stack

  • Language: TypeScript 5.x
  • Framework: React 18.x
  • Build Tool: Vite or Next.js (to be decided during implementation)
  • Component Library: Radix UI or shadcn-style accessible primitives
  • Styling: Tailwind CSS or modern CSS-in-JS
  • State Management: React Context + hooks (Zustand or Redux Toolkit for complex state)
  • Routing: React Router (Vite) or Next.js App Router
  • Forms: React Hook Form + Zod validation
  • API Client: Axios or Fetch with TypeScript types
  • Testing: Vitest + React Testing Library

Project Structure

Vite Option

frontend/
├── src/
│   ├── app/                  # App shell
│   │   ├── App.tsx
│   │   ├── Router.tsx
│   │   └── Layout.tsx
│   ├── pages/                # Page components
│   │   ├── Dashboard.tsx
│   │   ├── Tasks/
│   │   │   ├── TasksPage.tsx
│   │   │   ├── TaskDetail.tsx
│   │   │   └── TodayView.tsx
│   │   ├── Batches/
│   │   ├── Labor/
│   │   ├── Compliance/
│   │   └── Settings/
│   ├── components/           # Reusable components
│   │   ├── ui/               # Base UI components (buttons, inputs, etc.)
│   │   ├── tasks/            # Task-specific components
│   │   ├── batches/
│   │   └── shared/           # Shared components (nav, header, etc.)
│   ├── hooks/                # Custom React hooks
│   │   ├── useAuth.ts
│   │   ├── useTasks.ts
│   │   └── useApi.ts
│   ├── lib/                  # Utilities and helpers
│   │   ├── api.ts            # API client
│   │   ├── auth.ts           # Auth helpers
│   │   └── utils.ts
│   ├── types/                # TypeScript types
│   │   ├── api.ts
│   │   └── models.ts
│   ├── styles/               # Global styles
│   │   ├── index.css
│   │   └── tailwind.css
│   └── main.tsx              # Entry point
├── public/                   # Static assets
├── .env.example
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md

Next.js Option

frontend/
├── app/                      # Next.js App Router
│   ├── layout.tsx
│   ├── page.tsx              # Dashboard
│   ├── tasks/
│   │   ├── page.tsx
│   │   └── [id]/page.tsx
│   ├── batches/
│   ├── labor/
│   └── api/                  # API routes (if needed)
├── components/               # Same as Vite
├── hooks/
├── lib/
├── types/
├── styles/
└── public/

Design System

Component Library (Radix UI / shadcn)

Use accessible primitives for:

  • Buttons: Primary, secondary, destructive
  • Inputs: Text, number, date, select
  • Dialogs: Modals, drawers, alerts
  • Dropdowns: Menus, selects, comboboxes
  • Tabs: Navigation, content switching
  • Cards: Content containers
  • Badges: Status indicators

Styling (Tailwind CSS)

/* Design tokens */
:root {
  --color-primary: #10b981;      /* Green for cultivation */
  --color-secondary: #3b82f6;    /* Blue for actions */
  --color-danger: #ef4444;       /* Red for alerts */
  --color-warning: #f59e0b;      /* Orange for warnings */
  --color-success: #10b981;      /* Green for success */
  
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'Fira Code', monospace;
}

Mobile-First Design

  • Big Tap Targets: Minimum 44×44px for all interactive elements
  • Dark Mode: Default dark theme with light mode option
  • Responsive Breakpoints:
    • Mobile: < 640px
    • Tablet: 640px - 1024px
    • Desktop: > 1024px

Key Features

1. Today View (Mobile-Optimized)

// TodayView.tsx
export const TodayView = () => {
  const { tasks, loading } = useTasks({ dueToday: true });
  
  return (
    <div className="p-4 space-y-4">
      <h1 className="text-2xl font-bold">Today's Tasks</h1>
      {tasks.map(task => (
        <TaskCard key={task.id} task={task} />
      ))}
    </div>
  );
};

2. Timeclock Widget

// TimeclockWidget.tsx
export const TimeclockWidget = () => {
  const { currentShift, clockIn, clockOut } = useTimeclock();
  
  return (
    <Card className="p-6">
      {currentShift ? (
        <div>
          <p>Clocked in: {formatTime(currentShift.clockIn)}</p>
          <Button onClick={clockOut} size="lg">Clock Out</Button>
        </div>
      ) : (
        <Button onClick={clockIn} size="lg">Clock In</Button>
      )}
    </Card>
  );
};

3. Batch Timeline

// BatchTimeline.tsx
export const BatchTimeline = ({ batchId }: { batchId: string }) => {
  const { events } = useBatchTimeline(batchId);
  
  return (
    <div className="space-y-4">
      {events.map(event => (
        <TimelineEvent key={event.id} event={event} />
      ))}
    </div>
  );
};

API Integration

API Client

// lib/api.ts
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Add auth token to requests
api.interceptors.request.use(config => {
  const token = localStorage.getItem('accessToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Handle token refresh
api.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      // Attempt token refresh
      const refreshToken = localStorage.getItem('refreshToken');
      if (refreshToken) {
        const { data } = await axios.post('/api/auth/refresh', { refreshToken });
        localStorage.setItem('accessToken', data.accessToken);
        error.config.headers.Authorization = `Bearer ${data.accessToken}`;
        return axios(error.config);
      }
    }
    return Promise.reject(error);
  }
);

export default api;

Custom Hooks

// hooks/useTasks.ts
export const useTasks = (filters?: TaskFilters) => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchTasks = async () => {
      const { data } = await api.get('/api/tasks', { params: filters });
      setTasks(data.data);
      setLoading(false);
    };
    fetchTasks();
  }, [filters]);
  
  return { tasks, loading };
};

Authentication

Auth Context

// hooks/useAuth.ts
const AuthContext = createContext<AuthContextType | null>(null);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<User | null>(null);
  
  const login = async (email: string, password: string) => {
    const { data } = await api.post('/api/auth/login', { email, password });
    localStorage.setItem('accessToken', data.accessToken);
    localStorage.setItem('refreshToken', data.refreshToken);
    setUser(data.user);
  };
  
  const logout = () => {
    localStorage.removeItem('accessToken');
    localStorage.removeItem('refreshToken');
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
};

Testing

Component Tests (Vitest + React Testing Library)

// TaskCard.test.tsx
import { render, screen } from '@testing-library/react';
import { TaskCard } from './TaskCard';

describe('TaskCard', () => {
  it('renders task name', () => {
    const task = { id: '1', name: 'Water plants', status: 'PENDING' };
    render(<TaskCard task={task} />);
    expect(screen.getByText('Water plants')).toBeInTheDocument();
  });
});

Environment Variables

# API
VITE_API_URL=http://localhost:3000/api

# Auth
VITE_AUTH_ENABLED=true

# Features
VITE_METRC_ENABLED=false

Development Workflow

Setup

# Install dependencies
npm install

# Set up environment variables
cp .env.example .env

# Start dev server
npm run dev

Scripts

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "lint": "eslint src --ext .ts,.tsx",
    "format": "prettier --write src"
  }
}

Accessibility

  • WCAG 2.1 AA Compliance: All components meet accessibility standards
  • Keyboard Navigation: All interactive elements keyboard-accessible
  • Screen Reader Support: Proper ARIA labels and roles
  • Focus Management: Clear focus indicators and logical tab order
  • Color Contrast: Minimum 4.5:1 contrast ratio for text

Mobile & PWA

Progressive Web App (Stretch Goal)

// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'CA Grow Ops Manager',
        short_name: 'Grow Ops',
        theme_color: '#10b981',
        icons: [
          { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
        ],
      },
    }),
  ],
});

Next Steps

  1. Choose Build Tool: Decide between Vite and Next.js
  2. Set Up Project: Initialize React project with TypeScript
  3. Configure Tailwind: Set up Tailwind CSS and design tokens
  4. Build Design System: Implement base UI components
  5. Implement Pages: Build page components per spec
  6. Write Tests: Component and integration tests
  7. Deploy: Set up CI/CD and deploy to staging

Resources