From da33a2a6b4a8a2b8262d6725cb0793813810d137 Mon Sep 17 00:00:00 2001 From: darcszn Date: Mon, 19 Oct 2026 15:58:36 +0100 Subject: [PATCH] fix: address issues #1040-#1042 --- .env.local.example | 3 +++ .../components/streams/qr-share-dialog.test.tsx | 3 ++- app/app/stream/[id]/metadata.ts | 5 +++-- app/app/stream/[id]/page.tsx | 3 ++- app/layout.tsx | 5 +++-- app/sitemap.ts | 3 ++- components/streams/qr-share-dialog.stories.tsx | 3 ++- components/streams/stream-card.tsx | 3 ++- eslint.config.mjs | 7 +++++++ lib/site.ts | 1 + next.config.mjs | 2 ++ public/placeholder-logo.png | Bin 568 -> 0 bytes public/placeholder-logo.svg | 1 - public/placeholder-user.jpg | Bin 1635 -> 0 bytes public/placeholder.jpg | Bin 1064 -> 0 bytes public/placeholder.svg | 1 - 16 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 lib/site.ts delete mode 100644 public/placeholder-logo.png delete mode 100644 public/placeholder-logo.svg delete mode 100644 public/placeholder-user.jpg delete mode 100644 public/placeholder.jpg delete mode 100644 public/placeholder.svg 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 8a792ac2ddfbe047639f7907c82f14c73e3de3d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 568 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5Ca^FANm1Vwi-43tfKP}kNR$*1WiP`GRL@@$ z}KL6V^XjP|gFiXN9MWV@SoVw>S2B83zimJup|_^-FbrjeV3it>ba2M!&I{-9Oz_xPcYF3oFZ=eo>b ztd*bf%*2|<`prCX7bFlbJSYChhATpnZHm_gDmxOE$M3Ov^QCh;k9GZ#-G|g`wsZ1y zY-hCA6Y*4%kP@8PzH+9qgqG>UuX7G~Jv3yxb4X!L$WkVer)+Lao)20%*YwyPoNauk zS!$0_-Kr#3+3w{V4Ccu^47H4DY>jP zOx?nl+kWZD?x4qFw+?-2Jz{$0yTXmVj$U6an>Gc>YVI}tW1#2eCg2#H$RT;W`9jE4 z<(d!P-_Kw0oV0yf*RuH&47YRnuIuc%FZkt*pVIL&DpPv8{ \ No newline at end of file diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg deleted file mode 100644 index 6fa7543d38ed8fdb38f03b5c0f40ad2d66827d4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1635 zcmex=cRarq{jAqGJXh7$}Ym>C5bm;@P_1sVSzVUPwol9>?*7~p`Fjh%&&i5Vyo`r#xP0%qgXfnHy zn7E>$Lug{-LIb0a!ifhj3W%7RB^MNLx}>0_tP&7BY4IaqjBo(siP4_n_ky5FEmL(` zmhdc@Jb`OxUUPQK!#sq(C`ZJd3YCA-U zh)gnS;tOzhJ@#Yf$SRGl2DVF#R;SL@d2ijhdh5m?{S3~XPpnp*$)2k2D4JJuYTMD;n0K36=Y8OJ z-mS1ZZC1 z49BdqoLoM_v$9v)Hm?5W<`Stp{n4{)u7wjb{~fvi z!T;47&))dGMoGtVj(5+GENxwrz4z{INB>V}1-4JUxp@DgJ8LKAUAQWhoR~X5%Wp!~ z=~v%IB`XWv>j>XTei(%h-3q0fm1bY5udCHKmu^f&G@&$==-St!-gGk?J$~inXs)aj#q07WR4- zM=rxkFUy8C!QJtD_bgKux*|B+dhW6{-z2Xe?@~0Ks(ijMro-3a&Mj#N$xpGBRnmWt zTsUxz-_bhtPjF={K-%Z^57b<}UkWR&Lo)qWhnrV2yD?(SL?Q-l9d; zHS6^j9!_Ejaqqemoyn}=@ptvvS-S7I7N7FZ5}(WNv3l;#-@XTLOmcJGRz1a7FhcBC z-nPY(UsETGE4&Rdme=pC+jj2j;!j+6o-!m}7oD?jz2dGcAvK4%qSmfE=U+z7Z+`#j z$Mx4a@1yJ$R+zt@y0z(yW_ISH^4k|<^!Al@?-M=r`gz3ehXgqVyM1>}k-Q}GF<-;) z*LLGyVd)Qk|Ji$(J?dTY-07-((_=n_`3T&d5FiH|guzja;wny>q}tw+C34*a{8 zfJ9i0DbumfeTVp1E#|(wudjdSJE%AnL@H&$nSBB{^D;34Gp|fN9u>e68m!_b0O__- AD*ylh diff --git a/public/placeholder.jpg b/public/placeholder.jpg deleted file mode 100644 index 6bfe96336dbe567f5f1e9129f5bccdc78b114bba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1064 zcmb7@Nla5w6o$`zL;Ko7c&|)~7+MC)5J;K9giwZ-mWToZDl29|7tA;;v?z!g5*b9% zRSP(-1ZOwO;J6Z;05{Ybq6y%HZtA^{7Ghj^Zu0K`Ud}o9eCM5OpR~^apJ^}|03iS) zUSPLDyMAv~1wd97gaQBuEK&lE6~YUU53r&J$VBT_bS?^Z_)q|CRlqoW3h_=1XC~2( zowlIWj23Fe8(HX8O~UM7AR)W5x}mzhvbshU9ifGUR8tm1vSSl)WSJzz{QQ zmi2yKNVU))a_#4eg7^#He zB=v{_T40oFHqzanm!#xGc@)^&2;D-M({g?EZs?j~+jH z`uxSqSCdoIGw \ No newline at end of file