fluxy-bot 0.3.5 → 0.3.6

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Self-hosted AI bot — run your own AI assistant from anywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  import { useState, useRef, useEffect, type KeyboardEvent } from 'react';
2
- import { ArrowRight, LoaderCircle, ExternalLink, ClipboardPaste, RefreshCw, Check, ChevronDown, Mic } from 'lucide-react';
2
+ import { ArrowRight, LoaderCircle, ExternalLink, ClipboardPaste, RefreshCw, Check, ChevronDown, Mic, Eye, EyeOff } from 'lucide-react';
3
3
  import { motion, AnimatePresence } from 'framer-motion';
4
4
 
5
5
  /* ── Provider config ── */
@@ -25,7 +25,7 @@ const MODELS: Record<string, { id: string; label: string }[]> = {
25
25
  ],
26
26
  };
27
27
 
28
- const TOTAL_STEPS = 6; // 0..5
28
+ // TOTAL_STEPS is dynamic set inside the component based on isInitialSetup
29
29
 
30
30
  const HANDLES = [
31
31
  { tier: 'premium', label: (n: string) => `${n}.fluxy.bot`, badge: '$5', badgeCls: 'bg-[#AF27E3]/15 text-[#AF27E3] border-[#AF27E3]/20', highlight: true },
@@ -86,9 +86,12 @@ function ModelDropdown({ models, value, onChange }: { models: { id: string; labe
86
86
 
87
87
  interface Props {
88
88
  onComplete: () => void;
89
+ isInitialSetup?: boolean;
89
90
  }
90
91
 
91
- export default function OnboardWizard({ onComplete }: Props) {
92
+ export default function OnboardWizard({ onComplete, isInitialSetup = false }: Props) {
93
+ const TOTAL_STEPS = isInitialSetup ? 7 : 6; // 0..5 normal, +step 6 "All Set" for initial
94
+
92
95
  const [step, setStep] = useState(0);
93
96
  const [userName, setUserName] = useState('');
94
97
  const [provider, setProvider] = useState('anthropic');
@@ -129,11 +132,12 @@ export default function OnboardWizard({ onComplete }: Props) {
129
132
  const [changingHandle, setChangingHandle] = useState(false);
130
133
 
131
134
  // Portal credentials (step 3)
132
- const [portalUser, setPortalUser] = useState('');
135
+ const [portalUser, setPortalUser] = useState('admin');
133
136
  const [portalPass, setPortalPass] = useState('');
134
137
  const [portalPassConfirm, setPortalPassConfirm] = useState('');
135
138
  const [portalCopied, setPortalCopied] = useState(false);
136
139
  const [portalExists, setPortalExists] = useState(false);
140
+ const [showPassAllSet, setShowPassAllSet] = useState(false);
137
141
 
138
142
  // Portal old password (for changing existing credentials)
139
143
  const [portalOldPass, setPortalOldPass] = useState('');
@@ -428,12 +432,12 @@ export default function OnboardWizard({ onComplete }: Props) {
428
432
 
429
433
  /* ── Navigation ── */
430
434
 
431
- // Steps: 0=Welcome, 1=Name, 2=Bot name + Handle, 3=Portal credentials, 4=Provider, 5=Whisper+Complete
435
+ // Steps: 0=Welcome, 1=Name, 2=Bot name + Handle, 3=Password, 4=Provider, 5=Whisper+Complete, 6=All Set (initial only)
432
436
  const portalPassMatch = portalPass === portalPassConfirm;
433
- const portalValid = portalUser.trim().length >= 3 && portalPass.length >= 6 && portalPassMatch;
437
+ const portalValid = portalPass.length >= 6 && portalPassMatch;
434
438
  // When portal exists: can continue if no password fields touched, or old pass verified + new pass valid
435
439
  const portalCanContinue = portalExists
436
- ? (portalUser.trim().length >= 3 && (portalPass.length === 0 || (portalOldPassVerified && portalValid)))
440
+ ? (portalPass.length === 0 || (portalOldPassVerified && portalValid))
437
441
  : portalValid;
438
442
 
439
443
  const canNext = (() => {
@@ -473,7 +477,12 @@ export default function OnboardWizard({ onComplete }: Props) {
473
477
  portalPass,
474
478
  }),
475
479
  });
476
- onComplete();
480
+ if (isInitialSetup) {
481
+ setSaving(false);
482
+ setStep(6);
483
+ } else {
484
+ onComplete();
485
+ }
477
486
  } catch (err) {
478
487
  console.error('Onboard failed:', err);
479
488
  setSaving(false);
@@ -762,67 +771,26 @@ export default function OnboardWizard({ onComplete }: Props) {
762
771
  </div>
763
772
  )}
764
773
 
765
- {/* ── Step 3: Portal credentials ── */}
774
+ {/* ── Step 3: Password ── */}
766
775
  {step === 3 && (
767
776
  <div>
768
777
  <h1 className="text-xl font-bold text-white tracking-tight">
769
- Secure your portal
778
+ Set a password
770
779
  </h1>
771
780
  <p className="text-white/40 text-[13px] mt-1.5 leading-relaxed">
772
- Create credentials to protect your dashboard. Anyone with your URL will need these to log in.
781
+ You'll need this password to access your agent's chat. Keep it safe — anyone with your URL will need it to log in.
773
782
  </p>
774
783
 
775
- {/* Relay URL display + copy */}
776
- {(registered && registeredUrl) && (
777
- <div className="mt-4 flex items-center gap-2 bg-white/[0.03] border border-white/[0.06] rounded-xl px-4 py-3">
778
- <span className="font-mono text-[13px] text-white/60 truncate flex-1">{registeredUrl}</span>
779
- <button
780
- onClick={() => {
781
- navigator.clipboard.writeText(registeredUrl);
782
- setPortalCopied(true);
783
- setTimeout(() => setPortalCopied(false), 2000);
784
- }}
785
- className="shrink-0 text-white/30 hover:text-white/60 transition-colors"
786
- >
787
- {portalCopied ? (
788
- <Check className="h-4 w-4 text-emerald-400" />
789
- ) : (
790
- <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
791
- <rect x="9" y="9" width="13" height="13" rx="2" />
792
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
793
- </svg>
794
- )}
795
- </button>
796
- </div>
797
- )}
798
-
799
784
  {/* Already configured hint */}
800
785
  {portalExists && (
801
786
  <div className="mt-4 bg-white/[0.02] border border-white/[0.06] rounded-xl px-4 py-2.5">
802
- <p className="text-white/40 text-[12px]">Credentials already set. Leave password fields empty to keep your current password, or enter your current password to change it.</p>
787
+ <p className="text-white/40 text-[12px]">Password already set. Leave fields empty to keep your current password, or enter your current password to change it.</p>
803
788
  </div>
804
789
  )}
805
790
 
806
- {/* Username */}
807
- <div className="mt-5">
808
- <label className="text-[12px] text-white/40 font-medium mb-1.5 block">Username</label>
809
- <input
810
- type="text"
811
- value={portalUser}
812
- onChange={(e) => setPortalUser(e.target.value.replace(/\s/g, '').toLowerCase())}
813
- placeholder="admin"
814
- autoFocus
815
- autoComplete="username"
816
- className={inputCls + ' font-mono'}
817
- />
818
- {portalUser.length > 0 && portalUser.trim().length < 3 && (
819
- <p className="text-amber-400/70 text-[11px] mt-1">At least 3 characters</p>
820
- )}
821
- </div>
822
-
823
791
  {/* Current password (only when changing existing credentials) */}
824
792
  {portalExists && (
825
- <div className="mt-3">
793
+ <div className="mt-5">
826
794
  <label className="text-[12px] text-white/40 font-medium mb-1.5 block">Current password</label>
827
795
  <div className="flex items-center gap-2">
828
796
  <input
@@ -831,6 +799,7 @@ export default function OnboardWizard({ onComplete }: Props) {
831
799
  onChange={(e) => { setPortalOldPass(e.target.value); setPortalOldPassError(''); setPortalOldPassVerified(false); }}
832
800
  placeholder="Enter current password to change it"
833
801
  autoComplete="current-password"
802
+ autoFocus
834
803
  className={inputCls + ' flex-1'}
835
804
  />
836
805
  {portalOldPass.length > 0 && !portalOldPassVerified && (
@@ -877,7 +846,7 @@ export default function OnboardWizard({ onComplete }: Props) {
877
846
  {/* New password + confirm (shown when no existing credentials, or when old password verified) */}
878
847
  {(!portalExists || portalOldPassVerified) && (
879
848
  <>
880
- <div className="mt-3">
849
+ <div className={portalExists ? 'mt-3' : 'mt-5'}>
881
850
  <label className="text-[12px] text-white/40 font-medium mb-1.5 block">
882
851
  {portalExists ? 'New password' : 'Password'}
883
852
  </label>
@@ -887,6 +856,7 @@ export default function OnboardWizard({ onComplete }: Props) {
887
856
  onChange={(e) => setPortalPass(e.target.value)}
888
857
  placeholder="••••••••"
889
858
  autoComplete="new-password"
859
+ autoFocus={!portalExists}
890
860
  onKeyDown={handleKeyDown}
891
861
  className={inputCls}
892
862
  />
@@ -1226,11 +1196,81 @@ export default function OnboardWizard({ onComplete }: Props) {
1226
1196
  )}
1227
1197
  </div>
1228
1198
  )}
1199
+
1200
+ {/* ── Step 6: All Set (initial onboard only) ── */}
1201
+ {step === 6 && isInitialSetup && (
1202
+ <div className="flex flex-col items-center text-center">
1203
+ <div className="w-16 h-16 rounded-full bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center mb-5">
1204
+ <Check className="h-8 w-8 text-emerald-400" />
1205
+ </div>
1206
+ <h1 className="text-2xl font-bold text-white tracking-tight">
1207
+ All Set!
1208
+ </h1>
1209
+ <p className="text-white/40 text-[13px] mt-2 leading-relaxed max-w-[340px]">
1210
+ Your agent is live and ready. From now on, access it using your custom URL below.
1211
+ </p>
1212
+
1213
+ {/* Custom URL */}
1214
+ <div className="w-full mt-6 flex items-center gap-2 bg-white/[0.03] border border-white/[0.06] rounded-xl px-4 py-3">
1215
+ <span className="font-mono text-[13px] text-white/70 truncate flex-1 text-left">{registeredUrl}</span>
1216
+ <button
1217
+ onClick={() => {
1218
+ const url = registeredUrl.startsWith('http') ? registeredUrl : `https://${registeredUrl}`;
1219
+ navigator.clipboard.writeText(url);
1220
+ setPortalCopied(true);
1221
+ setTimeout(() => setPortalCopied(false), 2000);
1222
+ }}
1223
+ className="shrink-0 text-white/30 hover:text-white/60 transition-colors"
1224
+ >
1225
+ {portalCopied ? (
1226
+ <Check className="h-4 w-4 text-emerald-400" />
1227
+ ) : (
1228
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
1229
+ <rect x="9" y="9" width="13" height="13" rx="2" />
1230
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
1231
+ </svg>
1232
+ )}
1233
+ </button>
1234
+ </div>
1235
+
1236
+ {/* Password display with eye toggle */}
1237
+ <div className="w-full mt-3">
1238
+ <label className="text-[12px] text-white/40 font-medium mb-1.5 block text-left">Your password</label>
1239
+ <div className="flex items-center gap-2 bg-white/[0.03] border border-white/[0.06] rounded-xl px-4 py-3">
1240
+ <span className="font-mono text-[13px] text-white/70 truncate flex-1 text-left">
1241
+ {showPassAllSet ? portalPass : '\u2022'.repeat(Math.max(portalPass.length, 8))}
1242
+ </span>
1243
+ <button
1244
+ onClick={() => setShowPassAllSet((v) => !v)}
1245
+ className="shrink-0 text-white/30 hover:text-white/60 transition-colors"
1246
+ >
1247
+ {showPassAllSet ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
1248
+ </button>
1249
+ </div>
1250
+ </div>
1251
+
1252
+ {/* Redirect button */}
1253
+ <button
1254
+ onClick={() => {
1255
+ const url = registeredUrl.startsWith('http') ? registeredUrl : `https://${registeredUrl}`;
1256
+ window.location.href = url;
1257
+ }}
1258
+ className="w-full mt-6 py-3 bg-gradient-brand hover:opacity-90 text-white text-[14px] font-semibold rounded-full transition-colors flex items-center justify-center gap-2"
1259
+ >
1260
+ Go to your agent
1261
+ <ExternalLink className="h-4 w-4" />
1262
+ </button>
1263
+
1264
+ <p className="text-white/20 text-[11px] mt-3 leading-relaxed">
1265
+ You'll be redirected to your custom URL.
1266
+ </p>
1267
+ </div>
1268
+ )}
1229
1269
  </motion.div>
1230
1270
  </AnimatePresence>
1231
1271
 
1232
- {/* Back button */}
1233
- {step > 0 && (
1272
+ {/* Back button (hidden on All Set step) */}
1273
+ {step > 0 && step < TOTAL_STEPS - 1 && !(step === 6 && isInitialSetup) && (
1234
1274
  <div className="px-8 pb-5 -mt-3">
1235
1275
  <button
1236
1276
  onClick={back}
@@ -9,7 +9,7 @@ function App() {
9
9
  window.parent?.postMessage({ type: 'fluxy:onboard-complete' }, '*');
10
10
  };
11
11
 
12
- return <OnboardWizard onComplete={handleComplete} />;
12
+ return <OnboardWizard onComplete={handleComplete} isInitialSetup />;
13
13
  }
14
14
 
15
15
  ReactDOM.createRoot(document.getElementById('root')!).render(