diff --git a/.env.local.example b/.env.local.example index 4b27096..23b95f6 100644 --- a/.env.local.example +++ b/.env.local.example @@ -4,6 +4,9 @@ # # `.env.local` is git-ignored, so your local values never get committed. +# Public site origin used for canonical, Open Graph, sitemap, and share URLs. +NEXT_PUBLIC_SITE_URL=https://flowstar.app + # ─── Stellar contract ───────────────────────────────────────────────────────── # Deployed streaming contract addresses on Stellar. diff --git a/__tests__/components/streams/qr-share-dialog.test.tsx b/__tests__/components/streams/qr-share-dialog.test.tsx index 4899512..96b8391 100644 --- a/__tests__/components/streams/qr-share-dialog.test.tsx +++ b/__tests__/components/streams/qr-share-dialog.test.tsx @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { fireEvent, render, screen } from '@testing-library/react' import { QrShareDialog } from '@/components/streams/qr-share-dialog' +import { SITE_URL } from '@/lib/site' import type { StreamData } from '@/types/stream' const { mockToastSuccess, mockWriteText } = vi.hoisted(() => ({ @@ -23,7 +24,7 @@ vi.mock('qrcode.react', () => ({ ), })) -const STREAM_URL = 'https://flowstar.app/stream/stream-123' +const STREAM_URL = `${SITE_URL}/stream/stream-123` function makeStream(overrides?: Partial): StreamData { return { diff --git a/app/app/stream/[id]/metadata.ts b/app/app/stream/[id]/metadata.ts index 9c4062f..e3587d9 100644 --- a/app/app/stream/[id]/metadata.ts +++ b/app/app/stream/[id]/metadata.ts @@ -3,6 +3,7 @@ import { fetchStream } from "@/lib/contract"; import { NETWORK } from "@/lib/stellar"; import { shortenAddress } from "@/lib/stream-utils"; import { captureError } from "@/lib/sentry"; +import { SITE_URL } from "@/lib/site"; export async function generateMetadata({ params, @@ -20,7 +21,7 @@ export async function generateMetadata({ }; } - const ogImageUrl = new URL("/api/og", "https://flowstar.app"); + const ogImageUrl = new URL("/api/og", SITE_URL); ogImageUrl.searchParams.append( "amount", ( @@ -45,7 +46,7 @@ export async function generateMetadata({ return { title: `Stream #${stream.id}`, description, - metadataBase: new URL("https://flowstar.app"), + metadataBase: new URL(SITE_URL), openGraph: { type: "website", siteName: "FlowStar", diff --git a/app/app/stream/[id]/page.tsx b/app/app/stream/[id]/page.tsx index a02edd2..14a22fe 100644 --- a/app/app/stream/[id]/page.tsx +++ b/app/app/stream/[id]/page.tsx @@ -22,6 +22,7 @@ import { } from 'lucide-react' import { toast } from 'sonner' import { ConnectWalletButton } from '@/components/layout/connect-wallet-button' +import { SITE_URL } from '@/lib/site' import { StreamStatusBadge } from '@/components/streams/stream-status-badge' import { ProgressBar } from '@/components/ui/progress-bar' import { TokenAmount } from '@/components/ui/token-amount' @@ -795,7 +796,7 @@ function ShareButtons({ const streamUrl = typeof window !== 'undefined' ? window.location.href - : `https://flowstar.app/app/stream/${streamId}` + : `${SITE_URL}/app/stream/${streamId}` const shareText = copy.shareButtons.shareText(streamId) function copyLink() { diff --git a/app/layout.tsx b/app/layout.tsx index fb365b7..2bf32a4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,6 +7,7 @@ import { WalletProvider } from '@/components/providers/wallet-provider' import { Toaster } from '@/components/ui/sonner' import { ServiceWorkerRegister } from '@/components/pwa/service-worker-register' import { InstallPrompt } from '@/components/pwa/install-prompt' +import { SITE_URL } from '@/lib/site' import './globals.css' // JSON-LD structured data @@ -15,7 +16,7 @@ const jsonLd = { '@type': 'WebApplication', name: 'FlowStar', description: 'Stream tokens by the second with cliffs and cancellations on Stellar Soroban', - url: 'https://flowstar.app', + url: SITE_URL, applicationCategory: 'FinanceApplication', operatingSystem: 'Web', offers: { @@ -43,7 +44,7 @@ export const metadata: Metadata = { description: 'Stream tokens by the second with cliffs and cancellations on Stellar Soroban. Create vesting schedules, payroll, and grants that unlock continuously — withdraw anytime, cancel anytime.', keywords: ['stellar', 'soroban', 'token streaming', 'DeFi', 'payment streams', 'vesting', 'payroll', 'grants'], - metadataBase: new URL('https://flowstar.app'), + metadataBase: new URL(SITE_URL), alternates: { canonical: '/', }, diff --git a/app/sitemap.ts b/app/sitemap.ts index a290683..e46254c 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,7 +1,8 @@ import { MetadataRoute } from 'next' +import { SITE_URL } from '@/lib/site' export default function sitemap(): MetadataRoute.Sitemap { - const baseUrl = 'https://flowstar.app' + const baseUrl = SITE_URL return [ { diff --git a/components/streams/qr-share-dialog.stories.tsx b/components/streams/qr-share-dialog.stories.tsx index 4464b82..741d334 100644 --- a/components/streams/qr-share-dialog.stories.tsx +++ b/components/streams/qr-share-dialog.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react' import { useState } from 'react' import { Toaster } from 'sonner' import { QrShareDialog } from './qr-share-dialog' +import { SITE_URL } from '@/lib/site' import { Button } from '@/components/ui/button' import type { StreamData } from '@/types/stream' @@ -56,7 +57,7 @@ export const OpenWithSampleStream: Story = { args: { open: true, onOpenChange: () => {}, - streamUrl: 'https://flowstar.app/app/streams/42', + streamUrl: `${SITE_URL}/app/streams/42`, stream: sampleStream, status: 'streaming', }, diff --git a/components/streams/stream-card.tsx b/components/streams/stream-card.tsx index 7b52dc0..df1ffaa 100644 --- a/components/streams/stream-card.tsx +++ b/components/streams/stream-card.tsx @@ -30,6 +30,7 @@ import { } from '@/components/ui/dropdown-menu' import { getFederationNameForAddress } from '@/lib/address-book' import { streamCardCopy as copy } from '@/lib/copy/streams' +import { SITE_URL } from '@/lib/site' import type { StreamData } from '@/types/stream' // Pick update interval based on a quick pre-check of stream state. @@ -146,7 +147,7 @@ function StreamCardInner({ const url = typeof window !== 'undefined' ? `${window.location.origin}/app/stream/${stream.id}` - : `https://flowstar.app/app/stream/${stream.id}` + : `${SITE_URL}/app/stream/${stream.id}` navigator.clipboard.writeText(url) setCopiedLink(true) setTimeout(() => setCopiedLink(false), 1500) diff --git a/eslint.config.mjs b/eslint.config.mjs index 74767a6..8f31374 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -34,4 +34,11 @@ export default [ }, }, }, + { + files: ['scripts/**/*.mjs'], + rules: { + 'no-console': 'off', + 'security/detect-non-literal-fs-filename': 'off', + }, + }, ] diff --git a/lib/site.ts b/lib/site.ts new file mode 100644 index 0000000..1f7e487 --- /dev/null +++ b/lib/site.ts @@ -0,0 +1 @@ +export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://flowstar.app' \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 2933a93..bde4017 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -12,6 +12,8 @@ async function withBundleAnalyzer(config) { const { default: analyzer } = await import('@next/bundle-analyzer') return analyzer({ enabled: true })(config) } catch { + // This warning explains why optional bundle analysis was skipped. + // eslint-disable-next-line no-console console.warn('[FlowStar] @next/bundle-analyzer not installed — skipping analysis') return config } diff --git a/public/placeholder-logo.png b/public/placeholder-logo.png deleted file mode 100644 index 8a792ac..0000000 Binary files a/public/placeholder-logo.png and /dev/null differ diff --git a/public/placeholder-logo.svg b/public/placeholder-logo.svg deleted file mode 100644 index b1695aa..0000000 --- a/public/placeholder-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg deleted file mode 100644 index 6fa7543..0000000 Binary files a/public/placeholder-user.jpg and /dev/null differ diff --git a/public/placeholder.jpg b/public/placeholder.jpg deleted file mode 100644 index 6bfe963..0000000 Binary files a/public/placeholder.jpg and /dev/null differ diff --git a/public/placeholder.svg b/public/placeholder.svg deleted file mode 100644 index e763910..0000000 --- a/public/placeholder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file