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 backend/convex/convex.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineApp } from 'convex/server';
import pushNotifications from '@convex-dev/expo-push-notifications/convex.config.js';

const app = defineApp();
const app: ReturnType<typeof defineApp> = defineApp();
app.use(pushNotifications);

export default app;
7 changes: 5 additions & 2 deletions backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"composite": true,
"rootDir": "..",
"tsBuildInfoFile": "../.cache/tsbuildinfo/backend.tsbuildinfo",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
Expand All @@ -17,6 +20,6 @@
},
"types": ["node"]
},
"include": ["convex/**/*"],
"exclude": ["node_modules", "dist", "convex/__tests__"]
"include": ["convex/**/*", "../packages/shared/**/*.ts"],
"exclude": ["node_modules", "dist", "convex/**/*.test.ts", "../packages/shared/**/*.test.ts"]
}
3 changes: 2 additions & 1 deletion frontend/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@
"typedRoutes": true
},
"web": {
"bundler": "metro"
"bundler": "metro",
"favicon": "./assets/images/icon.png"
},
"extra": {
"router": {
Expand Down
159 changes: 20 additions & 139 deletions frontend/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,27 @@
import { useEffect, useMemo, useState } from 'react';
import { Link, Slot, useLocalSearchParams, usePathname, useRouter } from 'expo-router';
import { useEffect } from 'react';
import { Slot, usePathname, useRouter } from 'expo-router';
import { NativeTabs } from 'expo-router/unstable-native-tabs';
import { useQuery } from 'convex/react';
import { api } from 'convex/_generated/api';
import { Platform, Pressable, StyleSheet, Text, View } from 'react-native';
import { Platform, StyleSheet, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { TextInput } from 'react-native';
import { AppNavContainer } from '../../components/AppNav';
import { useAuth } from '../../hooks/useAuth';
import { getSignedOutFallback, getVisibleNativeTabs } from '../../lib/navigation/guestAccess';
import { borderRadius, colors, spacing, typography } from '../../theme/tokens';
import { SearchProvider } from '../../contexts/SearchContext';
import { colors } from '../../theme/tokens';
import { nativeChrome } from '../../theme/nativeChrome';

function WebHeaderLayout() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useLocalSearchParams() as Record<string, string | string[] | undefined>;
const q = searchParams.q;
const currentQuery = useMemo(() => {
if (Array.isArray(q)) {
return q[0] ?? '';
}
return q ?? '';
}, [q]);
const [searchInput, setSearchInput] = useState(currentQuery);
const searchActive =
pathname === '/home'
? searchInput.trim().length > 0
: pathname === '/search' || pathname.startsWith('/search/');
const searchControlStyle = StyleSheet.flatten([
styles.webSearchControl,
searchActive && styles.webSearchControlActive,
]);
const mergedParams = useMemo(() => {
const nextParams: Record<string, string | string[]> = {};

for (const [key, value] of Object.entries(searchParams)) {
if (key === 'q' || value === undefined) {
continue;
}
nextParams[key] = value;
}

return nextParams;
}, [searchParams]);

useEffect(() => {
setSearchInput(currentQuery);
}, [currentQuery]);

useEffect(() => {
const timeout = setTimeout(() => {
const trimmed = searchInput.trim();
if (trimmed === currentQuery) {
return;
}

router.replace({
pathname: '/home' as never,
params: trimmed.length > 0 ? { ...mergedParams, q: trimmed } : mergedParams,
});
}, 250);

return () => clearTimeout(timeout);
}, [currentQuery, mergedParams, router, searchInput]);

function WebLayout() {
return (
<View style={styles.webRoot}>
<View style={styles.webHeaderBorder}>
<View style={styles.webHeaderContent}>
<Link href="/home" asChild>
<Pressable accessibilityRole="link" accessibilityLabel="Go to home">
<Text style={styles.brand}>PolyBuys</Text>
</Pressable>
</Link>
<View style={searchControlStyle}>
<TextInput
value={searchInput}
onChangeText={setSearchInput}
placeholder="Search listings..."
placeholderTextColor={colors.muted}
selectionColor={colors.primary}
cursorColor={colors.primary}
style={styles.webSearchInput}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="search"
accessibilityLabel="Search listings"
/>
</View>
<SearchProvider>
<View style={styles.webRoot}>
<AppNavContainer />
<View style={styles.webContent}>
<Slot />
</View>
</View>
<Slot />
</View>
</SearchProvider>
);
}

Expand Down Expand Up @@ -123,7 +53,7 @@ export default function TabsLayout() {
}, [isAuthenticated, isSessionLoading, isWeb, pathname, router]);

if (isWeb) {
return <WebHeaderLayout />;
return <WebLayout />;
}

const tabTint = nativeChrome.tabIconSelectedColor;
Expand Down Expand Up @@ -243,62 +173,13 @@ const styles = StyleSheet.create({
},
webRoot: {
flex: 1,
backgroundColor: colors.surface,
},
webHeaderBorder: {
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
backgroundColor: colors.surface,
},
webHeaderContent: {
width: '100%',
maxWidth: 1240,
alignSelf: 'center',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: spacing.md,
borderRadius: borderRadius.lg,
borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.border,
backgroundColor: colors.white,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
},
brand: {
...typography.title1,
color: colors.textDark,
fontSize: 28,
lineHeight: 34,
flexDirection: 'column',
backgroundColor: colors.background,
overflow: 'hidden',
},
webSearchControl: {
minWidth: 280,
maxWidth: 360,
width: '100%',
minHeight: 44,
borderRadius: borderRadius.full,
borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.border,
backgroundColor: colors.surface,
paddingHorizontal: spacing.md,
justifyContent: 'center',
},
webSearchControlActive: {
backgroundColor: colors.white,
borderColor: colors.primary,
},
webSearchInput: {
...typography.subhead,
width: '100%',
paddingHorizontal: 0,
paddingVertical: 0,
borderWidth: 0,
backgroundColor: 'transparent',
fontWeight: '600',
color: colors.textDark,
outlineWidth: 0,
outlineColor: 'transparent',
boxShadow: 'none',
webContent: {
flex: 1,
minHeight: 0,
},
nativeTabLabelDefault: {
fontSize: 11,
Expand Down
Loading
Loading