bloby-bot 0.39.1 → 0.40.0
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/package.json +1 -1
- package/workspace/client/index.html +0 -3
- package/workspace/client/src/App.tsx +9 -20
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +113 -117
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +34 -32
- package/workspace/client/src/components/Layout/MobileNav.tsx +6 -103
- package/workspace/client/src/components/Layout/Sidebar.tsx +10 -11
- package/workspace/client/src/styles/globals.css +4 -89
- package/workspace/client/public/.well-known/assetlinks.json +0 -8
- package/workspace/client/public/bloby-cyberpunk.png +0 -0
- package/workspace/client/public/brand/blackrock.svg +0 -8
- package/workspace/client/public/kid-breakfast.png +0 -0
- package/workspace/client/public/wallpapers/bg.jpg +0 -0
- package/workspace/client/public/wallpapers/crypto_bg.png +0 -0
- package/workspace/client/public/wallpapers/wp-dusk.jpg +0 -0
- package/workspace/client/public/wallpapers/wp-mountain.jpg +0 -0
- package/workspace/client/public/wallpapers/wp-ocean.jpg +0 -0
- package/workspace/client/src/components/Dashboard/AiChatPage.tsx +0 -145
- package/workspace/client/src/components/Dashboard/CryptoPage.tsx +0 -470
- package/workspace/client/src/components/Dashboard/WishlistPage.tsx +0 -464
- package/workspace/client/src/components/Layout/MiniSidebar.tsx +0 -64
- package/workspace/client/src/components/Lock/PinInput.tsx +0 -107
- package/workspace/client/src/components/Lock/WorkspaceLock.tsx +0 -484
- package/workspace/client/src/components/StickyNotes/StickyNotesOverlay.tsx +0 -396
- package/workspace/client/src/components/StickyNotes/StickyNotesSettingsPage.tsx +0 -427
- package/workspace/client/src/components/Wallpaper/WallpaperBackground.tsx +0 -12
- package/workspace/client/src/components/Wallpaper/WallpaperContext.tsx +0 -160
- package/workspace/client/src/components/Wallpaper/WallpaperPicker.tsx +0 -67
package/package.json
CHANGED
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
<link rel="apple-touch-icon" href="/bloby-icon-192.png" />
|
|
12
12
|
<link rel="manifest" href="/manifest.json" />
|
|
13
13
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
|
|
14
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
17
14
|
<title>Bloby - AI agent with its own workspace</title>
|
|
18
15
|
</head>
|
|
19
16
|
<body class="bg-background text-foreground" style="background-color:#0A0A0A">
|
|
@@ -4,11 +4,6 @@ import ErrorBoundary from './components/ErrorBoundary';
|
|
|
4
4
|
import DashboardLayout from './components/Layout/DashboardLayout';
|
|
5
5
|
import DashboardPage from './components/Dashboard/DashboardPage';
|
|
6
6
|
import WorkspaceTour from './components/deleteme_onboarding/WorkspaceTour';
|
|
7
|
-
import { WorkspaceLock } from './components/Lock/WorkspaceLock';
|
|
8
|
-
import StickyNotesSettingsPage from './components/StickyNotes/StickyNotesSettingsPage';
|
|
9
|
-
import AiChatPage from './components/Dashboard/AiChatPage';
|
|
10
|
-
import WishlistPage from './components/Dashboard/WishlistPage';
|
|
11
|
-
import CryptoPage from './components/Dashboard/CryptoPage';
|
|
12
7
|
|
|
13
8
|
function DashboardError() {
|
|
14
9
|
return (
|
|
@@ -21,9 +16,9 @@ function DashboardError() {
|
|
|
21
16
|
playsInline
|
|
22
17
|
style={{ height: 120, width: 120, borderRadius: '50%', objectFit: 'cover', marginBottom: 32 }}
|
|
23
18
|
/>
|
|
24
|
-
<h1 style={{ fontSize: 20, fontWeight: 600, marginBottom: 8 }}>
|
|
19
|
+
<h1 style={{ fontSize: 20, fontWeight: 600, marginBottom: 8 }}>Oopss.. Something wrong is not right</h1>
|
|
25
20
|
<p style={{ fontSize: 14, color: 'rgba(255,255,255,0.5)', maxWidth: 320, lineHeight: 1.5 }}>
|
|
26
|
-
|
|
21
|
+
If your agent is working, this is normal. If not, go poke them
|
|
27
22
|
</p>
|
|
28
23
|
</div>
|
|
29
24
|
);
|
|
@@ -162,19 +157,13 @@ export default function App() {
|
|
|
162
157
|
return (
|
|
163
158
|
<>
|
|
164
159
|
<ErrorBoundary fallback={<DashboardError />}>
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<Route path="/crypto" element={<CryptoPage />} />
|
|
173
|
-
<Route path="*" element={<DashboardPage />} />
|
|
174
|
-
</Routes>
|
|
175
|
-
</DashboardLayout>
|
|
176
|
-
<WorkspaceTour disabled={showOnboard} />
|
|
177
|
-
</WorkspaceLock>
|
|
160
|
+
<DashboardLayout userName={userName} botName={botName}>
|
|
161
|
+
<Routes>
|
|
162
|
+
<Route path="/" element={<DashboardPage />} />
|
|
163
|
+
<Route path="*" element={<DashboardPage />} />
|
|
164
|
+
</Routes>
|
|
165
|
+
</DashboardLayout>
|
|
166
|
+
<WorkspaceTour disabled={showOnboard} />
|
|
178
167
|
</ErrorBoundary>
|
|
179
168
|
|
|
180
169
|
{showOnboard && (
|
|
@@ -1,138 +1,134 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Heart, Zap, Eye, ArrowUpRight } from 'lucide-react';
|
|
1
|
+
import { Search, Mail, DollarSign, TrendingUp } from 'lucide-react';
|
|
2
|
+
import { AreaChart, Area, BarChart, Bar, ResponsiveContainer } from 'recharts';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
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';
|
|
8
|
+
|
|
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
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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>;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export default function DashboardPage() {
|
|
20
|
-
const navigate = useNavigate();
|
|
21
|
-
const [wlStats, setWlStats] = useState<WishlistStats | null>(null);
|
|
22
|
-
const [wlLatestDeal, setWlLatestDeal] = useState<{ name: string; deal: string } | null>(null);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
fetch('/app/api/wishlist/stats')
|
|
26
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
27
|
-
.then((s) => { if (s) setWlStats(s); })
|
|
28
|
-
.catch(() => {});
|
|
29
|
-
fetch('/app/api/wishlist')
|
|
30
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
31
|
-
.then((items: WishlistItem[] | null) => {
|
|
32
|
-
if (!items) return;
|
|
33
|
-
const withDeal = items.find((i) => i.last_deal_found);
|
|
34
|
-
if (withDeal) setWlLatestDeal({ name: withDeal.name, deal: withDeal.last_deal_found! });
|
|
35
|
-
})
|
|
36
|
-
.catch(() => {});
|
|
37
|
-
}, []);
|
|
38
|
-
|
|
39
23
|
return (
|
|
40
|
-
<div className="flex flex-col h-full px-4 sm:px-6
|
|
41
|
-
<
|
|
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
|
+
>
|
|
29
|
+
Let's get started
|
|
30
|
+
</h1>
|
|
31
|
+
<p className="text-muted-foreground text-xs mb-6">Your workspace at a glance.</p>
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
className=
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
</div>
|
|
54
|
-
<div className="flex-1 min-w-0">
|
|
55
|
-
<h3 className="text-base font-semibold text-white tracking-tight">Wishlist</h3>
|
|
56
|
-
<p className="text-xs text-white/60 mt-0.5">Items you're tracking, urgent picks, and the latest deal.</p>
|
|
33
|
+
<div className="grid grid-cols-3 gap-2.5">
|
|
34
|
+
|
|
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>
|
|
57
43
|
</div>
|
|
58
|
-
<div className="
|
|
59
|
-
<
|
|
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>
|
|
60
47
|
</div>
|
|
61
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>
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
gradientFrom="#FFB37A"
|
|
76
|
-
gradientTo="#FF7AAA"
|
|
77
|
-
/>
|
|
78
|
-
<Stat
|
|
79
|
-
icon={Heart}
|
|
80
|
-
label="Total"
|
|
81
|
-
value={wlStats?.total ?? 0}
|
|
82
|
-
gradientFrom="#C9A7E8"
|
|
83
|
-
gradientTo="#F2B5C7"
|
|
84
|
-
/>
|
|
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>
|
|
85
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>
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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>
|
|
95
104
|
</div>
|
|
96
|
-
)}
|
|
105
|
+
))}
|
|
97
106
|
</div>
|
|
98
|
-
</
|
|
107
|
+
</div>
|
|
99
108
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
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>
|
|
104
130
|
|
|
105
|
-
function Stat({
|
|
106
|
-
icon: Icon,
|
|
107
|
-
label,
|
|
108
|
-
value,
|
|
109
|
-
gradientFrom,
|
|
110
|
-
gradientTo,
|
|
111
|
-
}: {
|
|
112
|
-
icon: React.ComponentType<{ className?: string }>;
|
|
113
|
-
label: string;
|
|
114
|
-
value: number;
|
|
115
|
-
gradientFrom: string;
|
|
116
|
-
gradientTo: string;
|
|
117
|
-
}) {
|
|
118
|
-
const gradient = `linear-gradient(135deg, ${gradientFrom}, ${gradientTo})`;
|
|
119
|
-
return (
|
|
120
|
-
<div className="relative">
|
|
121
|
-
<div className="flex items-center gap-1.5 mb-2">
|
|
122
|
-
<Icon className="h-3 w-3 text-white/70" />
|
|
123
|
-
<span
|
|
124
|
-
className="text-[10px] font-semibold uppercase tracking-wider"
|
|
125
|
-
style={{
|
|
126
|
-
backgroundImage: gradient,
|
|
127
|
-
WebkitBackgroundClip: 'text',
|
|
128
|
-
WebkitTextFillColor: 'transparent',
|
|
129
|
-
backgroundClip: 'text',
|
|
130
|
-
}}
|
|
131
|
-
>
|
|
132
|
-
{label}
|
|
133
|
-
</span>
|
|
134
131
|
</div>
|
|
135
|
-
<p className="text-3xl sm:text-4xl font-semibold tracking-tight text-white tabular-nums">{value}</p>
|
|
136
132
|
</div>
|
|
137
133
|
);
|
|
138
134
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
+
import Sidebar from './Sidebar';
|
|
3
4
|
import MobileNav from './MobileNav';
|
|
4
|
-
import MiniSidebar from './MiniSidebar';
|
|
5
|
-
import StickyNotesOverlay from '../StickyNotes/StickyNotesOverlay';
|
|
6
|
-
import { WallpaperProvider } from '../Wallpaper/WallpaperContext';
|
|
7
|
-
import WallpaperBackground from '../Wallpaper/WallpaperBackground';
|
|
8
5
|
|
|
9
6
|
interface Props {
|
|
10
7
|
children: ReactNode;
|
|
@@ -17,9 +14,16 @@ export default function DashboardLayout({ children, userName, botName = 'Bloby'
|
|
|
17
14
|
|
|
18
15
|
useEffect(() => {
|
|
19
16
|
const check = () => {
|
|
17
|
+
console.log('[health] checking /app/api/health…');
|
|
20
18
|
fetch('/app/api/health', { signal: AbortSignal.timeout(3000) })
|
|
21
|
-
.then((r) =>
|
|
22
|
-
|
|
19
|
+
.then((r) => {
|
|
20
|
+
console.log(`[health] response: ${r.status} ok=${r.ok}`);
|
|
21
|
+
setStatus(r.ok ? 'healthy' : 'restarting');
|
|
22
|
+
})
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
console.warn('[health] fetch failed:', err.message ?? err);
|
|
25
|
+
setStatus('restarting');
|
|
26
|
+
});
|
|
23
27
|
};
|
|
24
28
|
check();
|
|
25
29
|
const id = setInterval(check, 10_000);
|
|
@@ -27,36 +31,34 @@ export default function DashboardLayout({ children, userName, botName = 'Bloby'
|
|
|
27
31
|
}, []);
|
|
28
32
|
|
|
29
33
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</div>
|
|
41
|
-
<div className="w-10" />
|
|
42
|
-
</header>
|
|
43
|
-
|
|
44
|
-
<div className="flex flex-1 overflow-hidden">
|
|
45
|
-
{/* Floating mini-sidebar (desktop only) */}
|
|
46
|
-
<div id="tour-sidebar" className="contents">
|
|
47
|
-
<MiniSidebar />
|
|
48
|
-
</div>
|
|
34
|
+
<div className="flex h-dvh flex-col">
|
|
35
|
+
{/* Mobile header */}
|
|
36
|
+
<header className="flex items-center justify-between px-4 py-3 md:hidden">
|
|
37
|
+
<MobileNav userName={userName} botName={botName} backendStatus={status} />
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
<img src="/bloby.png" alt={botName} className="h-6 w-auto" />
|
|
40
|
+
<span className="font-semibold text-base">{botName}</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="w-10" />
|
|
43
|
+
</header>
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
<div className="flex flex-1 overflow-hidden">
|
|
46
|
+
{/* Desktop sidebar — floating card with shiny border */}
|
|
47
|
+
<div id="tour-sidebar" className="hidden md:flex shrink-0 pl-3 pt-3 pb-3">
|
|
48
|
+
<div className="relative rounded-3xl overflow-hidden h-full">
|
|
49
|
+
{/* Shiny border highlight */}
|
|
50
|
+
<div className="absolute inset-0 rounded-3xl bg-gradient-to-b from-white/[0.12] via-white/[0.04] to-transparent pointer-events-none" />
|
|
51
|
+
<div className="relative rounded-3xl bg-[#1A1A1A] m-px h-full">
|
|
52
|
+
<Sidebar userName={userName} botName={botName} backendStatus={status} />
|
|
53
|
+
</div>
|
|
56
54
|
</div>
|
|
57
55
|
</div>
|
|
58
56
|
|
|
57
|
+
{/* Main content + footer */}
|
|
58
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
59
|
+
<main id="tour-dashboard" className="flex-1 overflow-y-auto">{children}</main>
|
|
60
|
+
</div>
|
|
59
61
|
</div>
|
|
60
|
-
</
|
|
62
|
+
</div>
|
|
61
63
|
);
|
|
62
64
|
}
|
|
@@ -1,127 +1,30 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { Menu, LayoutDashboard, StickyNote, MessageCircle, Heart, Settings, Bitcoin, X } from 'lucide-react';
|
|
2
|
+
import { Menu } from 'lucide-react';
|
|
4
3
|
import {
|
|
5
4
|
Sheet,
|
|
6
5
|
SheetContent,
|
|
7
6
|
SheetTitle,
|
|
8
7
|
} from '@/components/ui/sheet';
|
|
9
|
-
import
|
|
10
|
-
import { cn } from '@/lib/utils';
|
|
8
|
+
import Sidebar from './Sidebar';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
userName?: string;
|
|
14
|
-
botName?: string;
|
|
15
|
-
backendStatus?: 'healthy' | 'restarting';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default function MobileNav({ userName, botName = 'Bloby', backendStatus = 'healthy' }: Props) {
|
|
10
|
+
export default function MobileNav({ userName, botName, backendStatus }: { userName?: string; botName?: string; backendStatus?: 'healthy' | 'restarting' }) {
|
|
19
11
|
const [open, setOpen] = useState(false);
|
|
20
|
-
const [pickerOpen, setPickerOpen] = useState(false);
|
|
21
|
-
const firstName = userName?.split(/\s+/)[0] || '';
|
|
22
|
-
|
|
23
|
-
const close = () => setOpen(false);
|
|
24
12
|
|
|
25
13
|
return (
|
|
26
14
|
<>
|
|
27
15
|
<button
|
|
28
16
|
onClick={() => setOpen(true)}
|
|
29
|
-
className="flex items-center justify-center h-10 w-10 rounded-lg text-
|
|
17
|
+
className="flex items-center justify-center h-10 w-10 rounded-lg text-muted-foreground hover:text-foreground transition-colors md:hidden"
|
|
30
18
|
>
|
|
31
19
|
<Menu className="h-5 w-5" />
|
|
32
20
|
<span className="sr-only">Open navigation</span>
|
|
33
21
|
</button>
|
|
34
22
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
35
|
-
<SheetContent side="left" className="p-0 w-
|
|
23
|
+
<SheetContent side="left" className="p-0 w-64" showCloseButton={false}>
|
|
36
24
|
<SheetTitle className="sr-only">Navigation</SheetTitle>
|
|
37
|
-
<
|
|
38
|
-
<div className="glass-card h-full p-5 flex flex-col" style={{ borderRadius: 24 }}>
|
|
39
|
-
<div className="flex items-center justify-between mb-6">
|
|
40
|
-
<div>
|
|
41
|
-
<p className="text-xs text-white/60">Workspace</p>
|
|
42
|
-
<p className="text-lg font-semibold tracking-tight">{botName}</p>
|
|
43
|
-
</div>
|
|
44
|
-
<button
|
|
45
|
-
type="button"
|
|
46
|
-
onClick={close}
|
|
47
|
-
className="h-9 w-9 rounded-full glass-pill flex items-center justify-center text-white/80"
|
|
48
|
-
aria-label="Close navigation"
|
|
49
|
-
>
|
|
50
|
-
<X className="h-4 w-4" />
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
{firstName && (
|
|
55
|
-
<p className="text-sm text-white/70 mb-4">Hi, {firstName}.</p>
|
|
56
|
-
)}
|
|
57
|
-
|
|
58
|
-
<nav className="flex-1 space-y-1">
|
|
59
|
-
<Item to="/" icon={LayoutDashboard} label="Dashboard" onNavigate={close} />
|
|
60
|
-
<Item to="/sticky-notes" icon={StickyNote} label="Sticky Notes" onNavigate={close} />
|
|
61
|
-
<Item to="/aichat" icon={MessageCircle} label="AI Chat" onNavigate={close} />
|
|
62
|
-
<Item to="/wishlist" icon={Heart} label="Wishlist" onNavigate={close} />
|
|
63
|
-
<Item to="/crypto" icon={Bitcoin} label="Crypto" onNavigate={close} />
|
|
64
|
-
|
|
65
|
-
<button
|
|
66
|
-
type="button"
|
|
67
|
-
onClick={() => {
|
|
68
|
-
setPickerOpen(true);
|
|
69
|
-
close();
|
|
70
|
-
}}
|
|
71
|
-
className="w-full mt-2 flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-white/80 hover:text-white hover:bg-white/10 transition"
|
|
72
|
-
>
|
|
73
|
-
<Settings className="h-[18px] w-[18px]" />
|
|
74
|
-
<span>Wallpaper</span>
|
|
75
|
-
</button>
|
|
76
|
-
</nav>
|
|
77
|
-
|
|
78
|
-
<div className="rounded-xl glass-pill px-3.5 py-2.5 flex items-center gap-2 mt-4">
|
|
79
|
-
<span
|
|
80
|
-
className={`h-2 w-2 rounded-full shrink-0 ${
|
|
81
|
-
backendStatus === 'healthy' ? 'bg-emerald-400' : 'bg-orange-400 animate-pulse'
|
|
82
|
-
}`}
|
|
83
|
-
/>
|
|
84
|
-
<span className="text-xs text-white/70">
|
|
85
|
-
Workspace: {backendStatus === 'healthy' ? 'Live' : 'Restarting'}
|
|
86
|
-
</span>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
25
|
+
<Sidebar userName={userName} botName={botName} backendStatus={backendStatus} onNavigate={() => setOpen(false)} />
|
|
90
26
|
</SheetContent>
|
|
91
27
|
</Sheet>
|
|
92
|
-
|
|
93
|
-
<WallpaperPicker open={pickerOpen} onClose={() => setPickerOpen(false)} />
|
|
94
28
|
</>
|
|
95
29
|
);
|
|
96
30
|
}
|
|
97
|
-
|
|
98
|
-
function Item({
|
|
99
|
-
to,
|
|
100
|
-
icon: Icon,
|
|
101
|
-
label,
|
|
102
|
-
onNavigate,
|
|
103
|
-
}: {
|
|
104
|
-
to: string;
|
|
105
|
-
icon: React.ComponentType<{ className?: string }>;
|
|
106
|
-
label: string;
|
|
107
|
-
onNavigate: () => void;
|
|
108
|
-
}) {
|
|
109
|
-
return (
|
|
110
|
-
<NavLink
|
|
111
|
-
to={to}
|
|
112
|
-
end
|
|
113
|
-
onClick={onNavigate}
|
|
114
|
-
className={({ isActive }) =>
|
|
115
|
-
cn(
|
|
116
|
-
'flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm transition',
|
|
117
|
-
isActive
|
|
118
|
-
? 'bg-white/15 text-white font-medium'
|
|
119
|
-
: 'text-white/70 hover:text-white hover:bg-white/10',
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
>
|
|
123
|
-
<Icon className="h-[18px] w-[18px]" />
|
|
124
|
-
<span>{label}</span>
|
|
125
|
-
</NavLink>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { NavLink } from 'react-router';
|
|
2
2
|
import {
|
|
3
3
|
LayoutDashboard,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Bitcoin,
|
|
4
|
+
AppWindow,
|
|
5
|
+
Search,
|
|
6
|
+
CircleHelp,
|
|
8
7
|
} from 'lucide-react';
|
|
9
8
|
import { cn } from '@/lib/utils';
|
|
10
9
|
|
|
@@ -27,8 +26,9 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
27
26
|
return (
|
|
28
27
|
<aside className="flex flex-col h-full w-64 bg-transparent p-5 pt-8">
|
|
29
28
|
{/* Logo */}
|
|
30
|
-
<div className="flex items-center mb-8">
|
|
31
|
-
<img src="/
|
|
29
|
+
<div className="flex items-center gap-2.5 mb-8">
|
|
30
|
+
<img src="/bloby.png" alt={botName} className="h-7 w-auto" />
|
|
31
|
+
<span className="font-bold text-lg" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{botName}</span>
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
{/* Greeting */}
|
|
@@ -40,7 +40,7 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
40
40
|
<h2
|
|
41
41
|
className="text-4xl font-bold mt-0.5 tracking-tight leading-[1.08] w-fit"
|
|
42
42
|
style={{
|
|
43
|
-
backgroundImage: 'linear-gradient(to right, #
|
|
43
|
+
backgroundImage: 'linear-gradient(to right, #4FF2FE, #BC20DE, #FE546B)',
|
|
44
44
|
WebkitBackgroundClip: 'text',
|
|
45
45
|
WebkitTextFillColor: 'transparent',
|
|
46
46
|
backgroundClip: 'text',
|
|
@@ -54,10 +54,9 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
54
54
|
{/* Navigation */}
|
|
55
55
|
<nav className="flex-1 space-y-0.5">
|
|
56
56
|
<NavItem to="/" icon={LayoutDashboard} label="Dashboard" onNavigate={onNavigate} />
|
|
57
|
-
<NavItem to="/
|
|
58
|
-
<NavItem to="/
|
|
59
|
-
<NavItem to="/
|
|
60
|
-
<NavItem to="/crypto" icon={Bitcoin} label="Crypto" onNavigate={onNavigate} />
|
|
57
|
+
<NavItem to="/app1" icon={AppWindow} label="App 1" sub="Ask Bloby to create your first app and remove this placeholder" onNavigate={onNavigate} />
|
|
58
|
+
<NavItem to="/research" icon={Search} label="Research" sub="Ask Bloby to research anything for you, maybe even daily" onNavigate={onNavigate} />
|
|
59
|
+
<NavItem to="/whatelse" icon={CircleHelp} label="What Else?" sub="The sky is the limit, just ask and Bloby will do it!" onNavigate={onNavigate} />
|
|
61
60
|
</nav>
|
|
62
61
|
|
|
63
62
|
{/* Backend status */}
|