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
11 changes: 11 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,16 @@ module.exports = {
'@typescript-eslint/no-require-imports': 'off',
},
},
{
// Web-only marketing pages render raw DOM (not React Native <Text>),
// so the RN text-wrapping rule doesn't apply.
files: [
'frontend/components/landing/**/*.{ts,tsx}',
'frontend/components/LandingScreen.web.tsx',
],
rules: {
'react-native/no-raw-text': 'off',
},
},
],
};
83 changes: 83 additions & 0 deletions PRIVACY_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# PolyBuys Privacy Policy

Last updated: April 20, 2026

PolyBuys is a student marketplace for the Cal Poly community. This Privacy Policy explains what information we collect, how we use it, and the choices you have when you use the PolyBuys app, website, and related services.

This policy applies to the PolyBuys iOS app, Android app, website, and any related features we operate.

## Quick summary

- We collect your Cal Poly email, account details, profile information, listings, listing photos, messages, reports, and other content you choose to submit.
- We also collect optional push notification tokens and limited device, app, crash, and error diagnostics to keep PolyBuys working reliably.
- We use this information to authenticate users, operate the marketplace, enable messaging and notifications, prevent abuse, and improve the service.

## Information we collect

We collect information you provide directly when you create an account, verify your email, edit your profile, upload photos, post a listing, message another user, save a listing, report content, or contact support.

This can include:

- Account and verification data, such as your Cal Poly email address, authentication records, and session information.
- Profile data, such as your name, bio, major, graduation year, and profile photo.
- Marketplace content, such as listing titles, descriptions, prices, item condition, categories, listing images, saved listings, messages, reports, and blocks.
- Notification and preference data, such as your push notification token and whether message notifications are enabled.
- Technical and diagnostic data, such as device/app diagnostics, crash reports, and error logs.

## How we use information

We use information we collect to:

- verify eligible users and secure accounts;
- create profiles, publish listings, show marketplace content, and enable messaging between users;
- send verification emails and optional push notifications;
- detect spam, scams, abusive content, and other misuse of the service; and
- troubleshoot bugs, monitor reliability, respond to support requests, and improve PolyBuys.

## Permissions and device access

Some features ask for device permissions only when you choose to use them.

- Photo library or camera access is used only if you choose to upload listing photos or a profile picture.
- Notification permission is used only if you allow PolyBuys to send message-related push notifications.
- PolyBuys stores authentication/session data locally on your device so you can stay signed in.

## What we do not currently collect

- We do not currently collect precise GPS location.
- We do not run third-party advertising inside PolyBuys or sell personal information.
- We do not process payments inside the app.

## How information is shared

Some information is shared with other users as part of the marketplace experience. For example, your public profile details, listings, listing photos, and messages are visible to the users involved in those interactions.

We may also share information:

- with service providers that help us operate PolyBuys, including Convex for backend infrastructure and storage, Resend for verification emails, Expo push notification services, and Sentry for crash and error monitoring; and
- when required by law, legal process, or a good-faith belief that sharing is necessary to protect the safety, rights, or integrity of PolyBuys, our users, or the public.

## Retention and deletion

We keep information for as long as needed to operate the service, maintain security, resolve disputes, and enforce our policies.

If you delete your account, we will remove or de-identify associated account data from active systems, subject to records we may need to keep for fraud prevention, abuse investigation, or legal compliance.

## Your choices

- You can update your profile details in the app.
- You can remove or replace uploaded photos and listings you no longer want to display.
- You can disable message notifications.
- You can request account deletion from the app settings.

## Children's privacy

PolyBuys is not directed to children under 13, and we do not knowingly collect personal information from children under 13.

## Changes to this policy

We may update this Privacy Policy from time to time. If we make material changes, we will post the updated policy here and update the "Last updated" date.

## Contact us

If you have questions about this Privacy Policy or how PolyBuys handles data, contact us at [support@polybuys.com](mailto:support@polybuys.com).
66 changes: 66 additions & 0 deletions TERMS_OF_SERVICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# PolyBuys Terms of Service

Last updated: April 20, 2026

These Terms of Service govern your use of PolyBuys. By accessing or using the PolyBuys app, website, or related services, you agree to these terms.

PolyBuys is an independent student marketplace and is not affiliated with California Polytechnic State University.

## Eligibility and accounts

- You must be at least 13 years old and legally allowed to use the service.
- You must use a valid `@calpoly.edu` email address or another account expressly approved by PolyBuys.
- You are responsible for keeping your account information accurate and for activity that occurs under your account.

## What PolyBuys provides

PolyBuys provides a platform that lets users browse listings, post items, manage profiles, and message one another.

PolyBuys is not the buyer, seller, broker, shipper, insurer, or guarantor in transactions between users.

## Your content and listings

- You are responsible for the listings, photos, profile content, messages, and other material you submit.
- You must have the right to post any content you upload and any item you offer for sale.
- By posting content to PolyBuys, you give us a limited license to host, store, reproduce, and display that content as needed to operate the service.
- You must keep listing information accurate, lawful, and not misleading.

## Prohibited conduct

You may not:

- post illegal, stolen, counterfeit, unsafe, or otherwise prohibited goods;
- scam, spam, harass, threaten, impersonate, or abuse other users;
- submit false reports, evade blocks or moderation, or try to bypass account restrictions;
- scrape the service, interfere with its operation, upload malicious code, or attempt unauthorized access; or
- share another person's private information without permission.

## Transactions and safety

- Users are solely responsible for their own transactions, including pricing, payment, delivery, pickup, inspections, and resolving disputes.
- PolyBuys encourages users to meet in safe, public locations and to use reasonable caution before completing a transaction.
- PolyBuys does not guarantee item quality, seller identity, buyer identity, payment completion, or transaction outcomes.

## Enforcement and termination

We may remove content, limit features, suspend accounts, or terminate access at any time if we believe a user has violated these terms, created a safety risk, or exposed PolyBuys or other users to legal or operational harm.

## Disclaimers

PolyBuys is provided on an "as is" and "as available" basis. To the fullest extent permitted by law, we disclaim warranties of merchantability, fitness for a particular purpose, non-infringement, and uninterrupted availability.

## Limitation of liability

To the fullest extent permitted by law, PolyBuys and its operators will not be liable for indirect, incidental, special, consequential, or punitive damages, or for losses arising from user-to-user transactions, listings, messages, or use of the service.

## Privacy

Your use of PolyBuys is also governed by the [Privacy Policy](/privacy), which explains how we collect, use, and share data.

## Changes to these terms

We may update these Terms of Service from time to time. If we do, we will post the revised version here and update the "Last updated" date.

## Contact us

If you have questions about these Terms of Service, contact us at [support@polybuys.com](mailto:support@polybuys.com).
8 changes: 4 additions & 4 deletions frontend/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function WebHeaderLayout() {
}, [q]);
const [searchInput, setSearchInput] = useState(currentQuery);
const searchActive =
pathname === '/'
pathname === '/home'
? searchInput.trim().length > 0
: pathname === '/search' || pathname.startsWith('/search/');
const searchControlStyle = StyleSheet.flatten([
Expand Down Expand Up @@ -55,7 +55,7 @@ function WebHeaderLayout() {
}

router.replace({
pathname: '/' as never,
pathname: '/home' as never,
params: trimmed.length > 0 ? { ...mergedParams, q: trimmed } : mergedParams,
});
}, 250);
Expand All @@ -67,7 +67,7 @@ function WebHeaderLayout() {
<View style={styles.webRoot}>
<View style={styles.webHeaderBorder}>
<View style={styles.webHeaderContent}>
<Link href="/" asChild>
<Link href="/home" asChild>
<Pressable accessibilityRole="link" accessibilityLabel="Go to home">
<Text style={styles.brand}>PolyBuys</Text>
</Pressable>
Expand Down Expand Up @@ -132,7 +132,7 @@ export default function TabsLayout() {
disableTransparentOnScrollEdge
>
<NativeTabs.Trigger
name="index"
name="home"
disableTransparentOnScrollEdge
contentStyle={styles.nativeTabContent}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ export default function HomeScreen() {
const handleToggleSave = useCallback(
async (listingId: Id<'listings'>) => {
if (isWeb) {
router.push('/auth/login?returnTo=%2F' as never);
Alert.alert('Open in the PolyBuys app', 'Saving listings is available in the mobile app.');
return;
}

if (!isAuthenticated) {
router.replace('/auth/login?returnTo=%2F' as never);
router.replace('/auth/login?returnTo=%2Fhome' as never);
return;
}

Expand Down Expand Up @@ -262,10 +262,7 @@ export default function HomeScreen() {

const handleCreateListing = () => {
if (isWeb) {
router.push({
pathname: '/auth/login',
params: { returnTo: '/listings/new' },
} as never);
Alert.alert('Open in the PolyBuys app', 'Creating listings is available in the mobile app.');
return;
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/(tabs)/inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ export default function InboxScreen() {
path="/inbox"
buttonLabel="Open Inbox in App"
secondaryActionLabel="Back to home"
onSecondaryAction={() => router.replace('/')}
onSecondaryAction={() => router.replace('/home')}
/>
);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/(tabs)/my-listings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export default function MyListingsScreen() {
path="/my-listings"
buttonLabel="Open My Listings in App"
secondaryActionLabel="Back to home"
onSecondaryAction={() => router.replace('/')}
onSecondaryAction={() => router.replace('/home')}
/>
);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default function SettingsScreen() {
path="/settings"
buttonLabel="Open Profile in App"
secondaryActionLabel="Back to home"
onSecondaryAction={() => router.replace('/')}
onSecondaryAction={() => router.replace('/home')}
/>
);
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function RootLayout() {
contentStyle: { backgroundColor: colors.surface },
}}
>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false, title: 'Home' }} />
<Stack.Screen
name="listings/[id]"
Expand Down Expand Up @@ -121,6 +122,8 @@ function RootLayout() {
name="profile/[userId]"
options={{ title: 'Profile', headerBackTitle: 'Back' }}
/>
<Stack.Screen name="privacy" options={{ headerShown: false }} />
<Stack.Screen name="terms" options={{ headerShown: false }} />
<Stack.Screen name="l/[id]" options={{ headerShown: false }} />
</Stack>
</FlashProvider>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/account-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export default function AccountSettingsScreen() {
path="/account-settings"
buttonLabel="Open in app"
secondaryActionLabel="Back to home"
onSecondaryAction={() => router.replace('/')}
onSecondaryAction={() => router.replace('/home')}
/>
);
}
Expand Down
22 changes: 9 additions & 13 deletions frontend/app/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { useEntranceAnimation } from '../../hooks/useEntranceAnimation';
import { useAuth } from '../../hooks/useAuth';
import { requestPermissionAndSyncToken } from '../../hooks/usePushNotifications';
import { getLoginEntryAction, type LoginStep } from './loginRedirect';
import OpenInAppPrompt from '../../components/OpenInAppPrompt';
import { colors, typography, spacing, borderRadius } from '../../theme/tokens';

const APP_REVIEW_EMAIL = (process.env.EXPO_PUBLIC_APP_REVIEW_EMAIL ?? '').toLowerCase().trim();
Expand Down Expand Up @@ -70,13 +69,19 @@ export default function LoginScreen() {
normalizedReturnTo.startsWith('/') &&
!normalizedReturnTo.startsWith('//')
? (normalizedReturnTo as Href)
: '/';
: '/home';
const [successRedirect, setSuccessRedirect] = useState<Href>(postAuthRedirect);

useEffect(() => {
setSuccessRedirect(postAuthRedirect);
}, [postAuthRedirect]);

useEffect(() => {
if (isWeb) {
router.replace(postAuthRedirect);
}
}, [isWeb, postAuthRedirect, router]);

useEffect(() => {
const entryAction = getLoginEntryAction({
isSessionLoading,
Expand Down Expand Up @@ -308,16 +313,7 @@ export default function LoginScreen() {
const verificationEmail = typeof step === 'object' && 'email' in step ? step.email : '';

if (isWeb) {
return (
<OpenInAppPrompt
title="Sign in on mobile"
body="Login is only available in the PolyBuys mobile app."
path={String(postAuthRedirect)}
buttonLabel="Open PolyBuys App"
secondaryActionLabel="Back to home"
onSecondaryAction={() => router.replace('/')}
/>
);
return null;
}

if (isWelcomeStep) {
Expand Down Expand Up @@ -380,7 +376,7 @@ export default function LoginScreen() {
}

const finishAndRedirect = () => {
setSuccessRedirect('/');
setSuccessRedirect(postAuthRedirect);
setStep('success');
};

Expand Down
9 changes: 9 additions & 0 deletions frontend/app/auth/login.web.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Redirect } from 'expo-router';

/**
* Sign-in and sign-up are only supported in the native app. Web visitors hitting
* /auth/login (bookmark, deep link, or in-app navigation) are sent to the landing page.
*/
export default function LoginWebRedirect() {
return <Redirect href="/" />;
}
36 changes: 36 additions & 0 deletions frontend/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Redirect } from 'expo-router';
import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native';
import LandingScreen from '../components/LandingScreen';
import { useAuth } from '../hooks/useAuth';
import { colors } from '../theme/tokens';

export default function IndexRoute() {
const { isAuthenticated, isLoading } = useAuth();

if (Platform.OS !== 'web') {
return <Redirect href="/home" />;
}

if (isLoading) {
return (
<View style={styles.boot}>
<ActivityIndicator size="large" color={colors.primary} accessibilityLabel="Loading" />
</View>
);
}

if (isAuthenticated) {
return <Redirect href="/home" />;
}

return <LandingScreen />;
}

const styles = StyleSheet.create({
boot: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background,
},
});
2 changes: 1 addition & 1 deletion frontend/app/l/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default function ShortListingRedirect() {
const { id } = useLocalSearchParams<{ id?: string }>();

if (typeof id !== 'string' || id.trim().length === 0) {
return <Redirect href="/" />;
return <Redirect href="/home" />;
}

return <Redirect href={`/listings/${encodeURIComponent(id.trim())}`} />;
Expand Down
Loading
Loading