Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,18 @@ export default function DashboardPage() {
)}

{address && !loading && loadError && streams.length === 0 && (
<p className="mt-8 text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API. Is the backend running?
</p>
<div className="mt-8">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t load your streams. Please check your connection and try again.
</p>
<button
type="button"
onClick={refresh}
className="mt-3 rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</button>
</div>
)}

{address && !loading && !loadError && streams.length === 0 && (
Expand Down
2 changes: 1 addition & 1 deletion src/app/embed/[ngoId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default async function EmbedDonatePage({ params }: Props) {
return (
<EmbedAutoResizer>
<main id="main" className="p-6 text-center text-sm text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API.
Couldn&apos;t load the donation form. Please try refreshing the page.
</main>
</EmbedAutoResizer>
);
Expand Down
9 changes: 9 additions & 0 deletions src/app/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 15 additions & 4 deletions src/app/impact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const POLL_INTERVAL_MS = 20_000;
export default function ImpactPage() {
const [impact, setImpact] = useState<PlatformImpact | null>(null);
const [loadError, setLoadError] = useState(false);
// Incrementing this triggers the effect to remount and retry immediately.
const [retryCount, setRetryCount] = useState(0);

useEffect(() => {
// Guards every setState below against a tick that is still in flight
Expand Down Expand Up @@ -63,7 +65,7 @@ export default function ImpactPage() {
clearInterval(interval);
activeController.abort();
};
}, []);
}, [retryCount]);

return (
<>
Expand All @@ -75,9 +77,18 @@ export default function ImpactPage() {
</p>

{loadError && !impact && (
<p className="mt-8 text-red-600">
Couldn&apos;t reach the StreamGive API. Is the backend running?
</p>
<div className="mt-8">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t load platform stats. Please check your connection and try again.
</p>
<button
type="button"
onClick={() => { setLoadError(false); setRetryCount((c) => c + 1); }}
className="mt-3 rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</button>
</div>
)}

{/* Keep the last good numbers on screen when a later poll fails;
Expand Down
16 changes: 15 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const geistMono = Geist_Mono({
const DESCRIPTION = 'Recurring, streaming donations for verified NGOs on Stellar.';

export const metadata: Metadata = {
// metadataBase is required for Next.js to resolve relative URLs used in
// openGraph.images and twitter.images into fully-qualified URLs for crawlers.
// Falls back to the local dev origin; set NEXT_PUBLIC_APP_URL in production.
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3001'),
title: {
default: 'StreamGive',
// Child routes set just their own segment (e.g. "Explore NGOs") and
Expand All @@ -27,15 +31,25 @@ export const metadata: Metadata = {
template: '%s — StreamGive',
},
description: DESCRIPTION,
// Browser toolbar / PWA chrome colour (matches the manifest and the logo).
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#0d9488' },
{ media: '(prefers-color-scheme: dark)', color: '#0f172a' },
],
openGraph: {
title: 'StreamGive',
description: DESCRIPTION,
type: 'website',
siteName: 'StreamGive',
// /opengraph-image is generated by src/app/opengraph-image.tsx.
images: [{ url: '/opengraph-image', width: 1200, height: 630, alt: 'StreamGive' }],
},
twitter: {
card: 'summary',
// summary_large_image renders the OG image as a large card, not a thumbnail.
card: 'summary_large_image',
title: 'StreamGive',
description: DESCRIPTION,
images: ['/opengraph-image'],
},
};

Expand Down
32 changes: 32 additions & 0 deletions src/app/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { MetadataRoute } from 'next';

/**
* Web app manifest — served at /manifest.webmanifest.
* Enables "Add to Home Screen" on mobile and provides browser-level theming.
* Next.js wires up the <link rel="manifest"> automatically when this file exists.
*/
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'StreamGive',
short_name: 'StreamGive',
description: 'Recurring, streaming donations for verified NGOs on Stellar.',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#0d9488',
icons: [
{
src: '/icon.svg',
sizes: 'any',
type: 'image/svg+xml',
purpose: 'any',
},
{
src: '/icon.svg',
sizes: 'any',
type: 'image/svg+xml',
purpose: 'maskable',
},
],
};
}
15 changes: 12 additions & 3 deletions src/app/ngo-admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,18 @@ export default function NgoAdminPage() {
)}

{address && !loading && loadError && (
<p className="mt-8 text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API. Is the backend running?
</p>
<div className="mt-8">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t load your NGO data. Please check your connection and try again.
</p>
<button
type="button"
onClick={() => void refresh()}
className="mt-3 rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</button>
</div>
)}

{address && !loading && !loadError && ngo === null && (
Expand Down
9 changes: 8 additions & 1 deletion src/app/ngos/[id]/donate/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { notFound } from 'next/navigation';

import { CreateStreamForm } from '@/components/donate/CreateStreamForm';
Expand Down Expand Up @@ -26,8 +27,14 @@ export default async function DonatePage({ params }: Props) {
<Header />
<main id="main" className="px-6 py-16 sm:px-12">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API. Is the backend running?
Couldn&apos;t load this NGO&apos;s details. Please check your connection and try again.
</p>
<Link
href={`/ngos/${id}/donate`}
className="mt-3 inline-block rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</Link>
</main>
<Footer />
</>
Expand Down
8 changes: 7 additions & 1 deletion src/app/ngos/[id]/donate/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ export default async function DonateSuccessPage({ params, searchParams }: Props)
<Header />
<main id="main" className="px-6 py-16 sm:px-12">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API. Is the backend running?
Couldn&apos;t load this page. Please check your connection and try again.
</p>
<Link
href={`/ngos/${id}/donate/success${streamId ? `?streamId=${streamId}` : ''}`}
className="mt-3 inline-block rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</Link>
</main>
<Footer />
</>
Expand Down
8 changes: 7 additions & 1 deletion src/app/ngos/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,14 @@ export default async function NgoProfilePage({ params }: Props) {
<Header />
<main id="main" className="px-6 py-16 sm:px-12">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API. Is the backend running?
Couldn&apos;t load this NGO&apos;s profile. Please check your connection and try again.
</p>
<Link
href={`/ngos/${id}`}
className="mt-3 inline-block rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</Link>
</main>
<Footer />
</>
Expand Down
15 changes: 12 additions & 3 deletions src/app/ngos/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { Suspense } from 'react';

import { Footer } from '@/components/layout/Footer';
Expand Down Expand Up @@ -42,9 +43,17 @@ export default async function NgosPage() {
<h1 className="text-2xl font-bold">Explore NGOs</h1>

{loadError && (
<p className="mt-4 text-red-600 dark:text-red-400">
Couldn&apos;t reach the StreamGive API. Is the backend running?
</p>
<div className="mt-4">
<p className="text-red-600 dark:text-red-400">
Couldn&apos;t load the NGO list. Please check your connection and try again.
</p>
<Link
href="/ngos"
className="mt-3 inline-block rounded-md border border-gray-300 px-4 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
>
Retry
</Link>
</div>
)}

{!loadError && (
Expand Down
85 changes: 85 additions & 0 deletions src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ImageResponse } from 'next/og';

export const runtime = 'edge';
export const alt = 'StreamGive — Recurring streaming donations for verified NGOs on Stellar';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

/**
* Default Open Graph / Twitter social-share image.
* Generated at request time via the Edge runtime — no static PNG to commit.
* Next.js serves it at /opengraph-image and wires it into <head> automatically
* when `metadataBase` is set in the root layout.
*/
export default function OgImage() {
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
background: 'linear-gradient(135deg, #0f172a 0%, #134e4a 100%)',
fontFamily: 'system-ui, sans-serif',
padding: '60px',
}}
>
{/* Logo mark */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 80,
height: 80,
borderRadius: 20,
background: '#0d9488',
marginBottom: 32,
}}
>
<svg width="48" height="48" viewBox="0 0 32 32" fill="none">
<path
d="M4 20 Q7 12 11 16 Q15 20 19 12 Q23 4 24 8"
stroke="white"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<circle cx="4" cy="20" r="1.5" fill="white" />
</svg>
</div>

{/* Wordmark */}
<div
style={{
fontSize: 72,
fontWeight: 700,
color: 'white',
letterSpacing: '-2px',
lineHeight: 1,
marginBottom: 20,
}}
>
StreamGive
</div>

{/* Tagline */}
<div
style={{
fontSize: 28,
color: '#99f6e4',
textAlign: 'center',
maxWidth: 700,
lineHeight: 1.4,
}}
>
Recurring, streaming donations for verified NGOs on Stellar.
</div>
</div>
),
{ ...size },
);
}
7 changes: 4 additions & 3 deletions src/components/dashboard/StreamControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useState } from 'react';
import { useToast } from '@/components/toast/ToastProvider';
import { useWallet } from '@/components/wallet/WalletProvider';
import { getStreams, type Stream } from '@/lib/api';
import { describeContractError } from '@/lib/contractTypes';
import { useDonationVaultClient } from '@/lib/donationVaultClient';
import { describeAmountError, formatEstimatedFee, parseAmount, TOKEN_DECIMALS } from '@/lib/format';
import { txToastAction } from '@/lib/stellar';
Expand Down Expand Up @@ -88,7 +89,7 @@ export function StreamControls({
onOptimisticUpdate?.({ balance: (BigInt(stream.balance) + topUpAmountRaw).toString() });
onChanged();
} catch (err) {
showToast('error', err instanceof Error ? err.message : 'Something went wrong.');
showToast('error', describeContractError(err));
} finally {
setPending(null);
setEstimatedFee(null);
Expand All @@ -107,7 +108,7 @@ export function StreamControls({
onOptimisticUpdate?.({ status: 'CANCELLED' });
onChanged();
} catch (err) {
showToast('error', err instanceof Error ? err.message : 'Something went wrong.');
showToast('error', describeContractError(err));
} finally {
setPending(null);
setEstimatedFee(null);
Expand Down Expand Up @@ -162,7 +163,7 @@ export function StreamControls({
showToast('success', `Rate updated — ${INDEXING_LAG_NOTE}.`, txToastAction(sent, tx));
onChanged();
} catch (err) {
showToast('error', err instanceof Error ? err.message : 'Something went wrong.');
showToast('error', describeContractError(err));
} finally {
setPending(null);
setEstimatedFee(null);
Expand Down
3 changes: 2 additions & 1 deletion src/components/donate/CreateStreamForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getUsdcAssetAddress,
txToastAction,
} from '@/lib/stellar';
import { describeContractError } from '@/lib/contractTypes';

const DURATIONS = [
{ label: '1 week', seconds: 7 * 24 * 60 * 60 },
Expand Down Expand Up @@ -281,7 +282,7 @@ export function CreateStreamForm({
router.push(`/ngos/${ngoId}/donate/success?streamId=${newStreamId}`);
}
} catch (err) {
setErrorMessage(err instanceof Error ? err.message : 'Something went wrong.');
setErrorMessage(describeContractError(err));
setSubmitState('error');
} finally {
setEstimatedFee(null);
Expand Down
Loading
Loading