add this to live version
This commit is contained in:
parent
35ced67475
commit
ba48046f5b
13 changed files with 1281 additions and 427 deletions
|
|
@ -41,25 +41,43 @@ Got you — here’s a clean, founder-brain-friendly summary of **Stripe → Inv
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Known issues you’ve already identified
|
## ✅ Recently fixed
|
||||||
|
|
||||||
* Xero contact creation:
|
* **Xero contact creation** — Now checks for existing contacts by email first, reuses if found, only creates if missing
|
||||||
|
* **Stripe OAuth app reuse** — Added unique constraints on `userId` and `stripeAccountId` to prevent duplicate connections
|
||||||
|
* **Smart redirect flow** — Users are automatically routed based on connection state:
|
||||||
|
* Both connected → `/dashboard`
|
||||||
|
* Only Stripe → `/connect/xero`
|
||||||
|
* No connections → `/connect/stripe`
|
||||||
|
* **Connection visibility** — Dashboard now displays connected Stripe account ID and Xero tenant ID
|
||||||
|
|
||||||
* Need to **check for existing contact by email first** (currently can fail automation)
|
### Frontend Improvements Details
|
||||||
* Stripe OAuth:
|
|
||||||
|
**Smart Onboarding Flow**
|
||||||
|
* Automatic routing based on connection state
|
||||||
|
* Users never see unnecessary steps
|
||||||
|
* Seamless progression: Login → Stripe → Xero → Dashboard
|
||||||
|
|
||||||
|
**Dashboard Enhancements**
|
||||||
|
* Connected account visibility (Stripe account ID + Xero tenant)
|
||||||
|
* Account code configuration (sales + clearing accounts)
|
||||||
|
* Real-time save confirmation
|
||||||
|
* Clean, minimal UI
|
||||||
|
|
||||||
|
**Development Experience**
|
||||||
|
* Development mode fallback for webhook testing
|
||||||
|
* Comprehensive logging at each webhook stage
|
||||||
|
* Environment-aware configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Known issues
|
||||||
|
|
||||||
* Currently creates a **new Stripe app every time** instead of reusing → needs fixing
|
|
||||||
* Account codes:
|
* Account codes:
|
||||||
|
|
||||||
* Default behaviour needed on first Xero connection:
|
|
||||||
|
|
||||||
* `salesAccountCode = 200`
|
|
||||||
* `stripeClearingAccountCode = 610`
|
|
||||||
* Future: auto-detect or create a proper “Stripe Clearing” account and store it
|
|
||||||
* Missing UX guardrails:
|
* Missing UX guardrails:
|
||||||
|
|
||||||
* No clear **pre-payment checklist** before enabling sync
|
* No clear **pre-payment checklist** before enabling sync
|
||||||
* No UI yet to review/change account codes (fine for v1, but coming)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -81,15 +99,11 @@ Got you — here’s a clean, founder-brain-friendly summary of **Stripe → Inv
|
||||||
|
|
||||||
These unlock charging real money.
|
These unlock charging real money.
|
||||||
|
|
||||||
* [ ] Fix Xero contact creation:
|
* [x] ~~Fix Xero contact creation~~ ✅ DONE
|
||||||
|
|
||||||
* Check by email → reuse if exists → only create if missing
|
* ~~Check by email → reuse if exists → only create if missing~~
|
||||||
* [ ] Fix Stripe OAuth app reuse (stop creating new apps)
|
* [x] ~~Fix Stripe OAuth app reuse (stop creating new apps)~~ ✅ DONE
|
||||||
* [ ] Ensure default Xero account codes are set **on first connection**
|
* [x] ~~Re-enable "mark invoice as paid" via Stripe Clearing once accounts are valid~~ ✅ DONE
|
||||||
|
|
||||||
* sales = 200
|
|
||||||
* clearing = 610
|
|
||||||
* [ ] Re-enable “mark invoice as paid” via Stripe Clearing once accounts are valid
|
|
||||||
|
|
||||||
> Outcome: rock-solid, boring, accountant-approved flow
|
> Outcome: rock-solid, boring, accountant-approved flow
|
||||||
|
|
||||||
|
|
@ -97,18 +111,30 @@ These unlock charging real money.
|
||||||
|
|
||||||
### 2️⃣ Add a tiny **pre-flight checklist UI** (not a full settings page)
|
### 2️⃣ Add a tiny **pre-flight checklist UI** (not a full settings page)
|
||||||
|
|
||||||
* One screen before enabling sync:
|
* [x] ~~Dashboard shows connected accounts~~ ✅ DONE
|
||||||
|
|
||||||
* ✔ Stripe connected
|
* ~~Stripe account ID displayed~~
|
||||||
* ✔ Xero connected
|
* ~~Xero tenant ID displayed~~
|
||||||
* ✔ VAT status detected
|
* [x] ~~Smart redirect flow based on connection state~~ ✅ DONE
|
||||||
* ✔ Sales account code shown (read-only for now)
|
* [ ] VAT status detection
|
||||||
* ✔ Stripe clearing account shown
|
* [ ] Sales account code shown (editable)
|
||||||
* Even if it’s ugly — this prevents 80% of future support pain
|
* [ ] Stripe clearing account shown (editable)
|
||||||
|
|
||||||
|
> Even basic connection visibility prevents 80% of future support pain
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3️⃣ Switch from “design partner” → **first paid customer mode**
|
### 3️⃣ Implement subscription billing (enables first paid customer)
|
||||||
|
|
||||||
|
* Integrate Stripe Billing for subscription management
|
||||||
|
* Add usage tracking (invoice count per month)
|
||||||
|
* Create pricing page and checkout flow
|
||||||
|
* Implement subscription status checks in webhook handler
|
||||||
|
* Remove "internal test" banner once billing is live
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4️⃣ Switch from "design partner" → **first paid customer mode**
|
||||||
|
|
||||||
* Pick **one**:
|
* Pick **one**:
|
||||||
|
|
||||||
|
|
@ -116,16 +142,16 @@ These unlock charging real money.
|
||||||
* A cold UK Stripe + Xero business with obvious VAT needs
|
* A cold UK Stripe + Xero business with obvious VAT needs
|
||||||
* Offer:
|
* Offer:
|
||||||
|
|
||||||
* £10–£30/month
|
* £15/month Starter plan
|
||||||
* “Early access / founder pricing”
|
* "Early access / founder pricing" (50% off for life)
|
||||||
* Manual support included
|
* Manual support included
|
||||||
* Goal is **money changing hands**, not scale
|
* Goal is **money changing hands**, not scale
|
||||||
|
|
||||||
> You’ve said it yourself: getting paid energises you — lean into that.
|
> You've said it yourself: getting paid energises you — lean into that.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4️⃣ Do *targeted* cold outreach (low volume, high signal)
|
### 5️⃣ Do *targeted* cold outreach (low volume, high signal)
|
||||||
|
|
||||||
* 5–10 emails max, not a campaign
|
* 5–10 emails max, not a campaign
|
||||||
* Target:
|
* Target:
|
||||||
|
|
@ -135,21 +161,81 @@ These unlock charging real money.
|
||||||
* Clearly VAT-registered
|
* Clearly VAT-registered
|
||||||
* Lead with:
|
* Lead with:
|
||||||
|
|
||||||
* “I built this because my accountant hated existing tools”
|
* "I built this because my accountant hated existing tools"
|
||||||
* Emphasise **audit-safe, VAT-correct invoices**
|
* Emphasise **audit-safe, VAT-correct invoices**
|
||||||
* Not “automation”, not “syncing”
|
* Not "automation", not "syncing"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5️⃣ Only then: small UX polish + automation
|
### 6️⃣ Future UX polish + automation (after first paying customers)
|
||||||
|
|
||||||
* UI to review/change account codes
|
* Auto-detect or create Stripe Clearing account in Xero
|
||||||
* Auto-detect or create Stripe Clearing account
|
* Bulk historical invoice sync
|
||||||
|
* Invoice preview before creation
|
||||||
* Reduce manual fixes you find yourself repeating
|
* Reduce manual fixes you find yourself repeating
|
||||||
* Nothing else until:
|
* Nothing else until:
|
||||||
|
|
||||||
* You have **~3–5 paying users**
|
* You have **~3–5 paying users**
|
||||||
* And they’re still using it after month 1
|
* And they're still using it after month 1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💳 SaaS Subscription Model (proposed)
|
||||||
|
|
||||||
|
### Pricing Tiers
|
||||||
|
|
||||||
|
**Starter — £15/month**
|
||||||
|
* Up to 50 invoices/month
|
||||||
|
* Stripe Payment Links + one-off payments
|
||||||
|
* Email support (48h response)
|
||||||
|
* Perfect for: Solo founders, side projects, small consultancies
|
||||||
|
|
||||||
|
**Professional — £35/month**
|
||||||
|
* Up to 200 invoices/month
|
||||||
|
* Everything in Starter, plus:
|
||||||
|
* Stripe Subscriptions support
|
||||||
|
* Priority email support (24h response)
|
||||||
|
* Custom account code mapping
|
||||||
|
* Perfect for: Growing businesses, SaaS products, agencies
|
||||||
|
|
||||||
|
**Business — £75/month**
|
||||||
|
* Unlimited invoices
|
||||||
|
* Everything in Professional, plus:
|
||||||
|
* Multi-currency support (planned)
|
||||||
|
* Dedicated Slack support
|
||||||
|
* Early access to new features
|
||||||
|
* Perfect for: Established businesses, high-volume merchants
|
||||||
|
|
||||||
|
### Implementation Notes
|
||||||
|
|
||||||
|
* **Billing via Stripe Checkout** (dogfooding our own product)
|
||||||
|
* **Monthly recurring subscriptions** with automatic renewal
|
||||||
|
* **14-day free trial** — no credit card required
|
||||||
|
* **Founder pricing lock-in** — First 50 customers get lifetime 50% off
|
||||||
|
* **Usage tracking** — Invoice count displayed in dashboard, soft warnings at 80% of limit
|
||||||
|
* **Graceful degradation** — Over-limit users get notified but sync continues (no hard cutoff)
|
||||||
|
|
||||||
|
### Revenue Model
|
||||||
|
|
||||||
|
* **Target: 100 paying customers in 6 months**
|
||||||
|
* 60% Starter (£900/mo)
|
||||||
|
* 30% Professional (£1,050/mo)
|
||||||
|
* 10% Business (£750/mo)
|
||||||
|
* Total: ~£2,700/mo MRR
|
||||||
|
|
||||||
|
* **Conservative burn**
|
||||||
|
* Hosting: £50/mo (Vercel + DB)
|
||||||
|
* Email: £10/mo (AWS SES)
|
||||||
|
* Support: Founder time only
|
||||||
|
* Net: ~£2,640/mo profit margin
|
||||||
|
|
||||||
|
### Next Steps for Monetization
|
||||||
|
|
||||||
|
1. Add Stripe Billing integration to the app
|
||||||
|
2. Implement usage tracking in webhook handler
|
||||||
|
3. Create pricing page on landing site
|
||||||
|
4. Add subscription management in dashboard
|
||||||
|
5. Enable payments and remove "internal test" banner
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// app/app/page.tsx
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
@ -14,65 +13,119 @@ export default async function AppPage() {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has Stripe connection
|
|
||||||
const stripeConn = await db
|
const stripeConn = await db
|
||||||
.select()
|
.select()
|
||||||
.from(stripeAccounts)
|
.from(stripeAccounts)
|
||||||
.where(eq(stripeAccounts.userId, user.id))
|
.where(eq(stripeAccounts.userId, user.id))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
// Check if user has Xero connection
|
|
||||||
const xeroConn = await db
|
const xeroConn = await db
|
||||||
.select()
|
.select()
|
||||||
.from(xeroConnections)
|
.from(xeroConnections)
|
||||||
.where(eq(xeroConnections.userId, user.id))
|
.where(eq(xeroConnections.userId, user.id))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
// If both connected, go to dashboard
|
|
||||||
if (stripeConn.length > 0 && xeroConn.length > 0) {
|
if (stripeConn.length > 0 && xeroConn.length > 0) {
|
||||||
redirect("/dashboard");
|
redirect("/dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only Stripe connected, go to Xero connection
|
|
||||||
if (stripeConn.length > 0 && xeroConn.length === 0) {
|
if (stripeConn.length > 0 && xeroConn.length === 0) {
|
||||||
redirect("/connect/xero");
|
redirect("/connect/xero");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, show Stripe connection step
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-2xl mx-auto p-8 space-y-10">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
<h1 className="text-2xl font-semibold">
|
{/* Navigation */}
|
||||||
Welcome{user.email ? `, ${user.email}` : ""}
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||||
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
|
S2X
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-slate-600">
|
||||||
|
{user.email}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
<div className="max-w-2xl mx-auto px-6 py-16">
|
||||||
|
{/* Welcome Section */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-3">
|
||||||
|
Welcome, {user.email?.split("@")[0]}! 👋
|
||||||
</h1>
|
</h1>
|
||||||
|
<p className="text-lg text-slate-600">
|
||||||
|
Let's set up your Stripe to Xero automation in just two steps.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Progress */}
|
{/* Progress Visualization */}
|
||||||
<ol className="space-y-4">
|
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-8 mb-8">
|
||||||
<li className="flex items-center gap-3">
|
<h2 className="text-lg font-semibold text-slate-900 mb-6">Setup progress</h2>
|
||||||
<span className="text-green-600">✔</span>
|
|
||||||
<span>
|
|
||||||
Logged in as <strong>{user.email}</strong>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li className="flex items-center gap-3">
|
<div className="space-y-4">
|
||||||
<span className="text-blue-600">→</span>
|
{/* Step 1 - Login */}
|
||||||
<span className="font-medium">Connect Stripe</span>
|
<div className="flex gap-4">
|
||||||
</li>
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 border-2 border-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<svg className="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="w-0.5 h-8 bg-green-200 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Email verified</p>
|
||||||
|
<p className="text-sm text-slate-600">{user.email}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<li className="text-gray-400">
|
{/* Step 2 - Stripe */}
|
||||||
Xero will be connected after Stripe
|
<div className="flex gap-4">
|
||||||
</li>
|
<div className="flex flex-col items-center">
|
||||||
</ol>
|
<div className="w-10 h-10 rounded-full bg-blue-100 border-2 border-blue-600 flex items-center justify-center flex-shrink-0 font-semibold text-blue-600">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div className="w-0.5 h-8 bg-slate-200 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Connect Stripe</p>
|
||||||
|
<p className="text-sm text-slate-600">Link your Stripe account to detect payments</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Primary CTA */}
|
{/* Step 3 - Xero */}
|
||||||
<div className="pt-6">
|
<div className="flex gap-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-slate-100 border-2 border-slate-300 flex items-center justify-center flex-shrink-0 font-semibold text-slate-600">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Connect Xero</p>
|
||||||
|
<p className="text-sm text-slate-600">Link your Xero organisation to create invoices</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 border border-blue-200 rounded-xl p-8">
|
||||||
|
<h2 className="text-xl font-semibold text-slate-900 mb-3">
|
||||||
|
Ready to automate?
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-700 mb-6">
|
||||||
|
Connect your Stripe account to start detecting payments. You'll connect Xero next.
|
||||||
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/connect/stripe"
|
href="/connect/stripe"
|
||||||
className="inline-block rounded bg-black text-white px-5 py-3"
|
className="inline-block px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition"
|
||||||
>
|
>
|
||||||
Connect Stripe
|
Connect Stripe →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,35 @@ export default function AuthCallbackClient() {
|
||||||
}, [params, router]);
|
}, [params, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen flex items-center justify-center">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex items-center justify-center">
|
||||||
<p className="text-sm text-gray-500">Signing you in…</p>
|
<div className="text-center">
|
||||||
</main>
|
{/* Animated loader */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-blue-100">
|
||||||
|
<div className="relative w-12 h-12">
|
||||||
|
<div className="absolute inset-0 rounded-full border-2 border-slate-200"></div>
|
||||||
|
<div className="absolute inset-0 rounded-full border-2 border-transparent border-t-blue-600 border-r-blue-600 animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text */}
|
||||||
|
<h1 className="text-2xl font-bold text-slate-900 mb-2">Signing you in…</h1>
|
||||||
|
<p className="text-slate-600">
|
||||||
|
Please wait while we authenticate your account
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Progress indication */}
|
||||||
|
<div className="mt-8">
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<span className="w-2 h-2 bg-blue-600 rounded-full animate-pulse"></span>
|
||||||
|
<span className="w-2 h-2 bg-blue-600 rounded-full animate-pulse" style={{ animationDelay: "0.2s" }}></span>
|
||||||
|
<span className="w-2 h-2 bg-blue-600 rounded-full animate-pulse" style={{ animationDelay: "0.4s" }}></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
// app/connect/stripe/page.tsx
|
|
||||||
//
|
|
||||||
// STEP 2 — Connect Stripe
|
|
||||||
// Purpose:
|
|
||||||
// - Explain why Stripe access is needed
|
|
||||||
// - Provide a single, clear action
|
|
||||||
// - Feel safe, boring, and familiar (Zapier-style)
|
|
||||||
|
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
|
@ -13,65 +5,132 @@ export default async function ConnectStripePage() {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const session = cookieStore.get("session");
|
const session = cookieStore.get("session");
|
||||||
|
|
||||||
// Safety: if not logged in, bounce to login
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-2xl mx-auto p-8 space-y-10">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
{/* --------------------------------------------------
|
{/* Navigation */}
|
||||||
Header
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
-------------------------------------------------- */}
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center">
|
||||||
<section>
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
<h1 className="text-2xl font-semibold">
|
S2X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
<div className="max-w-2xl mx-auto px-6 py-16">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<div className="inline-flex items-center gap-2 mb-4 px-3 py-1 bg-blue-50 rounded-full border border-blue-200">
|
||||||
|
<span className="w-2 h-2 bg-blue-600 rounded-full"></span>
|
||||||
|
<span className="text-sm font-medium text-blue-700">Step 2 of 3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-4">
|
||||||
Connect Stripe
|
Connect Stripe
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="mt-3 text-gray-700">
|
<p className="text-lg text-slate-600">
|
||||||
We need read-only access to your Stripe account so we can
|
We need read-only access to your Stripe account to monitor successful payments and automatically create invoices in Xero.
|
||||||
detect successful payments and automatically reconcile
|
|
||||||
invoices in Xero.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
{/* Two Column Layout */}
|
||||||
What will happen
|
<div className="grid md:grid-cols-3 gap-8 mb-12">
|
||||||
-------------------------------------------------- */}
|
{/* Left Column - Information */}
|
||||||
<section>
|
<div className="md:col-span-2">
|
||||||
<h2 className="text-lg font-medium">
|
{/* What we access */}
|
||||||
What happens next
|
<div className="bg-white border border-slate-200 rounded-xl p-6 mb-6">
|
||||||
</h2>
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">What we can access</h2>
|
||||||
|
<ul className="space-y-3">
|
||||||
<ul className="mt-3 space-y-2 list-disc list-inside text-gray-700">
|
<li className="flex gap-3">
|
||||||
<li>You’ll be redirected to Stripe</li>
|
<svg className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<li>You’ll choose which Stripe account to connect</li>
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
<li>You’ll be sent back here once connected</li>
|
</svg>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-900">Payment events</p>
|
||||||
|
<p className="text-sm text-slate-600">Successful charges and refunds</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<svg className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-900">Account information</p>
|
||||||
|
<p className="text-sm text-slate-600">Your account ID and basic metadata</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
{/* What we cannot do */}
|
||||||
Trust / reassurance
|
<div className="bg-amber-50 border border-amber-200 rounded-xl p-6 mb-6">
|
||||||
-------------------------------------------------- */}
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">What we cannot do</h2>
|
||||||
<section className="text-sm text-gray-600">
|
<ul className="space-y-2 text-sm text-slate-700">
|
||||||
<p>
|
<li className="flex gap-2">
|
||||||
We never see your passwords.
|
<span className="text-amber-600">○</span>
|
||||||
<br />
|
<span>Charge customers or initiate refunds</span>
|
||||||
Access can be revoked at any time from Stripe.
|
</li>
|
||||||
</p>
|
<li className="flex gap-2">
|
||||||
</section>
|
<span className="text-amber-600">○</span>
|
||||||
|
<span>See or store sensitive payment information</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-2">
|
||||||
|
<span className="text-amber-600">○</span>
|
||||||
|
<span>Modify your Stripe settings or payouts</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
{/* How it works */}
|
||||||
Primary action
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
||||||
-------------------------------------------------- */}
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">What happens next</h2>
|
||||||
<section className="pt-4 border-t">
|
<ol className="space-y-3">
|
||||||
|
<li className="flex gap-4">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-600">1</span>
|
||||||
|
<span className="text-slate-700">You'll be securely redirected to Stripe</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-4">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-600">2</span>
|
||||||
|
<span className="text-slate-700">Select which Stripe account to connect</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-4">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-600">3</span>
|
||||||
|
<span className="text-slate-700">You'll be returned here to connect Xero</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - CTA Card */}
|
||||||
|
<div>
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 border-2 border-blue-600 rounded-xl p-6 sticky top-24">
|
||||||
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Ready?</h3>
|
||||||
<a
|
<a
|
||||||
href="/api/stripe/connect"
|
href="/api/stripe/connect"
|
||||||
className="inline-block px-6 py-3 bg-black text-white rounded text-sm"
|
className="block w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg text-center hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition"
|
||||||
>
|
>
|
||||||
Connect Stripe →
|
Connect Stripe →
|
||||||
</a>
|
</a>
|
||||||
</section>
|
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||||
|
You'll be redirected to Stripe's secure OAuth page
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust badges */}
|
||||||
|
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
|
||||||
|
<p className="text-sm text-green-900">
|
||||||
|
<strong>🔒 Your security matters:</strong> We use OAuth, so you can revoke access from Stripe at any time. We never see or store your Stripe password.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,43 +2,104 @@ import Link from "next/link";
|
||||||
|
|
||||||
export default function StripeSuccessPage() {
|
export default function StripeSuccessPage() {
|
||||||
return (
|
return (
|
||||||
<main className="max-w-2xl mx-auto p-8 space-y-10">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
<h1 className="text-2xl font-semibold">
|
{/* Navigation */}
|
||||||
Stripe connected 🎉
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center">
|
||||||
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
|
S2X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
<div className="max-w-2xl mx-auto px-6 py-16">
|
||||||
|
{/* Success Animation */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="inline-flex items-center justify-center w-20 h-20 bg-green-100 rounded-full mb-6 animate-bounce">
|
||||||
|
<svg className="w-10 h-10 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-3">
|
||||||
|
Stripe connected! 🎉
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-gray-600">
|
<p className="text-lg text-slate-600">
|
||||||
Your Stripe account is now linked. We can now detect successful
|
Your Stripe account is now securely linked. We can detect successful payments and automatically reconcile invoices in Xero.
|
||||||
payments and automatically reconcile invoices in Xero.
|
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Progress */}
|
{/* Progress Card */}
|
||||||
<ol className="space-y-4">
|
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-8 mb-8">
|
||||||
<li className="flex items-center gap-3">
|
<h2 className="text-lg font-semibold text-slate-900 mb-6">Setup progress</h2>
|
||||||
<span className="text-green-600">✔</span>
|
|
||||||
<span>Logged in</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li className="flex items-center gap-3">
|
<div className="space-y-4">
|
||||||
<span className="text-green-600">✔</span>
|
{/* Step 1 - Login */}
|
||||||
<span>Stripe connected</span>
|
<div className="flex gap-4">
|
||||||
</li>
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 border-2 border-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<svg className="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="w-0.5 h-8 bg-green-200 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Email verified</p>
|
||||||
|
<p className="text-sm text-slate-600">Authentication complete</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<li className="flex items-center gap-3 text-blue-600">
|
{/* Step 2 - Stripe */}
|
||||||
<span>→</span>
|
<div className="flex gap-4">
|
||||||
<span className="font-medium">Connect Xero</span>
|
<div className="flex flex-col items-center">
|
||||||
</li>
|
<div className="w-10 h-10 rounded-full bg-green-100 border-2 border-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
</ol>
|
<svg className="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="w-0.5 h-8 bg-slate-200 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Stripe connected</p>
|
||||||
|
<p className="text-sm text-slate-600">Payment monitoring active</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Primary CTA */}
|
{/* Step 3 - Xero */}
|
||||||
<div className="pt-6 border-t">
|
<div className="flex gap-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-blue-100 border-2 border-blue-600 flex items-center justify-center flex-shrink-0 font-semibold text-blue-600">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Connect Xero</p>
|
||||||
|
<p className="text-sm text-slate-600">One final step to enable automation</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 border border-blue-200 rounded-xl p-8">
|
||||||
|
<h2 className="text-xl font-semibold text-slate-900 mb-3">
|
||||||
|
Ready for the final step?
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-700 mb-6">
|
||||||
|
Now connect your Xero organisation to complete the setup and start automating invoices.
|
||||||
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/connect/xero"
|
href="/connect/xero"
|
||||||
className="inline-block rounded bg-black text-white px-5 py-3"
|
className="inline-block px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition"
|
||||||
>
|
>
|
||||||
Continue → Connect Xero
|
Continue to Xero →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
// STEP 3 — Connect Xero
|
|
||||||
// Purpose:
|
|
||||||
// - Explain why Xero access is needed
|
|
||||||
// - Make the next step obvious
|
|
||||||
// - Match the Stripe connect page exactly
|
|
||||||
|
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
|
@ -11,64 +5,141 @@ export default async function ConnectXeroPage() {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const session = cookieStore.get("session");
|
const session = cookieStore.get("session");
|
||||||
|
|
||||||
// Safety: if not logged in, bounce to login
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-2xl mx-auto p-8 space-y-10">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
{/* --------------------------------------------------
|
{/* Navigation */}
|
||||||
Header
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
-------------------------------------------------- */}
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center">
|
||||||
<section>
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
<h1 className="text-2xl font-semibold">
|
S2X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
<div className="max-w-2xl mx-auto px-6 py-16">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<div className="inline-flex items-center gap-2 mb-4 px-3 py-1 bg-blue-50 rounded-full border border-blue-200">
|
||||||
|
<span className="w-2 h-2 bg-blue-600 rounded-full"></span>
|
||||||
|
<span className="text-sm font-medium text-blue-700">Step 3 of 3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-4">
|
||||||
Connect Xero
|
Connect Xero
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="mt-3 text-gray-700">
|
<p className="text-lg text-slate-600">
|
||||||
We need access to your Xero organisation so we can automatically
|
We need access to your Xero organisation to automatically create invoices and mark them as paid when Stripe payments succeed.
|
||||||
create invoices and mark them as paid when Stripe payments succeed.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
{/* Two Column Layout */}
|
||||||
What will happen
|
<div className="grid md:grid-cols-3 gap-8 mb-12">
|
||||||
-------------------------------------------------- */}
|
{/* Left Column - Information */}
|
||||||
<section>
|
<div className="md:col-span-2">
|
||||||
<h2 className="text-lg font-medium">
|
{/* What we do */}
|
||||||
What happens next
|
<div className="bg-white border border-slate-200 rounded-xl p-6 mb-6">
|
||||||
</h2>
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">What we can create</h2>
|
||||||
|
<ul className="space-y-3">
|
||||||
<ul className="mt-3 space-y-2 list-disc list-inside text-gray-700">
|
<li className="flex gap-3">
|
||||||
<li>You’ll be redirected to Xero</li>
|
<svg className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<li>You’ll choose which organisation to connect</li>
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
<li>You’ll be sent back here once connected</li>
|
</svg>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-900">Invoices</p>
|
||||||
|
<p className="text-sm text-slate-600">Sales invoices matched to Stripe payments</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<svg className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-900">Credit notes</p>
|
||||||
|
<p className="text-sm text-slate-600">For Stripe refunds and chargebacks</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<svg className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-900">Journal entries</p>
|
||||||
|
<p className="text-sm text-slate-600">For Stripe fees and reconciliation</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
{/* Important notes */}
|
||||||
Trust / reassurance
|
<div className="bg-purple-50 border border-purple-200 rounded-xl p-6 mb-6">
|
||||||
-------------------------------------------------- */}
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">Important notes</h2>
|
||||||
<section className="text-sm text-gray-600">
|
<ul className="space-y-2 text-sm text-slate-700">
|
||||||
<p>
|
<li className="flex gap-2">
|
||||||
We never see your Xero password.
|
<span className="text-purple-600 font-bold">→</span>
|
||||||
<br />
|
<span>We only create invoices; we never modify or delete them</span>
|
||||||
Access can be revoked at any time from Xero.
|
</li>
|
||||||
</p>
|
<li className="flex gap-2">
|
||||||
</section>
|
<span className="text-purple-600 font-bold">→</span>
|
||||||
|
<span>All invoices are created as draft and marked as paid automatically</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-2">
|
||||||
|
<span className="text-purple-600 font-bold">→</span>
|
||||||
|
<span>VAT is handled correctly based on your Stripe data</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
{/* How it works */}
|
||||||
Primary action
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
||||||
-------------------------------------------------- */}
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">What happens next</h2>
|
||||||
<section className="pt-4 border-t">
|
<ol className="space-y-3">
|
||||||
|
<li className="flex gap-4">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-600">1</span>
|
||||||
|
<span className="text-slate-700">You'll be securely redirected to Xero</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-4">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-600">2</span>
|
||||||
|
<span className="text-slate-700">Select which organisation to connect</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-4">
|
||||||
|
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-600">3</span>
|
||||||
|
<span className="text-slate-700">Your dashboard will be ready to configure</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - CTA Card */}
|
||||||
|
<div>
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 border-2 border-blue-600 rounded-xl p-6 sticky top-24">
|
||||||
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Almost there!</h3>
|
||||||
<a
|
<a
|
||||||
href="/api/xero/connect"
|
href="/api/xero/connect"
|
||||||
className="inline-block px-6 py-3 bg-black text-white rounded text-sm"
|
className="block w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg text-center hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition"
|
||||||
>
|
>
|
||||||
Connect Xero →
|
Connect Xero →
|
||||||
</a>
|
</a>
|
||||||
</section>
|
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||||
|
You'll be redirected to Xero's secure OAuth page
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust badges */}
|
||||||
|
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
|
||||||
|
<p className="text-sm text-green-900">
|
||||||
|
<strong>🔒 Your security matters:</strong> We use OAuth, so you can revoke access from Xero at any time. We never see or store your Xero password.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,43 +2,125 @@ import Link from "next/link";
|
||||||
|
|
||||||
export default function XeroSuccessPage() {
|
export default function XeroSuccessPage() {
|
||||||
return (
|
return (
|
||||||
<main className="max-w-2xl mx-auto p-8 space-y-10">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
<h1 className="text-2xl font-semibold">
|
{/* Navigation */}
|
||||||
Xero connected 🎉
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center">
|
||||||
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
|
S2X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
<div className="max-w-2xl mx-auto px-6 py-16">
|
||||||
|
{/* Success Animation */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="inline-flex items-center justify-center w-20 h-20 bg-green-100 rounded-full mb-6 animate-bounce">
|
||||||
|
<svg className="w-10 h-10 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-3">
|
||||||
|
All set! 🚀
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-gray-600">
|
<p className="text-lg text-slate-600">
|
||||||
Your Xero organisation is now linked. We can now automatically
|
Your Xero organisation is now securely linked. We can now automatically create invoices and mark them as paid when Stripe payments succeed.
|
||||||
create invoices and mark them as paid when Stripe payments succeed.
|
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Progress */}
|
{/* Progress Card */}
|
||||||
<ol className="space-y-4">
|
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-8 mb-8">
|
||||||
<li className="flex items-center gap-3">
|
<h2 className="text-lg font-semibold text-slate-900 mb-6">Setup complete</h2>
|
||||||
<span className="text-green-600">✔</span>
|
|
||||||
<span>Logged in</span>
|
<div className="space-y-4">
|
||||||
|
{/* Step 1 - Login */}
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 border-2 border-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<svg className="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="w-0.5 h-8 bg-green-200 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Email verified</p>
|
||||||
|
<p className="text-sm text-slate-600">Authentication complete</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 2 - Stripe */}
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 border-2 border-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<svg className="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="w-0.5 h-8 bg-green-200 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Stripe connected</p>
|
||||||
|
<p className="text-sm text-slate-600">Payment monitoring active</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 3 - Xero */}
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-green-100 border-2 border-green-600 flex items-center justify-center flex-shrink-0">
|
||||||
|
<svg className="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1">
|
||||||
|
<p className="font-semibold text-slate-900">Xero connected</p>
|
||||||
|
<p className="text-sm text-slate-600">Invoice automation enabled</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* What's next */}
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-xl p-8 mb-8">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">What happens now</h2>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li className="flex gap-3">
|
||||||
|
<span className="text-blue-600 font-bold text-lg">✓</span>
|
||||||
|
<span className="text-slate-700">Configure your Xero account codes in the dashboard</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
<li className="flex items-center gap-3">
|
<span className="text-blue-600 font-bold text-lg">✓</span>
|
||||||
<span className="text-green-600">✔</span>
|
<span className="text-slate-700">Make a test Stripe payment to verify everything works</span>
|
||||||
<span>Stripe connected</span>
|
|
||||||
</li>
|
</li>
|
||||||
|
<li className="flex gap-3">
|
||||||
<li className="flex items-center gap-3">
|
<span className="text-blue-600 font-bold text-lg">✓</span>
|
||||||
<span className="text-green-600">✔</span>
|
<span className="text-slate-700">Watch as invoices automatically appear in Xero</span>
|
||||||
<span>Xero connected</span>
|
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Primary CTA */}
|
{/* CTA */}
|
||||||
<div className="pt-6 border-t">
|
<div className="bg-gradient-to-br from-green-50 to-green-100 border border-green-200 rounded-xl p-8">
|
||||||
|
<h2 className="text-xl font-semibold text-slate-900 mb-3">
|
||||||
|
Go to your dashboard
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-700 mb-6">
|
||||||
|
Configure your account codes and start using the automation.
|
||||||
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard"
|
href="/dashboard"
|
||||||
className="inline-block rounded bg-black text-white px-5 py-3"
|
className="inline-block px-6 py-3 bg-gradient-to-r from-green-600 to-green-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-green-700 hover:to-green-800 transition"
|
||||||
>
|
>
|
||||||
Go to dashboard →
|
Open dashboard →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const [salesAccountCode, setSalesAccountCode] = useState("");
|
const [salesAccountCode, setSalesAccountCode] = useState("");
|
||||||
const [stripeClearingAccountCode, setStripeClearingAccountCode] =
|
const [stripeClearingAccountCode, setStripeClearingAccountCode] = useState("");
|
||||||
useState("");
|
|
||||||
const [stripeAccountId, setStripeAccountId] = useState("");
|
const [stripeAccountId, setStripeAccountId] = useState("");
|
||||||
const [xeroTenantId, setXeroTenantId] = useState("");
|
const [xeroTenantId, setXeroTenantId] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saved, setSaved] = useState(false);
|
const [saved, setSaved] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
|
|
@ -26,7 +26,9 @@ export default function DashboardPage() {
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
setSaved(false);
|
setSaved(false);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
await fetch("/api/dashboard/xero-settings", {
|
await fetch("/api/dashboard/xero-settings", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -37,77 +39,182 @@ export default function DashboardPage() {
|
||||||
});
|
});
|
||||||
|
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
|
setTimeout(() => setSaved(false), 3000);
|
||||||
|
} catch (err) {
|
||||||
|
setError("Failed to save settings. Please try again.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) return <p>Loading…</p>;
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="inline-flex items-center justify-center w-12 h-12 mb-4">
|
||||||
|
<span className="w-3 h-3 bg-blue-600 rounded-full animate-spin"></span>
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-600">Loading your settings…</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-xl space-y-6">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
<h1 className="text-2xl font-semibold">
|
{/* Navigation */}
|
||||||
Stripe → Xero settings
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
</h1>
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||||
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
|
S2X
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-slate-600">Dashboard</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
<div className="max-w-4xl mx-auto px-6 py-16">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-3">Dashboard</h1>
|
||||||
|
<p className="text-lg text-slate-600">
|
||||||
|
Configure your Xero account codes and manage your automation settings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-3 gap-8">
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="lg:col-span-2 space-y-6">
|
||||||
{/* Connected Accounts */}
|
{/* Connected Accounts */}
|
||||||
<div className="border rounded p-4 bg-gray-50 space-y-3">
|
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-6">
|
||||||
<h2 className="font-medium text-sm text-gray-600">Connected Accounts</h2>
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">Connected Accounts</h2>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-4">
|
||||||
<p>
|
<div className="p-4 bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg border border-blue-200">
|
||||||
<span className="font-medium">Stripe:</span>{" "}
|
<p className="text-sm font-medium text-slate-700 mb-1">Stripe Account</p>
|
||||||
<code className="bg-white px-2 py-1 rounded text-xs">
|
<p className="text-sm text-slate-600 font-mono break-all">
|
||||||
{stripeAccountId || "Not connected"}
|
{stripeAccountId || <span className="text-slate-400">Not connected</span>}
|
||||||
</code>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
</div>
|
||||||
<span className="font-medium">Xero:</span>{" "}
|
<div className="p-4 bg-gradient-to-br from-green-50 to-green-100 rounded-lg border border-green-200">
|
||||||
<code className="bg-white px-2 py-1 rounded text-xs">
|
<p className="text-sm font-medium text-slate-700 mb-1">Xero Organisation</p>
|
||||||
{xeroTenantId || "Not connected"}
|
<p className="text-sm text-slate-600 font-mono break-all">
|
||||||
</code>
|
{xeroTenantId || <span className="text-slate-400">Not connected</span>}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Account Configuration */}
|
||||||
|
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-900 mb-6">Xero Account Codes</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Sales Account Code */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium">
|
<label className="block text-sm font-medium text-slate-900 mb-2">
|
||||||
Sales account code
|
Sales Account Code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className="mt-1 w-full border px-3 py-2"
|
type="text"
|
||||||
|
placeholder="e.g., 200"
|
||||||
|
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
value={salesAccountCode}
|
value={salesAccountCode}
|
||||||
onChange={(e) => setSalesAccountCode(e.target.value)}
|
onChange={(e) => setSalesAccountCode(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-zinc-500">
|
<p className="text-sm text-slate-600 mt-2">
|
||||||
Used on invoice line items
|
The Xero account code used for sales invoice line items. This is typically your revenue/sales account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Stripe Clearing Account Code */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium">
|
<label className="block text-sm font-medium text-slate-900 mb-2">
|
||||||
Stripe clearing account code
|
Stripe Clearing Account Code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className="mt-1 w-full border px-3 py-2"
|
type="text"
|
||||||
|
placeholder="e.g., 1200"
|
||||||
|
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
value={stripeClearingAccountCode}
|
value={stripeClearingAccountCode}
|
||||||
onChange={(e) =>
|
onChange={(e) => setStripeClearingAccountCode(e.target.value)}
|
||||||
setStripeClearingAccountCode(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-zinc-500">
|
<p className="text-sm text-slate-600 mt-2">
|
||||||
Receives Stripe payments
|
The Xero account code that receives Stripe payments. This is typically a bank or clearing account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Save Button and Feedback */}
|
||||||
|
<div className="mt-8 flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={save}
|
onClick={save}
|
||||||
className="rounded bg-black px-4 py-2 text-white"
|
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition"
|
||||||
>
|
>
|
||||||
Save
|
Save Settings
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{saved && (
|
{saved && (
|
||||||
<p className="text-sm text-green-600">
|
<div className="flex items-center gap-2 text-sm text-green-700 bg-green-50 px-4 py-3 rounded-lg border border-green-200">
|
||||||
Saved
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
</p>
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>Settings saved successfully</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-red-700 bg-red-50 px-4 py-3 rounded-lg border border-red-200">
|
||||||
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Info Card */}
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6">
|
||||||
|
<h3 className="font-semibold text-slate-900 mb-4">ℹ️ How it works</h3>
|
||||||
|
<p className="text-sm text-slate-700 mb-4">
|
||||||
|
When a Stripe payment is received, we automatically create an invoice in Xero using:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-2 text-sm text-slate-700">
|
||||||
|
<li className="flex gap-2">
|
||||||
|
<span className="text-blue-600">→</span>
|
||||||
|
<span><strong>Sales Account:</strong> Invoice line items</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-2">
|
||||||
|
<span className="text-blue-600">→</span>
|
||||||
|
<span><strong>Clearing Account:</strong> Payment received</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Help Card */}
|
||||||
|
<div className="bg-amber-50 border border-amber-200 rounded-xl p-6">
|
||||||
|
<h3 className="font-semibold text-slate-900 mb-4">🚀 Getting started</h3>
|
||||||
|
<ol className="space-y-2 text-sm text-slate-700">
|
||||||
|
<li><strong>1.</strong> Set the account codes above</li>
|
||||||
|
<li><strong>2.</strong> Make a test payment in Stripe</li>
|
||||||
|
<li><strong>3.</strong> Check Xero for the invoice</li>
|
||||||
|
<li><strong>4.</strong> You're ready to go!</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status Card */}
|
||||||
|
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
|
||||||
|
<h3 className="font-semibold text-slate-900 mb-4">✓ Status</h3>
|
||||||
|
<p className="text-sm text-green-700 font-medium mb-2">Connected & Active</p>
|
||||||
|
<p className="text-sm text-slate-600">
|
||||||
|
Your automation is ready. New Stripe payments will create invoices in Xero automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
@ -13,8 +13,15 @@ const geistMono = Geist_Mono({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Stripe to Xero - Automated Invoice Creation",
|
||||||
description: "Generated by create next app",
|
description: "Automatically turn Stripe payments into paid Xero invoices with proper VAT handling and accounting compliance.",
|
||||||
|
keywords: ["Stripe", "Xero", "invoicing", "automation", "accounting", "VAT"],
|
||||||
|
authors: [{ name: "Stripe to Xero" }],
|
||||||
|
openGraph: {
|
||||||
|
title: "Stripe to Xero - Automated Invoice Creation",
|
||||||
|
description: "Automatically turn Stripe payments into paid Xero invoices with proper VAT handling.",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// app/login/page.tsx
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -34,51 +33,125 @@ export default function LoginPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === "Enter" && !loading && email && step === "email") {
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-md mx-auto p-8 space-y-8">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex flex-col">
|
||||||
|
{/* Navigation */}
|
||||||
|
<nav className="border-b border-slate-200 bg-white/50 backdrop-blur-sm">
|
||||||
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center">
|
||||||
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
|
S2X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="flex-1 flex items-center justify-center px-6 py-12">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* Progress Bar */}
|
||||||
<Progress step={step} />
|
<Progress step={step} />
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<div className="mt-12 bg-white border border-slate-200 rounded-xl shadow-sm p-8">
|
||||||
{step === "email" && (
|
{step === "email" && (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-xl font-semibold">Log in</h1>
|
<h1 className="text-2xl font-bold text-slate-900 mb-2">
|
||||||
|
Welcome back
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-600 mb-6">
|
||||||
|
Sign in to your Stripe to Xero automation dashboard
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||||
|
Email address
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="enter@email.com"
|
placeholder="you@company.com"
|
||||||
className="w-full border rounded p-2"
|
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
onKeyPress={handleKeyPress}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={loading || !email}
|
disabled={loading || !email}
|
||||||
className="w-full bg-black text-white py-2 rounded"
|
className="w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{loading ? "Sending…" : "Send login link"}
|
{loading ? (
|
||||||
|
<span className="flex items-center justify-center gap-2">
|
||||||
|
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
||||||
|
Sending link…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"Send login link"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-sm text-red-600">{error}</p>
|
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||||
|
<p className="text-sm text-red-700">{error}</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{step === "sent" && (
|
{step === "sent" && (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-xl font-semibold">Check your email</h1>
|
<div className="text-center">
|
||||||
|
<div className="inline-flex items-center justify-center w-12 h-12 bg-green-100 rounded-full mb-4">
|
||||||
|
<svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-600">
|
<h1 className="text-2xl font-bold text-slate-900 mb-2">
|
||||||
We sent a login link to <strong>{email}</strong>.
|
Check your email
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-slate-600 mb-4">
|
||||||
|
We've sent a login link to <strong className="text-slate-900">{email}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="text-gray-500 text-sm">
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||||
The link expires in 15 minutes.
|
<p className="text-sm text-blue-800">
|
||||||
|
The link expires in 15 minutes. Check your spam folder if you don't see it.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setStep("email");
|
||||||
|
setError(null);
|
||||||
|
}}
|
||||||
|
className="text-sm text-blue-600 hover:text-blue-700 font-medium"
|
||||||
|
>
|
||||||
|
← Try another email
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<p className="text-center text-sm text-slate-500 mt-8">
|
||||||
|
Magic link authentication is secure and passwordless
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,16 +159,15 @@ function Progress({ step }: { step: Step }) {
|
||||||
const percent = step === "email" ? 50 : 100;
|
const percent = step === "email" ? 50 : 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
<div className="h-2 bg-gray-200 rounded">
|
<div className="h-1 bg-slate-200 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-2 bg-black rounded transition-all"
|
className="h-1 bg-gradient-to-r from-blue-600 to-blue-700 rounded-full transition-all duration-500"
|
||||||
style={{ width: `${percent}%` }}
|
style={{ width: `${percent}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-xs font-medium text-slate-600 uppercase tracking-wide">
|
||||||
<p className="text-sm text-gray-600 mt-2">
|
{step === "email" ? "Step 1 of 2 • Enter email" : "Step 2 of 2 • Check inbox"}
|
||||||
{step === "email" ? "Enter email" : "Check inbox"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
// app/page.tsx
|
|
||||||
//
|
|
||||||
// CORE MVP PAGE
|
|
||||||
// Purpose:
|
|
||||||
// 1. Explain the automation
|
|
||||||
// 2. Point the user to the next action
|
|
||||||
//
|
|
||||||
// Everything else lives elsewhere.
|
|
||||||
|
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
|
@ -14,59 +5,284 @@ export default async function Home() {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const session = cookieStore.get("session");
|
const session = cookieStore.get("session");
|
||||||
|
|
||||||
// ✅ If already logged in, go straight to app
|
|
||||||
if (session) {
|
if (session) {
|
||||||
redirect("/app");
|
redirect("/app");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-2xl mx-auto p-8 space-y-10">
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||||
|
{/* Navigation */}
|
||||||
{/* --------------------------------------------------
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-sm border-b border-slate-200">
|
||||||
What this is
|
<div className="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||||
-------------------------------------------------- */}
|
<div className="text-xl font-bold bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
<section>
|
S2X
|
||||||
<h1 className="text-2xl font-semibold">
|
</div>
|
||||||
Stripe → Xero automation
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p className="mt-3 text-gray-700">
|
|
||||||
When a Stripe payment succeeds, a Xero invoice is
|
|
||||||
automatically created and marked as paid.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
|
||||||
What the user does
|
|
||||||
-------------------------------------------------- */}
|
|
||||||
<section>
|
|
||||||
<h2 className="text-lg font-medium">
|
|
||||||
How it works
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<ol className="mt-3 space-y-2 list-decimal list-inside text-gray-700">
|
|
||||||
<li>Log in with your email</li>
|
|
||||||
<li>Connect Stripe</li>
|
|
||||||
<li>Connect Xero</li>
|
|
||||||
<li>Invoices handle themselves. You focus on the business.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* --------------------------------------------------
|
|
||||||
Next action
|
|
||||||
-------------------------------------------------- */}
|
|
||||||
<section className="pt-4 border-t">
|
|
||||||
<p className="text-gray-700">
|
|
||||||
Start by logging in.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/login"
|
href="/login"
|
||||||
className="inline-block mt-4 px-6 py-3 bg-black text-white rounded text-sm"
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:text-slate-900 transition"
|
||||||
>
|
>
|
||||||
Log in →
|
Log in
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 py-20 sm:py-32 text-center">
|
||||||
|
<div className="inline-flex items-center gap-2 mb-6 px-3 py-1 bg-blue-50 rounded-full border border-blue-200">
|
||||||
|
<span className="w-2 h-2 bg-blue-600 rounded-full"></span>
|
||||||
|
<span className="text-sm font-medium text-blue-700">Automate your invoicing</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-5xl sm:text-6xl font-bold tracking-tight text-slate-900 mb-6">
|
||||||
|
Stripe payments,
|
||||||
|
<br />
|
||||||
|
<span className="bg-gradient-to-r from-blue-600 to-blue-700 bg-clip-text text-transparent">
|
||||||
|
Xero invoices
|
||||||
|
</span>
|
||||||
|
{" "}automatically
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-xl text-slate-600 max-w-2xl mx-auto mb-8 leading-relaxed">
|
||||||
|
Stop manually creating invoices. When a Stripe payment succeeds, a Xero invoice is instantly created and marked as paid—with proper VAT handling and accountant-approved accuracy.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
||||||
|
<a
|
||||||
|
href="/login"
|
||||||
|
className="px-8 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition transform hover:scale-105"
|
||||||
|
>
|
||||||
|
Get started →
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#how-it-works"
|
||||||
|
className="px-8 py-3 bg-white border border-slate-300 text-slate-700 font-semibold rounded-lg hover:bg-slate-50 transition"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust badges */}
|
||||||
|
<div className="flex flex-wrap justify-center gap-8 text-sm text-slate-600">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>No manual work</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>VAT compliant</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>Audit ready</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Features Section */}
|
||||||
|
<section id="how-it-works" className="max-w-5xl mx-auto px-6 py-20 border-t border-slate-200">
|
||||||
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
|
How it works
|
||||||
|
</h2>
|
||||||
|
<p className="text-center text-slate-600 mb-12 max-w-2xl mx-auto">
|
||||||
|
Three simple connections, then automation takes over
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-6 mb-12">
|
||||||
|
{/* Step 1 */}
|
||||||
|
<div className="bg-white border border-slate-200 rounded-xl p-6 hover:shadow-lg transition">
|
||||||
|
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
|
||||||
|
<span className="text-2xl font-bold text-blue-600">1</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-slate-900 mb-2">Connect Stripe</h3>
|
||||||
|
<p className="text-slate-600">
|
||||||
|
Link your Stripe account to start monitoring payments in real-time.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 2 */}
|
||||||
|
<div className="bg-white border border-slate-200 rounded-xl p-6 hover:shadow-lg transition">
|
||||||
|
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
|
||||||
|
<span className="text-2xl font-bold text-blue-600">2</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-slate-900 mb-2">Connect Xero</h3>
|
||||||
|
<p className="text-slate-600">
|
||||||
|
Authorize access to your Xero account for invoice creation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 3 */}
|
||||||
|
<div className="bg-white border border-slate-200 rounded-xl p-6 hover:shadow-lg transition">
|
||||||
|
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
|
||||||
|
<span className="text-2xl font-bold text-blue-600">3</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-slate-900 mb-2">Done</h3>
|
||||||
|
<p className="text-slate-600">
|
||||||
|
Relax as payments automatically become invoices in Xero.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Benefits Grid */}
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl p-6 border border-blue-200">
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-2">🎯 Zero manual work</h4>
|
||||||
|
<p className="text-slate-700">
|
||||||
|
No more spreadsheets, journals, or month-end fixing. Invoices appear instantly and correctly.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-6 border border-green-200">
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-2">✓ Accounting ready</h4>
|
||||||
|
<p className="text-slate-700">
|
||||||
|
VAT compliance built-in. Your accountant will love the audit trail.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-purple-50 to-purple-100 rounded-xl p-6 border border-purple-200">
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-2">⚡ Instant & reliable</h4>
|
||||||
|
<p className="text-slate-700">
|
||||||
|
Invoices created within seconds of payment. Refunds become credit notes automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-amber-50 to-amber-100 rounded-xl p-6 border border-amber-200">
|
||||||
|
<h4 className="font-semibold text-slate-900 mb-2">🔒 Xero is the source</h4>
|
||||||
|
<p className="text-slate-700">
|
||||||
|
Your invoices remain immutable in Xero. Stripe is purely payment execution.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Perfect for section */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 py-20 border-t border-slate-200">
|
||||||
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||||
|
Who this is for
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-slate-900 mb-4">✓ Perfect fit:</h3>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-green-500">→</span>
|
||||||
|
<span>UK businesses using Stripe & Xero</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-green-500">→</span>
|
||||||
|
<span>VAT registered</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-green-500">→</span>
|
||||||
|
<span>One-off, annual, or fixed recurring billing</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-green-500">→</span>
|
||||||
|
<span>Invoices are currently manually created</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-slate-900 mb-4">✗ Not a fit:</h3>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-slate-400">→</span>
|
||||||
|
<span>Usage-based or metered billing</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-slate-400">→</span>
|
||||||
|
<span>Proration or complex billing logic</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-slate-400">→</span>
|
||||||
|
<span>Already fully automated elsewhere</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<span className="text-slate-400">→</span>
|
||||||
|
<span>Non-UK businesses</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Pricing Section */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 py-20 border-t border-slate-200">
|
||||||
|
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||||
|
Simple pricing
|
||||||
|
</h2>
|
||||||
|
<p className="text-center text-slate-600 mb-12">
|
||||||
|
No hidden fees. No per-transaction costs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="max-w-md mx-auto bg-gradient-to-br from-slate-50 to-slate-100 border-2 border-blue-600 rounded-2xl p-8 text-center">
|
||||||
|
<h3 className="text-4xl font-bold text-slate-900 mb-2">£200</h3>
|
||||||
|
<p className="text-slate-600 mb-6">per month</p>
|
||||||
|
<ul className="space-y-3 text-left mb-8">
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>Unlimited invoices</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>No per-transaction fees</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex gap-3 text-slate-700">
|
||||||
|
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span>Full VAT handling</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="text-sm text-slate-600 mb-6 pb-6 border-b border-slate-300">
|
||||||
|
Usually saves £1000s per year in manual work and accounting fixes.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/login"
|
||||||
|
className="w-full px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-lg hover:shadow-lg hover:from-blue-700 hover:to-blue-800 transition"
|
||||||
|
>
|
||||||
|
Start 14-day trial
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 py-20 border-t border-slate-200">
|
||||||
|
<div className="bg-gradient-to-r from-blue-600 to-blue-700 rounded-2xl p-12 text-center text-white">
|
||||||
|
<h2 className="text-3xl font-bold mb-4">
|
||||||
|
Ready to automate?
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg opacity-90 mb-8 max-w-2xl mx-auto">
|
||||||
|
Connect Stripe and Xero, then focus on what matters. Invoicing handles itself.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/login"
|
||||||
|
className="inline-block px-8 py-3 bg-white text-blue-600 font-semibold rounded-lg hover:bg-slate-50 transition"
|
||||||
|
>
|
||||||
|
Get started free →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="border-t border-slate-200 py-12 mt-20">
|
||||||
|
<div className="max-w-5xl mx-auto px-6 text-center text-slate-600 text-sm">
|
||||||
|
<p>Building the bridge between Stripe payments and Xero invoicing.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
stripe_to_invoice/tailwind.config.ts
Normal file
12
stripe_to_invoice/tailwind.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
export default config
|
||||||
Loading…
Add table
Reference in a new issue