Restart app work

This commit is contained in:
Arthur Belleville 2025-07-14 22:37:37 +02:00
parent 2ff34dba52
commit 4f49bb279c
No known key found for this signature in database
10 changed files with 357 additions and 53 deletions

View file

@ -21,3 +21,12 @@ dev:
update-types:
npx supabase gen types typescript --project-id "mhcafqvzbrrwvahpvvzd" --schema public > ui/src/types/database.types.ts && cp ui/src/types/database.types.ts api/src/database.types.ts
expo-install-all:
cd xtablo-expo && npx expo install -- --legacy-peer-deps
expo-install package:
cd xtablo-expo && npx expo install {{package}} -- --legacy-peer-deps
expo-start:
cd xtablo-expo && npx expo start

View file

@ -1,8 +1,11 @@
import React, { useState } from "react";
import { StyleSheet, View, Text } from "react-native";
import { StyleSheet, View, Text, Image } from "react-native";
import { Button, Input } from "@rn-vui/themed";
import { useAuth } from "@/stores/auth";
import { Link } from "expo-router";
import { Mail, Lock } from "lucide-react-native";
import { GoogleLoginButton } from "@/components/GoogleLoginButton";
import { AppleLoginButton } from "@/components/AppleLoginButton";
export default function Auth() {
const [email, setEmail] = useState("");
@ -13,42 +16,54 @@ export default function Auth() {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome Back!</Text>
<Text style={styles.subtitle}>Sign in to your account</Text>
<View style={[styles.verticallySpaced, styles.mt40]}>
<Image source={require("@/assets/images/logo.jpg")} style={styles.logo} />
<Text style={styles.title}>Connexion XTablo</Text>
<Text style={styles.subtitle}>Connectez-vous à votre compte</Text>
<View style={[styles.verticallySpaced, styles.mt10]}>
<Input
label="Email"
leftIcon={{ type: "font-awesome", name: "envelope" }}
label="Adresse email"
leftIcon={<Mail size={20} color="#666" />}
onChangeText={(text) => setEmail(text)}
value={email}
placeholder="email@address.com"
placeholder="jean@dupont.com"
autoCapitalize={"none"}
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Password"
leftIcon={{ type: "font-awesome", name: "lock" }}
label="Mot de passe"
leftIcon={<Lock size={20} color="#666" />}
onChangeText={(text) => setPassword(text)}
value={password}
secureTextEntry={true}
placeholder="Password"
placeholder="Mot de passe"
autoCapitalize={"none"}
/>
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button
title="Sign in"
title="Se connecter"
disabled={authLoading}
onPress={() => login(email, password)}
buttonStyle={styles.button}
titleStyle={styles.buttonTitle}
/>
</View>
<View style={styles.separatorContainer}>
<View style={styles.separator} />
<Text style={styles.separatorText}>ou</Text>
<View style={styles.separator} />
</View>
<View style={styles.verticallySpaced}>
<GoogleLoginButton />
</View>
<View style={[styles.verticallySpaced, styles.mt5]}>
<AppleLoginButton />
</View>
<View style={styles.linkContainer}>
<Text style={styles.linkText}>Don't have an account? </Text>
<Text style={styles.linkText}>Pas encore de compte ? </Text>
<Link href="/signup" style={styles.link}>
Sign Up
S'inscrire
</Link>
</View>
</View>
@ -59,33 +74,46 @@ const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 24,
padding: 16,
backgroundColor: "#f5f5f5", // Light grey background
},
logo: {
width: 80,
height: 80,
alignSelf: "center",
marginBottom: 8,
borderRadius: 40,
},
title: {
fontSize: 32,
fontSize: 24,
fontWeight: "bold",
textAlign: "center",
marginBottom: 10,
marginBottom: 3,
color: "#333",
},
subtitle: {
fontSize: 16,
fontSize: 14,
textAlign: "center",
marginBottom: 40,
marginBottom: 16,
color: "#666",
},
verticallySpaced: {
paddingTop: 8, // Increased padding
paddingBottom: 8, // Increased padding
paddingTop: 2,
paddingBottom: 2,
alignSelf: "stretch",
},
mt20: {
marginTop: 20,
marginTop: 12,
},
mt40: {
marginTop: 40, // Increased top margin for the first input
},
mt10: {
marginTop: 6,
},
mt5: {
marginTop: 3,
},
button: {
paddingVertical: 12,
borderRadius: 8, // Rounded corners
@ -95,10 +123,25 @@ const styles = StyleSheet.create({
fontWeight: "bold",
color: "#fff", // Ensure text is visible on colored button
},
separatorContainer: {
flexDirection: "row",
alignItems: "center",
marginVertical: 12,
},
separator: {
flex: 1,
height: 1,
backgroundColor: "#ddd",
},
separatorText: {
marginHorizontal: 15,
color: "#666",
fontSize: 14,
},
linkContainer: {
flexDirection: "row",
justifyContent: "center",
marginTop: 20,
marginTop: 12,
},
linkText: {
color: "#666",

View file

@ -1,10 +1,14 @@
import React, { useState } from "react";
import { StyleSheet, View, Text } from "react-native";
import { StyleSheet, View, Text, Image } from "react-native";
import { Button, Input } from "@rn-vui/themed";
import { useAuth } from "@/stores/auth";
import { Link } from "expo-router";
import { Mail, Lock, User, Building2 } from "lucide-react-native";
export default function SignUp() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [companyName, setCompanyName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@ -13,42 +17,76 @@ export default function SignUp() {
return (
<View style={styles.container}>
<Text style={styles.title}>Create Account</Text>
<Text style={styles.subtitle}>Join us!</Text>
<View style={[styles.verticallySpaced, styles.mt40]}>
<Image source={require("@/assets/images/logo.jpg")} style={styles.logo} />
<Text style={styles.title}>Créer un compte XTablo</Text>
<Text style={styles.subtitle}>Rejoignez-nous !</Text>
<View style={[styles.verticallySpaced, styles.mt10]}>
<Input
label="Email"
leftIcon={{ type: "font-awesome", name: "envelope" }}
onChangeText={(text) => setEmail(text)}
value={email}
placeholder="email@address.com"
autoCapitalize={"none"}
label="Prénom"
leftIcon={<User size={20} color="#666" />}
onChangeText={(text) => setFirstName(text)}
value={firstName}
placeholder="Jean"
autoCapitalize={"words"}
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Password"
leftIcon={{ type: "font-awesome", name: "lock" }}
label="Nom"
leftIcon={<User size={20} color="#666" />}
onChangeText={(text) => setLastName(text)}
value={lastName}
placeholder="Dupont"
autoCapitalize="words"
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Nom de l'entreprise"
leftIcon={<Building2 size={20} color="#666" />}
onChangeText={(text) => setCompanyName(text)}
value={companyName}
placeholder="Mon Entreprise"
autoCapitalize="words"
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Adresse email"
leftIcon={<Mail size={20} color="#666" />}
onChangeText={(text) => setEmail(text)}
value={email}
placeholder="jean@dupont.com"
autoCapitalize="none"
keyboardType="email-address"
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Mot de passe"
leftIcon={<Lock size={20} color="#666" />}
onChangeText={(text) => setPassword(text)}
value={password}
secureTextEntry={true}
placeholder="Password"
autoCapitalize={"none"}
secureTextEntry
placeholder="Mot de passe"
autoCapitalize="none"
/>
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button
title="Sign up"
title="S'inscrire"
disabled={authLoading}
onPress={() => signUp(email, password)}
onPress={() =>
signUp(email, password, firstName, lastName, companyName)
}
buttonStyle={styles.button}
titleStyle={styles.buttonTitle}
/>
</View>
<View style={styles.linkContainer}>
<Text style={styles.linkText}>Already have an account? </Text>
<Text style={styles.linkText}>Vous avez déjà un compte ? </Text>
<Link href="/login" style={styles.link}>
Sign In
Se connecter
</Link>
</View>
</View>
@ -60,33 +98,46 @@ const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 24,
padding: 16,
backgroundColor: "#f5f5f5", // Light grey background
},
logo: {
width: 80,
height: 80,
alignSelf: "center",
marginBottom: 8,
borderRadius: 40,
},
title: {
fontSize: 32,
fontSize: 24,
fontWeight: "bold",
textAlign: "center",
marginBottom: 10,
marginBottom: 3,
color: "#333",
},
subtitle: {
fontSize: 16,
fontSize: 14,
textAlign: "center",
marginBottom: 40,
marginBottom: 16,
color: "#666",
},
verticallySpaced: {
paddingTop: 8,
paddingBottom: 8,
paddingTop: 2,
paddingBottom: 2,
alignSelf: "stretch",
},
mt20: {
marginTop: 20,
marginTop: 12,
},
mt40: {
marginTop: 40,
},
mt10: {
marginTop: 6,
},
mt5: {
marginTop: 3,
},
button: {
paddingVertical: 12,
borderRadius: 8,
@ -96,10 +147,25 @@ const styles = StyleSheet.create({
fontWeight: "bold",
color: "#fff",
},
separatorContainer: {
flexDirection: "row",
alignItems: "center",
marginVertical: 12,
},
separator: {
flex: 1,
height: 1,
backgroundColor: "#ddd",
},
separatorText: {
marginHorizontal: 15,
color: "#666",
fontSize: 14,
},
linkContainer: {
flexDirection: "row",
justifyContent: "center",
marginTop: 20,
marginTop: 12,
},
linkText: {
color: "#666",

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -0,0 +1,59 @@
import React from "react";
import { StyleSheet, View, Text, TouchableOpacity } from "react-native";
import { useAuth } from "@/stores/auth";
import { Svg, Path } from "react-native-svg";
const AppleIcon = ({ color = "#fff", size = 20 }) => (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<Path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.653-.026 2.681-1.507 3.694-2.961 1.169-1.69 1.648-3.327 1.679-3.418-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.646 1.09z"
fill={color}
/>
<Path
d="M15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701z"
fill={color}
/>
</Svg>
);
export const AppleLoginButton = () => {
const loginWithApple = useAuth((state) => state.loginWithApple);
const authLoading = useAuth((state) => state.loading);
return (
<TouchableOpacity
style={styles.button}
onPress={() => loginWithApple()}
disabled={authLoading}
>
<View style={styles.contentWrapper}>
<AppleIcon color="#fff" size={20} />
<Text style={styles.text}>Continuer avec Apple</Text>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
backgroundColor: "#000000",
borderRadius: 8,
height: 48,
paddingHorizontal: 12,
alignItems: "center",
justifyContent: "center",
alignSelf: "stretch",
},
contentWrapper: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
text: {
color: "#ffffff",
fontSize: 14,
fontWeight: "600",
textAlign: "center",
marginLeft: 12,
},
});

View file

@ -0,0 +1,54 @@
import React from "react";
import { StyleSheet, View, Text, TouchableOpacity, Image } from "react-native";
import { useAuth } from "@/stores/auth";
export const GoogleLoginButton = () => {
const loginWithGoogle = useAuth((state) => state.loginWithGoogle);
const authLoading = useAuth((state) => state.loading);
return (
<TouchableOpacity
style={styles.button}
onPress={() => loginWithGoogle()}
disabled={authLoading}
>
<View style={styles.contentWrapper}>
<Image
source={require("@/assets/images/google.png")}
style={styles.icon}
/>
<Text style={styles.text}>Continuer avec Google</Text>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
backgroundColor: "#ffffff",
borderColor: "#747775",
borderWidth: 1,
borderRadius: 8,
height: 48,
paddingHorizontal: 12,
alignItems: "center",
justifyContent: "center",
alignSelf: "stretch",
},
contentWrapper: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
icon: {
height: 20,
width: 20,
marginRight: 12,
},
text: {
color: "#1f1f1f",
fontSize: 14,
fontWeight: "500",
textAlign: "center",
},
});

View file

@ -31,6 +31,7 @@
"expo-symbols": "~0.4.4",
"expo-system-ui": "~5.0.7",
"expo-web-browser": "~14.1.6",
"lucide-react-native": "^0.525.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.2",
@ -8450,6 +8451,16 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react-native": {
"version": "0.525.0",
"resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-0.525.0.tgz",
"integrity": "sha512-f9iIdoZJCDIXhzAMQCNgaiWdiq42ls5ieXvVXwlNHN1q7c5zC1n1U0yOVu6J0oNSF8zvYqY3pK3UPOX+T2YpIQ==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-native": "*",
"react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",

View file

@ -38,6 +38,7 @@
"expo-symbols": "~0.4.4",
"expo-system-ui": "~5.0.7",
"expo-web-browser": "~14.1.6",
"lucide-react-native": "^0.525.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.2",

View file

@ -1,6 +1,7 @@
import { create } from "zustand";
import { Session } from "@supabase/supabase-js";
import { supabase } from "@/lib/supabase";
import * as WebBrowser from "expo-web-browser";
interface AuthState {
session: Session | null;
@ -8,10 +9,20 @@ interface AuthState {
initialize: () => Promise<void>;
setSession: (session: Session | null) => void;
login: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string) => Promise<void>;
signUp: (
email: string,
password: string,
firstName: string,
lastName: string,
companyName: string
) => Promise<void>;
loginWithGoogle: () => Promise<void>;
loginWithApple: () => Promise<void>;
signOut: () => Promise<void>;
}
WebBrowser.maybeCompleteAuthSession();
export const useAuth = create<AuthState>((set) => ({
session: null,
loading: true,
@ -26,10 +37,60 @@ export const useAuth = create<AuthState>((set) => ({
await supabase.auth.signInWithPassword({ email, password });
set({ loading: false });
},
signUp: async (email: string, password: string) => {
await supabase.auth.signUp({ email, password });
signUp: async (
email: string,
password: string,
firstName: string,
lastName: string,
companyName: string
) => {
await supabase.auth.signUp({
email,
password,
options: {
data: {
firstName,
lastName,
companyName,
},
},
});
set({ loading: false });
},
loginWithGoogle: async () => {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: "exp://localhost:8081/--/(home)/(tabs)",
},
});
if (error) {
console.error("Google login error:", error);
return;
}
if (data.url) {
await WebBrowser.openBrowserAsync(data.url);
}
},
loginWithApple: async () => {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "apple",
options: {
redirectTo: "exp://localhost:8081/--/(home)/(tabs)",
},
});
if (error) {
console.error("Apple login error:", error);
return;
}
if (data.url) {
await WebBrowser.openBrowserAsync(data.url);
}
},
signOut: async () => {
await supabase.auth.signOut();
set({ loading: false });