feat(ui): Integrate AuraUI Hero component
- Installed framer-motion dependency - Added Hero1 component with 'Digital Command Center' branding - Replaced MetrcDashboardPage header with new Hero component
This commit is contained in:
parent
da5af8c288
commit
4a2533f121
5 changed files with 770 additions and 436 deletions
64
docs/AURA_UI_PLAN.md
Normal file
64
docs/AURA_UI_PLAN.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# AuraUI Integration Plan and Audit
|
||||
|
||||
## Snapshot Info
|
||||
|
||||
- **Commit Hash**: `da5af8c` (Before AuraUI)
|
||||
- **Rollback Command**: `git reset --hard da5af8c && git push -f origin main`
|
||||
|
||||
## Component Audit
|
||||
|
||||
We have selected the following components from the [AuraUI Library](https://auraui.com) to enhance the `ca-grow-ops-manager`:
|
||||
|
||||
### 1. 3D Digital Twin Hero (`feature-4-3d`)
|
||||
|
||||
- **Visuals**: Dark mode, floating 3D elements (cubes/spheres) with lime/purple accents.
|
||||
- **Use Case**: **Facility 3D Viewer Loading/Landing State**.
|
||||
- **Reason**: Perfectly aligns with the "Digital Twin" core pillar. Replaces the generic "Loading..." spinner with a technical, high-end visual.
|
||||
|
||||
### 2. High-Trust Hero (`hero-1`)
|
||||
|
||||
- **Visuals**: Centered H1, clear subtext, primary/secondary action buttons.
|
||||
- **Use Case**: **Main Dashboard Header**.
|
||||
- **Reason**: Provides immediate access to critical actions ("Emergency Stop", "Metrc Sync") and sets the tone for the application.
|
||||
|
||||
### 3. Split-Screen Auth Form (`signup-1`)
|
||||
|
||||
- **Visuals**: Left side form, Right side high-quality operational image.
|
||||
- **Use Case**: **Visitor Kiosk Check-In** and **SOP Acknowledgement**.
|
||||
- **Reason**: The "Compliance" aspect needs to feel authoritative and polished. The split view allows us to show legal text/images alongside the signature input.
|
||||
|
||||
### 4. Minimal Navbar (`header-6`)
|
||||
|
||||
- **Visuals**: Dark background, subtle hover effects, active state indicators.
|
||||
- **Use Case**: **Global App Shell Navigation**.
|
||||
- **Reason**: The current sidebar is functional but bulky. Moving to a sleek top-nav (or refined side-nav) using these styles cleans up screen real estate for the 3D viewer.
|
||||
|
||||
### 5. Bento Grid (`feature-14`)
|
||||
|
||||
- **Visuals**: Grid of varied-size cards, bento-box style.
|
||||
- **Use Case**: **Operations Dashboard**.
|
||||
- **Reason**: We have too many disparate metrics (Temp, Humidity, Task Count, Compliance Status). A bento grid unifies them into a single "Command Center" view.
|
||||
|
||||
## Integration Strategy
|
||||
|
||||
### Prerequisites (Checked)
|
||||
|
||||
- [x] `clsx` (Installed)
|
||||
- [x] `tailwind-merge` (Installed)
|
||||
- [x] `class-variance-authority` (Installed)
|
||||
- [x] `lucide-react` (Installed)
|
||||
- [ ] `framer-motion` (Missing - **Install Required**)
|
||||
|
||||
### Implementation Order
|
||||
|
||||
1. **Install `framer-motion`**: Required for AuraUI animations.
|
||||
2. **Setup Utils**: Ensure `lib/utils.ts` has the standard `cn()` helper (it likely does, but verify).
|
||||
3. **Implement `Hero-1`**: Replace the Dashboard header first as a low-risk, high-impact test.
|
||||
4. **Implement `Feature-4-3d`**: Add to the 3D Viewer page.
|
||||
|
||||
### Rollback Plan
|
||||
|
||||
If the new components break the build or styling:
|
||||
|
||||
1. Revert to the snapshot commit.
|
||||
2. Clean `node_modules` if `framer-motion` causes issues.
|
||||
43
frontend/package-lock.json
generated
43
frontend/package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
|||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.23.26",
|
||||
"i18next": "^25.7.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immer": "^11.0.1",
|
||||
|
|
@ -5200,6 +5201,33 @@
|
|||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.23.26",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz",
|
||||
"integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.23.23",
|
||||
"motion-utils": "^12.23.6",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
|
@ -6577,6 +6605,21 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.23.23",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.23.6"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.23.6",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.23.26",
|
||||
"i18next": "^25.7.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immer": "^11.0.1",
|
||||
|
|
|
|||
245
frontend/src/components/aura/Hero.tsx
Normal file
245
frontend/src/components/aura/Hero.tsx
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Download,
|
||||
Github,
|
||||
Sparkles,
|
||||
Loader,
|
||||
Copy,
|
||||
Check,
|
||||
StopCircle,
|
||||
Activity,
|
||||
Leaf
|
||||
} from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* AuraUI Hero 1 Component
|
||||
* Customized for CA Grow Ops Manager
|
||||
* Displays the main dashboard header with 'Digital Command Center' branding
|
||||
*/
|
||||
const Hero1 = () => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [currentSentence, setCurrentSentence] = useState(0);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [showCorrected, setShowCorrected] = useState(false);
|
||||
const [typingText, setTypingText] = useState("");
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const [isSelected, setIsSelected] = useState(false);
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
|
||||
// Customized demo text for Grow Ops context
|
||||
const demoSentences = [
|
||||
{
|
||||
original: "Room 4 temp is 82 humidity 65 check veg lighting",
|
||||
corrected:
|
||||
"Room 4: Temp 82°F, Humidity 65%. Veg lighting inspection required.",
|
||||
},
|
||||
{
|
||||
original: "feed batch 156 with heavy bloom schedule today",
|
||||
corrected:
|
||||
"Action: Feed Batch B-23-156. Schedule: Heavy Bloom. Date: Today.",
|
||||
},
|
||||
{
|
||||
original: "visitor arrived for checkin name is john doe",
|
||||
corrected:
|
||||
"Visitor Log: John Doe checked in. NDA signature pending.",
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const startDemo = () => {
|
||||
// Reset all states
|
||||
setIsTyping(true);
|
||||
setIsSelected(false);
|
||||
setShowCorrected(false);
|
||||
setTypingText("");
|
||||
|
||||
// Type the text character by character - much faster
|
||||
const text = demoSentences[currentSentence].original;
|
||||
let i = 0;
|
||||
|
||||
const typeInterval = setInterval(() => {
|
||||
if (i < text.length) {
|
||||
setTypingText(text.substring(0, i + 1));
|
||||
i++;
|
||||
} else {
|
||||
clearInterval(typeInterval);
|
||||
setIsTyping(false);
|
||||
|
||||
// Wait a bit, then select text
|
||||
setTimeout(() => {
|
||||
setIsSelected(true);
|
||||
|
||||
// Wait for selection, then start refining
|
||||
setTimeout(() => {
|
||||
setIsAnimating(true);
|
||||
|
||||
// Show refined text after processing
|
||||
setTimeout(() => {
|
||||
setShowCorrected(true);
|
||||
setIsAnimating(false);
|
||||
setIsSelected(false);
|
||||
|
||||
// Wait before next cycle
|
||||
setTimeout(() => {
|
||||
setCurrentSentence(
|
||||
(prev) => (prev + 1) % demoSentences.length,
|
||||
);
|
||||
}, 2500);
|
||||
}, 1800);
|
||||
}, 1200);
|
||||
}, 800);
|
||||
}
|
||||
}, 25); // Much faster typing speed
|
||||
};
|
||||
|
||||
const interval = setInterval(startDemo, 8500);
|
||||
startDemo(); // Start immediately
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [currentSentence]);
|
||||
|
||||
return (
|
||||
<section className="relative w-full overflow-hidden bg-background">
|
||||
{/* Extended background gradient with enhanced grid */}
|
||||
<div className="cosmic-gradient cosmic-grid-enhanced min-h-[60vh] flex flex-col justify-center items-center relative py-20">
|
||||
|
||||
{/* Hero Content */}
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-10 max-w-5xl mx-auto px-6 text-center transition-all duration-700 transform",
|
||||
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-center mb-6">
|
||||
<Badge variant="secondary" className="gap-2 cosmic-glass">
|
||||
<Activity className="h-3 w-3 animate-pulse text-primary" />
|
||||
System Status: Nominal
|
||||
<Sparkles className="h-3 w-3 text-yellow-400" />
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold tracking-tighter text-balance text-foreground mb-6 max-w-5xl">
|
||||
Command Center for <span className="text-primary">Modern Cultivation</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-8 leading-relaxed text-balance">
|
||||
Real-time compliance, environmental monitoring, and task management in a single digital twin interface.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center pt-2 mb-16">
|
||||
<Button
|
||||
size="lg"
|
||||
className="gap-2 h-12 px-8"
|
||||
onClick={() => { }} // Hook up to actual actions
|
||||
>
|
||||
<Leaf className="h-5 w-5" />
|
||||
Metrc Sync
|
||||
</Button>
|
||||
<Button variant="outline" size="lg" className="gap-2 h-12 px-8">
|
||||
<Activity className="h-5 w-5" />
|
||||
View Alerts
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Demo Box - Made taller */}
|
||||
<div className="max-w-4xl mx-auto mb-8 cursor-default select-none pointer-events-none opacity-80 scale-90">
|
||||
<div className="relative">
|
||||
{/* Window-like demo box */}
|
||||
<div className="bg-card/90 backdrop-blur-md rounded-xl border border-border/50 shadow-2xl overflow-hidden">
|
||||
{/* Window header */}
|
||||
<div className="bg-muted/30 px-4 py-3 border-b border-border/30 flex items-center gap-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/70"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/70"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/70"></div>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground ml-4">
|
||||
AI Compliance Assistant
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content area - Made taller */}
|
||||
<div className="p-8 min-h-[120px] flex items-center justify-center">
|
||||
<div className="w-full max-w-2xl">
|
||||
<div
|
||||
className={cn(
|
||||
"text-lg leading-relaxed transition-all duration-500",
|
||||
isAnimating ? "bg-accent/30 rounded-md p-3" : "",
|
||||
isSelected ? "bg-primary/20 rounded-md p-3" : ""
|
||||
)}
|
||||
>
|
||||
{showCorrected ? (
|
||||
<span className="text-foreground font-mono">
|
||||
{demoSentences[currentSentence].corrected}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground font-mono">
|
||||
{typingText}
|
||||
{isTyping && <span className="animate-pulse">_</span>}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Status indicator */}
|
||||
<div className="flex items-center gap-2 mt-4 text-xs font-mono uppercase tracking-widest">
|
||||
{isAnimating ? (
|
||||
<>
|
||||
<Loader className="h-3 w-3 animate-spin text-primary" />
|
||||
<span className="text-muted-foreground">
|
||||
Processing SOP...
|
||||
</span>
|
||||
</>
|
||||
) : showCorrected ? (
|
||||
<>
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
<span className="text-muted-foreground">
|
||||
Compliance Verified
|
||||
</span>
|
||||
</>
|
||||
) : isSelected ? (
|
||||
<>
|
||||
<Copy className="h-3 w-3 text-blue-500" />
|
||||
<span className="text-muted-foreground">
|
||||
Analyzing Input
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-muted-foreground">
|
||||
Awaiting Voice/Text Command...
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Glow effect below the demo box - moved up and positioned better */}
|
||||
<div className="absolute bottom-16 left-1/2 -translate-x-1/2 w-[600px] h-[120px] opacity-20 bg-primary blur-[80px] rounded-full -z-20"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showPopup && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
{/* Popup content */}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero1;
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from 'lucide-react';
|
||||
import { metrcApi, MetrcLocation, MetrcDiscrepancy, MetrcReportResponse, MetrcAuditResponse } from '../lib/metrcApi';
|
||||
import { PageHeader, EmptyState, CardSkeleton } from '../components/ui/LinearPrimitives';
|
||||
import Hero1 from '../components/aura/Hero';
|
||||
|
||||
const SEVERITY_CONFIG = {
|
||||
CRITICAL: { color: 'text-red-600 bg-red-100 dark:bg-red-900/30', label: 'Critical' },
|
||||
|
|
@ -88,35 +89,9 @@ export default function MetrcDashboardPage() {
|
|||
|
||||
return (
|
||||
<div className="space-y-6 pb-20 animate-in">
|
||||
<PageHeader
|
||||
title="METRC Integration"
|
||||
subtitle="California Track-and-Trace Compliance"
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleExportCsv}
|
||||
className="btn btn-ghost"
|
||||
title="Export CSV for manual METRC upload"
|
||||
>
|
||||
<Download size={16} />
|
||||
<span className="hidden sm:inline">Export</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSync}
|
||||
disabled={syncing}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{syncing ? (
|
||||
<Loader2 size={16} className="animate-spin" />
|
||||
) : (
|
||||
<RefreshCw size={16} />
|
||||
)}
|
||||
<span className="hidden sm:inline">Sync</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Hero1 />
|
||||
|
||||
<div className="px-6 space-y-6">
|
||||
{/* Connection Status Banner */}
|
||||
<div className={`card p-4 ${connectionStatus === 'demo' ? 'bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800' : 'bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800'}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
@ -156,7 +131,8 @@ export default function MetrcDashboardPage() {
|
|||
|
||||
{/* Tab Navigation */}
|
||||
<div className="flex gap-1 p-1 bg-secondary rounded-lg">
|
||||
{[
|
||||
{
|
||||
[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'plants', label: 'Plant Locations' },
|
||||
{ id: 'audit', label: 'Audit Trail' }
|
||||
|
|
@ -171,10 +147,12 @@ export default function MetrcDashboardPage() {
|
|||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</div >
|
||||
|
||||
{loading ? (
|
||||
{
|
||||
loading ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" >
|
||||
{Array.from({ length: 4 }).map((_, i) => <CardSkeleton key={i} />)}
|
||||
</div>
|
||||
|
|
@ -471,9 +449,11 @@ export default function MetrcDashboardPage() {
|
|||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
)
|
||||
}
|
||||
{/* Plant History Modal */}
|
||||
{selectedHistoryPlant && (
|
||||
{
|
||||
selectedHistoryPlant && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 animate-in fade-in">
|
||||
<div className="card w-full max-w-2xl max-h-[80vh] flex flex-col shadow-2xl animate-in zoom-in-95">
|
||||
<div className="p-4 border-b border-subtle flex justify-between items-center">
|
||||
|
|
@ -545,7 +525,8 @@ export default function MetrcDashboardPage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue