mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
Update
This commit is contained in:
1 parent
d1a2032724
commit
dfcf0ba289
10 files changed
+630
-28
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -40,7 +40,17 @@ const GENDER_OPTIONS: { value: Gender; label: string; icon: string }[] = [
|
||||
{ value: 'prefer-not-to-say', label: 'Skip', icon: 'remove-circle-outline' },
|
||||
];
|
||||
|
||||
type Stage = 'name' | 'gender' | 'explicit' | 'notifications';
|
||||
type UsageReason = 'improve_communication' | 'spice_up_intimacy' | 'deeper_connection' | 'have_fun' | 'strengthen_relationship';
|
||||
|
||||
const PURPOSE_OPTIONS: { value: UsageReason; label: string; icon: string }[] = [
|
||||
{ value: 'improve_communication', label: 'Improve communication', icon: 'chatbubbles' },
|
||||
{ value: 'spice_up_intimacy', label: 'Spice up intimacy', icon: 'flame' },
|
||||
{ value: 'deeper_connection', label: 'Build deeper connection', icon: 'heart' },
|
||||
{ value: 'have_fun', label: 'Have fun together', icon: 'happy' },
|
||||
{ value: 'strengthen_relationship', label: 'Strengthen our relationship', icon: 'shield-checkmark' },
|
||||
];
|
||||
|
||||
type Stage = 'name' | 'gender' | 'purpose' | 'explicit' | 'notifications';
|
||||
|
||||
export default function OnboardingScreen() {
|
||||
const router = useRouter();
|
||||
@@ -48,11 +58,16 @@ export default function OnboardingScreen() {
|
||||
const [stage, setStage] = useState<Stage>('name');
|
||||
const [name, setName] = useState(user?.name || '');
|
||||
const [gender, setGender] = useState<Gender | null>(user?.gender || null);
|
||||
const [usageReason, setUsageReason] = useState<UsageReason | null>(null);
|
||||
const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
|
||||
useEffect(() => {
|
||||
Events.onboardingStart();
|
||||
}, []);
|
||||
|
||||
const handleNameSubmit = () => {
|
||||
if (!name.trim()) {
|
||||
setError('Please enter your name');
|
||||
@@ -74,10 +89,24 @@ export default function OnboardingScreen() {
|
||||
setError('Please select a gender or skip');
|
||||
return;
|
||||
}
|
||||
setStage('explicit');
|
||||
setStage('purpose');
|
||||
Events.onboardingStageComplete('gender');
|
||||
};
|
||||
|
||||
const handlePurposeSelect = (selectedReason: UsageReason) => {
|
||||
setUsageReason(selectedReason);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const handlePurposeContinue = () => {
|
||||
if (!usageReason) {
|
||||
setError('Please select what brings you to Sauci');
|
||||
return;
|
||||
}
|
||||
setStage('explicit');
|
||||
Events.onboardingStageComplete('purpose');
|
||||
};
|
||||
|
||||
const handleExplicitContinue = () => {
|
||||
setStage('notifications');
|
||||
Events.onboardingStageComplete('explicit');
|
||||
@@ -115,6 +144,7 @@ export default function OnboardingScreen() {
|
||||
.update({
|
||||
name: name.trim(),
|
||||
gender,
|
||||
usage_reason: usageReason,
|
||||
show_explicit_content: showExplicit,
|
||||
onboarding_completed: true,
|
||||
updated_at: new Date().toISOString(),
|
||||
@@ -260,6 +290,82 @@ export default function OnboardingScreen() {
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
case 'purpose':
|
||||
return (
|
||||
<Animated.View
|
||||
key="purpose"
|
||||
entering={SlideInRight.duration(400)}
|
||||
exiting={SlideOutLeft.duration(300)}
|
||||
style={styles.stageContainer}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<LinearGradient
|
||||
colors={gradients.primary as [string, string]}
|
||||
style={styles.iconContainer}
|
||||
>
|
||||
<Ionicons name="sparkles" size={40} color={colors.text} />
|
||||
</LinearGradient>
|
||||
<Text style={styles.title}>What brings you here?</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
This helps us personalize your experience
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<GlassCard style={styles.card}>
|
||||
<Text style={styles.label}>I'm using Sauci to...</Text>
|
||||
<View style={styles.purposeList}>
|
||||
{PURPOSE_OPTIONS.map((option) => (
|
||||
<Pressable
|
||||
key={option.value}
|
||||
style={[
|
||||
styles.purposeOption,
|
||||
usageReason === option.value && styles.purposeOptionSelected,
|
||||
]}
|
||||
onPress={() => handlePurposeSelect(option.value)}
|
||||
>
|
||||
<View style={[
|
||||
styles.purposeIcon,
|
||||
usageReason === option.value && styles.purposeIconSelected
|
||||
]}>
|
||||
<Ionicons
|
||||
name={option.icon as any}
|
||||
size={24}
|
||||
color={usageReason === option.value ? colors.primary : colors.textSecondary}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
styles.purposeLabel,
|
||||
usageReason === option.value && styles.purposeLabelSelected,
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
</Text>
|
||||
<View style={[
|
||||
styles.radioOuter,
|
||||
usageReason === option.value && styles.radioOuterSelected
|
||||
]}>
|
||||
{usageReason === option.value && <View style={styles.radioInner} />}
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
{error && (
|
||||
<View style={styles.errorContainer}>
|
||||
<Ionicons name="alert-circle" size={16} color={colors.error} />
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
</View>
|
||||
)}
|
||||
</GlassCard>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<GlassButton onPress={handlePurposeContinue} fullWidth size="lg">
|
||||
Continue
|
||||
</GlassButton>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
case 'explicit':
|
||||
return (
|
||||
<Animated.View
|
||||
@@ -401,7 +507,7 @@ export default function OnboardingScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.notificationFeature}>
|
||||
<View style={[styles.notificationFeature, styles.notificationFeatureLast]}>
|
||||
<View style={styles.notificationIcon}>
|
||||
<Ionicons name="chatbubble" size={24} color={colors.primary} />
|
||||
</View>
|
||||
@@ -456,7 +562,8 @@ export default function OnboardingScreen() {
|
||||
style={styles.backButton}
|
||||
onPress={() => {
|
||||
if (stage === 'notifications') setStage('explicit');
|
||||
else if (stage === 'explicit') setStage('gender');
|
||||
else if (stage === 'explicit') setStage('purpose');
|
||||
else if (stage === 'purpose') setStage('gender');
|
||||
else setStage('name');
|
||||
}}
|
||||
>
|
||||
@@ -468,6 +575,7 @@ export default function OnboardingScreen() {
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={[styles.progressDot, stage === 'name' && styles.progressDotActive]} />
|
||||
<View style={[styles.progressDot, stage === 'gender' && styles.progressDotActive]} />
|
||||
<View style={[styles.progressDot, stage === 'purpose' && styles.progressDotActive]} />
|
||||
<View style={[styles.progressDot, stage === 'explicit' && styles.progressDotActive]} />
|
||||
<View style={[styles.progressDot, stage === 'notifications' && styles.progressDotActive]} />
|
||||
</View>
|
||||
@@ -617,6 +725,43 @@ const styles = StyleSheet.create({
|
||||
color: colors.primary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
purposeList: {
|
||||
gap: spacing.sm,
|
||||
},
|
||||
purposeOption: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.glass.background,
|
||||
borderRadius: radius.lg,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.glass.border,
|
||||
padding: spacing.md,
|
||||
},
|
||||
purposeOptionSelected: {
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.primaryLight,
|
||||
},
|
||||
purposeIcon: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
backgroundColor: colors.glass.background,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
purposeIconSelected: {
|
||||
backgroundColor: colors.primaryLight,
|
||||
},
|
||||
purposeLabel: {
|
||||
...typography.subhead,
|
||||
color: colors.textSecondary,
|
||||
flex: 1,
|
||||
},
|
||||
purposeLabelSelected: {
|
||||
color: colors.text,
|
||||
fontWeight: '600',
|
||||
},
|
||||
explicitOption: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -696,6 +841,9 @@ const styles = StyleSheet.create({
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.glass.border,
|
||||
},
|
||||
notificationFeatureLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
notificationIcon: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
|
||||
Reference in new issue
Block a user