Skip to content

Commit

Permalink
Merge pull request #3206 from KBVE/patch-atomic-preparing-the-expo-pr…
Browse files Browse the repository at this point in the history
…ofile-update-11-10-2024-1731253886

[CI] Merge patch-atomic-preparing-the-expo-profile-update-11-10-2024-1731253886 into dev
  • Loading branch information
h0lybyte authored Nov 10, 2024
2 parents d298547 + a2043ed commit 7d95c3e
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 2 deletions.
1 change: 1 addition & 0 deletions apps/expo-lcagents/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function RootLayoutNav() {
<Stack.Screen name="register" options={{ animation: 'fade' }} />
<Stack.Screen name="login" options={{ animation: 'fade' }} />
<Stack.Screen name="profile" options={{ animation: 'fade' }} />


</Stack>

Expand Down
119 changes: 119 additions & 0 deletions apps/expo-lcagents/src/app/onboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { useMemo, useCallback, useEffect, useState, useRef } from 'react';
import { YStack, XStack, SizableText, Separator, ScrollView, Button } from 'tamagui';
import { TamaOnboard, LottieAnimation, TamaSheet, useBBQ } from '@kbve/expo-bbq';
import { useNavigation } from 'expo-router';

const Onboard = () => {

// [C ~~> Nav[]]
const navigation = useNavigation();
const router = useBBQ();
// [Sheet]
const sheetRef = useRef<{
showSheet: () => void;
hideSheet: () => void;
} | null>(null);

// [States]
const [sheetMessage, setSheetMessage] = useState('');
const [isComplete, setIsComplete] = useState(false);
const [hasError, setHasError] = useState(false);

// [Lottie] => (Memo) -> JSON animation for the onboarding screen, since its a static asset.
const lottieOnboardAnimation = useMemo(
() => require('../../assets/json/360-vr.json'),
[],
);

// [Navigation] => (useCallback) <~> (useEffect)
const updateNavigationOptions = useCallback(() => {
navigation.setOptions({
title: 'Onboarding',
headerBackTitle: 'Back',
});

}, [navigation]);

useEffect(
() => {
updateNavigationOptions();
}, [updateNavigationOptions]
);

// [onSuccess] ?=>
const handleSuccess = () => {
setIsComplete(true);
setHasError(false);
// [i18n] ?? NULL => TamaI18N('[onboard_true']); ($->trigger)
setSheetMessage('Onboarding completed successfully!');
if(sheetRef.current) {
sheetRef.current.showSheet();
}
};

// [onError] ?=>
const handleError = (error: string) => {
setIsComplete(false);
setHasError(true);
// [i18n] ?? NULL => TamaI18N('[onboard_error']); ($->trigger)[error:key]
if(sheetRef.current) {
sheetRef.current.showSheet();
}
};


const renderSheetContent = () => {
return (
<YStack ai="center" jc="center" paddingVertical={10}>
<SizableText>{sheetMessage}</SizableText>
{isComplete && !hasError && (
<Button
onPress={() => {
if (sheetRef.current) {
sheetRef.current.hideSheet();
}
router.go('/profile'); // Navigate to the profile page after successful onboarding
}}
color="green"
size="$4"
marginTop={10}
>
Go to Profile
</Button>
)}
</YStack>
);
};

const MemoizedLottieAnimation = React.memo(LottieAnimation);

return (
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
<YStack f={1} jc="center" ai="center" padding="$4">
<SizableText size="$4" theme="alt2">Welcome to Onboarding</SizableText>
<MemoizedLottieAnimation
lottieJSON={lottieOnboardAnimation}
style={{
width: 150,
height: 'auto',
aspectRatio: 1,
maxWidth: 800,
}}
/>
<TamaOnboard
supabaseUrl="https://supabase.kbve.com"
supabaseAnonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzI0NTM2ODAwLAogICJleHAiOiAxODgyMzAzMjAwCn0._fmEmblm0afeLoPXxt8wP2mYpa9gzU-ufx3v8oRTFGg"
onSuccess={handleSuccess}
onError={handleError}
/>
</YStack>

<TamaSheet ref={sheetRef} title="Onboarding Status">
{renderSheetContent()}
</TamaSheet>
</ScrollView>
);

};

export default Onboard;
11 changes: 9 additions & 2 deletions packages/expo-bbq/src/components/auth/TamaProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,15 @@ export function TamaProfile({
.eq('id', session.session.user.id) // Use session.user.id for filtering
.single();

if (profileError) {
throw profileError;
// 11-10-2024 - Going to redirect the user to the onboarding.
// if (profileError) {
// throw profileError;
// }

if (profileError || !profileData) {
// If no profile exists for the new user, redirect to onboarding
router.replace('/onboard');
return;
}

setUsername(
Expand Down
174 changes: 174 additions & 0 deletions packages/expo-bbq/src/components/auth/user/TamaOnboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { YStack, Input, Button, TextArea, Text } from 'tamagui';
import { createSupabaseClient } from '../../wrapper/Supabase';

export function TamaOnboard({
supabaseUrl,
supabaseAnonKey,
onSuccess,
onError,
}: {
supabaseUrl: string;
supabaseAnonKey: string;
onSuccess?: () => void;
onError?: (error: string) => void;
}) {

// [States]
const [step, setStep] = useState(1);
const [username, setUsername] = useState('');
const [bio, setBio] = useState('');
const [socials, setSocials] = useState('');
const [style, setStyle] = useState('');
const [loading, setLoading] = useState(true);


// [Supabase]
const supabase = useMemo(
() => createSupabaseClient(supabaseUrl, supabaseAnonKey),
[supabaseUrl, supabaseAnonKey],
);

// [User] -> Memoize the user object.
// const user = useMemo(async () => {
// return (await supabase.auth.getUser())?.data.user;
// }, [supabase]);


// Check if username is already set
useEffect(() => {
const checkExistingUsername = async () => {
try {
const user = (await supabase.auth.getUser())?.data.user;
if (!user) throw new Error('User data not found');

const { data, error } = await supabase
.from('user_profiles')
.select('username')
.eq('id', user.id)
.single();

if (error) throw new Error(error.message);

// If username exists, skip to step 2
if (data?.username) {
setStep(2);
}
} catch (err) {
if (onError) onError('Failed to check username. Please try again.');
console.error(err);
} finally {
setLoading(false);
}
};

checkExistingUsername();
}, [supabase, onError]);

// Handler to submit the username
const handleUsernameSubmit = useCallback(async () => {
try {
const user = (await supabase.auth.getUser())?.data.user;
if (!user) throw new Error('User data not found');

// Lowercase the username for consistency
const lowercasedUsername = username.toLowerCase();


// Call the stored function to handle the new user profile
const { data, error } = await supabase
.rpc('create_user_profile', { _username: lowercasedUsername });


if (error) throw new Error(JSON.stringify(error));

// Check if data was returned to confirm successful update
if (data && data.length > 0) {
setStep(2); // Move to the next step for bio/socials
} else {
throw new Error('Update failed, no data returned');
}
} catch (err) {
if (onError) onError('Failed to set username. Please try again.');
console.error(err);
}
}, [username, supabase, onError]);

// Handler to submit the user card details
const handleUserCardSubmit = useCallback(async () => {
try {
const user = (await supabase.auth.getUser())?.data.user;
if (!user) throw new Error('User data not found');

// Prepare the payload, excluding `socials` and `style` if they're empty
const payload: Record<string, any> = { id: user.id, bio };
if (socials) payload.socials = socials;
if (style) payload.style = style;

// Use upsert to insert or update the row
const { data, error } = await supabase
.from('user_cards')
.upsert(payload, { onConflict: 'id' }) // Handle conflicts based on the user's id
.select();

if (error) throw new Error(JSON.stringify(error));
if (!data || data.length === 0) throw new Error('Failed to save user card details');

if (onSuccess) onSuccess();
} catch (err) {
if (onError) onError('Failed to save user card details. Please try again.');
console.error(err);
}
}, [bio, socials, style, supabase, onSuccess, onError]);


if (loading) {
return <Text>Loading...</Text>;
}

return (
<YStack
padding="$4"
alignItems="center"
justifyContent="center"
flex={1}
gap={2}>
{step === 1 ? (
<>
<Text>Please choose your public username:</Text>
<Input
value={username}
onChangeText={setUsername}
placeholder="Username"
/>

<Button onPress={handleUsernameSubmit}>Next</Button>
</>
) : (
<>
<Text>Tell us more about yourself:</Text>
<TextArea
value={bio}
onChangeText={setBio}
placeholder="Write a short bio"
/>
<Input
value={socials}
onChangeText={setSocials}
placeholder="Your social links"
/>
<Input
value={style}
onChangeText={setStyle}
placeholder="Preferred style"
/>
<Button onPress={handleUserCardSubmit}>
Complete Onboarding
</Button>
</>
)}
</YStack>
);
}

export default TamaOnboard;
1 change: 1 addition & 0 deletions packages/expo-bbq/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { TamaRegister } from './components/auth/TamaRegister';
export { TamaLogin } from './components/auth/TamaLogin';
export { TamaProfile } from './components/auth/TamaProfile';
export { TamaUserCard } from './components/auth/user/TamaUserCard';
export { TamaOnboard } from './components/auth/user/TamaOnboard';

// [PANELS]
export { TamaSheet } from './components/panels/TamaSheet';
Expand Down

0 comments on commit 7d95c3e

Please sign in to comment.