🔧 Build Fixes:
- Created FloorToggle component
- Created HealthLegend component
- Added name field to User interface
Components complete for heatmap feature
|
||
|---|---|---|
| .. | ||
| public/assets | ||
| src | ||
| components.json | ||
| Dockerfile | ||
| index.html | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.js | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
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
- Choose Build Tool: Decide between Vite and Next.js
- Set Up Project: Initialize React project with TypeScript
- Configure Tailwind: Set up Tailwind CSS and design tokens
- Build Design System: Implement base UI components
- Implement Pages: Build page components per spec
- Write Tests: Component and integration tests
- Deploy: Set up CI/CD and deploy to staging