Feat/marketing landing - #95
Conversation
Expo resolves @sentry/react-native/expo relative to the frontend package; it was only on the root workspace so Vercel installs did not expose the plugin and expo export failed. Made-with: Cursor
- Add web-only landing at / with value props, CTAs, and App Store compliance - Desktop: QR code and mailto draft for the download link - Narrow web: official App Store badge linking to APP_STORE_URL - Rename (tabs)/index to home so / is the landing without route conflicts - Point in-app "home" navigation and post-auth default to /home - Register root index in the stack; dedupe @sentry/react-native in package.json - Refresh lockfile so workspace resolves expo-blur for typecheck/bundling Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 35 minutes and 19 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (26)
📝 WalkthroughWalkthroughIntroduces a web-only landing page and moves root routing targets from Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
|
@dfed25 this has button to sign in/sign up, but we want to not allow anyone to sign in or signup on web. We want to actually not allow people to visit that route on web. |
- Add auth/login.web.tsx so /auth/login always redirects to / on web - Remove Sign in header link and Sign up button from LandingScreen - Clarify copy: sign-in is app-only; web is browse-focused Made-with: Cursor
Resolve login.tsx: keep dev successRedirect sync for post-onboarding redirect, default postAuthRedirect to /home (marketing feed route). Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
frontend/app/index.tsx (1)
5-11: Consider redirecting authenticated web users to/home.Currently, any visit to
/on web renders the marketingLandingScreen, even for signed-in users who have previously used the browse experience. If that's the intent (web = marketing landing regardless of auth state), this is fine. Otherwise, consider reading the auth/session and redirecting authenticated web visitors to/homeso they aren't re-shown the marketing page on every root visit.Please confirm the desired behavior. If authenticated web users should skip the landing, something like the following would work:
Optional redirect for authenticated web users
-import { Redirect } from 'expo-router'; -import { Platform } from 'react-native'; -import LandingScreen from '../components/LandingScreen'; - -export default function IndexRoute() { - if (Platform.OS !== 'web') { - return <Redirect href="/home" />; - } - - return <LandingScreen />; -} +import { Redirect } from 'expo-router'; +import { Platform } from 'react-native'; +import LandingScreen from '../components/LandingScreen'; +import { useAuth } from '../hooks/useAuth'; + +export default function IndexRoute() { + const { isAuthenticated } = useAuth(); + if (Platform.OS !== 'web') { + return <Redirect href="/home" />; + } + if (isAuthenticated) { + return <Redirect href="/home" />; + } + return <LandingScreen />; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/index.tsx` around lines 5 - 11, IndexRoute currently always returns LandingScreen on web; modify it to check the user's authentication/session state and redirect authenticated web users to "/home" instead of rendering LandingScreen. Specifically, inside IndexRoute (the Platform.OS !== 'web' branch can remain), call your existing auth/session accessor (e.g., currentUser(), useSession(), getAuthState()) and, if it indicates an authenticated user on web, return <Redirect href="/home" />; otherwise continue to render <LandingScreen />; ensure you use the same Redirect and LandingScreen symbols shown so behavior is consistent across platforms.frontend/components/LandingScreen.tsx (3)
71-74: Consider an anchor to/homein the top bar for parity with the tabs layout.The tabs
WebHeaderLayoutmakes the "PolyBuys" brand aLinkto/home, but here the same brand text is a plain<Text>with no action. For consistency and to give web visitors an easy "back to marketplace" affordance from the landing (e.g., after scrolling), consider wrapping it in aLink href="/home"pressable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/LandingScreen.tsx` around lines 71 - 74, Replace the plain brand Text in LandingScreen.tsx with a pressable Link to "/home" so the top bar matches WebHeaderLayout behavior: wrap or replace the Text with a Link (e.g., Link href="/home") preserving the styles from styles.brandMark and keeping it inside the same SafeAreaView/View topology (topBar -> brandMark). Ensure the Link is keyboard accessible and retains the existing style and layout so clicking the brand navigates back to the marketplace.
144-156: App Store badge tap may leave a stranded web tab.On mobile web (non-desktop), tapping the App Store badge calls
Linking.openURL(APP_STORE_URL). On iOS Safari this generally navigates the current tab; on some browsers it will open a new tab. Consider using an<a href={APP_STORE_URL} target="_blank" rel="noopener noreferrer">via theLink/anchor pattern on web so clicks get native browser semantics (new tab, middle-click, right-click → copy, proper SEO), and fall back toLinking.openURLonly for native. This also improves accessibility for assistive tech that treats anchors differently from pressables.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/LandingScreen.tsx` around lines 144 - 156, The Pressable currently always calls Linking.openURL(APP_STORE_URL) which on web can navigate the current tab; update LandingScreen.tsx to render a web anchor for web builds and keep the Pressable for native: detect web (Platform.OS === 'web' or react-native-web check) and when on web output an <a href={APP_STORE_URL} target="_blank" rel="noopener noreferrer" aria-label="Download on the App Store"> wrapping the <img> (use APP_STORE_BADGE_URI and styles.appStoreBadge) to preserve native browser semantics, accessibility and SEO, and for non-web platforms keep the existing Pressable that calls Linking.openURL(APP_STORE_URL) with the same accessibilityRole/Label and styles.
22-29: Third-party QR service: reliability and privacy trade-off.Fetching the QR code from
api.qrserver.commeans:
- Every desktop-web visitor's IP and User-Agent are exposed to a third party.
- If that service is down/rate-limited/discontinued, the QR silently breaks.
- It adds an external dependency with no integrity check (no CSP/SRI on images).
Consider generating the QR locally (e.g.,
qrcodepackage → data URL at build time, since the URL is static) or hosting a pre-generated PNG as a static asset. The target URL isAPP_STORE_URL, which is a compile-time constant, so a static asset is the simplest fix.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/LandingScreen.tsx` around lines 22 - 29, The QR image currently comes from an external service via the qrCodeImageUri function which exposes visitor metadata and is fragile; change it to use a locally-generated asset or a build-time data URL instead: either pre-generate a PNG for APP_STORE_URL and serve it from your static assets (update qrCodeImageUri to return that asset path) or generate a data-URL at build time using the "qrcode" package and have qrCodeImageUri return that constant data URL (ensure APP_STORE_URL is used as the source), removing any runtime fetch to api.qrserver.com and adjusting types where qrCodeImageUri is referenced in the component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/app/`(tabs)/home.tsx:
- Around line 201-205: The handleToggleSave callback currently redirects
unauthenticated users to /auth/login via router.replace even on web; update
handleToggleSave to check isWeb when !isAuthenticated and, if isWeb, do not call
router.replace but instead surface an app-only message (e.g., trigger the
existing app-only toast/modal or set a local state to show the UI hint) and
return; only call router.replace('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/auth/login?...') for non-web platforms.
Reference handleToggleSave, isAuthenticated, isWeb and router.replace when
making the change.
In `@frontend/components/LandingScreen.tsx`:
- Line 15: Landing page currently imports APP_STORE_URL (used in
LandingScreen.tsx for QR, email link, and App Store badge) which is a
placeholder value in frontend/constants/app.ts
('https://polybuys.com/download'); before shipping, either replace APP_STORE_URL
with the official App Store deep/link or confirm that
https://polybuys.com/download exists and reliably redirects to the App Store (or
serves a valid interstitial). Update the constant in frontend/constants/app.ts
to the canonical App Store URL (or an approved redirecting/interstitial URL),
then verify LandingScreen.tsx (references to APP_STORE_URL) shows the correct
link in the QR, the "Email the download link" flow, and the badge; if you cannot
supply a verified canonical URL, block the PR and leave APP_STORE_URL unchanged
until a valid URL is provided.
---
Nitpick comments:
In `@frontend/app/index.tsx`:
- Around line 5-11: IndexRoute currently always returns LandingScreen on web;
modify it to check the user's authentication/session state and redirect
authenticated web users to "/home" instead of rendering LandingScreen.
Specifically, inside IndexRoute (the Platform.OS !== 'web' branch can remain),
call your existing auth/session accessor (e.g., currentUser(), useSession(),
getAuthState()) and, if it indicates an authenticated user on web, return
<Redirect href="/home" />; otherwise continue to render <LandingScreen />;
ensure you use the same Redirect and LandingScreen symbols shown so behavior is
consistent across platforms.
In `@frontend/components/LandingScreen.tsx`:
- Around line 71-74: Replace the plain brand Text in LandingScreen.tsx with a
pressable Link to "/home" so the top bar matches WebHeaderLayout behavior: wrap
or replace the Text with a Link (e.g., Link href="/home") preserving the styles
from styles.brandMark and keeping it inside the same SafeAreaView/View topology
(topBar -> brandMark). Ensure the Link is keyboard accessible and retains the
existing style and layout so clicking the brand navigates back to the
marketplace.
- Around line 144-156: The Pressable currently always calls
Linking.openURL(APP_STORE_URL) which on web can navigate the current tab; update
LandingScreen.tsx to render a web anchor for web builds and keep the Pressable
for native: detect web (Platform.OS === 'web' or react-native-web check) and
when on web output an <a href={APP_STORE_URL} target="_blank" rel="noopener
noreferrer" aria-label="Download on the App Store"> wrapping the <img> (use
APP_STORE_BADGE_URI and styles.appStoreBadge) to preserve native browser
semantics, accessibility and SEO, and for non-web platforms keep the existing
Pressable that calls Linking.openURL(APP_STORE_URL) with the same
accessibilityRole/Label and styles.
- Around line 22-29: The QR image currently comes from an external service via
the qrCodeImageUri function which exposes visitor metadata and is fragile;
change it to use a locally-generated asset or a build-time data URL instead:
either pre-generate a PNG for APP_STORE_URL and serve it from your static assets
(update qrCodeImageUri to return that asset path) or generate a data-URL at
build time using the "qrcode" package and have qrCodeImageUri return that
constant data URL (ensure APP_STORE_URL is used as the source), removing any
runtime fetch to api.qrserver.com and adjusting types where qrCodeImageUri is
referenced in the component.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ea7dcc04-067a-409a-a6a7-201df4ab6f1e
📒 Files selected for processing (15)
frontend/app/(tabs)/_layout.tsxfrontend/app/(tabs)/home.tsxfrontend/app/(tabs)/inbox.tsxfrontend/app/(tabs)/my-listings.tsxfrontend/app/(tabs)/settings.tsxfrontend/app/_layout.tsxfrontend/app/account-settings.tsxfrontend/app/auth/login.tsxfrontend/app/auth/login.web.tsxfrontend/app/index.tsxfrontend/app/l/[id].tsxfrontend/app/listings/new.tsxfrontend/components/LandingScreen.tsxfrontend/components/ListingUnavailable.tsxfrontend/package.json
| useWindowDimensions, | ||
| } from 'react-native'; | ||
| import { SafeAreaView } from 'react-native-safe-area-context'; | ||
| import { APP_STORE_URL } from '../constants/app'; |
There was a problem hiding this comment.
APP_STORE_URL is still a placeholder.
Per frontend/constants/app.ts, APP_STORE_URL is defined as 'https://polybuys.com/download' with a TODO to replace it with the actual App Store URL. Shipping the marketing landing with a placeholder link means the QR, "Email the download link", and the App Store badge all point to a non-canonical URL. Please confirm polybuys.com/download exists and either redirects to the App Store or is a valid interstitial before releasing this page publicly, or block this PR on updating APP_STORE_URL.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/components/LandingScreen.tsx` at line 15, Landing page currently
imports APP_STORE_URL (used in LandingScreen.tsx for QR, email link, and App
Store badge) which is a placeholder value in frontend/constants/app.ts
('https://polybuys.com/download'); before shipping, either replace APP_STORE_URL
with the official App Store deep/link or confirm that
https://polybuys.com/download exists and reliably redirects to the App Store (or
serves a valid interstitial). Update the constant in frontend/constants/app.ts
to the canonical App Store URL (or an approved redirecting/interstitial URL),
then verify LandingScreen.tsx (references to APP_STORE_URL) shows the correct
link in the QR, the "Email the download link" flow, and the badge; if you cannot
supply a verified canonical URL, block the PR and leave APP_STORE_URL unchanged
until a valid URL is provided.
- index: redirect authenticated web users to /home; show boot spinner while auth loads - LandingScreen: brand Link to /home; static QR asset (no third-party API); App Store badge uses Link target=_blank on web - types/assets.d.ts: declare *.png for Metro image imports - constants: document APP_STORE_URL + QR regeneration - home: web save/create no longer hits blocked /auth/login; use app-only alerts Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/app/auth/login.tsx (2)
310-321:⚠️ Potential issue | 🟡 MinorWeb secondary action still navigates to
'/'."Back to home" calls
router.replace('/')while the rest of the file (and PR) standardizes on'/home'. For consistency with the new routing targets, this should also use'/home'.Also worth confirming with the reviewer feedback on the PR (evan-taylor): if web users shouldn't be able to reach
/auth/loginat all, this whole web branch may be dead code once the route-blocking lands — consider redirecting unconditionally here instead of renderingOpenInAppPrompt.🔧 Proposed fix
- onSecondaryAction={() => router.replace('/')} + onSecondaryAction={() => router.replace('/home')}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/auth/login.tsx` around lines 310 - 321, The web branch rendering OpenInAppPrompt uses router.replace('/') for the "Back to home" action; update the onSecondaryAction to call router.replace('/home') to match the new routing convention (locate the isWeb check and the OpenInAppPrompt usage and change its onSecondaryAction). Also, consider the reviewer note that /auth/login may be unreachable after route-blocking—if so, replace the entire isWeb branch with an unconditional redirect (use router.replace('/home') or router.replace(postAuthRedirect ?? '/home')) instead of rendering OpenInAppPrompt.
382-385:⚠️ Potential issue | 🟠 Major
finishAndRedirectstill targets'/'— inconsistent with the/homemigration.This PR moves root routing targets from
'/'to'/home'(and the fallback on Line 73 was updated accordingly), butfinishAndRedirecthard-codessetSuccessRedirect('/'). After a new user completes the profile + push steps, they'll be routed to'/'instead ofpostAuthRedirect/'/home', which undoes the intent of the fallback change and ignores any validreturnTo.🔧 Proposed fix
- const finishAndRedirect = () => { - setSuccessRedirect('/'); - setStep('success'); - }; + const finishAndRedirect = () => { + setSuccessRedirect(postAuthRedirect); + setStep('success'); + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/auth/login.tsx` around lines 382 - 385, finishAndRedirect currently hard-codes setSuccessRedirect('/') which contradicts the migration to '/home' and ignores any returnTo/postAuthRedirect. Update finishAndRedirect to set the success redirect to the negotiated post-auth path instead of '/', e.g. call setSuccessRedirect(postAuthRedirect ?? '/home') (or the equivalent variable that holds returnTo/postAuthRedirect) and then setStep('success'); keep the rest of the function unchanged and reference the finishAndRedirect function and setSuccessRedirect call when making the change.
🧹 Nitpick comments (4)
frontend/components/landing/helpers.ts (1)
9-9: Preferwindow.openover assigninglocation.hrefformailto:.Setting
window.location.hrefto amailto:URL can unload the current SPA in some browsers (noticeably on iOS Safari / some desktop Chrome configurations where no mail handler is registered, leaving the user on a blank page). Usingwindow.open(url, '_self')(or a transient<a>click) is the more reliable pattern and keeps history intact if the OS handoff is cancelled.♻️ Proposed change
- window.location.href = `mailto:?subject=${subject}&body=${body}`; + window.open(`mailto:?subject=${subject}&body=${body}`, '_self');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/landing/helpers.ts` at line 9, Replace the direct assignment to window.location.href for the mailto flow with a window.open call or a transient anchor click to avoid unloading the SPA; locate the line that sets window.location.href = `mailto:?subject=${subject}&body=${body}` and change it to open the same mailto URL using window.open(mailtoUrl, '_self') (or programmatically create, click, and remove an <a> element) so the mail handoff is attempted without leaving the app or creating a blank history entry.frontend/components/landing/LegalDocument.web.tsx (3)
14-14: Nit:documentprop shadows the globaldocument.The destructured
documentparameter shadows the DOMdocumentglobal inside this component. Not a bug today (nowindow.documentusage here), but it can trip up future edits (e.g., adding adocument.querySelectorcall) and confuse readers/linters. Consider renaming todocorlegalDocument.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/landing/LegalDocument.web.tsx` at line 14, The parameter name document in the LegalDocumentPage function shadows the global DOM document; rename the prop to something like legalDocument or doc across the component signature (LegalDocumentPage({ document, siblingHref, siblingLabel } -> LegalDocumentPage({ legalDocument, siblingHref, siblingLabel })) and update every usage inside the component body that referenced document to the new identifier, and then update all call sites/JSX props that pass the document prop to use the new prop name (and update the LegalDocumentPageProps/type accordingly) to avoid future global shadowing and linter warnings.
53-74: Using text content as React keys is fragile.
paragraphandbulletstrings are used as keys forintro,section.paragraphs, andsection.bullets. If any legal doc ever contains two identical paragraphs/bullets (easy to happen with short items like "N/A" or repeated clauses), React will warn about duplicate keys and may misbehave on re-render. Since these lists are static and never reordered, the index is a safe and cheap key here.♻️ Proposed fix
- {document.intro.map((paragraph) => ( - <p key={paragraph}>{paragraph}</p> + {document.intro.map((paragraph, i) => ( + <p key={i}>{paragraph}</p> ))} @@ - {section.paragraphs?.map((paragraph) => ( - <p key={paragraph} className="pb-doc__paragraph"> + {section.paragraphs?.map((paragraph, i) => ( + <p key={i} className="pb-doc__paragraph"> {paragraph} </p> ))} @@ - {section.bullets.map((bullet) => ( - <li key={bullet}>{bullet}</li> + {section.bullets.map((bullet, i) => ( + <li key={i}>{bullet}</li> ))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/landing/LegalDocument.web.tsx` around lines 53 - 74, The current key usage in the JSX maps (document.intro.map, section.paragraphs?.map, section.bullets.map) uses the paragraph/bullet text as the React key which can produce duplicate-key warnings; change these to use the iteration index as the key (e.g., use the map index for the <p> in intro, for <p> in section.paragraphs, and for <li> in section.bullets) since these lists are static and never reordered, ensuring unique stable keys without relying on content strings.
29-29: Move<style>{GLOBAL_CSS}</style>into<Head>for consistency.The
expo-router/headHead component supports arbitrary children including<style>tags. For a full-page legal document, keeping global CSS in the document head alongside metadata and font links is cleaner and avoids a brief FOUC on first paint. Since you're already usingexpo-router/headfor document-level tags, placing the style there keeps intent consistent with other head elements.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/landing/LegalDocument.web.tsx` at line 29, The inline style tag <style>{GLOBAL_CSS}</style> should be moved into the document head for the LegalDocument component: locate the LegalDocument component (and its use of Head from 'expo-router/head') and place the <style>{GLOBAL_CSS}</style> as a child of Head (alongside any meta/font links) instead of rendering it directly in the component body, ensuring global CSS is injected in the head to avoid FOUC and keep head-level assets together.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/components/landing/Button.tsx`:
- Around line 52-64: The anchor branch in Button.tsx should enforce a safe
default rel when opening a link in a new tab: if 'href' in props and
props.target === "_blank" and props.rel is undefined/null, set rel to "noopener
noreferrer" before rendering; if props.rel is provided, use it unchanged (or
optionally merge to ensure these tokens are present). Update the anchor render
logic in the component that checks 'href'/props to compute a finalRel variable
and pass finalRel to the <a rel={finalRel}> attribute.
In `@frontend/components/landing/DownloadButton.tsx`:
- Around line 30-38: The click handler (handleClick) opens a QR modal on desktop
even when QR_SRC is empty, causing a useless empty scan flow; change the logic
to treat an empty QR_SRC as unavailable and fall back to APP_STORE_URL: in
handleClick check both touchPrimary and whether QR_SRC is truthy (e.g., QR_SRC
&& QR_SRC.length) before opening the modal via setModalOpen(true), and if QR_SRC
is falsy navigate to APP_STORE_URL instead; apply the same truthy-QR_SRC check
to the duplicate desktop flow referenced around the other handler (lines
~108-115) so both entry points fallback to APP_STORE_URL when QR_SRC is empty.
- Around line 71-90: The modal currently sets aria-modal and focuses closeBtnRef
but does not trap Tab focus, allowing focus to escape; inside the useEffect in
DownloadButton add a focus-trap: collect all focusable elements inside cardRef
(e.g., querySelectorAll for anchors, buttons, inputs, [tabindex]), and add a
keydown handler that intercepts Tab/Shift+Tab to cycle focus to the first/last
focusable element accordingly (keeping existing Escape handler logic using
onKey), or alternatively integrate an existing dialog primitive/focus-trap
utility and attach it when the modal mounts and remove it on cleanup; ensure you
reference closeBtnRef and cardRef so initial focus and the trap target are the
same and remove the trap in the return cleanup along with existing listeners.
In `@frontend/components/landing/Nav.tsx`:
- Line 14: Update the Brand link in Nav.tsx so it points to the new app home
route: change the Brand component's href prop from "/" to "/home" (the JSX
element is Brand href="/" ariaLabel="PolyBuys home" inside the Nav component) so
the landing navigation sends users to /home instead of the old root route.
In `@TERMS_OF_SERVICE.md`:
- Line 58: The relative markdown link "[Privacy Policy](./PRIVACY_POLICY.md)" in
TERMS_OF_SERVICE.md can break when the content is rendered on the website;
update that link to the canonical web route (e.g. "/privacy") or include both
references. Replace the existing link target "./PRIVACY_POLICY.md" with
"/privacy" (or add a parenthetical/second link to "/privacy") so the Privacy
Policy resolves correctly when the markdown is served via the LegalDocumentPage
or the /terms route.
---
Outside diff comments:
In `@frontend/app/auth/login.tsx`:
- Around line 310-321: The web branch rendering OpenInAppPrompt uses
router.replace('/') for the "Back to home" action; update the onSecondaryAction
to call router.replace('/home') to match the new routing convention (locate the
isWeb check and the OpenInAppPrompt usage and change its onSecondaryAction).
Also, consider the reviewer note that /auth/login may be unreachable after
route-blocking—if so, replace the entire isWeb branch with an unconditional
redirect (use router.replace('/home') or router.replace(postAuthRedirect ??
'/home')) instead of rendering OpenInAppPrompt.
- Around line 382-385: finishAndRedirect currently hard-codes
setSuccessRedirect('/') which contradicts the migration to '/home' and ignores
any returnTo/postAuthRedirect. Update finishAndRedirect to set the success
redirect to the negotiated post-auth path instead of '/', e.g. call
setSuccessRedirect(postAuthRedirect ?? '/home') (or the equivalent variable that
holds returnTo/postAuthRedirect) and then setStep('success'); keep the rest of
the function unchanged and reference the finishAndRedirect function and
setSuccessRedirect call when making the change.
---
Nitpick comments:
In `@frontend/components/landing/helpers.ts`:
- Line 9: Replace the direct assignment to window.location.href for the mailto
flow with a window.open call or a transient anchor click to avoid unloading the
SPA; locate the line that sets window.location.href =
`mailto:?subject=${subject}&body=${body}` and change it to open the same mailto
URL using window.open(mailtoUrl, '_self') (or programmatically create, click,
and remove an <a> element) so the mail handoff is attempted without leaving the
app or creating a blank history entry.
In `@frontend/components/landing/LegalDocument.web.tsx`:
- Line 14: The parameter name document in the LegalDocumentPage function shadows
the global DOM document; rename the prop to something like legalDocument or doc
across the component signature (LegalDocumentPage({ document, siblingHref,
siblingLabel } -> LegalDocumentPage({ legalDocument, siblingHref, siblingLabel
})) and update every usage inside the component body that referenced document to
the new identifier, and then update all call sites/JSX props that pass the
document prop to use the new prop name (and update the
LegalDocumentPageProps/type accordingly) to avoid future global shadowing and
linter warnings.
- Around line 53-74: The current key usage in the JSX maps (document.intro.map,
section.paragraphs?.map, section.bullets.map) uses the paragraph/bullet text as
the React key which can produce duplicate-key warnings; change these to use the
iteration index as the key (e.g., use the map index for the <p> in intro, for
<p> in section.paragraphs, and for <li> in section.bullets) since these lists
are static and never reordered, ensuring unique stable keys without relying on
content strings.
- Line 29: The inline style tag <style>{GLOBAL_CSS}</style> should be moved into
the document head for the LegalDocument component: locate the LegalDocument
component (and its use of Head from 'expo-router/head') and place the
<style>{GLOBAL_CSS}</style> as a child of Head (alongside any meta/font links)
instead of rendering it directly in the component body, ensuring global CSS is
injected in the head to avoid FOUC and keep head-level assets together.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e6537ddf-2acd-45f6-a103-f221c03155db
⛔ Files ignored due to path filters (2)
frontend/assets/images/polybuys-download-qr.pngis excluded by!**/*.pngpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (42)
.eslintrc.jsPRIVACY_POLICY.mdTERMS_OF_SERVICE.mdfrontend/app/(tabs)/home.tsxfrontend/app/(tabs)/inbox.tsxfrontend/app/(tabs)/settings.tsxfrontend/app/_layout.tsxfrontend/app/account-settings.tsxfrontend/app/auth/login.tsxfrontend/app/index.tsxfrontend/app/listings/new.tsxfrontend/app/privacy.tsxfrontend/app/privacy.web.tsxfrontend/app/terms.tsxfrontend/app/terms.web.tsxfrontend/components/LandingScreen.tsxfrontend/components/LandingScreen.web.tsxfrontend/components/landing/AppleIcon.tsxfrontend/components/landing/AvatarStack.tsxfrontend/components/landing/Brand.tsxfrontend/components/landing/Button.tsxfrontend/components/landing/DownloadButton.tsxfrontend/components/landing/Eyebrow.tsxfrontend/components/landing/Footer.tsxfrontend/components/landing/GetApp.tsxfrontend/components/landing/Hero.tsxfrontend/components/landing/LegalDocument.web.tsxfrontend/components/landing/ListingCard.tsxfrontend/components/landing/Nav.tsxfrontend/components/landing/SectionHead.tsxfrontend/components/landing/Ticker.tsxfrontend/components/landing/Why.tsxfrontend/components/landing/cx.tsfrontend/components/landing/data.tsfrontend/components/landing/helpers.tsfrontend/components/landing/index.tsfrontend/components/landing/legalContent.tsfrontend/components/landing/styles.tsfrontend/components/landing/useScrolled.tsfrontend/constants/app.tsfrontend/package.jsonfrontend/types/assets.d.ts
✅ Files skipped from review due to trivial changes (10)
- frontend/app/(tabs)/settings.tsx
- frontend/app/account-settings.tsx
- frontend/app/privacy.web.tsx
- frontend/types/assets.d.ts
- frontend/components/landing/cx.ts
- .eslintrc.js
- frontend/constants/app.ts
- frontend/components/landing/styles.ts
- frontend/components/landing/legalContent.ts
- frontend/components/LandingScreen.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
- frontend/app/listings/new.tsx
- frontend/package.json
- frontend/app/(tabs)/inbox.tsx
- frontend/app/_layout.tsx
- frontend/app/(tabs)/home.tsx
- frontend/app/index.tsx
* feat: misc frontend UI fixes and improvements - Fix duplicate sign-in messages on profile tab - Redirect unauthenticated users to profile on Create Listing (web) - Fix browser tab title persisting after leaving a listing - Style placeholder text with lighter grey across profile and create listing forms - Standardize Create Listing button to brand green (#154734) - Capitalize category and condition values on listing detail page - Add profile setup check before creating listings with visible warning banner - Replace Alert.alert with cross-platform showAlert for web compatibility - Add Delete button to My Listings with confirmation dialog * fix: address PR review feedback - Extract showAlert into shared utils/showAlert.ts module - Prevent concurrent deletes in my-listings (guard + disable all buttons) - Fix year reset to empty string in settings signed-out useEffect - Remove unused Alert/Platform imports from new.tsx * fix(vercel): declare @sentry/react-native in frontend workspace Expo resolves @sentry/react-native/expo relative to the frontend package; it was only on the root workspace so Vercel installs did not expose the plugin and expo export failed. Made-with: Cursor * fix: address team feedback — auth timeout, seller avatar, download link, image lightbox - Add 8s timeout + retry button to auth 'checking' step to prevent infinite spinner - Replace empty grey seller avatar with initials (first letter of name) on listing detail - Make seller block tappable, navigates to public profile - Change 'Open in App' to 'Download App' with placeholder App Store URL - Add download hint link to OpenInAppPrompt component - Create ImageLightbox component for full-screen uncropped image viewing - Wrap all listing hero images in Pressable to open lightbox on click - Support keyboard navigation (Escape, ←, →) in lightbox on web * fix: address PR feedback for UI components and auth flow - Fix login retry timer leak - Fix ImageLightbox filtered array index mismatch and add auto-focus on web - Improve OpenInAppPrompt link error handling and accessibility labels - Move APP_STORE_URL and APP_SCHEME to shared constants * chore: satisfy eslint any warning for lightbox web props * fixed search * feat(frontend): dedicated account settings from profile tab - Add /account-settings stack screen with message notifications, sign out, and delete account - Profile tab shows a Settings row (and account settings on incomplete profile) linking there - Web: OpenInAppPrompt for account settings deep link, consistent with profile tab - Fix typed-route pushes with as never where the generated routes lag Made-with: Cursor * red box * red box * blocked users * code rabbit fix * more fixes * feat: add Other as a report reason for listing moderation (POLY-70) * fix: keyboard overlaps input on Create/Edit Listing screens (POLY-75) * feat: ui changes to save/share buttons * fix: login redirect bug * fix: route profileless users to onboarding Co-authored-by: Evan Taylor <evan-taylor@users.noreply.github.com> * test: cover login onboarding redirect * Match UI of MyListing and Home * chore: remove ios build files from PR * feat(frontend): dismissible flash banner and safe placement - Add FlashProvider with bottom-inset positioning above tab/home area - pointerEvents box-none/auto so banner receives taps; tap or Dismiss clears - Shorter auto-dismiss when reduce motion is enabled - Wire create/edit listing, mark sold, and report success to setFlash - ReportModal optional onReportSuccess for non-blocking confirmation Made-with: Cursor * transparent * more transparent * chore(frontend): shared report copy; longer flash for reduce motion - Add feedbackMessages constants for report success (flash + Alert) - Use REPORT_SUBMITTED_MESSAGE in listing and profile screens - Increase FLASH_DURATION_REDUCED_MS to 4000 for readability Made-with: Cursor * feat: remove tags from UI and backend (POLY-77) * Remove unrelated changes from PR * fix: coderabbit errors * fix(vercel): declare @sentry/react-native in frontend workspace Expo resolves @sentry/react-native/expo relative to the frontend package; it was only on the root workspace so Vercel installs did not expose the plugin and expo export failed. Made-with: Cursor * feat(frontend): polish listing cards (POLY-76) - Vertical stack: multi-line title (2 lines home, 3 default), condition caption, price - Clearer hierarchy: dark title, muted condition, bold accent price - More padding and md card radius; softer shadow; slightly looser grid spacing on home Made-with: Cursor * feat(frontend): short description preview on listing cards - Plain descriptions: up to 2 lines, footnote style, ellipsized - Multi-line or -/• lists: up to 2 bullet rows with trimmed markers - Include snippet in accessibility label (capped length) Made-with: Cursor * feat(frontend): smarter listing description bullets (heuristics) - Extract buildDescriptionPreview: newlines/markers, semicolons, then sentences - Use Intl.Segmenter when available with regex fallback for sentences - Up to 3 bullets; support numbered line prefixes - Tests for preview helper; exclude frontend __tests__ from tsc Made-with: Cursor * fix(frontend): omit prose after last bulleted line in card preview - When 2+ lines start with list markers, only those lines become bullets - descBlock uses overflow hidden as a layout safeguard Made-with: Cursor * fix(frontend): numbered lists without space after dot and inline 1. 2. - Scan for digit+dot boundaries (not decimals like 2.0) across lines and spaces - Relax line markers so 1.Item matches; run span scan before line-based bullets - Tests for tight 1.x/2.x lines and single-line numbered lists Made-with: Cursor * feat(frontend): one bullet per description line when user uses Enter - Drop short-line / looksLikeList gate: any 2+ non-empty lines become bullets - Keep marked-only slice when 2+ lines start with list markers (trailing prose omitted) - Cap 12 lines, 300 chars per line; strip optional -/1. prefixes per row Made-with: Cursor * fix(frontend): trim prose after last bullet (numbered + single dash) - Numbered items: drop flush lines after first line in each span; keep indented wraps - One dash bullet + following unmarked lines: show only the marked line - Tests for numbered tail and single-dash + prose Made-with: Cursor * chore(frontend): address CodeRabbit ListingCard + preview tests - Require listing.condition on ListingCard; exhaustive formatConditionLabel - Memoize accessibilityLabel from parts; spacing.smPlus for cardDetailsHome - Tests: 78-char cap and DESC_PREVIEW_BULLET_MAX Made-with: Cursor * feat: fixed a ton of stuff, standardize UI (#86) * feat: fixed a ton of stuff, standardize UI * fix block user success feedback Co-authored-by: Evan Taylor <evan-taylor@users.noreply.github.com> * style format conversation block flow Co-authored-by: Evan Taylor <evan-taylor@users.noreply.github.com> * fix listing sold state race checks Co-authored-by: Evan Taylor <evan-taylor@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Evan Taylor <evan-taylor@users.noreply.github.com> * feat: update UI themes and improve tab navigation (#87) - Changed userInterfaceStyle to 'light' in app.json for consistent appearance. - Refactored theme handling in _layout.tsx to use a custom navigation theme. - Updated tab navigation styles in (tabs)/_layout.tsx to utilize nativeChrome for better visual consistency. - Enhanced search and inbox screens to align with new theme settings. - Added nativeChrome configuration for improved UI elements across the app. This commit aims to standardize the UI and enhance user experience across different screens. * feat: implement profile pictures * fix: coderabbit changes, fix top border on profile page * feat: add ci/cd for builds (#89) * feat: add ci/cd for builds * fix: don't build on backend or docs changes * fix: inbox search keyboard close (#90) * feat: fix xcode provisioning on ci/cd (#91) * fix: add app id to expo (#92) * feat: run convex deploy and add agent skill (#93) * feat: run convex deploy and add agent skill * fix: run convex deploy on manual ci too * feat: add report and delete for entire conversations * feat: fix prevent swipe actions from showing through on row press * feat: add tooltip and add tap to close swipe * feat: add report and delete for individual messaging * fix(frontend): keyboard avoidance for price range picker modal - KeyboardAvoidingView (iOS padding, Android height) above bottom sheet - ScrollView so presets, inputs, and Apply stay reachable when keyboard is open - Backdrop as sibling Pressable so sheet scrolling is not blocked - Safe-area padding on scroll content; cap sheet height for smaller screens Made-with: Cursor * chore(frontend): dedupe Sentry dep; dim price picker backdrop - Remove duplicate @sentry/react-native from package.json - Restore modal scrim on PriceRangePicker backdrop (rgba 0.35) Made-with: Cursor * fix: remove listings when conversation reported, clean up functionality * fix: no deleting individual messages * fix: remove tooltip * feat: redirect new users to home * feat: redirect to home after logging in * fix: add code rabbit changes * fix: onboarding copy so first-time users do not see Welcome back (POLY-86) * feat: add required field indicators and inline validation to listing creation (POLY-91) * feat: add date posted metadata to listing cards and detail page (POLY-92) * fix choppyness (#101) * feat: add zoom support to listing photos * fix: address lightbox lint warnings * chore: rerun ci for listing photo zoom * fix: address lightbox review feedback * feat: prevent functionality on web * fix: ci config setup (#109) * fix: match Dispatch<SetStateAction> signature for onImagesChange * Feat/marketing landing (#95) * fix(vercel): declare @sentry/react-native in frontend workspace Expo resolves @sentry/react-native/expo relative to the frontend package; it was only on the root workspace so Vercel installs did not expose the plugin and expo export failed. Made-with: Cursor * feat(frontend): add marketing landing page and /home feed route - Add web-only landing at / with value props, CTAs, and App Store compliance - Desktop: QR code and mailto draft for the download link - Narrow web: official App Store badge linking to APP_STORE_URL - Rename (tabs)/index to home so / is the landing without route conflicts - Point in-app "home" navigation and post-auth default to /home - Register root index in the stack; dedupe @sentry/react-native in package.json - Refresh lockfile so workspace resolves expo-blur for typecheck/bundling Made-with: Cursor * feat(web): block auth routes and remove login CTAs from landing - Add auth/login.web.tsx so /auth/login always redirects to / on web - Remove Sign in header link and Sign up button from LandingScreen - Clarify copy: sign-in is app-only; web is browse-focused Made-with: Cursor * chore(frontend): address CodeRabbit marketing + web auth UX - index: redirect authenticated web users to /home; show boot spinner while auth loads - LandingScreen: brand Link to /home; static QR asset (no third-party API); App Store badge uses Link target=_blank on web - types/assets.d.ts: declare *.png for Metro image imports - constants: document APP_STORE_URL + QR regeneration - home: web save/create no longer hits blocked /auth/login; use app-only alerts Made-with: Cursor * feat: revamp landing page, add legal docs * fix: board and coderabbit feedback * fix: feedback --------- Co-authored-by: Evan Taylor <eltaylor1104@gmail.com> * Feature/poly 90 button visibility (#108) * feat: improve search tab and button visibility * feat: polish search, buttons, and messaging flows * fix: address ui review feedback * fix: padding and styling changes --------- Co-authored-by: Evan Taylor <eltaylor1104@gmail.com> * fixes * fix: address listing form validation review feedback * fix: align home route references with expo router * feat: add support page and links across the application (#111) - Introduced a new support page for user assistance, accessible via /support. - Updated LandingScreen to include a link to the support page and added legal links for Privacy and Terms. - Enhanced the footer to include a link to the support page. - Implemented support document retrieval with contact options for user inquiries. - Adjusted styles for legal links and contact information display. * fix: restore web browse route targets * fix: improve keyboard avoidance in chat and price picker * fix: address keyboard and safety banner review feedback * featL liquid glass app icon (#112) * feat: add support page and links across the application - Introduced a new support page for user assistance, accessible via /support. - Updated LandingScreen to include a link to the support page and added legal links for Privacy and Terms. - Enhanced the footer to include a link to the support page. - Implemented support document retrieval with contact options for user inquiries. - Adjusted styles for legal links and contact information display. * feat: add liquid glass app icon * fix: revert message keyboard avoidance changes * fix: avoid native driver conflict in price picker * fix: make profile email read-only * feat: polish login and profile flows * fix: tighten profile tab and major selection * fix: polish frontend login and profile flows * fix: standardize keyboard chrome across frontend flows * feat: polish frontend launch readiness * chore: apply final review hardening updates * chore: apply final review hardening updates * fix: address accessibility and profile review comments * fix: address frontend review findings * fix: harden native image uploads and backend validation --------- Co-authored-by: Cole <hackman@calpoly.edu> Co-authored-by: dfed25 <domfederico21@gmail.com> Co-authored-by: Jaydon Chen <79879038+jaydonkc@users.noreply.github.com> Co-authored-by: MatthewPhan <Matthewminhphan@gmail.com> Co-authored-by: SamanSP1386 <saman.sepehr86@gmail.com> Co-authored-by: Haixin <haixinhuang502@gmail.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Evan Taylor <evan-taylor@users.noreply.github.com> Co-authored-by: lheutchy <lheutchy@gmail.com> Co-authored-by: Taye-Staats <tayestaats@outlook.com> Co-authored-by: dfed25 <150391626+dfed25@users.noreply.github.com> Co-authored-by: BoB121isawesome <79879038+BoB121isawesome@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Linked Issues
Closes #
Linear: (e.g., POLY-123)
Summary
Briefly explain the change and why.
How to Test
Steps to verify locally:
npm run lintnpm run typechecknpm testnpm run dev:backend(in terminal A)npm run dev(in terminal B)Checklist
npm run lint)devScreenshots / Demos
(if UI or visible behavior - attach images, videos, or GIFs)
Summary by CodeRabbit
New Features
Documentation
Chores