diff --git a/frontend/src/pages/subscribe/Subscribe.css b/frontend/src/pages/subscribe/Subscribe.css index 87fe097..b2b48f7 100644 --- a/frontend/src/pages/subscribe/Subscribe.css +++ b/frontend/src/pages/subscribe/Subscribe.css @@ -111,52 +111,6 @@ color: #888; } -/* Step 5 summary */ -.subscribe__summary { - background-color: #1e2e1e; - border: 1px solid #2d5a2d; - border-radius: var(--radius-lg); - padding: 1rem 1.25rem; - margin-bottom: 1.5rem; -} - -.subscribe__summary-title { - margin: 0 0 0.5rem; - color: #90ee90; - font-weight: bold; -} - -.subscribe__summary-list { - margin: 0; - padding-left: 1.25rem; - color: var(--color-text-label); - line-height: 1.8; -} - -/* Checkbox rows */ -.checkbox-row { - margin-bottom: 1rem; -} - -.checkbox-row__label { - display: flex; - align-items: center; - gap: 0.6rem; - cursor: pointer; - color: #ddd; -} - -.checkbox-row__input { - width: 16px; - height: 16px; - accent-color: var(--color-primary); -} - -.checkbox-row__nested { - margin-top: 0.5rem; - margin-left: 1.75rem; -} - /* Footer navigation */ .subscribe__footer { display: flex; @@ -167,20 +121,6 @@ border-top: 1px solid var(--color-border-light); } -/* Test results */ -.test-result { - font-size: 0.9rem; - margin-bottom: 0.25rem; -} - -.test-result--success { - color: #90ee90; -} - -.test-result--failure { - color: var(--color-error); -} - /* Step subtitle */ .subscribe__subtitle { color: #aaa; @@ -188,7 +128,7 @@ font-size: 0.9rem; } -/* Subscribe card (Step 2) */ +/* Subscribe card (Step 2 tier cards) */ .subscribe-card { background-color: var(--color-bg-card, #1a1a2e); border: 1px solid var(--color-border-light); @@ -241,29 +181,6 @@ font-style: italic; } -/* Tier limit hints */ -.subscribe__tier-hint { - font-size: 0.82rem; - color: #ffcc44; - margin-top: 0.5rem; -} - -/* Disabled channel group */ -.subscribe__channel-disabled { - opacity: 0.5; - pointer-events: none; -} - -.subscribe__channel-disabled .subscribe__tier-hint { - pointer-events: auto; - opacity: 1; -} - -/* Disabled checkbox row */ -.checkbox-row--disabled { - opacity: 0.5; -} - /* ── Subscribe Responsive ────────────────────────────────── */ @media (max-width: 480px) { @@ -284,4 +201,4 @@ height: 26px; font-size: 0.75rem; } -} \ No newline at end of file +} diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx index 3a04cf7..4031120 100644 --- a/frontend/src/pages/subscribe/Subscribe.jsx +++ b/frontend/src/pages/subscribe/Subscribe.jsx @@ -1,32 +1,17 @@ import { useState, useEffect } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { useAuth } from "../../contexts/AuthContext"; -import { createAddress, getAddresses } from "../../api/addresses"; -import { createAlert } from "../../api/alerts"; import { - updateNotificationConfig, - testNotificationChannels, -} from "../../api/notificationConfig"; -import { createOnboardingCheckout, verifyCheckoutSession, activateFreeTier } from "../../api/stripe"; + createOnboardingCheckout, + verifyCheckoutSession, + activateFreeTier, +} from "../../api/stripe"; import Input from "../../components/Input"; import Button from "../../components/Button"; import TierPicker from "../../components/TierPicker"; import "./Subscribe.css"; -const TIER_LIMITS = { - free: { maxAlertTypes: 1, channels: ["email"] }, - premium: { maxAlertTypes: 2, channels: ["email", "discord", "telegram"] }, - pro: { maxAlertTypes: 4, channels: ["email", "discord", "telegram", "slack"] }, -}; - -const STEPS = [ - "Create Account", - "Choose Plan", - "Add Wallet", - "Alert Rules", - "Notifications", - "Done", -]; +const STEPS = ["Create Account", "Choose Plan"]; export default function Subscribe() { const { currentUser, signup } = useAuth(); @@ -34,52 +19,22 @@ export default function Subscribe() { const [searchParams, setSearchParams] = useSearchParams(); const hasPaymentReturn = searchParams.get("payment") === "success"; - const isUpgrade = searchParams.get("upgrade") === "true"; const [step, setStep] = useState(hasPaymentReturn || currentUser ? 2 : 1); const [loading, setLoading] = useState(hasPaymentReturn); const [error, setError] = useState(""); - const [skipWarning, setSkipWarning] = useState(""); - const [testResults, setTestResults] = useState(null); - const [testLoading, setTestLoading] = useState(false); const [data, setData] = useState({ selectedTier: "", email: "", password: "", confirmPassword: "", - walletAddress: "", - walletLabel: "", - createdAddressId: null, - alertIncomingTx: false, - alertOutgoingTx: false, - alertLargeTransfer: false, - largeTransferThreshold: "", - alertBalanceBelow: false, - balanceBelowThreshold: "", - discordWebhookUrl: "", - slackWebhookUrl: "", - notificationEmail: "", - alertsCreated: [], - notificationConfigured: false, }); function set(field, value) { setData((prev) => ({ ...prev, [field]: value })); } - const tierLimits = TIER_LIMITS[data.selectedTier] || TIER_LIMITS.free; - - useEffect(() => { - if (!currentUser || isUpgrade) return; - getAddresses() - .then((addresses) => { - if (addresses.length > 0) { - navigate("/addresses", { replace: true }); - } - }) - .catch(() => { }); - }, [currentUser, navigate, isUpgrade]); - + // Handle Stripe payment returns useEffect(() => { const payment = searchParams.get("payment"); const sessionId = searchParams.get("session_id"); @@ -93,9 +48,8 @@ export default function Subscribe() { if (payment !== "success" || !sessionId) return; - // Phase 1: no account yet — create it. This triggers an auth state change - // which remounts the component (App.jsx swaps route trees). The URL params - // are preserved so Phase 2 runs on the next mount. + // Phase 1: no account yet — create it. Auth state change remounts the + // component (App.jsx swaps route trees) and Phase 2 runs on the next mount. if (!currentUser) { const savedEmail = sessionStorage.getItem("kp_onboard_email"); const savedPw = sessionStorage.getItem("kp_onboard_pw"); @@ -116,7 +70,7 @@ export default function Subscribe() { return; } - // Phase 2: authenticated — verify the checkout and activate subscription. + // Phase 2: authenticated — verify the checkout and go to the dashboard. setSearchParams({}, { replace: true }); setLoading(true); sessionStorage.removeItem("kp_onboard_email"); @@ -124,14 +78,14 @@ export default function Subscribe() { verifyCheckoutSession(sessionId) .then(() => { - setStep(3); + navigate("/addresses", { replace: true }); }) .catch((err) => { setError("Payment verification failed: " + err.message); setStep(2); }) .finally(() => setLoading(false)); - }, [currentUser, searchParams, setSearchParams, signup]); + }, [currentUser, searchParams, setSearchParams, signup, navigate]); // ── Step handlers ───────────────────────────────────────────────────────── @@ -166,13 +120,16 @@ export default function Subscribe() { await signup(data.email, data.password); } await activateFreeTier(); - setStep(3); + navigate("/addresses", { replace: true }); return; } sessionStorage.setItem("kp_onboard_email", data.email); sessionStorage.setItem("kp_onboard_pw", data.password); - const { url } = await createOnboardingCheckout(data.email, data.selectedTier); + const { url } = await createOnboardingCheckout( + data.email, + data.selectedTier, + ); window.location.href = url; } catch (err) { if (err.code === "auth/email-already-in-use") { @@ -189,125 +146,6 @@ export default function Subscribe() { } } - async function handleStep3() { - setError(""); - if (!data.walletAddress) { - setError("Please enter a wallet address"); - return; - } - if (!/^0x[0-9a-fA-F]{40}$/.test(data.walletAddress)) { - setError("Invalid ETH address (must be 0x followed by 40 hex characters)"); - return; - } - try { - setLoading(true); - const created = await createAddress({ - address: data.walletAddress, - label: data.walletLabel || undefined, - }); - set("createdAddressId", created.id); - setStep(4); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - } - - async function handleStep4() { - setError(""); - const rules = []; - if (data.alertIncomingTx) rules.push({ type: "incoming_tx" }); - if (data.alertOutgoingTx) rules.push({ type: "outgoing_tx" }); - if (data.alertLargeTransfer) { - if (!data.largeTransferThreshold) { - setError("Please enter a threshold for large transfers"); - return; - } - rules.push({ type: "large_transfer", threshold: data.largeTransferThreshold }); - } - if (data.alertBalanceBelow) { - if (!data.balanceBelowThreshold) { - setError("Please enter a threshold for balance below"); - return; - } - rules.push({ type: "balance_below", threshold: data.balanceBelowThreshold }); - } - - if (rules.length === 0) { - setStep(5); - return; - } - - try { - setLoading(true); - const created = []; - for (const rule of rules) { - const result = await createAlert(data.createdAddressId, rule); - created.push(result); - } - set("alertsCreated", created); - setStep(5); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - } - - async function handleStep5() { - setError(""); - const hasAny = - data.discordWebhookUrl || data.slackWebhookUrl || data.notificationEmail; - if (!hasAny) { - setStep(6); - return; - } - try { - setLoading(true); - await updateNotificationConfig({ - notification_enabled: true, - discord_webhook_url: data.discordWebhookUrl || undefined, - slack_webhook_url: data.slackWebhookUrl || undefined, - email: data.notificationEmail || undefined, - }); - set("notificationConfigured", true); - setStep(6); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - } - - async function handleTestChannels() { - setTestLoading(true); - setTestResults(null); - try { - const results = await testNotificationChannels(); - setTestResults(results); - } catch (err) { - setTestResults({ error: err.message }); - } finally { - setTestLoading(false); - } - } - - // ── Alert type limit helpers ────────────────────────────────────────────── - - function countSelectedAlerts() { - let count = 0; - if (data.alertIncomingTx) count++; - if (data.alertOutgoingTx) count++; - if (data.alertLargeTransfer) count++; - if (data.alertBalanceBelow) count++; - return count; - } - - function canSelectMoreAlerts() { - return tierLimits.maxAlertTypes > countSelectedAlerts(); - } - // ── Progress bar ────────────────────────────────────────────────────────── function ProgressBar() { @@ -327,10 +165,11 @@ export default function Subscribe() {
- Enter the Ethereum address you want to monitor. -
- set("walletAddress", v)} - disabled={loading} - placeholder="0x..." - /> - set("walletLabel", v)} - disabled={loading} - placeholder="e.g. My main wallet" - className="form-field--last" - /> - > - ); - } - - function StepAlertRules() { - const atLimit = !canSelectMoreAlerts(); - const maxTypes = tierLimits.maxAlertTypes; - - return ( - <> -- Choose which events trigger notifications ({countSelectedAlerts()}/{maxTypes} selected). - You can change these later. -
- -- Your {data.selectedTier} plan allows {maxTypes} alert type{maxTypes !== 1 ? "s" : ""} per address. Upgrade for more. -
- )} - > - ); - } - - function StepNotifications() { - const channels = tierLimits.channels; - const canDiscord = channels.includes("discord"); - const canSlack = channels.includes("slack"); - - return ( - <> -- Add at least one channel so you receive alerts. All fields are optional. -
- - set("notificationEmail", v)} - disabled={loading} - placeholder="you@example.com" - className="form-field--last" - /> - -Upgrade to Premium to enable Discord alerts
- )} -Upgrade to Pro to enable Slack alerts
- )} -Summary
-{testResults.error}
- ) : ( -- Already have an account?{" "} - Log in here + Already have an account? Log in here
)}