mirror of
https://github.com/urosran/cally.git
synced 2026-03-10 18:51:42 +00:00
New onboarding flow, calendar sync logic refactor
This commit is contained in:
246
app/(unauth)/cal_sync.tsx
Normal file
246
app/(unauth)/cal_sync.tsx
Normal file
@ -0,0 +1,246 @@
|
||||
import {SafeAreaView} from "react-native-safe-area-context";
|
||||
import {Button, Text, View} from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import {useCalSync} from "@/hooks/useCalSync";
|
||||
import GoogleIcon from "@/assets/svgs/GoogleIcon";
|
||||
import AppleIcon from "@/assets/svgs/AppleIcon";
|
||||
import OutlookIcon from "@/assets/svgs/OutlookIcon";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {StyleSheet} from "react-native";
|
||||
|
||||
export default function Screen() {
|
||||
const {profileData, setRedirectOverride} = useAuthContext()
|
||||
const {handleStartGoogleSignIn, handleAppleSignIn, handleMicrosoftSignIn} = useCalSync()
|
||||
|
||||
const hasSomeCalendarsSynced =
|
||||
!!profileData?.appleAccounts || !!profileData?.microsoftAccounts || !!profileData?.googleAccounts
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{flex: 1}}>
|
||||
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%", alignItems: "center"}}>
|
||||
<View gap-13 width={"100%"} marginB-20>
|
||||
<Text style={{fontSize: 40, fontFamily: 'Manrope_600SemiBold'}}>
|
||||
Let's get started!
|
||||
</Text>
|
||||
<Text color={"#919191"} style={{fontSize: 20}}>
|
||||
Add your calendar below to sync events to your Cally calendar
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View width={"100%"} gap-1>
|
||||
{!profileData?.googleAccounts && (
|
||||
<Button
|
||||
onPress={() => handleStartGoogleSignIn()}
|
||||
label={"Connect Google account"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2,
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)}
|
||||
|
||||
{profileData?.googleAccounts
|
||||
? Object.keys(profileData?.googleAccounts)?.map((googleMail) => {
|
||||
const googleToken = profileData?.googleAccounts?.[googleMail]?.accessToken;
|
||||
return (
|
||||
googleToken && (
|
||||
<Button
|
||||
key={googleMail}
|
||||
disabled
|
||||
label={`Connected ${googleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2,
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)
|
||||
);
|
||||
})
|
||||
: null}
|
||||
|
||||
{!profileData?.appleAccounts && (
|
||||
<Button
|
||||
onPress={() => handleAppleSignIn()}
|
||||
label={"Connect Apple"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2,
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)}
|
||||
|
||||
{profileData?.appleAccounts
|
||||
? Object.keys(profileData?.appleAccounts)?.map((appleEmail) => {
|
||||
const appleToken = profileData?.appleAccounts?.[appleEmail!];
|
||||
return (
|
||||
appleToken && (
|
||||
<Button
|
||||
key={appleEmail}
|
||||
disabled
|
||||
label={`Connected Apple Calendar`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2,
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)
|
||||
);
|
||||
})
|
||||
: null}
|
||||
|
||||
{!profileData?.microsoftAccounts && (
|
||||
<Button
|
||||
onPress={() => handleMicrosoftSignIn()}
|
||||
label={"Connect Outlook"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2,
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)}
|
||||
|
||||
{profileData?.microsoftAccounts
|
||||
? Object.keys(profileData?.microsoftAccounts)?.map(
|
||||
(microsoftEmail) => {
|
||||
const microsoftToken =
|
||||
profileData?.microsoftAccounts?.[microsoftEmail];
|
||||
return (
|
||||
microsoftToken && (
|
||||
<Button
|
||||
key={microsoftEmail}
|
||||
|
||||
label={`Connected ${microsoftEmail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2,
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
: null}
|
||||
</View>
|
||||
|
||||
<View flexG/>
|
||||
|
||||
<View width={"100%"}>
|
||||
<Button
|
||||
label={hasSomeCalendarsSynced ? "Continue" : "Skip this step"}
|
||||
onPress={() => setRedirectOverride(false)}
|
||||
marginT-50
|
||||
labelStyle={{
|
||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||
fontSize: 16,
|
||||
}}
|
||||
style={{height: 50}}
|
||||
backgroundColor="#fd1775"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
addCalBtn: {
|
||||
backgroundColor: "#ffffff",
|
||||
marginBottom: 15,
|
||||
justifyContent: "flex-start",
|
||||
paddingLeft: 25,
|
||||
},
|
||||
backBtn: {
|
||||
backgroundColor: "red",
|
||||
marginLeft: -2,
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
padding: 20,
|
||||
paddingBottom: 30,
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
noPaddingCard: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 51,
|
||||
borderRadius: 12,
|
||||
},
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
},
|
||||
addCalLbl: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSan_500Medium",
|
||||
flexWrap: "wrap",
|
||||
width: "75%",
|
||||
textAlign: "left",
|
||||
lineHeight: 20,
|
||||
overflow: "visible",
|
||||
},
|
||||
subTitle: {
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
import Entry from "@/components/pages/main/Entry";
|
||||
|
||||
export default function Screen() {
|
||||
return <Entry />;
|
||||
return <Entry />;
|
||||
}
|
||||
166
app/(unauth)/get_started.tsx
Normal file
166
app/(unauth)/get_started.tsx
Normal file
@ -0,0 +1,166 @@
|
||||
import {SafeAreaView} from "react-native-safe-area-context";
|
||||
import {Button, Colors, Dialog, LoaderScreen, Text, View} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import {useRouter} from "expo-router";
|
||||
import QRIcon from "@/assets/svgs/QRIcon";
|
||||
import Toast from "react-native-toast-message";
|
||||
import {Camera, CameraView} from "expo-camera";
|
||||
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
|
||||
|
||||
export default function Screen() {
|
||||
const router = useRouter()
|
||||
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
||||
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
||||
|
||||
const {mutateAsync: signInWithQrCode, isLoading} = useLoginWithQrCode();
|
||||
|
||||
const handleQrCodeScanned = async ({data}: { data: string }) => {
|
||||
setShowCameraDialog(false);
|
||||
try {
|
||||
await signInWithQrCode({userId: data});
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Login successful with QR code!",
|
||||
});
|
||||
} catch (err) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Error logging in with QR code",
|
||||
text2: `${err}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getCameraPermissions = async (callback: () => void) => {
|
||||
const {status} = await Camera.requestCameraPermissionsAsync();
|
||||
setHasPermission(status === "granted");
|
||||
if (status === "granted") {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenQrCodeDialog = () => {
|
||||
getCameraPermissions(() => setShowCameraDialog(true));
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{flex: 1}}>
|
||||
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%", alignItems: "center"}}>
|
||||
<View center>
|
||||
<Text style={{fontSize: 30, fontFamily: 'Manrope_600SemiBold'}}>
|
||||
Get started with Cally
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View width={"100%"} gap-30>
|
||||
<View>
|
||||
<Button
|
||||
label="Scan QR Code"
|
||||
marginT-50
|
||||
labelStyle={{
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
marginLeft: 10
|
||||
}}
|
||||
iconSource={() => <QRIcon color={"#07B8C7"}/>}
|
||||
onPress={handleOpenQrCodeDialog}
|
||||
style={{height: 50}}
|
||||
color={Colors.black}
|
||||
backgroundColor={Colors.white}
|
||||
/>
|
||||
{/* GOOGLE LOGIN HERE */}
|
||||
</View>
|
||||
|
||||
<View row center gap-20>
|
||||
<View flexG style={{backgroundColor: "#E2E2E2", height: 2}}/>
|
||||
<Text style={{fontSize: 16, fontFamily: 'PlusJakartaSans_300Light', color: "#7A7A7A"}}>
|
||||
or
|
||||
</Text>
|
||||
<View flexG style={{backgroundColor: "#E2E2E2", height: 2}}/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Button
|
||||
label="Contine with Email"
|
||||
labelStyle={{
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
marginLeft: 10
|
||||
}}
|
||||
onPress={() => router.push("/(unauth)/sign_up")}
|
||||
style={{height: 50, borderStyle: "solid", borderColor: "#E2E2E2", borderWidth: 2}}
|
||||
color={Colors.black}
|
||||
backgroundColor={"transparent"}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
<View flexG/>
|
||||
|
||||
<View row centerH gap-5>
|
||||
<Text style={{
|
||||
fontFamily: "PlusJakartaSans_300Light",
|
||||
fontSize: 16,
|
||||
color: "#484848"
|
||||
}} center>
|
||||
Already have an account?
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
label="Log in"
|
||||
link
|
||||
onPress={() => router.push("/(unauth)/sign_in")}
|
||||
labelStyle={[
|
||||
{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 16,
|
||||
color: "#919191",
|
||||
},
|
||||
{fontSize: 16, textDecorationLine: "none", color: "#fd1775"},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Legacy, move into separate component */}
|
||||
{/* Camera Dialog */}
|
||||
<Dialog
|
||||
visible={showCameraDialog}
|
||||
onDismiss={() => setShowCameraDialog(false)}
|
||||
bottom
|
||||
width="100%"
|
||||
height="70%"
|
||||
containerStyle={{padding: 15, backgroundColor: "white"}}
|
||||
>
|
||||
<Text center style={{fontSize: 16}} marginB-15>
|
||||
Scan a QR code presented from your family member of provider.
|
||||
</Text>
|
||||
{hasPermission === null ? (
|
||||
<Text>Requesting camera permissions...</Text>
|
||||
) : !hasPermission ? (
|
||||
<Text>No access to camera</Text>
|
||||
) : (
|
||||
<CameraView
|
||||
style={{flex: 1, borderRadius: 15}}
|
||||
onBarcodeScanned={handleQrCodeScanned}
|
||||
barcodeScannerSettings={{
|
||||
barcodeTypes: ["qr"],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
label="Cancel"
|
||||
onPress={() => setShowCameraDialog(false)}
|
||||
backgroundColor="#fd1775"
|
||||
style={{margin: 10, marginBottom: 30}}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
|
||||
{isLoading && (
|
||||
<LoaderScreen overlay message={"Signing in..."} backgroundColor={Colors.white} color={Colors.grey40}/>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@ -1,10 +1,44 @@
|
||||
import Entry from "@/components/pages/main/Entry";
|
||||
import {SafeAreaView} from "react-native-safe-area-context";
|
||||
import {Button, Image, Text, View} from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import {useRouter} from "expo-router";
|
||||
|
||||
export default function Screen() {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Entry/>
|
||||
<SafeAreaView style={{flex: 1}}>
|
||||
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%", alignItems: "center"}}>
|
||||
<View>
|
||||
<Image source={require("../../assets/images/splash.png")}/>
|
||||
</View>
|
||||
|
||||
<View center gap-13>
|
||||
<Text style={{fontSize: 40, fontFamily: 'Manrope_600SemiBold', marginLeft: 5}}>
|
||||
Welcome to Cally
|
||||
</Text>
|
||||
<Text center color={"#919191"} style={{fontSize: 20, maxWidth: 250}}>
|
||||
Lightening Mental Loads,
|
||||
One Family at a Time
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View flexG/>
|
||||
|
||||
<View width={"100%"}>
|
||||
<Button
|
||||
label="Continue"
|
||||
marginT-50
|
||||
labelStyle={{
|
||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||
fontSize: 16,
|
||||
}}
|
||||
onPress={() => router.push("/(unauth)/get_started")}
|
||||
style={{height: 50}}
|
||||
backgroundColor="#fd1775"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
6
app/(unauth)/reset_password.tsx
Normal file
6
app/(unauth)/reset_password.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import {ResetPasswordPage} from "@/components/pages/main/ResetPasswordPage";
|
||||
|
||||
export default function Screen() {
|
||||
return <ResetPasswordPage/>
|
||||
}
|
||||
6
app/(unauth)/sign_in.tsx
Normal file
6
app/(unauth)/sign_in.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import SignInPage from "@/components/pages/main/SignInPage";
|
||||
import React from "react";
|
||||
|
||||
export default function Screen() {
|
||||
return <SignInPage/>
|
||||
}
|
||||
8
app/(unauth)/sign_up.tsx
Normal file
8
app/(unauth)/sign_up.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import SignUpPage from "@/components/pages/main/SignUpPage";
|
||||
|
||||
export default function Screen() {
|
||||
return (
|
||||
<SignUpPage/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user