Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
b437175
init: template
Astrol99 Apr 15, 2025
787d60f
feat: added basic button variants and cleaned up screens
Astrol99 Apr 15, 2025
20ba107
ops: added zod react-hook-form and resolver
Astrol99 Apr 15, 2025
2ab7b1d
feat: added basic login form
Astrol99 Apr 15, 2025
e164800
feat: auth flow
Astrol99 Apr 15, 2025
3872a73
feat: fixed some styling
Astrol99 Apr 15, 2025
b934970
fix: auth flow bug
Astrol99 Apr 15, 2025
04433a4
fix: types
Astrol99 Apr 15, 2025
dbec280
fix: entire auth flow
Astrol99 Apr 15, 2025
cc03bb0
fix: auth store
Astrol99 Apr 15, 2025
a35238e
feat: added more tabs
Astrol99 Apr 15, 2025
830a936
ops: added recommended vscode extensions
Astrol99 Apr 15, 2025
ce1777a
updated extensions
Astrol99 Apr 16, 2025
6db2dba
feat: added user registration
Astrol99 Apr 16, 2025
7ed4def
chore: cleaned up some packages
Astrol99 Apr 16, 2025
fe92976
feat: added google oauth
Astrol99 Apr 16, 2025
66813ab
ops: supabase init
Astrol99 Apr 20, 2025
9008492
fix: migration history
Astrol99 Apr 20, 2025
846c47a
feat: spot db structure init
Astrol99 Apr 21, 2025
ca46303
feat: added local google oauth
Astrol99 Apr 21, 2025
64ef39e
fix: supabase RLS and indexings
Astrol99 Apr 21, 2025
fde3fa1
feat: added create spot modal
Astrol99 Apr 22, 2025
01cf780
chore: some cleanup
Astrol99 Apr 22, 2025
854ba70
feat: WORKING PERSISTENT AUTH FLOW
Astrol99 Apr 22, 2025
a83b7a0
feat: added database types with supazod
Astrol99 Apr 22, 2025
ffa44a9
fix: small auth flow error
Astrol99 Apr 22, 2025
495126d
style: updated colors
Astrol99 Apr 22, 2025
5041201
feat: added auth uid as default for user_id
Astrol99 Apr 22, 2025
0636592
feat: added spot creation
Astrol99 Apr 22, 2025
a9a023a
feat: added tagging feature
Astrol99 Apr 22, 2025
7b29286
feat: added spots list
Astrol99 Apr 22, 2025
4dc29c0
style: made it look better
Astrol99 Apr 22, 2025
2272721
style: added tab bar icons
Astrol99 Apr 22, 2025
2ac82a3
feat: added spot list refresh
Astrol99 Apr 22, 2025
c14e9f1
added owner
Astrol99 Apr 23, 2025
7a9f65a
feat: updated schema
Astrol99 Apr 23, 2025
61f1769
feat: added small loading shimmer
Astrol99 Apr 23, 2025
6976704
style: added tab bar icon active colors
Astrol99 Apr 23, 2025
d0887a0
feat: added image upload
Astrol99 Apr 23, 2025
581b0cb
refact: removed extra image item info
Astrol99 Apr 23, 2025
1f323d8
feat: added image upload!!!
Astrol99 Apr 23, 2025
3b2f8f5
fix: upsert slug tag conflict duplicates and removed created_by for tags
Astrol99 May 17, 2025
5fd8b4d
feat: fetched media coorelating to spot
Astrol99 May 17, 2025
1dbb7a8
feat: made storage buckets public
Astrol99 May 19, 2025
13722ff
pkgs: expo-clipboard
Astrol99 May 19, 2025
fd49288
feat: added image preview
Astrol99 May 19, 2025
c5f8167
feat: preview image uses user position index
Astrol99 May 19, 2025
cc18f7c
Merge branch 'main' of https://github.com/Longhorn-Developers/Longhor…
Astrol99 May 19, 2025
7fba92b
cleanup
Astrol99 May 19, 2025
09a27e8
fix: image upload to use Promise to prevent race condition
Astrol99 May 19, 2025
2bbb6d6
feat: hovering new spot button
Astrol99 May 19, 2025
b3dd56e
dep: bumped expo version to sdk 53 for expo-map support
Astrol99 May 19, 2025
2791798
feat: added basic map
Astrol99 May 19, 2025
1205725
feat: added location picker and image exif gps info
Astrol99 May 20, 2025
7de8d63
feat: added react hook form validation to location picker with zod
Astrol99 May 20, 2025
ff120fa
feat: call auth api to check on initial sessions
Astrol99 May 20, 2025
5c3ddf5
feat: saved geolocation data on backend
Astrol99 May 20, 2025
e3ceca2
feat: added db view of spots + tags + media + geo
Astrol99 May 21, 2025
e3a40ab
feat: added spot fetch with map markers
Astrol99 May 21, 2025
dd42523
feat: refetch spots on map screen + misc design updates
Astrol99 May 21, 2025
05f41bf
refact: used spots view instead of manual joining for explore
Astrol99 May 21, 2025
91fd774
feat: qualify of life stuff: fixed pin on map picker + image exif geo…
Astrol99 May 21, 2025
cb39a91
feat: removed groups for now
Astrol99 May 21, 2025
4e09cc0
feat: moved database types to supabase folder
Astrol99 May 22, 2025
db8763a
feat: moved spot creation to edge function
Astrol99 May 22, 2025
2eb1106
fix: zustand dependency bug
Astrol99 May 23, 2025
3165944
feat: added Safesearch APi
Astrol99 May 24, 2025
b8ca5da
Merge branch 'main' into feat/api-safesearch
Astrol99 Jun 2, 2025
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
6 changes: 2 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"deno.enablePaths": [
"longhorn-studies/supabase/functions"
],
"deno.enablePaths": ["longhorn-studies/supabase/functions"],
"deno.lint": true,
"deno.unstable": [
"bare-node-builtins",
Expand All @@ -20,5 +18,5 @@
],
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
}
11 changes: 9 additions & 2 deletions longhorn-studies/app.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"expo": {
"name": "longhorn-studies",
"name": "Longhorn Studies",
"slug": "longhorn-studies",
"owner": "longhorn-developers",
"version": "1.0.0",
Expand All @@ -23,7 +23,14 @@
{
"iosUrlScheme": "com.googleusercontent.apps.117866795198-nn8qmvqdg6fdhkd6lulnu005t0ct85o9"
}
]
],
[
"expo-maps",
{
"requestLocationPermission": "true"
}
],
"expo-web-browser"
],
"newArchEnabled": true,
"experiments": {
Expand Down
23 changes: 5 additions & 18 deletions longhorn-studies/app/(app)/(protected)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { AntDesign, Entypo, MaterialIcons } from '@expo/vector-icons';
import { Link, Tabs } from 'expo-router';

import { Button } from '~/components/Button';
import { AntDesign, Entypo } from '@expo/vector-icons';
import { Tabs } from 'expo-router';

export default function AppLayout() {
// This layout can be deferred because it's not the root layout.
Expand All @@ -10,26 +8,15 @@ export default function AppLayout() {
<Tabs.Screen
name="index"
options={{
title: 'Home',
headerRight: () => (
<Link href="/create-spot" asChild>
<Button size="small" icon={<Entypo name="plus" color="white" />} title="New Spot" />
</Link>
),
tabBarIcon: ({ color }) => <Entypo name="home" size={24} color={color} />,
}}
/>
<Tabs.Screen
name="groups/index"
options={{
title: 'Groups',
tabBarIcon: ({ color }) => <MaterialIcons name="group" size={24} color={color} />,
title: 'Explore',
tabBarIcon: ({ color }) => <Entypo name="magnifying-glass" size={24} color={color} />,
}}
/>
<Tabs.Screen
name="map/index"
options={{
title: 'Map',
headerShown: false,
tabBarIcon: ({ color }) => <Entypo name="map" size={24} color={color} />,
}}
/>
Expand Down
19 changes: 0 additions & 19 deletions longhorn-studies/app/(app)/(protected)/(tabs)/groups/index.tsx

This file was deleted.

121 changes: 73 additions & 48 deletions longhorn-studies/app/(app)/(protected)/(tabs)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
import { Entypo } from '@expo/vector-icons';
import { FlashList } from '@shopify/flash-list';
import { LinearGradient } from 'expo-linear-gradient';
import { Link } from 'expo-router';
import { useEffect, useState } from 'react';
import { Text, View, Pressable, Image } from 'react-native';
import ShimmerPlaceHolder from 'react-native-shimmer-placeholder';

import { Button } from '~/components/Button';
import { Container } from '~/components/Container';
import {
PublicMediaRowSchema,
PublicSpotsRowSchema,
PublicSpotsWithDetailsRowSchema,
PublicTagsRowSchema,
} from '~/types/schemas_infer';
PublicMediaRowSchema,
} from '~/supabase/functions/new-spot/types/schemas_infer';
import { supabase } from '~/utils/supabase';

// Add tags and media to the spot schema
type Spot = PublicSpotsRowSchema & {
tags: PublicTagsRowSchema[];
media: PublicMediaRowSchema[];
};

const SpotCard = ({ spot }: { spot: Spot }) => {
const [imageBase64, setImageBase64] = useState<string | null>(null);
const SpotCard = ({ spot }: { spot: PublicSpotsWithDetailsRowSchema }) => {
const [image, setImage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);

const hasMedia = spot.media && spot.media.length > 0;

// Check if the spot has media and if so, fetch the first image
useEffect(() => {
const fetchImage = async () => {
const mediaItem = spot.media.find((m) => m.position === 0) || spot.media[0];
// Cast Json[] to PublicMediaRowSchema[]
let media;
try {
media = spot.media as PublicMediaRowSchema[];
} catch (error) {
console.error('Error casting media:', error);
setIsLoading(false);
return;
}

// Check if media is empty
if (!media || media.length === 0) {
setIsLoading(false);
return;
}

// Get the media with position 0 or the first media item
const mediaItem = media.find((m) => m && m && m.position === 0) || media[0];

supabase.storage
.from('media')
Expand All @@ -37,36 +49,52 @@ const SpotCard = ({ spot }: { spot: Spot }) => {
fr.readAsText(data!);
fr.onload = () => {
const result = `data:${data?.type};base64,${fr.result as string}`;
setImageBase64(result);
setImage(result);
setIsLoading(false);
};
fr.onerror = (error) => {
console.error('Error reading file:', error);
setIsLoading(false);
};
});
};
if (hasMedia) {
fetchImage();
}
}, [hasMedia]);

fetchImage();
}, []);

// Check if the spot has tags
let tags;
try {
tags = spot.tags as PublicTagsRowSchema[];
} catch (error) {
console.error('Error casting tags:', error);
return;
}

return (
<Pressable className="my-2 flex-row items-center gap-4 rounded-xl border border-gray-200 px-5 py-3">
<View>
{spot.media && spot.media.length > 0 ? (
{spot.media ? (
<View>
{/* Using useState and useEffect to load base64 image */}
{isLoading || !imageBase64 ? (
{/* Loading shimmer placeholder */}
{isLoading ? (
<ShimmerPlaceHolder
LinearGradient={LinearGradient}
style={{ height: 80, width: 80, borderRadius: 12 }}
/>
) : (
) : // Display the image if available
image ? (
<Image
style={{ height: 80, width: 80 }}
source={{ uri: imageBase64 }}
source={{ uri: image }}
className="rounded-xl"
/>
) : (
<View
style={{ height: 80, width: 80 }}
className="items-center justify-center rounded-xl bg-gray-200">
<Entypo name="image" size={18} />
</View>
)}
</View>
) : (
Expand All @@ -83,9 +111,9 @@ const SpotCard = ({ spot }: { spot: Spot }) => {
</Text>
)}

{spot.tags && spot.tags.length > 0 && (
{tags && tags.length > 0 && (
<View className="mt-3 flex-row flex-wrap gap-2">
{spot.tags.map((tag) => (
{tags.map((tag) => (
<View key={tag.id} className="rounded-full bg-amber-600 px-3 py-1">
<Text className="text-xs font-medium text-white">{tag.label}</Text>
</View>
Expand All @@ -97,25 +125,17 @@ const SpotCard = ({ spot }: { spot: Spot }) => {
);
};

export default function Home() {
const [spots, setSpots] = useState<Spot[]>([]);
export default function Explore() {
const [spots, setSpots] = useState<PublicSpotsWithDetailsRowSchema[]>([]);
const [loading, setLoading] = useState(true);

async function fetchSpots() {
setLoading(true);
try {
// Fetch spots with their tags and media
const { data, error } = await supabase
.from('spots')
.select(
`
*,
tags:spot_tags(
tag:tags(*)
),
media:media(*)
`
)
.from('spots_with_details')
.select('*')
.order('created_at', { ascending: false })
.limit(20);

Expand All @@ -124,16 +144,8 @@ export default function Home() {
return;
}

// Transform the data to match our Spot type
const spotsJoined = data.map((spot) => {
return {
...spot,
tags: spot.tags ? spot.tags.map((st: any) => st.tag).filter(Boolean) : [],
media: spot.media ? spot.media.filter(Boolean) : [],
};
});

setSpots(spotsJoined);
setSpots(data);
console.log('Explore fetched spots');
} catch (error) {
console.error('Error in fetchSpots:', error);
} finally {
Expand All @@ -144,8 +156,10 @@ export default function Home() {
useEffect(() => {
fetchSpots();
}, []);

return (
<Container>
{/* Spot Explorer */}
<View className="flex-1">
<Text className="text-2xl font-bold text-gray-800">Study Spots</Text>

Expand All @@ -167,6 +181,17 @@ export default function Home() {
/>
</ShimmerPlaceHolder>
</View>

{/* Floating New Spot Button */}
<Link href="/create-spot" asChild>
<Button
title="New Spot"
icon={<Entypo name="plus" size={18} color="white" />}
iconPosition="left"
className="absolute bottom-1 right-1 h-12 w-28 justify-center"
size="small"
/>
</Link>
</Container>
);
}
65 changes: 51 additions & 14 deletions longhorn-studies/app/(app)/(protected)/(tabs)/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,56 @@
import { View } from 'react-native';
import { AppleMaps, GoogleMaps } from 'expo-maps';
import { AppleMapsMarker } from 'expo-maps/build/apple/AppleMaps.types';
import { useFocusEffect } from 'expo-router';
import { useCallback, useState } from 'react';
import { Platform } from 'react-native';

import { Button } from '~/components/Button';
import { useAuth } from '~/store/AuthProvider';
import { PublicSpotsWithDetailsRowSchema } from '~/supabase/functions/new-spot/types/schemas_infer';
import { supabase } from '~/utils/supabase';

export default function Home() {
const { signOut } = useAuth();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button
title="Sign Out"
onPress={() => {
// The `app/(app)/_layout.tsx` will redirect to the sign-in screen.
signOut();
}}
/>
</View>
const [spots, setSpots] = useState<PublicSpotsWithDetailsRowSchema[] | null>(null);

// Fetch spots from Supabase whenever the screen comes into focus
useFocusEffect(
useCallback(() => {
const fetchSpots = async () => {
const { data, error } = await supabase.from('spots_with_details').select();

if (error) {
console.error('Error fetching spots:', error);
return;
}

if (data) {
console.log('Map fetched spots');
setSpots(data);
}
};

fetchSpots();
}, [])
);

return Platform.OS === 'ios' ? (
<AppleMaps.View
style={{ flex: 1 }}
cameraPosition={{ coordinates: { latitude: 30.285, longitude: -97.739 }, zoom: 14.5 }}
markers={
spots
? spots.map<AppleMapsMarker>((spot) => ({
id: spot.id ?? '',
title: spot.title ?? '',
tintColor: '#ff7603',
systemImage: 'bookmark.fill',
coordinates: {
latitude: spot.latitude ?? undefined,
longitude: spot.longitude ?? undefined,
},
}))
: undefined
}
/>
) : (
<GoogleMaps.View style={{ flex: 1 }} />
);
}
Loading