diff --git a/package.json b/package.json index 6efa12d46..b43cc4742 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "alphahuman", "private": true, - "version": "0.47.0", + "version": "0.48.0", "type": "module", "scripts": { "dev": "vite", @@ -28,7 +28,12 @@ "test:e2e:build": "bash scripts/e2e-build.sh", "test:e2e:login": "bash scripts/e2e-login.sh", "test:e2e:auth": "bash scripts/e2e-auth.sh", - "test:e2e": "yarn test:e2e:build && yarn test:e2e:login && yarn test:e2e:auth", + "test:e2e:payment": "bash scripts/e2e-payment.sh", + "test:e2e:crypto-payment": "bash scripts/e2e-crypto-payment.sh", + "test:e2e:telegram": "bash scripts/e2e-telegram.sh", + "test:e2e:notion": "bash scripts/e2e-notion.sh", + "test:e2e:gmail": "bash scripts/e2e-gmail.sh", + "test:e2e": "yarn test:e2e:build && yarn test:e2e:login && yarn test:e2e:auth && yarn test:e2e:payment && yarn test:e2e:crypto-payment && yarn test:e2e:telegram && yarn test:e2e:notion && yarn test:e2e:gmail", "test:all": "yarn test:coverage && yarn test:rust && yarn test:e2e", "format": "prettier --write .", "format:check": "prettier --check .", @@ -39,6 +44,7 @@ "prepare": "husky" }, "dependencies": { + "@heroicons/react": "^2.2.0", "@reduxjs/toolkit": "^2.11.2", "@sentry/react": "^10.38.0", "@tauri-apps/api": "^2", diff --git a/scripts/e2e-crypto-payment.sh b/scripts/e2e-crypto-payment.sh new file mode 100755 index 000000000..9214688e9 --- /dev/null +++ b/scripts/e2e-crypto-payment.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# Run E2E cryptocurrency payment flow tests only. +# +# Starts Appium, cleans app caches, runs the crypto-payment-flow spec, +# then tears everything down. Each flow script is self-contained so +# specs don't pollute each other's Redux Persist state. +# +# Usage: +# ./scripts/e2e-crypto-payment.sh +# APPIUM_PORT=4723 ./scripts/e2e-crypto-payment.sh +# +set -euo pipefail + +APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" +SPEC="test/e2e/specs/crypto-payment-flow.spec.ts" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +# Clean cached app data for a fresh state — Redux Persist would otherwise +# remember the JWT from a previous run and skip the login flow. +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app +rm -rf "$HOME/Library/Application Support/com.alphahuman.app" + +# Verify the frontend dist has the mock server URL baked in. +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." + +# --- Resolve Node 24 via nvm --------------------------------------------------- +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +NODE24="$(nvm which 24 2>/dev/null || true)" +if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then + echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 + exit 1 +fi + +APPIUM_BIN="$(dirname "$NODE24")/appium" +if [ ! -x "$APPIUM_BIN" ]; then + echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 + exit 1 +fi + +# --- Start Appium in the background ------------------------------------------- +APPIUM_LOG="/tmp/appium-e2e-crypto-payment.log" +NODE_VER=$("$NODE24" --version) +echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." +echo " Appium logs: $APPIUM_LOG" +"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & +APPIUM_PID=$! + +cleanup() { + echo "Stopping Appium (pid $APPIUM_PID)..." + kill "$APPIUM_PID" 2>/dev/null || true + wait "$APPIUM_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# Wait for Appium to be ready +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then + echo "Appium is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "ERROR: Appium did not start within 30 seconds." >&2 + exit 1 + fi + sleep 1 +done + +# --- Run WebDriverIO ---------------------------------------------------------- +echo "Running E2E crypto payment flow tests ($SPEC)..." +npx wdio run wdio.conf.ts --spec "$SPEC" diff --git a/scripts/e2e-gmail.sh b/scripts/e2e-gmail.sh new file mode 100755 index 000000000..b471699b8 --- /dev/null +++ b/scripts/e2e-gmail.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# Run E2E Gmail integration flow tests only. +# +# Starts Appium, cleans app caches, runs the gmail-flow spec, +# then tears everything down. Each flow script is self-contained so +# specs don't pollute each other's Redux Persist state. +# +# Usage: +# ./scripts/e2e-gmail.sh +# APPIUM_PORT=4723 ./scripts/e2e-gmail.sh +# +set -euo pipefail + +APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" +SPEC="test/e2e/specs/gmail-flow.spec.ts" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +# Clean cached app data for a fresh state — Redux Persist would otherwise +# remember the JWT from a previous run and skip the login flow. +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app +rm -rf "$HOME/Library/Application Support/com.alphahuman.app" + +# Verify the frontend dist has the mock server URL baked in. +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." + +# --- Resolve Node 24 via nvm --------------------------------------------------- +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +NODE24="$(nvm which 24 2>/dev/null || true)" +if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then + echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 + exit 1 +fi + +APPIUM_BIN="$(dirname "$NODE24")/appium" +if [ ! -x "$APPIUM_BIN" ]; then + echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 + exit 1 +fi + +# --- Start Appium in the background ------------------------------------------- +APPIUM_LOG="/tmp/appium-e2e-gmail.log" +NODE_VER=$("$NODE24" --version) +echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." +echo " Appium logs: $APPIUM_LOG" +"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & +APPIUM_PID=$! + +cleanup() { + echo "Stopping Appium (pid $APPIUM_PID)..." + kill "$APPIUM_PID" 2>/dev/null || true + wait "$APPIUM_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# Wait for Appium to be ready +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then + echo "Appium is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "ERROR: Appium did not start within 30 seconds." >&2 + exit 1 + fi + sleep 1 +done + +# --- Run WebDriverIO ---------------------------------------------------------- +echo "Running E2E Gmail integration flow tests ($SPEC)..." +npx wdio run wdio.conf.ts --spec "$SPEC" diff --git a/scripts/e2e-notion.sh b/scripts/e2e-notion.sh new file mode 100755 index 000000000..0194a554f --- /dev/null +++ b/scripts/e2e-notion.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# Run E2E Notion integration flow tests only. +# +# Starts Appium, cleans app caches, runs the notion-flow spec, +# then tears everything down. Each flow script is self-contained so +# specs don't pollute each other's Redux Persist state. +# +# Usage: +# ./scripts/e2e-notion.sh +# APPIUM_PORT=4723 ./scripts/e2e-notion.sh +# +set -euo pipefail + +APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" +SPEC="test/e2e/specs/notion-flow.spec.ts" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +# Clean cached app data for a fresh state — Redux Persist would otherwise +# remember the JWT from a previous run and skip the login flow. +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app +rm -rf "$HOME/Library/Application Support/com.alphahuman.app" + +# Verify the frontend dist has the mock server URL baked in. +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." + +# --- Resolve Node 24 via nvm --------------------------------------------------- +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +NODE24="$(nvm which 24 2>/dev/null || true)" +if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then + echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 + exit 1 +fi + +APPIUM_BIN="$(dirname "$NODE24")/appium" +if [ ! -x "$APPIUM_BIN" ]; then + echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 + exit 1 +fi + +# --- Start Appium in the background ------------------------------------------- +APPIUM_LOG="/tmp/appium-e2e-notion.log" +NODE_VER=$("$NODE24" --version) +echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." +echo " Appium logs: $APPIUM_LOG" +"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & +APPIUM_PID=$! + +cleanup() { + echo "Stopping Appium (pid $APPIUM_PID)..." + kill "$APPIUM_PID" 2>/dev/null || true + wait "$APPIUM_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# Wait for Appium to be ready +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then + echo "Appium is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "ERROR: Appium did not start within 30 seconds." >&2 + exit 1 + fi + sleep 1 +done + +# --- Run WebDriverIO ---------------------------------------------------------- +echo "Running E2E Notion integration flow tests ($SPEC)..." +npx wdio run wdio.conf.ts --spec "$SPEC" diff --git a/scripts/e2e-payment.sh b/scripts/e2e-payment.sh new file mode 100755 index 000000000..e05a9335d --- /dev/null +++ b/scripts/e2e-payment.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# Run E2E card payment flow tests only. +# +# Starts Appium, cleans app caches, runs the card-payment-flow spec, +# then tears everything down. Each flow script is self-contained so +# specs don't pollute each other's Redux Persist state. +# +# Usage: +# ./scripts/e2e-payment.sh +# APPIUM_PORT=4723 ./scripts/e2e-payment.sh +# +set -euo pipefail + +APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" +SPEC="test/e2e/specs/card-payment-flow.spec.ts" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +# Clean cached app data for a fresh state — Redux Persist would otherwise +# remember the JWT from a previous run and skip the login flow. +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app +rm -rf "$HOME/Library/Application Support/com.alphahuman.app" + +# Verify the frontend dist has the mock server URL baked in. +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." + +# --- Resolve Node 24 via nvm --------------------------------------------------- +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +NODE24="$(nvm which 24 2>/dev/null || true)" +if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then + echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 + exit 1 +fi + +APPIUM_BIN="$(dirname "$NODE24")/appium" +if [ ! -x "$APPIUM_BIN" ]; then + echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 + exit 1 +fi + +# --- Start Appium in the background ------------------------------------------- +APPIUM_LOG="/tmp/appium-e2e-payment.log" +NODE_VER=$("$NODE24" --version) +echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." +echo " Appium logs: $APPIUM_LOG" +"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & +APPIUM_PID=$! + +cleanup() { + echo "Stopping Appium (pid $APPIUM_PID)..." + kill "$APPIUM_PID" 2>/dev/null || true + wait "$APPIUM_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# Wait for Appium to be ready +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then + echo "Appium is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "ERROR: Appium did not start within 30 seconds." >&2 + exit 1 + fi + sleep 1 +done + +# --- Run WebDriverIO ---------------------------------------------------------- +echo "Running E2E card payment flow tests ($SPEC)..." +npx wdio run wdio.conf.ts --spec "$SPEC" diff --git a/scripts/e2e-telegram.sh b/scripts/e2e-telegram.sh new file mode 100755 index 000000000..c02557e10 --- /dev/null +++ b/scripts/e2e-telegram.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# Run E2E Telegram integration flow tests only. +# +# Starts Appium, cleans app caches, runs the telegram-flow spec, +# then tears everything down. Each flow script is self-contained so +# specs don't pollute each other's Redux Persist state. +# +# Usage: +# ./scripts/e2e-telegram.sh +# APPIUM_PORT=4723 ./scripts/e2e-telegram.sh +# +set -euo pipefail + +APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" +SPEC="test/e2e/specs/telegram-flow.spec.ts" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +# Clean cached app data for a fresh state — Redux Persist would otherwise +# remember the JWT from a previous run and skip the login flow. +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app +rm -rf "$HOME/Library/Application Support/com.alphahuman.app" + +# Verify the frontend dist has the mock server URL baked in. +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." + +# --- Resolve Node 24 via nvm --------------------------------------------------- +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +# shellcheck source=/dev/null +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +NODE24="$(nvm which 24 2>/dev/null || true)" +if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then + echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2 + exit 1 +fi + +APPIUM_BIN="$(dirname "$NODE24")/appium" +if [ ! -x "$APPIUM_BIN" ]; then + echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2 + exit 1 +fi + +# --- Start Appium in the background ------------------------------------------- +APPIUM_LOG="/tmp/appium-e2e-telegram.log" +NODE_VER=$("$NODE24" --version) +echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..." +echo " Appium logs: $APPIUM_LOG" +"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 & +APPIUM_PID=$! + +cleanup() { + echo "Stopping Appium (pid $APPIUM_PID)..." + kill "$APPIUM_PID" 2>/dev/null || true + wait "$APPIUM_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# Wait for Appium to be ready +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then + echo "Appium is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "ERROR: Appium did not start within 30 seconds." >&2 + exit 1 + fi + sleep 1 +done + +# --- Run WebDriverIO ---------------------------------------------------------- +echo "Running E2E Telegram integration flow tests ($SPEC)..." +npx wdio run wdio.conf.ts --spec "$SPEC" diff --git a/src/components/settings/panels/BillingPanel.tsx b/src/components/settings/panels/BillingPanel.tsx index 6ec781155..0d704211b 100644 --- a/src/components/settings/panels/BillingPanel.tsx +++ b/src/components/settings/panels/BillingPanel.tsx @@ -38,8 +38,10 @@ const BillingPanel = () => { const [paymentMethod, setPaymentMethod] = useState<'card' | 'crypto'>('card'); const [isPurchasing, setIsPurchasing] = useState(false); const [purchasingTier, setPurchasingTier] = useState(null); + const [paymentConfirmed, setPaymentConfirmed] = useState(false); const pollRef = useRef | null>(null); const pollStartRef = useRef(0); + const timeoutRef = useRef(null); // Fetch current plan on mount useEffect(() => { @@ -60,6 +62,40 @@ const BillingPanel = () => { }; }, []); + // Handle payment:success deep link event + useEffect(() => { + const onPaymentSuccess = async () => { + // Stop any in-flight poll — we know checkout completed + if (pollRef.current) { + clearInterval(pollRef.current); + pollRef.current = null; + } + setIsPurchasing(false); + setPurchasingTier(null); + setPaymentConfirmed(true); + + // Fetch current plan from backend, then refresh user/teams in store + try { + await billingApi.getCurrentPlan(); + } catch (e) { + console.error('Failed to fetch current plan after payment', e); + } + dispatch(fetchCurrentUser()); + + // Auto-hide the success banner after 5 s + timeoutRef.current = window.setTimeout(() => setPaymentConfirmed(false), 5_000); + }; + + window.addEventListener('payment:success', onPaymentSuccess); + return () => { + window.removeEventListener('payment:success', onPaymentSuccess); + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [dispatch]); + // ── Poll for plan change after checkout ───────────────────────────── const currentTierRef = useRef(currentTier); useEffect(() => { @@ -295,6 +331,29 @@ const BillingPanel = () => { })} + {/* ── Payment confirmed banner ─────────────────────────── */} + {paymentConfirmed && ( +
+
+ + + +

+ Payment confirmed! Your plan has been updated. +

+
+
+ )} + {/* ── Purchasing overlay message ────────────────────────── */} {isPurchasing && (
diff --git a/src/components/settings/panels/TauriCommandsPanel.tsx b/src/components/settings/panels/TauriCommandsPanel.tsx index 8c7752f25..e57b11691 100644 --- a/src/components/settings/panels/TauriCommandsPanel.tsx +++ b/src/components/settings/panels/TauriCommandsPanel.tsx @@ -1,7 +1,19 @@ import { useMemo, useState } from 'react'; +import { + CogIcon, + CpuChipIcon, + ShieldCheckIcon, + ServerIcon, + WrenchScrewdriverIcon, + ChatBubbleLeftRightIcon, + DocumentTextIcon, +} from '@heroicons/react/24/outline'; import SettingsHeader from '../components/SettingsHeader'; import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; +import SectionCard from './components/SectionCard'; +import InputGroup, { Field, CheckboxField } from './components/InputGroup'; +import ActionPanel, { PrimaryButton } from './components/ActionPanel'; import { alphahumanAgentChat, alphahumanDecryptSecret, @@ -38,8 +50,17 @@ const formatJson = (value: unknown) => JSON.stringify(value, null, 2); const TauriCommandsPanel = () => { const { navigateBack } = useSettingsNavigation(); + + // View mode removed - always show all sections + const [expandedSections] = useState>( + new Set(['system-configuration', 'runtime-execution', 'security-data', 'network-infrastructure', 'development-operations', 'interactive-tools']) + ); + + // Output and error states const [output, setOutput] = useState(''); const [error, setError] = useState(''); + + // Form states (preserved from original) const [providerOverride, setProviderOverride] = useState(''); const [integrationName, setIntegrationName] = useState(''); const [hardwarePath, setHardwarePath] = useState(''); @@ -76,6 +97,10 @@ const TauriCommandsPanel = () => { const [chatModel, setChatModel] = useState(''); const [chatTemperature, setChatTemperature] = useState('0.7'); const [chatLog, setChatLog] = useState>([]); + + // Loading states + const [operationLoading, setOperationLoading] = useState(''); + const tauriAvailable = useMemo(() => isTauri(), []); const parseOptionalNumber = (value: string): number | null => { if (!value.trim()) { @@ -85,19 +110,23 @@ const TauriCommandsPanel = () => { return Number.isFinite(parsed) ? parsed : null; }; - const run = async (fn: () => Promise) => { + const run = async (fn: () => Promise, operationName?: string) => { setError(''); + if (operationName) setOperationLoading(operationName); try { const result = await fn(); setOutput(formatJson(result)); } catch (err) { const message = err instanceof Error ? err.message : String(err); setError(message); + } finally { + setOperationLoading(''); } }; - const runWithResult = async (fn: () => Promise): Promise => { + const runWithResult = async (fn: () => Promise, operationName?: string): Promise => { setError(''); + if (operationName) setOperationLoading(operationName); try { const result = await fn(); setOutput(formatJson(result)); @@ -106,11 +135,13 @@ const TauriCommandsPanel = () => { const message = err instanceof Error ? err.message : String(err); setError(message); return null; + } finally { + setOperationLoading(''); } }; const loadConfig = async () => { - const response = await runWithResult(() => alphahumanGetConfig()); + const response = await runWithResult(() => alphahumanGetConfig(), 'loadConfig'); if (!response) { return; } @@ -188,10 +219,11 @@ const TauriCommandsPanel = () => { default_provider: defaultProvider.trim() ? defaultProvider : null, default_model: defaultModel.trim() ? defaultModel : null, default_temperature: parseOptionalNumber(defaultTemp), - }) + }), + 'saveModelSettings' ); - const saveTunnelSettings = () => run(() => alphahumanUpdateTunnelSettings(buildTunnelConfig())); + const saveTunnelSettings = () => run(() => alphahumanUpdateTunnelSettings(buildTunnelConfig()), 'saveTunnelSettings'); const saveGatewaySettings = () => run(() => @@ -200,7 +232,8 @@ const TauriCommandsPanel = () => { port: parseOptionalNumber(gatewayPort), require_pairing: gatewayPairing, allow_public_bind: gatewayPublic, - }) + }), + 'saveGatewaySettings' ); const saveMemorySettings = () => @@ -211,7 +244,8 @@ const TauriCommandsPanel = () => { embedding_provider: embeddingProvider.trim() ? embeddingProvider : null, embedding_model: embeddingModel.trim() ? embeddingModel : null, embedding_dimensions: parseOptionalNumber(embeddingDims), - }) + }), + 'saveMemorySettings' ); const saveRuntimeSettings = () => @@ -219,7 +253,8 @@ const TauriCommandsPanel = () => { alphahumanUpdateRuntimeSettings({ kind: runtimeKind.trim() ? runtimeKind : null, reasoning_enabled: reasoningEnabled, - }) + }), + 'saveRuntimeSettings' ); const loadSkills = async () => { @@ -244,9 +279,9 @@ const TauriCommandsPanel = () => { const toggleSkill = async (skillId: string, nextEnabled: boolean) => { if (nextEnabled) { - await run(() => runtimeEnableSkill(skillId)); + await run(() => runtimeEnableSkill(skillId), 'enableSkill'); } else { - await run(() => runtimeDisableSkill(skillId)); + await run(() => runtimeDisableSkill(skillId), 'disableSkill'); } setSkills((prev) => prev.map((item) => @@ -270,13 +305,34 @@ const TauriCommandsPanel = () => { chatProvider.trim() ? chatProvider : undefined, chatModel.trim() ? chatModel : undefined, parseOptionalNumber(chatTemperature) ?? undefined - ) + ), + 'sendChat' ); if (response) { setChatLog((prev) => [...prev, { role: 'agent', text: response.result }]); } }; + // Always show all sections + const isSectionVisible = () => { + return true; // Always show all sections + }; + + const isCollapsed = (sectionId: string) => { + return !expandedSections.has(sectionId); + }; + + // Helper to check if a section is collapsed (currently unused but kept for future expansion) + // const toggleSection = (sectionId: string) => { + // const newExpanded = new Set(expandedSections); + // if (newExpanded.has(sectionId)) { + // newExpanded.delete(sectionId); + // } else { + // newExpanded.add(sectionId); + // } + // setExpandedSections(newExpanded); + // }; + return (
{ onBack={navigateBack} /> -
+
{!tauriAvailable && (
Tauri runtime not detected. Commands will fail in browser mode.
)} -
-

Config

-
- + {operationLoading && ( +
+
+
+ {operationLoading} +
-
+ )} -
-

Model API Keys

-
- - - - - -
- -
- -
-

Diagnostics

-
- - - setProviderOverride(event.target.value)} - /> -
-
+ + setApiKey(event.target.value)} + /> + + + setApiUrl(event.target.value)} + /> + + + setDefaultProvider(event.target.value)} + /> + + + setDefaultModel(event.target.value)} + /> + + + setDefaultTemp(event.target.value)} + /> + + -
-

Integrations

-
- - - setIntegrationName(event.target.value)} - /> -
-
+ + setRuntimeKind(event.target.value)} + /> + + + -
-

Models

-
-
+ + {/* Category 3: Security & Data - Full width in basic mode, grid in advanced+ */} + {isSectionVisible() && ( + } + collapsible={true} + defaultExpanded={!isCollapsed('security-data')} + hasChanges={false} + loading={operationLoading?.includes('Secret') || operationLoading?.includes('Models') || operationLoading?.includes('Integration')} + > +
+ + +