Skip to content

Commit 05f8f7c

Browse files
committed
2 parents 6ad40ab + 29bd846 commit 05f8f7c

37 files changed

+1381
-584
lines changed

frontend/app/(tabs)/shop.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,6 @@ export default function StoreScreen() {
339339
lightColor={Colors.light.transparent}
340340
darkColor={Colors.light.transparent}
341341
className="flex-1 px-4"
342-
style={{ paddingBottom: 110 }}
343342
>
344343
<SearchInputWithFilter
345344
value={searchText}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { View, Text, StyleSheet, Pressable, Alert } from 'react-native';
3+
import { SafeAreaView } from 'react-native-safe-area-context';
4+
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
5+
import { Event } from '@/types/api/event';
6+
import { eventService } from '@/services/eventService';
7+
import { LoadingScreen } from '@/components/LoadingScreen';
8+
import { Ionicons } from '@expo/vector-icons';
9+
import { Image } from 'expo-image';
10+
import {
11+
getEventRegistrations,
12+
unregister as unregisterRegistration,
13+
} from '@/services/registrationService';
14+
import { useAuth } from '@/context/AuthContext';
15+
import { useQueryClient } from '@tanstack/react-query';
16+
17+
export default function EventCancelPage() {
18+
const { eventId } = useLocalSearchParams();
19+
const router = useRouter();
20+
const { volunteer } = useAuth();
21+
const queryClient = useQueryClient();
22+
const [event, setEvent] = useState<Event | null>(null);
23+
const [loading, setLoading] = useState(true);
24+
const [cancelled, setCancelled] = useState(false);
25+
26+
useEffect(() => {
27+
const performCancellation = async () => {
28+
try {
29+
const eventData = await eventService.getEventById(eventId as string);
30+
setEvent(eventData);
31+
32+
if (volunteer?.id && eventData?.id) {
33+
const registrations = await getEventRegistrations(eventData.id);
34+
const myRegistration = registrations.find(
35+
r => r.volunteerId === volunteer.id
36+
);
37+
if (myRegistration) {
38+
await unregisterRegistration(myRegistration.id);
39+
await queryClient.invalidateQueries({
40+
queryKey: ['registration', 'events', volunteer.id, 'upcoming'],
41+
});
42+
setCancelled(true);
43+
}
44+
}
45+
} catch (error) {
46+
console.log('Error cancelling event:', error);
47+
Alert.alert(
48+
'Error',
49+
'Failed to cancel registration. Please try again.'
50+
);
51+
} finally {
52+
setLoading(false);
53+
}
54+
};
55+
56+
performCancellation();
57+
}, [eventId, volunteer?.id]);
58+
59+
if (loading) {
60+
return <LoadingScreen text="Cancelling registration..." />;
61+
}
62+
63+
if (!event || !cancelled) {
64+
return (
65+
<SafeAreaView style={styles.container}>
66+
<Text style={styles.errorText}>Unable to cancel registration</Text>
67+
</SafeAreaView>
68+
);
69+
}
70+
71+
const start = event.startDateTime ? new Date(event.startDateTime) : null;
72+
const end = event.endDateTime ? new Date(event.endDateTime) : null;
73+
74+
const dateFormatted = start
75+
? start.toLocaleDateString(undefined, {
76+
month: 'long',
77+
day: 'numeric',
78+
})
79+
: '';
80+
81+
const timeFormatted =
82+
start && end
83+
? `${start.toLocaleTimeString(undefined, {
84+
hour: 'numeric',
85+
minute: '2-digit',
86+
})} - ${end.toLocaleTimeString(undefined, {
87+
hour: 'numeric',
88+
minute: '2-digit',
89+
})}`
90+
: '';
91+
92+
return (
93+
<>
94+
<Stack.Screen options={{ headerShown: false }} />
95+
<SafeAreaView style={styles.container}>
96+
<View style={styles.content}>
97+
<View style={styles.iconContainer}>
98+
<Image
99+
source={require('@/assets/images/ship-wheel.svg')}
100+
style={styles.wheelIcon}
101+
contentFit="contain"
102+
/>
103+
</View>
104+
105+
<Text style={styles.title}>Course Changed...</Text>
106+
107+
<View style={styles.infoSection}>
108+
<View style={styles.infoRow}>
109+
<Ionicons name="calendar-outline" size={18} color="#1D0F48" />
110+
<Text style={styles.infoLabel}>Date:</Text>
111+
<Text style={styles.infoValue}>{dateFormatted}</Text>
112+
</View>
113+
<View style={styles.infoRow}>
114+
<Ionicons name="time-outline" size={18} color="#1D0F48" />
115+
<Text style={styles.infoLabel}>Time:</Text>
116+
<Text style={styles.infoValue}>{timeFormatted}</Text>
117+
</View>
118+
</View>
119+
120+
<Text style={styles.cancelMessage}>
121+
Your sign-up is successfully cancelled.
122+
</Text>
123+
124+
<Text style={styles.apologyMessage}>
125+
We&apos;re sorry to see you go.{'\n'}
126+
Hopefully we&apos;ll see you next time!
127+
</Text>
128+
129+
<Pressable
130+
style={styles.myEventsButton}
131+
onPress={() => router.replace('/')}
132+
>
133+
<Text style={styles.myEventsButtonText}>Go To My Events</Text>
134+
</Pressable>
135+
</View>
136+
</SafeAreaView>
137+
</>
138+
);
139+
}
140+
141+
const styles = StyleSheet.create({
142+
container: {
143+
flex: 1,
144+
backgroundColor: '#FFFDFA',
145+
},
146+
content: {
147+
flex: 1,
148+
paddingHorizontal: 33,
149+
paddingTop: 60,
150+
alignItems: 'center',
151+
},
152+
iconContainer: {
153+
marginBottom: 32,
154+
alignItems: 'center',
155+
justifyContent: 'center',
156+
},
157+
wheelIcon: {
158+
width: 109,
159+
height: 109,
160+
},
161+
title: {
162+
fontFamily: 'Ubuntu',
163+
fontSize: 48,
164+
fontWeight: '700',
165+
color: '#1D0F48',
166+
textAlign: 'center',
167+
marginBottom: 40,
168+
lineHeight: 60,
169+
},
170+
infoSection: {
171+
width: '100%',
172+
alignItems: 'center',
173+
gap: 12,
174+
marginBottom: 32,
175+
},
176+
infoRow: {
177+
flexDirection: 'row',
178+
alignItems: 'center',
179+
justifyContent: 'center',
180+
gap: 8,
181+
},
182+
infoLabel: {
183+
fontFamily: 'Inter',
184+
fontSize: 18,
185+
fontWeight: '700',
186+
color: '#1D0F48',
187+
},
188+
infoValue: {
189+
fontFamily: 'Inter',
190+
fontSize: 18,
191+
fontWeight: '400',
192+
color: '#1D0F48',
193+
},
194+
cancelMessage: {
195+
fontFamily: 'Inter',
196+
fontSize: 16,
197+
fontWeight: '700',
198+
color: '#1D0F48',
199+
textAlign: 'center',
200+
marginBottom: 16,
201+
},
202+
apologyMessage: {
203+
fontFamily: 'Inter',
204+
fontSize: 16,
205+
fontWeight: '400',
206+
color: '#1D0F48',
207+
textAlign: 'center',
208+
lineHeight: 24,
209+
marginBottom: 138,
210+
},
211+
myEventsButton: {
212+
backgroundColor: '#74C0EB',
213+
borderRadius: 16.333,
214+
paddingVertical: 13,
215+
paddingHorizontal: 33,
216+
alignItems: 'center',
217+
},
218+
myEventsButtonText: {
219+
fontFamily: 'Inter',
220+
fontSize: 18,
221+
fontWeight: '700',
222+
color: '#1D0F48',
223+
},
224+
errorText: {
225+
fontFamily: 'Inter',
226+
fontSize: 16,
227+
color: '#FF6B6B',
228+
textAlign: 'center',
229+
},
230+
});

0 commit comments

Comments
 (0)