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
2 changes: 1 addition & 1 deletion frontend/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"orientation": "portrait",
"userInterfaceStyle": "light",
"platforms": ["ios", "android", "web"],
"icon": "./assets/images/icon.png",
"icon": "./assets/polybuys-ios.icon",
"splash": {
"image": "./assets/images/icon.png",
"resizeMode": "contain",
Expand Down
5 changes: 5 additions & 0 deletions frontend/app/support.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Redirect, type Href } from 'expo-router';

export default function SupportRoute() {
return <Redirect href={'/home' as Href} />;
}
12 changes: 12 additions & 0 deletions frontend/app/support.web.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LegalDocumentPage } from '../components/landing/LegalDocument.web';
import { getSupportDocument } from '../components/landing/legalContent';

export default function SupportRoute() {
return (
<LegalDocumentPage
legalDocument={getSupportDocument()}
siblingHref="/privacy"
siblingLabel="Privacy Policy"
/>
);
}
7 changes: 7 additions & 0 deletions frontend/assets/polybuys-ios.icon/Assets/SVG Image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions frontend/assets/polybuys-ios.icon/icon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"color-space-for-untagged-svg-colors": "display-p3",
"fill": "automatic",
"groups": [
{
"layers": [
{
"blend-mode": "normal",
"fill": "automatic",
"glass": true,
"hidden": false,
"image-name": "SVG Image.svg",
"name": "SVG Image",
"opacity": 1,
"position": {
"scale": 1.84,
"translation-in-points": [-4.565996005466104, -62.15098236180194]
}
}
],
"shadow": {
"kind": "neutral",
"opacity": 0.5
},
"translucency": {
"enabled": true,
"value": 0.5
}
}
],
"supported-platforms": {
"circles": ["watchOS"],
"squares": ["iOS"]
}
}
66 changes: 63 additions & 3 deletions frontend/components/LandingScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Head from 'expo-router/head';
import { Link, useRouter } from 'expo-router';
import { Link, useRouter, type Href } from 'expo-router';
import {
Image,
Linking,
Expand All @@ -25,6 +25,11 @@ const APP_STORE_BADGE_URI =

const LAYOUT_BREAKPOINT = 768;

const HOME_HREF = '/home' as Href;
const SUPPORT_PAGE_HREF = '/support' as Href;
const PRIVACY_PAGE_HREF = '/privacy' as Href;
const TERMS_PAGE_HREF = '/terms' as Href;

function openDownloadLinkEmail() {
const subject = encodeURIComponent('PolyBuys — download link');
const body = encodeURIComponent(
Expand Down Expand Up @@ -66,7 +71,7 @@ export default function LandingScreen() {
) : null}
<SafeAreaView style={styles.safe} edges={['top', 'bottom']}>
<View style={styles.topBar}>
<Link href="/home" asChild>
<Link href={HOME_HREF} asChild>
<Pressable
accessibilityRole="link"
accessibilityLabel="Go to marketplace"
Expand Down Expand Up @@ -94,7 +99,7 @@ export default function LandingScreen() {
<View style={styles.ctaRow}>
<AppButton
size="lg"
onPress={() => router.push('/home')}
onPress={() => router.push(HOME_HREF)}
accessibilityLabel="Browse marketplace"
>
Browse marketplace
Expand Down Expand Up @@ -188,6 +193,50 @@ export default function LandingScreen() {
)}
</SectionCard>

{Platform.OS === 'web' ? (
<View style={styles.legalLinksRow}>
<Link href={SUPPORT_PAGE_HREF} asChild>
<Pressable
accessibilityRole="link"
accessibilityLabel="Support"
style={({ pressed }) => [pressed && styles.pressed]}
>
<AppText variant="footnoteMed" color="primary">
Support
</AppText>
</Pressable>
</Link>
<AppText variant="footnote" color="muted" style={styles.legalLinksSep}>
·
</AppText>
<Link href={PRIVACY_PAGE_HREF} asChild>
<Pressable
accessibilityRole="link"
accessibilityLabel="Privacy Policy"
style={({ pressed }) => [pressed && styles.pressed]}
>
<AppText variant="footnoteMed" color="primary">
Privacy
</AppText>
</Pressable>
</Link>
<AppText variant="footnote" color="muted" style={styles.legalLinksSep}>
·
</AppText>
<Link href={TERMS_PAGE_HREF} asChild>
<Pressable
accessibilityRole="link"
accessibilityLabel="Terms of Service"
style={({ pressed }) => [pressed && styles.pressed]}
>
<AppText variant="footnoteMed" color="primary">
Terms
</AppText>
</Pressable>
</Link>
</View>
) : null}

<AppText variant="footnote" color="muted" style={styles.footerNote}>
PolyBuys is an independent student marketplace and is not affiliated with California
Polytechnic State University.
Expand Down Expand Up @@ -304,6 +353,17 @@ const styles = StyleSheet.create({
lineHeight: 18,
maxWidth: 320,
},
legalLinksRow: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'center',
gap: spacing.sm,
marginTop: spacing.md,
},
legalLinksSep: {
lineHeight: 18,
},
footerNote: {
lineHeight: 18,
marginTop: spacing.sm,
Expand Down
3 changes: 3 additions & 0 deletions frontend/components/landing/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export function Footer() {
<a href="/terms" className="pb-footer__link">
Terms of Service
</a>
<a href="/support" className="pb-footer__link">
Support
</a>
<a href={`mailto:${LEGAL_SUPPORT_EMAIL}`} className="pb-footer__link">
{LEGAL_SUPPORT_EMAIL}
</a>
Expand Down
56 changes: 50 additions & 6 deletions frontend/components/landing/LegalDocument.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Brand } from './Brand';
import { Button } from './Button';
import { Footer } from './Footer';
import type { LegalDocument } from './legalContent';
import { LEGAL_SUPPORT_EMAIL } from './legalContent';
import { GLOBAL_CSS } from './styles';

interface LegalDocumentPageProps {
Expand All @@ -16,6 +17,8 @@ export function LegalDocumentPage({
siblingHref,
siblingLabel,
}: LegalDocumentPageProps) {
const isSupport = legalDocument.slug === 'support';

return (
<>
<Head>
Expand All @@ -41,9 +44,25 @@ export function LegalDocumentPage({
<a href="/" className="pb-doc__navLink">
Home
</a>
<a href={siblingHref} className="pb-doc__navLink">
{siblingLabel}
</a>
{isSupport ? (
<>
<a href="/privacy" className="pb-doc__navLink">
Privacy
</a>
<a href="/terms" className="pb-doc__navLink">
Terms
</a>
</>
) : (
<>
<a href="/support" className="pb-doc__navLink">
Support
</a>
<a href={siblingHref} className="pb-doc__navLink">
{siblingLabel}
</a>
</>
)}
</div>
</div>

Expand Down Expand Up @@ -76,6 +95,25 @@ export function LegalDocumentPage({
))}
</ul>
) : null}

{section.contactLines?.length ? (
<ul className="pb-doc__list">
{section.contactLines.map((line) => (
<li key={`${line.lead}-${line.href}`}>
<strong>{line.lead}:</strong>{' '}
<a
href={line.href}
className="pb-doc__contactLink"
{...(line.href.startsWith('http')
? { target: '_blank', rel: 'noopener noreferrer' }
: {})}
>
{line.text}
</a>
</li>
))}
</ul>
) : null}
</section>
))}
</div>
Expand All @@ -84,9 +122,15 @@ export function LegalDocumentPage({
<Button href="/" variant="ghost" size="md">
Back to home
</Button>
<Button href={siblingHref} size="md">
{siblingLabel}
</Button>
{isSupport ? (
<Button href={`mailto:${LEGAL_SUPPORT_EMAIL}`} size="md">
Email support
</Button>
) : (
<Button href={siblingHref} size="md">
{siblingLabel}
</Button>
)}
</div>
</article>
</div>
Expand Down
61 changes: 60 additions & 1 deletion frontend/components/landing/legalContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@ export const LEGAL_SUPPORT_EMAIL =

export const LEGAL_UPDATED_AT = 'April 20, 2026';

/** Public URLs on the /support page (hardcoded for App Store review). */
const SUPPORT_PUBLIC_EMAIL = 'support@polybuys.com';
const SUPPORT_PUBLIC_WEBSITE_HREF = 'https://polybuys.com';
const SUPPORT_PUBLIC_GITHUB_ISSUES_HREF = 'https://github.com/codebox-calpoly/PolyBuys/issues';

export type LegalContactLine = {
lead: string;
href: string;
text: string;
};

export type LegalSection = {
title: string;
paragraphs?: readonly string[];
bullets?: readonly string[];
contactLines?: readonly LegalContactLine[];
};

export type LegalDocument = {
slug: 'privacy' | 'terms';
slug: 'privacy' | 'terms' | 'support';
eyebrow: string;
title: string;
description: string;
Expand All @@ -19,6 +31,53 @@ export type LegalDocument = {
sections: readonly LegalSection[];
};

/** Support information page for App Store review and in-app help links. */
export function getSupportDocument(): LegalDocument {
const contactLines: LegalContactLine[] = [
{
lead: 'Email',
href: `mailto:${SUPPORT_PUBLIC_EMAIL}`,
text: SUPPORT_PUBLIC_EMAIL,
},
{
lead: 'Website',
href: SUPPORT_PUBLIC_WEBSITE_HREF,
text: 'polybuys.com',
},
{
lead: 'GitHub Issues',
href: SUPPORT_PUBLIC_GITHUB_ISSUES_HREF,
text: 'github.com/codebox-calpoly/PolyBuys',
},
];

return {
slug: 'support',
eyebrow: 'PolyBuys',
title: 'Support',
description:
'How to get help with the PolyBuys app or website — contact options and response times.',
updatedAt: LEGAL_UPDATED_AT,
intro: [
'PolyBuys — operated by the PolyBuys student team.',
'PolyBuys is an independent marketplace for verified Cal Poly students and is not affiliated with California Polytechnic State University.',
],
sections: [
{
title: 'How to Get Help',
paragraphs: [
"If you're running into an issue with the app or have a question, you can reach us through any of the following:",
],
contactLines,
},
{
title: 'Response time',
paragraphs: ["We're a small team, so please allow 1–3 business days for a response."],
},
],
};
}

export const PRIVACY_POLICY_DOC: LegalDocument = {
slug: 'privacy',
eyebrow: 'Privacy Policy',
Expand Down
9 changes: 9 additions & 0 deletions frontend/components/landing/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,15 @@ img { max-width: 100%; display: block; }
line-height: 1.65;
color: var(--pb-ink);
}
.pb-doc__contactLink {
color: var(--pb-green);
font-weight: 600;
text-decoration: underline;
text-underline-offset: 2px;
}
.pb-doc__contactLink:hover {
color: var(--pb-green-2);
}
.pb-doc__actions {
margin-top: 12px;
display: flex;
Expand Down
Loading