fluxy-bot 0.2.39 → 0.2.41
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
CHANGED
|
@@ -106,7 +106,7 @@ function FluxyApp() {
|
|
|
106
106
|
const cfgRes = await fetch('/api/auth/configured');
|
|
107
107
|
const cfg = await cfgRes.json();
|
|
108
108
|
if (!cfg.configured) {
|
|
109
|
-
setAuthState('first-run');
|
|
109
|
+
setAuthState('first-run');
|
|
110
110
|
} else {
|
|
111
111
|
setAuthState('unauthenticated');
|
|
112
112
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { Routes, Route, Navigate
|
|
2
|
+
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
3
3
|
import ErrorBoundary from './components/ErrorBoundary';
|
|
4
4
|
import DashboardLayout from './components/Layout/DashboardLayout';
|
|
5
5
|
import DashboardPage from './components/Dashboard/DashboardPage';
|
|
6
6
|
import LandingPage from './components/Landing/LandingPage';
|
|
7
|
-
import LoginPage from './components/Auth/LoginPage';
|
|
8
7
|
|
|
9
8
|
function DashboardError() {
|
|
10
9
|
return (
|
|
@@ -31,7 +30,6 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
|
31
30
|
useEffect(() => {
|
|
32
31
|
(async () => {
|
|
33
32
|
try {
|
|
34
|
-
// Check if credentials are configured at all
|
|
35
33
|
const cfgRes = await fetch('/api/auth/configured');
|
|
36
34
|
const cfg = await cfgRes.json();
|
|
37
35
|
if (!cfg.configured) {
|
|
@@ -40,12 +38,10 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
|
40
38
|
return;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
// Credentials exist — verify session
|
|
44
41
|
const meRes = await fetch('/api/auth/me');
|
|
45
42
|
const me = await meRes.json();
|
|
46
43
|
setStatus(me.authenticated ? 'ok' : 'denied');
|
|
47
44
|
} catch {
|
|
48
|
-
// Worker not ready — allow (onboarding scenario)
|
|
49
45
|
setStatus('ok');
|
|
50
46
|
}
|
|
51
47
|
})();
|
|
@@ -59,7 +55,8 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
|
59
55
|
);
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
// Not authenticated — send to landing page (login via chat bubble)
|
|
59
|
+
if (status === 'denied') return <Navigate to="/" replace />;
|
|
63
60
|
|
|
64
61
|
return <>{children}</>;
|
|
65
62
|
}
|
|
@@ -151,7 +148,6 @@ export default function App() {
|
|
|
151
148
|
return (
|
|
152
149
|
<Routes>
|
|
153
150
|
<Route path="/" element={<LandingPage />} />
|
|
154
|
-
<Route path="/login" element={<LoginPage />} />
|
|
155
151
|
<Route
|
|
156
152
|
path="/dashboard"
|
|
157
153
|
element={
|
|
@@ -5,19 +5,38 @@ export default function LandingPage() {
|
|
|
5
5
|
const navigate = useNavigate();
|
|
6
6
|
const [botName, setBotName] = useState('Fluxy');
|
|
7
7
|
const [isAuthed, setIsAuthed] = useState(false);
|
|
8
|
-
const [
|
|
8
|
+
const [showOnboard, setShowOnboard] = useState(false);
|
|
9
9
|
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
Promise.all([
|
|
12
12
|
fetch('/api/settings').then((r) => r.json()).catch(() => null),
|
|
13
13
|
fetch('/api/auth/me').then((r) => r.json()).catch(() => null),
|
|
14
|
-
|
|
14
|
+
fetch('/api/auth/configured').then((r) => r.json()).catch(() => null),
|
|
15
|
+
]).then(([settings, me, cfg]) => {
|
|
15
16
|
if (settings?.agent_name) setBotName(settings.agent_name);
|
|
16
17
|
if (me?.authenticated) setIsAuthed(true);
|
|
17
|
-
|
|
18
|
+
// First run — show onboard overlay
|
|
19
|
+
if (cfg && !cfg.configured && !cfg.onboardComplete) setShowOnboard(true);
|
|
18
20
|
});
|
|
19
21
|
}, []);
|
|
20
22
|
|
|
23
|
+
// Hide/show bubble during onboarding
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const bubble = document.getElementById('fluxy-widget-bubble');
|
|
26
|
+
if (bubble) bubble.style.display = showOnboard ? 'none' : '';
|
|
27
|
+
}, [showOnboard]);
|
|
28
|
+
|
|
29
|
+
// Listen for onboard complete from fluxy iframe
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const handler = (e: MessageEvent) => {
|
|
32
|
+
if (e.data?.type === 'fluxy:onboard-complete') {
|
|
33
|
+
setShowOnboard(false);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener('message', handler);
|
|
37
|
+
return () => window.removeEventListener('message', handler);
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
21
40
|
return (
|
|
22
41
|
<div className="min-h-dvh flex flex-col bg-background">
|
|
23
42
|
{/* Header */}
|
|
@@ -26,12 +45,12 @@ export default function LandingPage() {
|
|
|
26
45
|
<img src="/fluxy.png" alt={botName} className="h-6 w-auto" />
|
|
27
46
|
<span className="text-sm font-semibold">{botName}</span>
|
|
28
47
|
</div>
|
|
29
|
-
{
|
|
48
|
+
{isAuthed && (
|
|
30
49
|
<button
|
|
31
|
-
onClick={() => navigate(
|
|
50
|
+
onClick={() => navigate('/dashboard')}
|
|
32
51
|
className="text-sm font-medium px-4 py-2 rounded-full border border-white/[0.08] hover:bg-white/[0.06] transition-colors"
|
|
33
52
|
>
|
|
34
|
-
|
|
53
|
+
Dashboard
|
|
35
54
|
</button>
|
|
36
55
|
)}
|
|
37
56
|
</header>
|
|
@@ -58,6 +77,14 @@ export default function LandingPage() {
|
|
|
58
77
|
<footer className="py-4 text-center">
|
|
59
78
|
<p className="text-xs text-muted-foreground/60">Powered by Fluxy</p>
|
|
60
79
|
</footer>
|
|
80
|
+
|
|
81
|
+
{/* Onboard overlay (first run) — served from dist-fluxy (supervisor territory) */}
|
|
82
|
+
{showOnboard && (
|
|
83
|
+
<iframe
|
|
84
|
+
src="/fluxy/onboard.html"
|
|
85
|
+
style={{ position: 'fixed', inset: 0, width: '100vw', height: '100dvh', border: 'none', zIndex: 200 }}
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
61
88
|
</div>
|
|
62
89
|
);
|
|
63
90
|
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate, Link } from 'react-router-dom';
|
|
3
|
-
|
|
4
|
-
export default function LoginPage() {
|
|
5
|
-
const navigate = useNavigate();
|
|
6
|
-
const [username, setUsername] = useState('');
|
|
7
|
-
const [password, setPassword] = useState('');
|
|
8
|
-
const [error, setError] = useState('');
|
|
9
|
-
const [loading, setLoading] = useState(false);
|
|
10
|
-
|
|
11
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
12
|
-
e.preventDefault();
|
|
13
|
-
setError('');
|
|
14
|
-
setLoading(true);
|
|
15
|
-
try {
|
|
16
|
-
const res = await fetch('/api/auth/login', {
|
|
17
|
-
method: 'POST',
|
|
18
|
-
headers: { 'Content-Type': 'application/json' },
|
|
19
|
-
body: JSON.stringify({ username, password }),
|
|
20
|
-
});
|
|
21
|
-
const data = await res.json();
|
|
22
|
-
if (data.ok) {
|
|
23
|
-
navigate('/dashboard');
|
|
24
|
-
} else {
|
|
25
|
-
setError(data.error || 'Login failed');
|
|
26
|
-
}
|
|
27
|
-
} catch {
|
|
28
|
-
setError('Connection error');
|
|
29
|
-
} finally {
|
|
30
|
-
setLoading(false);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className="min-h-dvh flex flex-col items-center justify-center bg-background px-6">
|
|
36
|
-
<div className="w-full max-w-sm">
|
|
37
|
-
<div className="flex flex-col items-center mb-8">
|
|
38
|
-
<img src="/fluxy.png" alt="Fluxy" className="h-12 w-auto mb-4" />
|
|
39
|
-
<h1 className="text-xl font-semibold">Welcome Back</h1>
|
|
40
|
-
<p className="text-sm text-muted-foreground mt-1">Sign in to access the dashboard</p>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
|
|
44
|
-
<input
|
|
45
|
-
type="text"
|
|
46
|
-
placeholder="Username"
|
|
47
|
-
value={username}
|
|
48
|
-
onChange={(e) => setUsername(e.target.value)}
|
|
49
|
-
autoComplete="username"
|
|
50
|
-
autoFocus
|
|
51
|
-
className="w-full bg-white/[0.05] border border-white/[0.08] rounded-xl px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none input-glow"
|
|
52
|
-
/>
|
|
53
|
-
<input
|
|
54
|
-
type="password"
|
|
55
|
-
placeholder="Password"
|
|
56
|
-
value={password}
|
|
57
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
58
|
-
autoComplete="current-password"
|
|
59
|
-
className="w-full bg-white/[0.05] border border-white/[0.08] rounded-xl px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none input-glow"
|
|
60
|
-
/>
|
|
61
|
-
{error && (
|
|
62
|
-
<div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2 text-xs text-red-400">
|
|
63
|
-
{error}
|
|
64
|
-
</div>
|
|
65
|
-
)}
|
|
66
|
-
<button
|
|
67
|
-
type="submit"
|
|
68
|
-
disabled={loading || !username || !password}
|
|
69
|
-
className="w-full bg-gradient-brand text-white font-medium rounded-full py-3 text-sm hover:opacity-90 transition-opacity disabled:opacity-50"
|
|
70
|
-
>
|
|
71
|
-
{loading ? 'Signing in...' : 'Sign In'}
|
|
72
|
-
</button>
|
|
73
|
-
</form>
|
|
74
|
-
|
|
75
|
-
<div className="mt-6 text-center">
|
|
76
|
-
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
|
77
|
-
Back to home
|
|
78
|
-
</Link>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|