diff --git a/backend-go/README.md b/backend-go/README.md
index 105153a..4af37f2 100644
--- a/backend-go/README.md
+++ b/backend-go/README.md
@@ -2,7 +2,6 @@ Start DB:
brew services start postgresql@15
-
From the backend-go directory, you have a few options:
Option 1: Single command (both API + poller)
@@ -10,9 +9,9 @@ cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-gomake dev-all
Option 2: Two separate terminals
Terminal 1 (API server):
-cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-gogo run ./cmd/api
+cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-go go run ./cmd/api
Terminal 2 (Poller):
-cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-gogo run ./cmd/poller
+cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-go go run ./cmd/poller
make run — Builds and runs the API server.
make dev — Runs the API server with auto-reload via air (falls back to go run if air isn't installed).
diff --git a/backend-go/internal/handlers/stripe.go b/backend-go/internal/handlers/stripe.go
index 555f129..666b204 100644
--- a/backend-go/internal/handlers/stripe.go
+++ b/backend-go/internal/handlers/stripe.go
@@ -46,8 +46,8 @@ func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Req
Quantity: stripe.Int64(1),
},
},
- SuccessURL: stripe.String(h.cfg.FrontendURL + "/onboarding?payment=success&session_id={CHECKOUT_SESSION_ID}"),
- CancelURL: stripe.String(h.cfg.FrontendURL + "/onboarding?payment=cancelled"),
+ SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"),
+ CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"),
ClientReferenceID: stripe.String(userID),
CustomerEmail: stripe.String(user.Email),
}
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index c94c658..60dd37a 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -16,7 +16,7 @@ export default function App() {
} />
} />
- } />
+ } />
} />
);
@@ -30,7 +30,7 @@ export default function App() {
} />
} />
} />
- } />
+ } />
} />
diff --git a/frontend/src/components/Input.css b/frontend/src/components/Input.css
index 9b1452b..a12be6f 100644
--- a/frontend/src/components/Input.css
+++ b/frontend/src/components/Input.css
@@ -1,6 +1,6 @@
.input__label {
display: block;
- margin-bottom: 0.4rem;
+ margin: 0.8rem 0rem 0.4rem 0rem;
font-size: 0.9rem;
}
@@ -18,4 +18,4 @@
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.25);
-}
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Onboarding.jsx b/frontend/src/pages/Onboarding.jsx
index 786bcc6..022e2b0 100644
--- a/frontend/src/pages/Onboarding.jsx
+++ b/frontend/src/pages/Onboarding.jsx
@@ -1,7 +1,9 @@
/**
- * Onboarding Wizard
+ * Subscribe / Onboarding Wizard
*
- * 6-step guided flow: Create Account -> Subscribe -> Add Wallet -> Alert Rules -> Notifications -> Done
+ * 5-step guided flow: Create Account -> Add Wallet -> Alert Rules -> Notifications -> Done
+ * After account creation, user is redirected to Stripe Checkout for payment.
+ * On successful payment, they return here at step 2 (Add Wallet).
*/
import { useState, useEffect } from "react";
@@ -20,7 +22,6 @@ import "./Onboarding.css";
const STEPS = [
"Create Account",
- "Subscribe",
"Add Wallet",
"Alert Rules",
"Notifications",
@@ -80,11 +81,11 @@ export default function Onboarding() {
const payment = searchParams.get("payment");
if (payment === "success") {
setSearchParams({}, { replace: true });
- setStep(3);
+ setStep(2);
} else if (payment === "cancelled") {
setSearchParams({}, { replace: true });
- setStep(2);
- setError("Payment was cancelled. Please subscribe to continue.");
+ setStep(1);
+ setError("Payment was cancelled. Please try again.");
}
}, [currentUser, searchParams, setSearchParams]);
@@ -104,47 +105,33 @@ export default function Onboarding() {
setError("Password must be at least 6 characters");
return;
}
- if (!currentUser) {
- try {
- setLoading(true);
- await signup(data.email, data.password);
- } catch (err) {
- if (err.code === "auth/email-already-in-use") {
- setError("Email already in use. Try logging in instead.");
- } else if (err.code === "auth/invalid-email") {
- setError("Invalid email address");
- } else if (err.code === "auth/weak-password") {
- setError("Password is too weak");
- } else {
- setError("Failed to create account: " + err.message);
- }
- setLoading(false);
- return;
- } finally {
- setLoading(false);
- }
- }
- setStep(2);
- }
-
- async function handleStep2Subscribe() {
- setError("");
try {
setLoading(true);
+ if (!currentUser) {
+ await signup(data.email, data.password);
+ }
const status = await getSubscriptionStatus();
if (status.subscription_status === "active" || status.subscription_status === "trialing") {
- setStep(3);
+ setStep(2);
return;
}
const { url } = await createCheckoutSession();
window.location.href = url;
} catch (err) {
- setError(err.message);
+ if (err.code === "auth/email-already-in-use") {
+ setError("Email already in use. Try logging in instead.");
+ } else if (err.code === "auth/invalid-email") {
+ setError("Invalid email address");
+ } else if (err.code === "auth/weak-password") {
+ setError("Password is too weak");
+ } else {
+ setError("Failed to create account: " + err.message);
+ }
setLoading(false);
}
}
- async function handleStep3() {
+ async function handleStep2() {
setError("");
if (!data.walletAddress) {
setError("Please enter a wallet address");
@@ -161,7 +148,7 @@ export default function Onboarding() {
label: data.walletLabel || undefined,
});
set("createdAddressId", created.id);
- setStep(4);
+ setStep(3);
} catch (err) {
setError(err.message);
} finally {
@@ -169,7 +156,7 @@ export default function Onboarding() {
}
}
- async function handleStep4() {
+ async function handleStep3() {
setError("");
const rules = [];
if (data.alertIncomingTx) rules.push({ type: "incoming_tx" });
@@ -190,7 +177,7 @@ export default function Onboarding() {
}
if (rules.length === 0) {
- setStep(5);
+ setStep(4);
return;
}
@@ -202,7 +189,7 @@ export default function Onboarding() {
created.push(result);
}
set("alertsCreated", created);
- setStep(5);
+ setStep(4);
} catch (err) {
setError(err.message);
} finally {
@@ -210,12 +197,12 @@ export default function Onboarding() {
}
}
- async function handleStep5() {
+ async function handleStep4() {
setError("");
const hasAny =
data.discordWebhookUrl || data.slackWebhookUrl || data.notificationEmail;
if (!hasAny) {
- setStep(6);
+ setStep(5);
return;
}
try {
@@ -227,7 +214,7 @@ export default function Onboarding() {
email: data.notificationEmail || undefined,
});
set("notificationConfigured", true);
- setStep(6);
+ setStep(5);
} catch (err) {
setError(err.message);
} finally {
@@ -331,33 +318,6 @@ export default function Onboarding() {
}
function Step2() {
- return (
- <>
-
Subscribe to Koin Ping
-
- A subscription is required to use Koin Ping.
-
-
-
-
- $1.99
- /month
-
-
- - Unlimited wallet monitoring
- - Real-time alert notifications
- - Discord, Slack, Telegram & email alerts
- - Email digest reports
-
-
- 6-month minimum commitment
-
-
- >
- );
- }
-
- function Step3() {
return (
<>
Add a wallet address
@@ -383,7 +343,7 @@ export default function Onboarding() {
);
}
- function Step4() {
+ function Step3() {
return (
<>
Configure alert rules
@@ -443,7 +403,7 @@ export default function Onboarding() {
);
}
- function Step5() {
+ function Step4() {
return (
<>
Set up notifications
@@ -508,7 +468,7 @@ export default function Onboarding() {
);
}
- function Step6() {
+ function Step5() {
const alertCount = data.alertsCreated.length;
const hasNotif = data.notificationConfigured;
@@ -602,18 +562,17 @@ export default function Onboarding() {
// ── Footer navigation ─────────────────────────────────────────────────────
function Footer() {
- if (step === 6) return null;
+ if (step === 5) return null;
- const canSkip = step === 4 || step === 5;
- const canBack = step > 1 && step !== 2;
+ const canSkip = step === 3 || step === 4;
+ const canBack = step > 2;
async function handleNext() {
setSkipWarning("");
if (step === 1) await handleStep1();
- else if (step === 2) await handleStep2Subscribe();
+ else if (step === 2) await handleStep2();
else if (step === 3) await handleStep3();
else if (step === 4) await handleStep4();
- else if (step === 5) await handleStep5();
}
function handleSkip() {
@@ -628,9 +587,9 @@ export default function Onboarding() {
setStep((s) => s - 1);
}
- const nextLabel = step === 2
- ? "Subscribe — $1.99/mo"
- : step === 5
+ const nextLabel = step === 1
+ ? "Create Account & Subscribe"
+ : step === 4
? "Finish"
: "Next →";
@@ -673,12 +632,11 @@ export default function Onboarding() {
// ── Render ────────────────────────────────────────────────────────────────
const stepContent = {
- 1: ,
- 2: ,
- 3: ,
- 4: ,
- 5: ,
- 6: ,
+ 1: Step1(),
+ 2: Step2(),
+ 3: Step3(),
+ 4: Step4(),
+ 5: Step5(),
};
return (
@@ -686,7 +644,7 @@ export default function Onboarding() {
Koin Ping
-
+ {ProgressBar()}
{error && (
{error}
@@ -698,7 +656,7 @@ export default function Onboarding() {
{stepContent[step]}
-
+ {Footer()}
{step === 1 && (
diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx
index bbdd78e..1945c2c 100644
--- a/frontend/src/pages/Signup.jsx
+++ b/frontend/src/pages/Signup.jsx
@@ -1,5 +1,5 @@
import { Navigate } from "react-router-dom";
export default function Signup() {
- return
;
+ return
;
}