Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c139bfc
feat(frontend): add direct Leather wallet RPC integration
DevSolex Feb 17, 2026
e0f79af
chore: update @stacks/connect to 7.10.2
DevSolex Feb 17, 2026
4999cca
feat: add loading state to Connect Wallet button
DevSolex Feb 17, 2026
ef0ed07
feat: add shortenAddress and isValidPrincipal utils
DevSolex Feb 17, 2026
b2d5b98
feat: add useCopyAddress hook
DevSolex Feb 17, 2026
ee97382
feat: add copy address on click in Navbar
DevSolex Feb 17, 2026
8af73f4
feat: add LoadingSpinner component
DevSolex Feb 17, 2026
44d9e8f
refactor: use shortenAddress util in ConnectWallet
DevSolex Feb 17, 2026
4736d09
feat: add NetworkBadge (Mainnet) to Navbar
DevSolex Feb 17, 2026
9cfe2c1
feat: add username validation to RegisterUserForm
DevSolex Feb 17, 2026
a479f68
feat: add recipient and amount validation to PaymentForm
DevSolex Feb 17, 2026
52761d8
feat: add Footer component to layout
DevSolex Feb 17, 2026
fd292a5
a11y: add aria-labels to ConnectWallet buttons
DevSolex Feb 17, 2026
4660983
seo: add keywords and openGraph meta
DevSolex Feb 17, 2026
7958cc1
feat: show contract address on Pay page
DevSolex Feb 17, 2026
2b881e8
feat: add Connect wallet CTA on Register page when disconnected
DevSolex Feb 17, 2026
b7c129d
fix: validate amount in VaultDepositForm, add aria-label
DevSolex Feb 17, 2026
72fda29
refactor: add hooks and lib barrel exports
DevSolex Feb 17, 2026
31645d7
feat: add wallet types
DevSolex Feb 17, 2026
de80c2a
style: add responsive Navbar breakpoints
DevSolex Feb 17, 2026
8aedaea
feat: add formatStx and formatDate helpers
DevSolex Feb 17, 2026
16c7cd4
feat: add site config
DevSolex Feb 17, 2026
59a6f6b
a11y: add aria-labels to RegisterMerchantForm
DevSolex Feb 17, 2026
32c1492
chore: minor update (23)
DevSolex Feb 17, 2026
f80cb59
chore: minor update (24)
DevSolex Feb 17, 2026
f58c334
chore: minor update (25)
DevSolex Feb 17, 2026
3638c0c
chore: minor update (26)
DevSolex Feb 17, 2026
e738290
chore: minor update (27)
DevSolex Feb 17, 2026
f7623e4
chore: minor update (28)
DevSolex Feb 17, 2026
1900e9b
chore: minor update (29)
DevSolex Feb 17, 2026
edbbaa1
chore: minor update (30)
DevSolex Feb 17, 2026
112f8b2
chore: clean utils.ts
DevSolex Feb 17, 2026
456a7b1
refactor: export format from lib barrel
DevSolex Feb 17, 2026
c7d788b
chore: add ConnectWallet test placeholder
DevSolex Feb 17, 2026
ffbd3a3
docs: improve vault and merchant page subtitles
DevSolex Feb 17, 2026
b77a781
chore: clean Button component
DevSolex Feb 17, 2026
5b3e13f
docs: add frontend README
DevSolex Feb 17, 2026
da7b446
docs: add Stacks Mainnet to footer
DevSolex Feb 17, 2026
0917d5e
ux: improve form placeholders
DevSolex Feb 17, 2026
a9adfda
chore: add robots.txt
DevSolex Feb 17, 2026
a54840f
chore: add .nvmrc
DevSolex Feb 17, 2026
a011c6a
a11y: add aria-label to website input
DevSolex Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
18 changes: 17 additions & 1 deletion frontend/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# Final Frontend Polishing
# S-pay Frontend

Next.js frontend for S-pay protocol.

## Setup

```bash
npm install
npm run dev
```

## Features

- Connect wallet (Leather, Xverse)
- Register user / merchant
- Process payments, vault deposit
- Read-only contract queries
8 changes: 6 additions & 2 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import type { Metadata } from "next";
import "./globals.css";
import { StacksProvider } from "@/context/StacksContext";
import Navbar from "@/components/Navbar/Navbar";
import { Footer } from "@/components/Footer/Footer";

export const metadata: Metadata = {
title: "S-pay | Premium Stacks Payments",
description: "Experience the future of decentralized payments on Stacks.",
keywords: ["Stacks", "Bitcoin", "payments", "crypto", "decentralized"],
openGraph: { title: "S-pay | Premium Stacks Payments", description: "Fast, decentralized payments on Stacks." },
};

export default function RootLayout({
Expand All @@ -20,10 +23,11 @@ export default function RootLayout({
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
</head>
<body>
<body style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
<StacksProvider>
<Navbar />
{children}
<main style={{ flex: 1 }}>{children}</main>
<Footer />
</StacksProvider>
</body>
</html>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/merchant/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function MerchantRegisterPage() {
<div className={styles.page}>
<h1 className={styles.title}>Register as Merchant</h1>
<p className={styles.subtitle}>
Requires 50 STX verification stake (register-merchant)
Requires 50 STX verification stake. Refundable after verification.
</p>
<RegisterMerchantForm />
</div>
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/pay/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { PaymentForm } from "@/components/PaymentForm/PaymentForm";
import { SPAY_FULL_CONTRACT } from "@/lib/constants";
import styles from "./pay.module.css";

export default function PayPage() {
Expand All @@ -10,6 +11,7 @@ export default function PayPage() {
<p className={styles.subtitle}>
Send STX via S-pay protocol (process-payment)
</p>
<p className={styles.contract}>Contract: {SPAY_FULL_CONTRACT}</p>
<PaymentForm />
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions frontend/app/pay/pay.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@

.subtitle {
color: #a0a0aa;
margin-bottom: 0.5rem;
}

.contract {
font-size: 0.8rem;
font-family: monospace;
color: #6b6b7b;
margin-bottom: 2rem;
}
9 changes: 9 additions & 0 deletions frontend/app/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
"use client";

import Link from "next/link";
import { RegisterUserForm } from "@/components/RegisterUserForm/RegisterUserForm";
import { useStacks } from "@/context/StacksContext";
import styles from "./register.module.css";

export default function RegisterPage() {
const { address } = useStacks();

return (
<div className={styles.page}>
<h1 className={styles.title}>Register as User</h1>
<p className={styles.subtitle}>
Pick a unique username to join S-pay (string-ascii 24 max)
</p>
{!address && (
<p className={styles.cta}>
<Link href="/">Connect your wallet</Link> to get started.
</p>
)}
<RegisterUserForm />
</div>
);
Expand Down
10 changes: 10 additions & 0 deletions frontend/app/register/register.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,15 @@

.subtitle {
color: #a0a0aa;
margin-bottom: 1rem;
}

.cta {
color: var(--primary);
margin-bottom: 2rem;
}

.cta a {
color: inherit;
text-decoration: underline;
}
2 changes: 1 addition & 1 deletion frontend/app/vault/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function VaultPage() {
<div className={styles.page}>
<h1 className={styles.title}>Vault Deposit</h1>
<p className={styles.subtitle}>
Deposit STX to your S-pay vault (vault-deposit)
Deposit STX to your S-pay vault. Minimum 0.000001 STX.
</p>
<VaultDepositForm />
</div>
Expand Down
15 changes: 0 additions & 15 deletions frontend/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,3 @@ export default function Button({
</button>
);
}
// Feature extension 36
// Feature extension 37
// Feature extension 38
// Feature extension 39
// Feature extension 40
// Feature extension 41
// Feature extension 42
// Feature extension 43
// Feature extension 44
// Feature extension 45
// Feature extension 46
// Feature extension 47
// Feature extension 48
// Feature extension 49
// Feature extension 50
6 changes: 6 additions & 0 deletions frontend/components/ConnectWallet/ConnectWallet.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* ConnectWallet component tests placeholder.
* Add tests with React Testing Library.
*/

export const placeholder = true;
29 changes: 25 additions & 4 deletions frontend/components/ConnectWallet/ConnectWallet.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
"use client";

import { useState } from "react";
import { useStacks } from "@/context/StacksContext";
import { shortenAddress } from "@/lib/utils";
import styles from "./ConnectWallet.module.css";

export function ConnectWallet() {
const { address, handleConnect, handleDisconnect } = useStacks();
const [connecting, setConnecting] = useState(false);

const onConnect = async () => {
setConnecting(true);
try {
await handleConnect();
} finally {
setConnecting(false);
}
};

return (
<div className={styles.actions}>
{address ? (
<>
<span className={styles.address}>{address.slice(0, 6)}…{address.slice(-4)}</span>
<button className={styles.secondaryBtn} onClick={handleDisconnect}>
<span className={styles.address}>{shortenAddress(address)}</span>
<button
className={styles.secondaryBtn}
onClick={handleDisconnect}
aria-label="Disconnect wallet"
>
Disconnect
</button>
</>
) : (
<>
<button className={styles.primaryBtn} onClick={handleConnect}>
Connect Wallet
<button
className={styles.primaryBtn}
onClick={onConnect}
disabled={connecting}
aria-label="Connect wallet"
>
{connecting ? "Connecting…" : "Connect Wallet"}
</button>
<button className={styles.secondaryBtn}>Learn More</button>
</>
Expand Down
27 changes: 27 additions & 0 deletions frontend/components/Footer/Footer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.footer {
margin-top: auto;
padding: 2rem;
text-align: center;
border-top: 1px solid var(--glass-border);
}

.links {
display: flex;
justify-content: center;
gap: 2rem;
margin-bottom: 1rem;
}

.links a {
color: #a0a0aa;
text-decoration: none;
}

.links a:hover {
color: var(--foreground);
}

.copyright {
font-size: 0.85rem;
color: #6b6b7b;
}
19 changes: 19 additions & 0 deletions frontend/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import Link from "next/link";
import styles from "./Footer.module.css";

export function Footer() {
return (
<footer className={styles.footer}>
<div className={styles.links}>
<Link href="/">Home</Link>
<Link href="/docs">Docs</Link>
<a href="https://github.com/DevSolex/S-pay" target="_blank" rel="noopener noreferrer">
GitHub
</a>
</div>
<p className={styles.copyright}>© {new Date().getFullYear()} S-pay · Stacks Mainnet</p>
</footer>
);
}
14 changes: 14 additions & 0 deletions frontend/components/LoadingSpinner/LoadingSpinner.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.spinner {
border: 2px solid rgba(255, 255, 255, 0.2);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

.sm { width: 16px; height: 16px; border-width: 2px; }
.md { width: 24px; height: 24px; border-width: 2px; }
.lg { width: 40px; height: 40px; border-width: 3px; }

@keyframes spin {
to { transform: rotate(360deg); }
}
7 changes: 7 additions & 0 deletions frontend/components/LoadingSpinner/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use client";

import styles from "./LoadingSpinner.module.css";

export function LoadingSpinner({ size = "md" }: { size?: "sm" | "md" | "lg" }) {
return <div className={`${styles.spinner} ${styles[size]}`} aria-label="Loading" />;
}
11 changes: 11 additions & 0 deletions frontend/components/Navbar/Navbar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
background: rgba(255, 255, 255, 0.05);
padding: 0.4rem 0.8rem;
border-radius: 8px;
border: none;
cursor: pointer;
transition: var(--transition-smooth);
}

.connectBtn {
Expand All @@ -80,3 +83,11 @@
border-color: var(--primary);
box-shadow: 0 0 15px var(--primary-glow);
}

@media (max-width: 900px) {
.links { gap: 1.5rem; }
}

@media (max-width: 700px) {
.links { display: none; }
}
13 changes: 12 additions & 1 deletion frontend/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import Link from "next/link";
import { useStacks } from "@/context/StacksContext";
import { useCopyAddress } from "@/hooks/useCopyAddress";
import { shortenAddress } from "@/lib/utils";
import { NetworkBadge } from "@/components/NetworkBadge/NetworkBadge";
import styles from "./Navbar.module.css";

export default function Navbar() {
const { address: addr, handleConnect, handleDisconnect } = useStacks();
const { copy, copied } = useCopyAddress();

return (
<nav className={styles.navbar}>
Expand All @@ -23,9 +27,16 @@ export default function Navbar() {
<Link href="/docs" className={styles.link}>Developers</Link>
</div>
<div className={styles.actions}>
<NetworkBadge />
{addr ? (
<>
<span className={styles.address}>{addr.slice(0, 6)}…{addr.slice(-4)}</span>
<button
className={styles.address}
onClick={() => copy(addr)}
title={addr}
>
{shortenAddress(addr)}{copied ? " ✓" : ""}
</button>
<button className={styles.connectBtn} onClick={handleDisconnect}>Disconnect</button>
</>
) : (
Expand Down
9 changes: 9 additions & 0 deletions frontend/components/NetworkBadge/NetworkBadge.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.badge {
font-size: 0.7rem;
font-weight: 600;
padding: 0.2rem 0.5rem;
border-radius: 4px;
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
border: 1px solid rgba(34, 197, 94, 0.3);
}
11 changes: 11 additions & 0 deletions frontend/components/NetworkBadge/NetworkBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use client";

import styles from "./NetworkBadge.module.css";

export function NetworkBadge() {
return (
<span className={styles.badge} title="Stacks Mainnet">
Mainnet
</span>
);
}
6 changes: 6 additions & 0 deletions frontend/components/PaymentForm/PaymentForm.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@
border-radius: 8px;
font-weight: 600;
}

.error {
color: #ef4444;
font-size: 0.85rem;
margin: 0;
}
Loading
Loading