fluxy-bot 0.12.5 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-fluxy/assets/{fluxy-2X2LJqEN.js → fluxy-DwS9I76o.js} +1 -1
- package/dist-fluxy/fluxy.html +1 -1
- package/package.json +3 -1
- package/supervisor/chat/fluxy-main.tsx +13 -12
- package/vite.config.ts +1 -0
- package/worker/prompts/fluxy-system-prompt.txt +6 -1
- package/workspace/.backend.log +1 -0
- package/workspace/client/index.html +7 -5
- package/workspace/client/src/App.tsx +12 -3
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +120 -113
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +17 -14
- package/workspace/client/src/components/Layout/MobileNav.tsx +2 -2
- package/workspace/client/src/components/Layout/Sidebar.tsx +48 -15
- package/workspace/client/src/components/deleteme_onboarding/WorkspaceTour.tsx +94 -0
- package/workspace/client/src/components/deleteme_onboarding/tour-theme.css +75 -0
- package/workspace/client/src/components/ui/sheet.tsx +2 -1
- package/workspace/client/src/styles/globals.css +4 -1
- package/workspace/node_modules/.vite/deps/_metadata.json +116 -0
- package/workspace/node_modules/.vite/deps/clsx.js +18 -0
- package/workspace/node_modules/.vite/deps/clsx.js.map +1 -0
- package/workspace/node_modules/.vite/deps/driver__js.js +581 -0
- package/workspace/node_modules/.vite/deps/driver__js.js.map +1 -0
- package/workspace/node_modules/.vite/deps/framer-motion.js +13761 -0
- package/workspace/node_modules/.vite/deps/framer-motion.js.map +1 -0
- package/workspace/node_modules/.vite/deps/lucide-react.js +34357 -0
- package/workspace/node_modules/.vite/deps/lucide-react.js.map +1 -0
- package/workspace/node_modules/.vite/deps/package.json +3 -0
- package/workspace/node_modules/.vite/deps/radix-ui.js +13835 -0
- package/workspace/node_modules/.vite/deps/radix-ui.js.map +1 -0
- package/workspace/node_modules/.vite/deps/react-3_O8oni9.js +799 -0
- package/workspace/node_modules/.vite/deps/react-3_O8oni9.js.map +1 -0
- package/workspace/node_modules/.vite/deps/react-dom.js +185 -0
- package/workspace/node_modules/.vite/deps/react-dom.js.map +1 -0
- package/workspace/node_modules/.vite/deps/react-dom_client.js +14384 -0
- package/workspace/node_modules/.vite/deps/react-dom_client.js.map +1 -0
- package/workspace/node_modules/.vite/deps/react-router.js +9785 -0
- package/workspace/node_modules/.vite/deps/react-router.js.map +1 -0
- package/workspace/node_modules/.vite/deps/react.js +2 -0
- package/workspace/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
- package/workspace/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
- package/workspace/node_modules/.vite/deps/react_jsx-runtime.js +209 -0
- package/workspace/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
- package/workspace/node_modules/.vite/deps/recharts.js +34142 -0
- package/workspace/node_modules/.vite/deps/recharts.js.map +1 -0
- package/workspace/node_modules/.vite/deps/sonner.js +943 -0
- package/workspace/node_modules/.vite/deps/sonner.js.map +1 -0
- package/workspace/node_modules/.vite/deps/tailwind-merge.js +2025 -0
- package/workspace/node_modules/.vite/deps/tailwind-merge.js.map +1 -0
- package/workspace/node_modules/.vite/deps/use-sync-external-store.js +27 -0
- package/workspace/node_modules/.vite/deps/use-sync-external-store.js.map +1 -0
- package/workspace/node_modules/.vite/deps/use-sync-external-store_shim.js +75 -0
- package/workspace/node_modules/.vite/deps/use-sync-external-store_shim.js.map +1 -0
- package/workspace/node_modules/.vite/deps/zustand.js +49 -0
- package/workspace/node_modules/.vite/deps/zustand.js.map +1 -0
package/dist-fluxy/fluxy.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Fluxy Chat</title>
|
|
7
|
-
<script type="module" crossorigin src="/fluxy/assets/fluxy-
|
|
7
|
+
<script type="module" crossorigin src="/fluxy/assets/fluxy-DwS9I76o.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/fluxy/assets/globals-DCVXWA_E.js">
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/fluxy/assets/globals-2ZsZBw2U.css">
|
|
10
10
|
</head>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluxy-bot",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
4
4
|
"releaseNotes": [
|
|
5
5
|
"Adding a way for users to claim their fluxies on the fluxy.bot dashboard",
|
|
6
6
|
"2. ",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"build:fluxy": "vite build --config vite.fluxy.config.ts",
|
|
48
48
|
"start": "node --import tsx/esm supervisor/index.ts",
|
|
49
49
|
"postinstall": "node scripts/postinstall.js",
|
|
50
|
+
"dev:workspace": "vite",
|
|
50
51
|
"dev:docs": "cd ./docs && npx fumapress"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"commander": "^14.0.3",
|
|
61
62
|
"cron-parser": "^5.5.0",
|
|
62
63
|
"date-fns": "^4.1.0",
|
|
64
|
+
"driver.js": "^1.4.0",
|
|
63
65
|
"express": "^5.2.1",
|
|
64
66
|
"framer-motion": "^12.34.3",
|
|
65
67
|
"lucide-react": "^1.7.0",
|
|
@@ -24,18 +24,6 @@ function FluxyApp() {
|
|
|
24
24
|
// Backend health
|
|
25
25
|
const [backendHealthy, setBackendHealthy] = useState(true);
|
|
26
26
|
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (!authenticated) return;
|
|
29
|
-
const check = () => {
|
|
30
|
-
fetch('/app/api/health')
|
|
31
|
-
.then((r) => setBackendHealthy(r.ok))
|
|
32
|
-
.catch(() => setBackendHealthy(false));
|
|
33
|
-
};
|
|
34
|
-
check();
|
|
35
|
-
const id = setInterval(check, 10_000);
|
|
36
|
-
return () => clearInterval(id);
|
|
37
|
-
}, [authenticated]);
|
|
38
|
-
|
|
39
27
|
// Wallet balance
|
|
40
28
|
const [walletBalance, setWalletBalance] = useState<string | null>(null);
|
|
41
29
|
|
|
@@ -173,6 +161,19 @@ function FluxyApp() {
|
|
|
173
161
|
});
|
|
174
162
|
}, []);
|
|
175
163
|
|
|
164
|
+
// Backend health check
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!authenticated) return;
|
|
167
|
+
const check = () => {
|
|
168
|
+
fetch('/app/api/health')
|
|
169
|
+
.then((r) => setBackendHealthy(r.ok))
|
|
170
|
+
.catch(() => setBackendHealthy(false));
|
|
171
|
+
};
|
|
172
|
+
check();
|
|
173
|
+
const id = setInterval(check, 10_000);
|
|
174
|
+
return () => clearInterval(id);
|
|
175
|
+
}, [authenticated]);
|
|
176
|
+
|
|
176
177
|
// Check push state on mount
|
|
177
178
|
useEffect(() => {
|
|
178
179
|
if (!authenticated) return;
|
package/vite.config.ts
CHANGED
|
@@ -449,7 +449,12 @@ The workspace is **NOT secured by default**. If your human's Fluxy is accessible
|
|
|
449
449
|
They already have a chat password from onboarding. Don't confuse the two. Don't go looking in the worker database for `portal_pass`. Don't tell them "you already have a password set." Set the workspace password using the route above.
|
|
450
450
|
|
|
451
451
|
## Modular Philosophy
|
|
452
|
-
|
|
452
|
+
|
|
453
|
+
When your human asks for something new, don't redesign the workspace. Build a **mini app**: a new page component, a new React Router route, and a sidebar link. Each feature lives on its own page. Yesterday it was a CRM, today a finance tracker, tomorrow a diet log. They all coexist in the sidebar.
|
|
454
|
+
|
|
455
|
+
On top of the page, add a **small widget on the Dashboard** when it makes sense. The dashboard is a grid of compact cards (see the existing Stripe, Gmail, X, and Research widgets for the pattern). A widget shows a quick summary or key metric from the app. Not every app needs one, but most benefit from a glanceable overview.
|
|
456
|
+
|
|
457
|
+
Only redesign the workspace layout if your human explicitly asks you to. Otherwise, keep adding modules. The sidebar grows, the dashboard fills with widgets, and the workspace becomes more useful over time.
|
|
453
458
|
|
|
454
459
|
---
|
|
455
460
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[backend] Listening on port 3004
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" style="background:#
|
|
2
|
+
<html lang="en" style="background-color:#0A0A0A">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
<meta name="apple-mobile-web-app-title" content="Fluxy" />
|
|
10
10
|
<link rel="apple-touch-icon" href="/fluxy-icon-192.png" />
|
|
11
11
|
<link rel="manifest" href="/manifest.json" />
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
|
|
12
13
|
<title>Fluxy</title>
|
|
13
14
|
</head>
|
|
14
|
-
<body class="bg-background text-foreground" style="background:#
|
|
15
|
+
<body class="bg-background text-foreground" style="background-color:#0A0A0A">
|
|
15
16
|
<!-- Dark background fallback — visible instantly while widget.js loads.
|
|
16
17
|
The canvas animation (in widget.js) takes over as the actual splash. -->
|
|
17
|
-
<div id="splash" style="background:#
|
|
18
|
+
<div id="splash" style="background:#0A0A0A;position:fixed;inset:0;z-index:9998;transition:opacity .25s ease-out"></div>
|
|
19
|
+
<script>if(location.port==='5173'){var s=document.getElementById('splash');if(s)s.style.display='none'}</script>
|
|
18
20
|
|
|
19
21
|
<div id="root"></div>
|
|
20
22
|
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
var root = document.getElementById('root');
|
|
27
29
|
if (root && root.children.length === 0) {
|
|
28
30
|
root.innerHTML =
|
|
29
|
-
'<div style="background:#
|
|
31
|
+
'<div style="background:#0A0A0A;color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100dvh;width:100vw;position:fixed;inset:0;z-index:50;font-family:system-ui,-apple-system,sans-serif;text-align:center;padding:24px">' +
|
|
30
32
|
'<img src="/fluxy_frame1.png" style="height:120px;width:120px;border-radius:50%;object-fit:cover;margin-bottom:32px" alt="Fluxy" />' +
|
|
31
33
|
'<h1 style="font-size:20px;font-weight:600;margin-bottom:8px">Your app crashed</h1>' +
|
|
32
34
|
'<p style="font-size:14px;color:rgba(255,255,255,0.5);max-width:320px;line-height:1.5">Ask the agent to fix it using the chat.</p>' +
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
</script>
|
|
44
46
|
<script type="module" src="/src/main.tsx"></script>
|
|
45
47
|
<script>
|
|
46
|
-
if('serviceWorker' in navigator){
|
|
48
|
+
if('serviceWorker' in navigator && location.port !== '5173'){
|
|
47
49
|
var swRefreshing=false;
|
|
48
50
|
navigator.serviceWorker.addEventListener('controllerchange',function(){
|
|
49
51
|
if(swRefreshing)return;
|
|
@@ -3,6 +3,7 @@ import { Routes, Route } from 'react-router';
|
|
|
3
3
|
import ErrorBoundary from './components/ErrorBoundary';
|
|
4
4
|
import DashboardLayout from './components/Layout/DashboardLayout';
|
|
5
5
|
import DashboardPage from './components/Dashboard/DashboardPage';
|
|
6
|
+
import WorkspaceTour from './components/deleteme_onboarding/WorkspaceTour';
|
|
6
7
|
|
|
7
8
|
function DashboardError() {
|
|
8
9
|
return (
|
|
@@ -25,6 +26,7 @@ function DashboardError() {
|
|
|
25
26
|
|
|
26
27
|
export default function App() {
|
|
27
28
|
const [showOnboard, setShowOnboard] = useState(false);
|
|
29
|
+
const [userName, setUserName] = useState('');
|
|
28
30
|
const [rebuildState, setRebuildState] = useState<'idle' | 'rebuilding' | 'error'>('idle');
|
|
29
31
|
const [buildError, setBuildError] = useState('');
|
|
30
32
|
|
|
@@ -102,10 +104,15 @@ export default function App() {
|
|
|
102
104
|
}, []);
|
|
103
105
|
|
|
104
106
|
useEffect(() => {
|
|
105
|
-
fetch('/api/settings')
|
|
106
|
-
.then((r) =>
|
|
107
|
+
fetch('/api/settings', { signal: AbortSignal.timeout(3000) })
|
|
108
|
+
.then((r) => {
|
|
109
|
+
if (!r.ok) return;
|
|
110
|
+
return r.json();
|
|
111
|
+
})
|
|
107
112
|
.then((s) => {
|
|
113
|
+
if (!s) return;
|
|
108
114
|
if (s.onboard_complete !== 'true') setShowOnboard(true);
|
|
115
|
+
if (s.user_name) setUserName(s.user_name);
|
|
109
116
|
})
|
|
110
117
|
.catch(() => {});
|
|
111
118
|
}, []);
|
|
@@ -148,11 +155,13 @@ export default function App() {
|
|
|
148
155
|
return (
|
|
149
156
|
<>
|
|
150
157
|
<ErrorBoundary fallback={<DashboardError />}>
|
|
151
|
-
<DashboardLayout>
|
|
158
|
+
<DashboardLayout userName={userName}>
|
|
152
159
|
<Routes>
|
|
153
160
|
<Route path="/" element={<DashboardPage />} />
|
|
161
|
+
<Route path="*" element={<DashboardPage />} />
|
|
154
162
|
</Routes>
|
|
155
163
|
</DashboardLayout>
|
|
164
|
+
<WorkspaceTour />
|
|
156
165
|
</ErrorBoundary>
|
|
157
166
|
|
|
158
167
|
{showOnboard && (
|
|
@@ -1,127 +1,134 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Search, Mail, DollarSign, TrendingUp } from 'lucide-react';
|
|
2
|
+
import { AreaChart, Area, BarChart, Bar, ResponsiveContainer } from 'recharts';
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'Build a task manager',
|
|
8
|
-
];
|
|
4
|
+
const GRADIENT = 'linear-gradient(to right, #4FF2FE 10%, #BC20DE 55%, #FF6B8A 100%)';
|
|
5
|
+
const CARD = 'relative rounded-xl overflow-hidden';
|
|
6
|
+
const BORDER = 'absolute inset-0 rounded-xl bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent pointer-events-none';
|
|
7
|
+
const INNER = 'relative rounded-xl bg-[#141414] m-px p-3.5 h-full';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
);
|
|
23
|
-
if (input) {
|
|
24
|
-
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
25
|
-
window.HTMLTextAreaElement.prototype, 'value'
|
|
26
|
-
)?.set || Object.getOwnPropertyDescriptor(
|
|
27
|
-
window.HTMLInputElement.prototype, 'value'
|
|
28
|
-
)?.set;
|
|
29
|
-
nativeSetter?.call(input, text);
|
|
30
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
31
|
-
input.focus();
|
|
32
|
-
}
|
|
33
|
-
}, 400);
|
|
34
|
-
};
|
|
9
|
+
const rev = [{ v: 82 }, { v: 89 }, { v: 94 }, { v: 101 }, { v: 108 }, { v: 112 }, { v: 125 }];
|
|
10
|
+
const fol = [{ v: 12 }, { v: 18 }, { v: 9 }, { v: 24 }, { v: 31 }, { v: 19 }, { v: 27 }];
|
|
11
|
+
|
|
12
|
+
function StripeSvg() {
|
|
13
|
+
return <svg className="h-3.5 w-3.5 text-[#635BFF]" viewBox="0 0 24 24" fill="currentColor"><path d="M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.591-7.305z" /></svg>;
|
|
14
|
+
}
|
|
15
|
+
function GmailSvg() {
|
|
16
|
+
return <svg className="h-3.5 w-3.5 text-[#EA4335]" viewBox="0 0 24 24" fill="currentColor"><path d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z" /></svg>;
|
|
17
|
+
}
|
|
18
|
+
function XSvg() {
|
|
19
|
+
return <svg className="h-3.5 w-3.5 text-white" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /></svg>;
|
|
20
|
+
}
|
|
35
21
|
|
|
22
|
+
export default function DashboardPage() {
|
|
36
23
|
return (
|
|
37
|
-
<div className="flex flex-col
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
<div className="flex flex-col h-full px-4 sm:px-6 pt-8 sm:pt-12 pb-6 max-w-4xl mx-auto w-full overflow-y-auto">
|
|
25
|
+
<h1
|
|
26
|
+
className="text-xl sm:text-2xl font-bold mb-1 tracking-tight w-fit"
|
|
27
|
+
style={{ backgroundImage: GRADIENT, WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}
|
|
28
|
+
>
|
|
40
29
|
Let's get started
|
|
41
30
|
</h1>
|
|
42
|
-
<p className="text-muted-foreground text-
|
|
43
|
-
Tell me what to build and I'll create it for you, right here.
|
|
44
|
-
</p>
|
|
31
|
+
<p className="text-muted-foreground text-xs mb-6">Your workspace at a glance.</p>
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
<div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3 max-w-lg mb-12">
|
|
48
|
-
{starterSuggestions.map((suggestion) => (
|
|
49
|
-
<button
|
|
50
|
-
key={suggestion}
|
|
51
|
-
onClick={() => handleSuggestion(suggestion)}
|
|
52
|
-
className="px-4 py-2 sm:px-5 sm:py-2.5 rounded-full border border-border bg-card text-sm text-muted-foreground hover:text-foreground hover:border-primary/40 hover:bg-card/80 transition-all duration-200 active:scale-[0.97]"
|
|
53
|
-
>
|
|
54
|
-
{suggestion}
|
|
55
|
-
</button>
|
|
56
|
-
))}
|
|
57
|
-
</div>
|
|
33
|
+
<div className="grid grid-cols-3 gap-2.5">
|
|
58
34
|
|
|
59
|
-
|
|
60
|
-
|
|
35
|
+
{/* Stripe — 2 cols */}
|
|
36
|
+
<div className={`${CARD} col-span-2`}>
|
|
37
|
+
<div className={BORDER} />
|
|
38
|
+
<div className={INNER}>
|
|
39
|
+
<div className="flex items-center justify-between">
|
|
40
|
+
<div className="flex items-center gap-2">
|
|
41
|
+
<div className="h-7 w-7 rounded-lg bg-[#635BFF]/10 flex items-center justify-center"><StripeSvg /></div>
|
|
42
|
+
<span className="text-xs font-bold">Stripe</span>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="flex items-center gap-1 text-emerald-500">
|
|
45
|
+
<TrendingUp className="h-3 w-3" />
|
|
46
|
+
<span className="text-[10px] font-bold">+12.5%</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<p className="text-2xl font-bold tracking-tight mt-2">$12,480</p>
|
|
50
|
+
<p className="text-[10px] text-muted-foreground/50 mb-1">MRR</p>
|
|
51
|
+
<div className="h-12 overflow-hidden">
|
|
52
|
+
<ResponsiveContainer width="100%" height={48}>
|
|
53
|
+
<AreaChart data={rev}>
|
|
54
|
+
<defs>
|
|
55
|
+
<linearGradient id="sg" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stopColor="#4FF2FE" /><stop offset="50%" stopColor="#BC20DE" /><stop offset="100%" stopColor="#FE546B" /></linearGradient>
|
|
56
|
+
<linearGradient id="sf" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#BC20DE" stopOpacity={0.12} /><stop offset="100%" stopColor="#BC20DE" stopOpacity={0} /></linearGradient>
|
|
57
|
+
</defs>
|
|
58
|
+
<Area type="monotone" dataKey="v" stroke="url(#sg)" strokeWidth={1.5} fill="url(#sf)" />
|
|
59
|
+
</AreaChart>
|
|
60
|
+
</ResponsiveContainer>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
65
|
+
{/* X — 1 col */}
|
|
66
|
+
<div className={`${CARD} col-span-1`}>
|
|
67
|
+
<div className={BORDER} />
|
|
68
|
+
<div className={INNER}>
|
|
69
|
+
<div className="flex items-center gap-2 mb-2">
|
|
70
|
+
<div className="h-7 w-7 rounded-lg bg-white/[0.06] flex items-center justify-center"><XSvg /></div>
|
|
71
|
+
<span className="text-xs font-bold">X</span>
|
|
72
|
+
</div>
|
|
73
|
+
<p className="text-2xl font-bold tracking-tight">24.8K</p>
|
|
74
|
+
<div className="flex items-center gap-1 text-emerald-500 mb-1">
|
|
75
|
+
<TrendingUp className="h-2.5 w-2.5" />
|
|
76
|
+
<span className="text-[10px] font-bold">+1.4K</span>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="h-10 overflow-hidden">
|
|
79
|
+
<ResponsiveContainer width="100%" height={40}>
|
|
80
|
+
<BarChart data={fol} barCategoryGap="25%">
|
|
81
|
+
<defs>
|
|
82
|
+
<linearGradient id="xg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#BC20DE" stopOpacity={0.3} /><stop offset="100%" stopColor="#4FF2FE" stopOpacity={0.05} /></linearGradient>
|
|
83
|
+
</defs>
|
|
84
|
+
<Bar dataKey="v" fill="url(#xg)" radius={[2, 2, 0, 0]} />
|
|
85
|
+
</BarChart>
|
|
86
|
+
</ResponsiveContainer>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
{/* Gmail — 1 col */}
|
|
92
|
+
<div className={`${CARD} col-span-1`}>
|
|
93
|
+
<div className={BORDER} />
|
|
94
|
+
<div className={INNER}>
|
|
95
|
+
<div className="flex items-center gap-2 mb-2.5">
|
|
96
|
+
<div className="h-7 w-7 rounded-lg bg-[#EA4335]/10 flex items-center justify-center"><GmailSvg /></div>
|
|
97
|
+
<span className="text-xs font-bold">Gmail</span>
|
|
98
|
+
<span className="ml-auto text-[10px] text-muted-foreground/50">3 new</span>
|
|
99
|
+
</div>
|
|
100
|
+
{['Sarah Chen', 'Stripe', 'Alex R.'].map((n) => (
|
|
101
|
+
<div key={n} className="flex items-center gap-2 py-1.5">
|
|
102
|
+
<div className="h-5 w-5 rounded-full bg-white/[0.06] text-[9px] font-bold flex items-center justify-center shrink-0">{n[0]}</div>
|
|
103
|
+
<span className="text-[11px] truncate">{n}</span>
|
|
104
|
+
</div>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
86
108
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
{/* Research — 2 cols */}
|
|
110
|
+
<div className={`${CARD} col-span-2`}>
|
|
111
|
+
<div className={BORDER} />
|
|
112
|
+
<div className={INNER}>
|
|
113
|
+
<div className="flex items-center gap-2 mb-2.5">
|
|
114
|
+
<div className="h-7 w-7 rounded-lg bg-[#9235F9]/10 flex items-center justify-center"><Search className="h-3.5 w-3.5 text-[#9235F9]" /></div>
|
|
115
|
+
<span className="text-xs font-bold">Research</span>
|
|
116
|
+
<span className="ml-auto text-[10px] font-bold text-muted-foreground bg-white/[0.06] px-2 py-0.5 rounded-full">3</span>
|
|
117
|
+
</div>
|
|
118
|
+
{[
|
|
119
|
+
{ t: 'Competitor pricing', s: 'Done', c: 'text-emerald-500 bg-emerald-500/10' },
|
|
120
|
+
{ t: 'Market trends Q1', s: 'Done', c: 'text-emerald-500 bg-emerald-500/10' },
|
|
121
|
+
{ t: 'User feedback', s: 'Review', c: 'text-orange-400 bg-orange-400/10' },
|
|
122
|
+
].map((r) => (
|
|
123
|
+
<div key={r.t} className="flex items-center justify-between py-1.5">
|
|
124
|
+
<span className="text-[11px]">{r.t}</span>
|
|
125
|
+
<span className={`text-[9px] font-bold px-1.5 py-0.5 rounded-full ${r.c}`}>{r.s}</span>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
106
130
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
onClick={runTest}
|
|
110
|
-
disabled={status === 'loading'}
|
|
111
|
-
className="flex items-center gap-2.5 px-5 py-2.5 rounded-full border border-border bg-card text-sm text-muted-foreground hover:text-foreground hover:border-primary/40 hover:bg-card/80 transition-all duration-200 active:scale-[0.97] disabled:opacity-50"
|
|
112
|
-
>
|
|
113
|
-
<span
|
|
114
|
-
className={`h-2 w-2 rounded-full shrink-0 ${
|
|
115
|
-
status === 'ok' ? 'bg-green-500' :
|
|
116
|
-
status === 'error' ? 'bg-red-500' :
|
|
117
|
-
status === 'loading' ? 'bg-yellow-500 animate-pulse' :
|
|
118
|
-
'bg-muted-foreground/30'
|
|
119
|
-
}`}
|
|
120
|
-
/>
|
|
121
|
-
{status === 'idle' && 'Test Backend'}
|
|
122
|
-
{status === 'loading' && 'Testing...'}
|
|
123
|
-
{status === 'ok' && detail}
|
|
124
|
-
{status === 'error' && detail}
|
|
125
|
-
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
126
133
|
);
|
|
127
134
|
}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import Sidebar from './Sidebar';
|
|
4
|
-
import Footer from './Footer';
|
|
5
4
|
import MobileNav from './MobileNav';
|
|
6
5
|
|
|
7
6
|
interface Props {
|
|
8
7
|
children: ReactNode;
|
|
8
|
+
userName?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export default function DashboardLayout({ children }: Props) {
|
|
11
|
+
export default function DashboardLayout({ children, userName }: Props) {
|
|
12
12
|
const [status, setStatus] = useState<'healthy' | 'restarting'>('healthy');
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
const check = () => {
|
|
16
|
-
fetch('/app/api/health')
|
|
17
|
-
.then((r) =>
|
|
18
|
-
|
|
19
|
-
})
|
|
20
|
-
.catch(() => setStatus('restarting'));
|
|
16
|
+
fetch('/app/api/health', { signal: AbortSignal.timeout(3000) })
|
|
17
|
+
.then((r) => setStatus(r.ok ? 'healthy' : 'restarting'))
|
|
18
|
+
.catch(() => {});
|
|
21
19
|
};
|
|
22
20
|
check();
|
|
23
21
|
const id = setInterval(check, 10_000);
|
|
@@ -25,10 +23,10 @@ export default function DashboardLayout({ children }: Props) {
|
|
|
25
23
|
}, []);
|
|
26
24
|
|
|
27
25
|
return (
|
|
28
|
-
<div className="flex h-dvh flex-col
|
|
26
|
+
<div className="flex h-dvh flex-col">
|
|
29
27
|
{/* Mobile header */}
|
|
30
28
|
<header className="flex items-center justify-between px-4 py-3 md:hidden">
|
|
31
|
-
<MobileNav />
|
|
29
|
+
<MobileNav userName={userName} backendStatus={status} />
|
|
32
30
|
<div className="flex items-center gap-2">
|
|
33
31
|
<img src="/fluxy.png" alt="Fluxy" className="h-6 w-auto" />
|
|
34
32
|
<span className="font-semibold text-base">Fluxy</span>
|
|
@@ -37,15 +35,20 @@ export default function DashboardLayout({ children }: Props) {
|
|
|
37
35
|
</header>
|
|
38
36
|
|
|
39
37
|
<div className="flex flex-1 overflow-hidden">
|
|
40
|
-
{/* Desktop sidebar */}
|
|
41
|
-
<div className="hidden md:flex shrink-0">
|
|
42
|
-
<
|
|
38
|
+
{/* Desktop sidebar — floating card with shiny border */}
|
|
39
|
+
<div id="tour-sidebar" className="hidden md:flex shrink-0 pl-3 pt-3 pb-3">
|
|
40
|
+
<div className="relative rounded-3xl overflow-hidden h-full">
|
|
41
|
+
{/* Shiny border highlight */}
|
|
42
|
+
<div className="absolute inset-0 rounded-3xl bg-gradient-to-b from-white/[0.12] via-white/[0.04] to-transparent pointer-events-none" />
|
|
43
|
+
<div className="relative rounded-3xl bg-[#1A1A1A] m-px h-full">
|
|
44
|
+
<Sidebar userName={userName} backendStatus={status} />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
43
47
|
</div>
|
|
44
48
|
|
|
45
49
|
{/* Main content + footer */}
|
|
46
50
|
<div className="flex flex-1 flex-col overflow-hidden">
|
|
47
|
-
<main className="flex-1 overflow-y-auto">{children}</main>
|
|
48
|
-
<Footer status={status} />
|
|
51
|
+
<main id="tour-dashboard" className="flex-1 overflow-y-auto">{children}</main>
|
|
49
52
|
</div>
|
|
50
53
|
</div>
|
|
51
54
|
</div>
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '@/components/ui/sheet';
|
|
8
8
|
import Sidebar from './Sidebar';
|
|
9
9
|
|
|
10
|
-
export default function MobileNav() {
|
|
10
|
+
export default function MobileNav({ userName, backendStatus }: { userName?: string; backendStatus?: 'healthy' | 'restarting' }) {
|
|
11
11
|
const [open, setOpen] = useState(false);
|
|
12
12
|
|
|
13
13
|
return (
|
|
@@ -22,7 +22,7 @@ export default function MobileNav() {
|
|
|
22
22
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
23
23
|
<SheetContent side="left" className="p-0 w-64" showCloseButton={false}>
|
|
24
24
|
<SheetTitle className="sr-only">Navigation</SheetTitle>
|
|
25
|
-
<Sidebar onNavigate={() => setOpen(false)} />
|
|
25
|
+
<Sidebar userName={userName} backendStatus={backendStatus} onNavigate={() => setOpen(false)} />
|
|
26
26
|
</SheetContent>
|
|
27
27
|
</Sheet>
|
|
28
28
|
</>
|