Donor and NGO web app for StreamGive, a recurring/streaming donation platform for verified NGOs on Stellar.
- Next.js (App Router), React, TypeScript
- Tailwind CSS
See docs/COMPONENTS.md for a component tree of
src/components/ with a one-line description of each piece,
docs/STATE_ARCHITECTURE.md for the app's
context/state architecture (WalletProvider, ToastProvider, the
signature cache), and
docs/CONTRACT_CALLS.md for a reference mapping
every Soroban contract method the frontend calls to the UI flow and
arguments behind it.
cp .env.example .env
npm install
npm run dev # http://localhost:3001 — 3000 is taken by streamgive-backend
See ENVIRONMENT.md for a full reference of every NEXT_PUBLIC_* variable.
Vercel (recommended) — Next.js's own platform, effectively zero-config:
connect the repo, set the NEXT_PUBLIC_* env vars from .env.example in
the project settings, deploy. No Dockerfile involved.
Docker (self-hosting):
docker build -t streamgive-frontend .
docker run -p 3001:3001 --env-file .env streamgive-frontend
The image uses Next's standalone output — a minimal self-contained
server, not the full node_modules — and runs as a non-root user. Note
that NEXT_PUBLIC_* vars are baked in at build time, not read at
container startup — rebuild the image after changing any of them, an
--env-file at docker run alone won't pick up new values.
Either way, /embed/* is deliberately exempt from the X-Frame-Options
header the app sets everywhere else (see src/middleware.ts) — that
route exists specifically to be iframed on NGOs' own sites. See
docs/EMBED.md for the full integration guide (sizing,
security headers, and WordPress/Webflow/plain-HTML examples).
The platform admin panel (/platform-admin) has no separate login — it
authenticates by having the connected wallet sign a message per request,
rather than by holding a session cookie or API key.
For each admin request, adminFetch in
src/lib/adminApi.ts signs the string
${method}:${path}:${timestamp} via signMessage (from
src/components/wallet/WalletProvider.tsx,
which wraps StellarWalletsKit.signMessage) and sends the address,
signature and timestamp as the x-admin-address, x-admin-signature and
x-admin-timestamp headers. The backend's requireAdminSignature verifies
the signature was produced by the address configured as ADMIN_ADDRESS and
that the timestamp is within its clock-skew window, rejecting anything else
with a 401 — there's no separate allowlist or role table on the frontend
side to keep in sync.
Signing prompts the wallet extension, so adminApi.ts caches a signature
per address:method:path for a few minutes (SIGNATURE_REUSE_WINDOW_MS)
and reuses it across requests instead of prompting on every page visit. A
reused signature that gets rejected (e.g. the server clock has moved past
the reuse window) triggers exactly one retry with a freshly signed message.
Because authorization is entirely signature-based, only the wallet holding
the private key for ADMIN_ADDRESS can act on /platform-admin — there is
no separate admin account or password to provision or rotate.
Port already in use
npm run dev binds to 3001 (3000 is reserved for streamgive-backend).
If 3001 is also taken, stop whatever's holding it or pass a different
port: npm run dev -- -p 3002.
Wallet won't connect
- Make sure a Stellar wallet extension (e.g. Freighter) is installed and unlocked in the browser you're testing with.
- The wallet must be set to the same network the app expects —
NEXT_PUBLIC_NETWORK_PASSPHRASEin.env(testnet by default). - If
connect()silently fails or hangs, check the browser console —StellarWalletsKit's auth modal surfaces most errors there rather than in the UI. - A stale session after switching wallets/accounts usually clears up with
a hard refresh; the app re-checks
getAddress()on load.
API unreachable / requests failing
NEXT_PUBLIC_API_URL(in.env, defaulthttp://localhost:3000) must point at a runningstreamgive-backendinstance — this app has no API of its own.NEXT_PUBLIC_*vars are read at build time in production (see Deployment below), so changing.envrequires a dev-server restart (or a rebuild, in Docker) to take effect.- A CORS error in the console usually means the backend isn't configured to allow this app's origin — that's a backend-side fix, not frontend.
Contract calls failing (donations, withdrawals, NGO registry)
NEXT_PUBLIC_DONATION_VAULT_CONTRACT_IDandNEXT_PUBLIC_NGO_REGISTRY_CONTRACT_IDmust be filled in fromstreamgive-contracts/deployments.jsonfor the network you're using — they're blank in.env.example.NEXT_PUBLIC_SOROBAN_RPC_URLmust point at an RPC endpoint for that same network; a mismatched network/RPC/contract-ID combination typically fails with an XDR or "contract not found" style error rather than a clear message.
Env changes not taking effect
Next.js inlines NEXT_PUBLIC_* vars at build time. Restart npm run dev
after editing .env; in Docker, rebuild the image rather than swapping
--env-file on an existing image.
Impact page feels slow / makes a lot of requests
The /impact page polls every 20 seconds instead of receiving live updates
(there's no websocket/SSE push from the backend), and each poll is an N+1
fetch — it lists every verified NGO, then fetches each NGO's profile
individually and sums the totals client-side, because the backend has no
platform-wide aggregate endpoint. That's 1 + N requests per poll, where
N is the NGO count. This is known tech debt; see the comment above
POLL_INTERVAL_MS in src/app/impact/page.tsx and the docblock on
loadPlatformImpact in src/lib/impact.ts for details, and fix candidates
if you're picking this up (a real backend aggregate endpoint, or at least a
longer interval / backoff).
- streamgive-contracts — Soroban smart contracts
- streamgive-backend — indexer & API
- streamgive-docs — documentation
This table lists every route under src/app, who it is meant for, and its technical requirements.
| Route | Audience | Wallet / Admin Required? | Component Type |
|---|---|---|---|
/ |
Public | No | Server Component |
/apply |
NGO Applicant | Yes (Wallet) | Client Component |
/dashboard |
Donor | Yes (Wallet) | Client Component |
/embed/[ngoId] |
Embed Consumer (iframe) | No | Server Component |
/impact |
Public / Donor / NGO | No | Client Component |
/ngo-admin |
NGO Admin | Yes (Wallet) | Client Component |
/ngos |
Public | No | Server Component |
/ngos/[id] |
Public | No | Server Component |
/ngos/[id]/donate |
Donor | No (Form requires Wallet) | Server Component |
/platform-admin |
Platform Admin | Yes (Wallet + ADMIN_ADDRESS) |
Client Component |
See the Embed Widget Guide for details on embedding the /embed/[ngoId] widget.
See CONTRIBUTING.md for the accessibility checklist to run through before adding new interactive UI.
Early development.
Apache-2.0 — see LICENSE.