Skip to content
Open
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
12 changes: 12 additions & 0 deletions Uni-Squared/app/_layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Stack } from 'expo-router';

export default function Layout() {
return (
<Stack
screenOptions={{
headerShown: false,
contentStyle: { backgroundColor: 'transparent' },
}}
/>
);
}
50 changes: 50 additions & 0 deletions Uni-Squared/app/components/ClubCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';

// A simple card component to display club info
export default function ClubCard({ club, onPress }) {
return (
<Pressable
onPress={onPress}
accessibilityLabel={`${club.name}, ${club.category}`}
// 👇 This gives opacity feedback while pressed
style={({ pressed }) => [
styles.card,
pressed && { opacity: 0.6 }, // fade effect when pressed
]}
>
<View style={styles.image} />
<Text style={styles.title} numberOfLines={1}>{club.name}</Text>
<Text style={styles.subtitle} numberOfLines={2}>{club.description}</Text>
</Pressable>
);
}

// Visuals for club cards
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff5ddff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#e6dbc2ff',
padding: 10 ,
shadowColor: '#000',
shadowOpacity: 0.05,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 3,
elevation: 2, // for Android shadow
},
image: {
height: 72,
borderRadius: 8,
backgroundColor: '#e6d6abff',
marginBottom: 8 },
title: {
fontWeight: '700',
color: '#8b5e3c',
},
subtitle: {
fontSize: 12,
color: '#a87955ff',
marginTop: 2 },
});
37 changes: 37 additions & 0 deletions Uni-Squared/app/components/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { View, TextInput, Pressable, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

// A search bar component with menu and filter icons
export default function SearchBar({ value, onChangeText }) {
return (
<View style={styles.wrap}>
<Pressable hitSlop={10}><Ionicons name="menu" size={20} /></Pressable>
<TextInput
style={styles.input}
placeholder="Search for orgs"
value={value}
onChangeText={onChangeText}
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="while-editing"
/>
<Pressable hitSlop={10}><Ionicons name="filter" size={18} /></Pressable>
</View>
);
}

const styles = StyleSheet.create({
wrap: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff5ddff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#e6dbc2ff',
paddingHorizontal: 10,
paddingVertical: 8,
gap: 8
},
input: { flex: 1, fontSize: 14 },
});
41 changes: 41 additions & 0 deletions Uni-Squared/app/components/SectionHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';

export default function SectionHeader({ title, onViewMore }) {
return (
<View style={styles.row}>
<Text style={styles.title}>{title}</Text>
<Pressable
onPress={onViewMore}
hitSlop={8}
style={({ pressed }) => [
styles.fullPageWrap,
pressed && styles.fullPagePressed,
]}
>
<Text style={styles.fullPage}>View More</Text>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
},
title: {
fontSize: 20,
fontWeight: '800' ,
color: '#8b5e3c',
},
fullPage: {
color: '#a87955ff',
fontWeight: '700'
},
fullPagePressed: {
opacity: 0.6, // fade on press
transform: [{ scale: 0.96 }], // press-in feel
},
});
17 changes: 17 additions & 0 deletions Uni-Squared/app/data/clubs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const CATEGORIES = ['Cultural', 'Greek Life', 'STEM', 'Arts', 'Sports', 'Service'];

const base = (id, name, category) => ({
id: String(id),
name,
category,
description: 'Weekly meetups, workshops, and events.',
});

export const MOCK_BY_CATEGORY = {
Cultural: [base(1,'International Club','Cultural'), base(2,'Spanish Society','Cultural'), base(3,'K-Pop Fans','Cultural')],
'Greek Life':[base(4,'Alpha Beta','Greek Life'), base(5,'Delta Eta','Greek Life'), base(6,'Omega Phi','Greek Life')],
STEM: [base(7,'Robotics','STEM'), base(8,'AI Society','STEM'), base(9,'Cybersec','STEM')],
Arts: [base(10,'Photography','Arts'), base(11,'Choir','Arts'), base(12,'Theatre','Arts')],
Sports: [base(13,'Ultimate Frisbee','Sports'), base(14,'Running Club','Sports'), base(15,'Climbing','Sports')],
Service: [base(16,'Hearts for Homeless','Service'), base(17,'Clean City','Service'), base(18,'Tutoring','Service')],
};
15 changes: 4 additions & 11 deletions Uni-Squared/app/index.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { StyleSheet, Text, View, Image } from 'react-native'
import React from 'react';
import ClubOrgSearchScreen from './screens/HomeScreen';

const Home = () => {
return (
<View>
<Text>Home</Text>
</View>
)
export default function App() {
return <ClubOrgSearchScreen />;
}

export default Home

const styles = StyleSheet.create({})
Empty file.
132 changes: 132 additions & 0 deletions Uni-Squared/app/screens/HomeScreen.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useMemo, useState } from 'react';
import { View, Text, SectionList, FlatList, StyleSheet, StatusBar } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { LinearGradient } from 'expo-linear-gradient';
import SearchBar from '../components/SearchBar';
import SectionHeader from '../components/SectionHeader';
import ClubCard from '../components/ClubCard';
import { CATEGORIES, MOCK_BY_CATEGORY } from '../data/clubs';

export default function HomeScreen({ navigation }) {
const [query, setQuery] = useState('');

const sections = useMemo(() => {
const q = query.trim().toLowerCase();

return CATEGORIES.map((cat) => {
const all = MOCK_BY_CATEGORY[cat] || [];
const filtered = q
? all.filter((c) =>
(c.name + ' ' + c.description + ' ' + c.category)
.toLowerCase()
.includes(q)
)
: all;

return {
title: cat,
data: filtered.length ? [{ key: 'row', items: filtered }] : [],
};
}).filter((s) => s.data.length > 0);
}, [query]);

return (
<View style={styles.container}>
{/* iOS will let this be transparent; Android uses translucent */}
<StatusBar
translucent
backgroundColor="transparent"
barStyle="dark-content"
/>

{/* put gradient BEHIND the safe area so it covers top + bottom */}
<LinearGradient
colors={['#f8f0dcff', '#F5E7C4', '#F5E7C4', '#e8c3a3ff']}
style={styles.gradient}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
>
{/* tell SafeAreaView NOT to add top/left/right padding */}
<SafeAreaView style={styles.screen} edges={['top', 'left', 'right']}>
<View style={styles.top}>
<SearchBar value={query} onChangeText={setQuery} />
</View>

<SectionList
sections={sections}
keyExtractor={(item) => item.key}
contentContainerStyle={{ paddingBottom: 32 }}
stickySectionHeadersEnabled={false}
renderSectionHeader={({ section }) => (
<View style={styles.sectionHeaderWrap}>
<SectionHeader
title={section.title}
onViewMore={() =>
navigation?.navigate?.('Category', { cat: section.title })
}
/>
</View>
)}
renderItem={({ item }) => (
<FlatList
data={item.items}
keyExtractor={(club) => club.id}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.hList}
renderItem={({ item: club }) => (
<View style={styles.hItem}>
<ClubCard
club={club}
onPress={() =>
navigation?.navigate?.('ClubDetail', { id: club.id })
}
/>
</View>
)}
/>
)}
ListEmptyComponent={
<Text style={styles.empty}>No clubs match your search.</Text>
}
/>
</SafeAreaView>
</LinearGradient>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
// make sure the root isn't white
backgroundColor: '#f8f0dcff',
},
gradient: {
flex: 1,
},
screen: {
flex: 1,
},
top: {
padding: 16,
paddingBottom: 8,
},
sectionHeaderWrap: {
paddingHorizontal: 16,
marginTop: 8,
marginBottom: 6,
},
hList: {
paddingHorizontal: 12,
},
hItem: {
width: 180,
marginRight: 12,
},
empty: {
textAlign: 'center',
color: '#a87955ff',
marginTop: 24,
},
});
Loading