diff --git a/demo/src/App.tsx b/demo/src/App.tsx index a57f636..8444b33 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -11,6 +11,7 @@ import { EmployeeDashboard } from "./EmployeeDashboard"; import { StreamStatusCard } from "./StreamStatusCard"; import { BatchCreateStreams } from "./BatchCreateStreams"; import { ErrorBoundary } from "./ErrorBoundary"; +import { OnboardingWizard, shouldShowOnboarding } from "./OnboardingWizard"; const STROOP = 10_000_000n; // 1 XLM in stroops @@ -109,6 +110,7 @@ type AppView = "demo" | "dashboard" | "employee" | "batch"; export default function App() { const [dark, toggleDark] = useDarkMode(); const [view, setView] = useState("demo"); + const [showOnboarding, setShowOnboarding] = useState(() => shouldShowOnboarding()); const { publicKey, streams, claimableAmounts, error, loading, connect, loadStream, createStream, withdraw } = usePayStream(); const history = useTransactionHistory(); @@ -269,6 +271,9 @@ export default function App() { return (
+ {showOnboarding && ( + setShowOnboarding(false)} /> + )} {/* ── Header ── */}

💸 PayStream Demo

diff --git a/demo/src/OnboardingWizard.tsx b/demo/src/OnboardingWizard.tsx new file mode 100644 index 0000000..70bcb7b --- /dev/null +++ b/demo/src/OnboardingWizard.tsx @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +import React, { useState } from "react"; + +const STEPS = [ + { label: "Connect Wallet", icon: "🔗" }, + { label: "Fund Account", icon: "💰" }, + { label: "Configure Stream", icon: "⚙️" }, + { label: "Confirm", icon: "✅" }, +]; + +const STEP_CONTENT: React.FC<{ step: number }>[] = [ + () => ( +
+

Step 1: Connect Your Wallet

+

Install Freighter and connect it to PayStream. Your wallet is your identity on the Stellar network — no sign-up required.

+
    +
  • Install the Freighter browser extension
  • +
  • Create or import a Stellar account
  • +
  • Click Connect Freighter on the main page
  • +
+
+ ), + () => ( +
+

Step 2: Fund Your Account

+

Your account needs tokens to create a stream. On testnet you can use Friendbot to get free XLM.

+
    +
  • Ensure your account has enough XLM for the deposit
  • +
  • On testnet: use Stellar Friendbot
  • +
  • On mainnet: fund via an exchange or wallet transfer
  • +
+
+ ), + () => ( +
+

Step 3: Configure Your Stream

+

Set up the stream parameters for your employee.

+
    +
  • Enter the employee's Stellar public key
  • +
  • Choose the token (e.g., USDC)
  • +
  • Set the deposit amount and rate per second
  • +
  • Optionally set a stop time
  • +
+
+ ), + () => ( +
+

Step 4: Confirm & Launch

+

Review your stream details and submit the transaction. Once confirmed on-chain, your employee starts earning immediately.

+
    +
  • Double-check the employee address
  • +
  • Verify the deposit and rate
  • +
  • Approve the transaction in Freighter
  • +
  • Your stream is live! 🎉
  • +
+
+ ), +]; + +const STORAGE_KEY = "paystream_onboarding_done"; + +interface OnboardingWizardProps { + onComplete: () => void; +} + +export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { + const [step, setStep] = useState(0); + + const isLast = step === STEPS.length - 1; + + const handleNext = () => { + if (isLast) { + localStorage.setItem(STORAGE_KEY, "1"); + onComplete(); + } else { + setStep((s) => s + 1); + } + }; + + const handleSkip = () => { + localStorage.setItem(STORAGE_KEY, "1"); + onComplete(); + }; + + const StepBody = STEP_CONTENT[step]; + + return ( +
+
+ {/* Progress indicator */} +
+ {STEPS.map((s, i) => ( +
+ + {s.label} +
+ ))} +
+ + {/* Step content */} + + + {/* Navigation */} +
+ + + +
+
+
+ ); +} + +/** Returns true if the user has not yet completed onboarding. */ +export function shouldShowOnboarding(): boolean { + return !localStorage.getItem(STORAGE_KEY); +} diff --git a/demo/src/styles.css b/demo/src/styles.css index 68335a7..329942e 100644 --- a/demo/src/styles.css +++ b/demo/src/styles.css @@ -1364,3 +1364,94 @@ code { background: var(--input-bg); color: var(--text); } + +/* ── Onboarding Wizard (#232) ── */ +.wizard-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.55); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.wizard-card { + width: min(520px, 95vw); + padding: 28px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.wizard-progress { + display: flex; + justify-content: space-between; + gap: 8px; +} + +.wizard-step-dot { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + flex: 1; + opacity: 0.45; +} + +.wizard-step-dot--active, +.wizard-step-dot--done { + opacity: 1; +} + +.wizard-dot-icon { + font-size: 20px; + width: 36px; + height: 36px; + border-radius: 50%; + border: 2px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-card); +} + +.wizard-step-dot--active .wizard-dot-icon { + border-color: var(--btn-bg); + background: var(--btn-bg); + color: var(--btn-text); +} + +.wizard-step-dot--done .wizard-dot-icon { + border-color: var(--status-active); + color: var(--status-active); +} + +.wizard-dot-label { + font-size: 11px; + color: var(--text-muted); + text-align: center; +} + +.wizard-step-body h3 { + margin: 0 0 8px; +} + +.wizard-checklist { + padding-left: 20px; + margin: 8px 0 0; + color: var(--text-muted); + font-size: 14px; + line-height: 1.7; +} + +.wizard-nav { + display: flex; + gap: 8px; + justify-content: flex-end; + align-items: center; +} + +.wizard-skip { + margin-right: auto; +}