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 vynl/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"slug": "vynl",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"icon": "./assets/images/logo.png",
"scheme": "vynl",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
Expand Down
136 changes: 129 additions & 7 deletions vynl/src/app/(tabs)/SignupPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { useState } from "react";
import React, { useState, useMemo } from "react";
import { View, Text, StyleSheet, useColorScheme } from "react-native";
import { Colors } from '@/src/constants/theme';
import { Link } from 'expo-router';
import { Link, useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import AppButton from "@/src/components/AppButton"; // make sure path is correct
import InputField from '@/src/components/InputField';
import { validatePassword } from "@/scripts/validatePassword";
import { passwordErrorMessages } from "@/scripts/validatePassword";
import { supabase } from '@/src/utils/supabase';
import { useAuth } from '@/src/context/auth-context';

interface FormData {
email: string;
Expand All @@ -15,15 +17,31 @@ interface FormData {
}

const SignupPage: React.FC = () => {
const router = useRouter();
const { login } = useAuth();
const [formData, setFormData] = useState<FormData>({ email: "", password: "" , confirmPassword: ""});
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});
const [isLoading, setIsLoading] = useState(false);

const handleChange = (name: keyof FormData, value: string) => {
setFormData({ ...formData, [name]: value });
// clear error for this field when user edits it
setErrors((prev) => ({ ...prev, [name]: undefined }));
};

// Check individual password requirements
const passwordRequirements = useMemo(() => {
const password = formData.password;
return {
minLength: password.length >= 8,
maxLength: password.length <= 35,
hasNumber: /\d/.test(password),
hasSpecialChar: /[^A-Za-z0-9]/.test(password),
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
};
}, [formData.password]);

const handleSubmit = async () => {
// Validate email format
const email = formData.email.trim();
Expand All @@ -48,9 +66,8 @@ const SignupPage: React.FC = () => {

// Clear errors and proceed
setErrors({});
console.log("Form Data:", formData);
// Later: call API from services/api.ts
console.log('HERE');
setIsLoading(true);

try {
const { data, error: signupError } = await supabase.auth.signUp({
email: formData.email,
Expand All @@ -59,15 +76,32 @@ const SignupPage: React.FC = () => {
emailRedirectTo: 'https://example.com/welcome',
},
});

if (signupError) {
console.error('Sign up error:', signupError);
setErrors((prev) => ({ ...prev, email: 'Sign up failed. Please try again.' }));
setIsLoading(false);
return;
}

console.log('Sign up data:', data);

// After successful signup, log the user in automatically
try {
await login(formData.email, formData.password);
console.log("Signed up and logged in successfully");
// Navigate to home screen after successful signup and login
router.push('/(tabs)');
} catch (loginError: any) {
console.error('Auto-login error after signup:', loginError);
// If auto-login fails, still show success but user may need to log in manually
setErrors((prev) => ({ ...prev, email: 'Account created, but auto-login failed. Please log in manually.' }));
}
} catch (err) {
console.error('Unexpected sign up error:', err);
setErrors((prev) => ({ ...prev, email: 'Unexpected error. Please try again.' }));
} finally {
setIsLoading(false);
}
};

Expand All @@ -93,7 +127,26 @@ const SignupPage: React.FC = () => {
},
inputcontainer: {
marginVertical: 80,
marginBottom: 180,
marginBottom: 20,
},
requirementsContainer: {
marginTop: 8,
marginBottom: 20,
paddingLeft: 4,
},
requirementRow: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 4,
},
requirementText: {
fontSize: 13,
color: colors.text,
marginLeft: 8,
opacity: 0.7,
},
requirementTextMet: {
opacity: 1,
},
loginText: {
textAlign: 'center',
Expand Down Expand Up @@ -127,6 +180,70 @@ const SignupPage: React.FC = () => {
height={55}
error={errors.password}
/>
{formData.password.length > 0 && (
<View style={dynamicStyles.requirementsContainer}>
<View style={dynamicStyles.requirementRow}>
<Ionicons
name={passwordRequirements.minLength ? "checkmark-circle" : "close-circle"}
size={16}
color={passwordRequirements.minLength ? "#4CAF50" : "#F44336"}
/>
<Text style={[dynamicStyles.requirementText, passwordRequirements.minLength && dynamicStyles.requirementTextMet]}>
At least 8 characters
</Text>
</View>
<View style={dynamicStyles.requirementRow}>
<Ionicons
name={passwordRequirements.maxLength ? "checkmark-circle" : "close-circle"}
size={16}
color={passwordRequirements.maxLength ? "#4CAF50" : "#F44336"}
/>
<Text style={[dynamicStyles.requirementText, passwordRequirements.maxLength && dynamicStyles.requirementTextMet]}>
Less than 35 characters
</Text>
</View>
<View style={dynamicStyles.requirementRow}>
<Ionicons
name={passwordRequirements.hasNumber ? "checkmark-circle" : "close-circle"}
size={16}
color={passwordRequirements.hasNumber ? "#4CAF50" : "#F44336"}
/>
<Text style={[dynamicStyles.requirementText, passwordRequirements.hasNumber && dynamicStyles.requirementTextMet]}>
At least one number
</Text>
</View>
<View style={dynamicStyles.requirementRow}>
<Ionicons
name={passwordRequirements.hasSpecialChar ? "checkmark-circle" : "close-circle"}
size={16}
color={passwordRequirements.hasSpecialChar ? "#4CAF50" : "#F44336"}
/>
<Text style={[dynamicStyles.requirementText, passwordRequirements.hasSpecialChar && dynamicStyles.requirementTextMet]}>
At least one special character
</Text>
</View>
<View style={dynamicStyles.requirementRow}>
<Ionicons
name={passwordRequirements.hasUppercase ? "checkmark-circle" : "close-circle"}
size={16}
color={passwordRequirements.hasUppercase ? "#4CAF50" : "#F44336"}
/>
<Text style={[dynamicStyles.requirementText, passwordRequirements.hasUppercase && dynamicStyles.requirementTextMet]}>
At least one uppercase letter
</Text>
</View>
<View style={dynamicStyles.requirementRow}>
<Ionicons
name={passwordRequirements.hasLowercase ? "checkmark-circle" : "close-circle"}
size={16}
color={passwordRequirements.hasLowercase ? "#4CAF50" : "#F44336"}
/>
<Text style={[dynamicStyles.requirementText, passwordRequirements.hasLowercase && dynamicStyles.requirementTextMet]}>
At least one lowercase letter
</Text>
</View>
</View>
)}
<InputField
label="Confirm password"
value={formData.confirmPassword}
Expand All @@ -137,7 +254,12 @@ const SignupPage: React.FC = () => {
error={errors.confirmPassword}
/>
</View>
<AppButton title="Sign Up" onPress={handleSubmit} backgroundColor= {colors.primary} />
<AppButton
title={isLoading ? "Signing up..." : "Sign Up"}
onPress={handleSubmit}
backgroundColor={colors.primary}
disabled={isLoading}
/>
<Text style={dynamicStyles.loginText}>
Already have an account?{' '}
<Link href="/(tabs)/LoginPage" style={dynamicStyles.loginLink}>
Expand Down
59 changes: 51 additions & 8 deletions vynl/src/app/(tabs)/playlists.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useCallback, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { LinearGradient } from 'expo-linear-gradient';
import { Image as ExpoImage } from 'expo-image';
Expand All @@ -9,13 +9,15 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { useUser } from '@/src/hooks/use-user';
import { useUserPlaylists } from '@/src/hooks/use-playlist-for-user';
import { usePartyPlaylists } from '@/src/hooks/use-party-playlists';
import { useAuth } from '@/src/context/auth-context';

const PARTY_CODE_STORAGE_KEY = '@vynl:partyCode';

export default function PlaylistsScreen() {
const router = useRouter();
const insets = useSafeAreaInsets();
const { user, loading: authLoading } = useUser();
const { authToken } = useAuth();
const uid = user?.id;

const { playlists, loading: playlistLoading, error, refetch } = useUserPlaylists(uid ?? null);
Expand Down Expand Up @@ -99,12 +101,53 @@ export default function PlaylistsScreen() {
}, [refetch, uid])
);

const handleDelete = async (playlistId: number) => {
try {
//TODO : implement
} catch (error) {
console.error('Error deleting playlist:', error);
}
const handleDelete = (playlistId: number, playlistName: string) => {
Alert.alert(
'Delete Playlist',
`Are you sure you want to delete "${playlistName}"? This action cannot be undone.`,
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Delete',
style: 'destructive',
onPress: async () => {
try {
if (!authToken) {
console.error('No auth token available');
Alert.alert('Error', 'Authentication failed. Please try again.');
return;
}

const res = await fetch(`/api/playlist/${playlistId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
},
});

if (!res.ok) {
const errorText = await res.text();
console.error('Failed to delete playlist:', errorText);
Alert.alert('Error', 'Failed to delete playlist. Please try again.');
return;
}

// Refresh the playlists after successful deletion
if (refetch) {
refetch();
}
} catch (error) {
console.error('Error deleting playlist:', error);
Alert.alert('Error', 'An error occurred while deleting the playlist. Please try again.');
}
},
},
]
);
};

const formatDate = (timestamp: number) => {
Expand Down Expand Up @@ -184,7 +227,7 @@ export default function PlaylistsScreen() {
<TouchableOpacity
onPress={(e) => {
e.stopPropagation();
handleDelete(p.id);
handleDelete(p.id, p.name);
}}
style={styles.deleteButton}
>
Expand Down
Loading
Loading