Initial commit

Generated by create-expo-app 3.3.0.
This commit is contained in:
Dennis Hundertmark
2025-04-03 08:58:19 +02:00
commit 6ed3300183
113 changed files with 22036 additions and 0 deletions
@@ -0,0 +1,63 @@
import migrations from "@/drizzle/migrations";
import { NoteProvider } from "@/providers/NoteProvider";
import { useAuth } from "@/utils/AuthProvider";
import { Ionicons } from "@expo/vector-icons";
import { drizzle } from "drizzle-orm/expo-sqlite";
import { useMigrations } from "drizzle-orm/expo-sqlite/migrator";
import { Stack } from "expo-router";
import { SQLiteProvider, openDatabaseSync } from "expo-sqlite";
import { useDrizzleStudio } from "expo-drizzle-studio-plugin";
import { cssInterop } from "nativewind";
import React, { Suspense } from "react";
import { ActivityIndicator, StyleSheet, TouchableOpacity } from "react-native";
cssInterop(Ionicons, {
className: {
target: "style",
nativeStyleToProp: { color: true },
},
});
const Page = () => {
const { logout } = useAuth();
const expoDb = openDatabaseSync("notes.db");
useDrizzleStudio(expoDb);
const db = drizzle(expoDb);
const { success, error } = useMigrations(db, migrations);
return (
<Suspense fallback={<ActivityIndicator size="large" />}>
<SQLiteProvider
databaseName="notes.db"
options={{ enableChangeListener: true }}
useSuspense
>
<NoteProvider>
<Stack>
<Stack.Screen
name="home"
options={{
title: "Voice Notes",
headerRight: () => (
<TouchableOpacity className="p-2" onPress={logout}>
<Ionicons
name="log-out"
size={24}
className="color-blue-500"
/>
</TouchableOpacity>
),
}}
/>
</Stack>
</NoteProvider>
</SQLiteProvider>
</Suspense>
);
};
export default Page;
const styles = StyleSheet.create({});
+39
View File
@@ -0,0 +1,39 @@
import { NoteProvider } from "@/providers/NoteProvider";
import { useAuth } from "@/utils/AuthProvider";
import { Ionicons } from "@expo/vector-icons";
import { Stack } from "expo-router";
import { cssInterop } from "nativewind";
import React from "react";
import { StyleSheet, TouchableOpacity, Text } from "react-native";
cssInterop(Ionicons, {
className: {
target: "style",
nativeStyleToProp: { color: true },
},
});
const Page = () => {
const { logout } = useAuth();
return (
<NoteProvider>
<Stack>
<Stack.Screen
name="home"
options={{
title: "Voice Notes",
headerRight: () => (
<TouchableOpacity className="p-2 mr-4" onPress={logout}>
<Text className="text-blue-500">Sign out</Text>
</TouchableOpacity>
),
}}
/>
</Stack>
</NoteProvider>
);
};
export default Page;
const styles = StyleSheet.create({});
+14
View File
@@ -0,0 +1,14 @@
import { StyleSheet, Text, View } from "react-native";
import React from "react";
const Page = () => {
return (
<View>
<Text>Page</Text>
</View>
);
};
export default Page;
const styles = StyleSheet.create({});
+20
View File
@@ -0,0 +1,20 @@
import { useAuth } from "@/utils/AuthProvider";
import { Redirect, Slot, useSegments } from "expo-router";
import React from "react";
import { StyleSheet } from "react-native";
const Layout = () => {
const { isAuthenticated } = useAuth();
const segments = useSegments();
const isAuthGroup = segments[1] === "(authenticated)";
if (!isAuthenticated && isAuthGroup) {
return <Redirect href="/" />;
}
return <Slot />;
};
export default Layout;
const styles = StyleSheet.create({});
+39
View File
@@ -0,0 +1,39 @@
import { Stack, useRouter } from "expo-router";
import "@/global.css";
import { Platform } from "react-native";
import { AuthProvider, useAuth } from "@/utils/AuthProvider";
import React, { useEffect } from "react";
const InitialLayout = () => {
const isWeb = Platform.OS === "web";
const router = useRouter();
const { isAuthenticated, isLoaded } = useAuth();
useEffect(() => {
if (isLoaded && isAuthenticated) {
router.replace("/(app)/(authenticated)/home");
}
}, [isLoaded, isAuthenticated]);
return (
<Stack>
<Stack.Screen name="(app)" options={{ headerShown: false }} />
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen
name="register"
options={{ headerShown: isWeb ? false : true, title: "Register" }}
/>
</Stack>
);
};
export default function RootLayout() {
return (
<AuthProvider>
<InitialLayout />
</AuthProvider>
);
}
+27
View File
@@ -0,0 +1,27 @@
import { ActivityIndicator, Pressable, Text, View } from "react-native";
import { useAuth } from "@/utils/AuthProvider";
export default function Index() {
const { login, isLoading: authLoading } = useAuth();
if (authLoading) {
return (
<View className="flex-1 justify-center items-center bg-white">
<ActivityIndicator size="large" />
</View>
);
}
return (
<View className="flex-1 bg-white px-4 justify-center items-center">
<Pressable
className="w-full flex-row justify-center items-center bg-black py-3 rounded-lg"
onPress={login}
>
<Text className="text-white text-center font-semibold ml-2">
Sign in with Keycloak
</Text>
</Pressable>
</View>
);
}
+136
View File
@@ -0,0 +1,136 @@
import {
View,
Text,
TextInput,
TouchableOpacity,
Pressable,
Alert,
Platform,
ActivityIndicator,
} from "react-native";
import { useState } from "react";
import { Link } from "expo-router";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
password: z.string().min(1, "Password is required"),
});
type FormData = z.infer<typeof schema>;
export default function RegisterScreen() {
const [loading, setLoading] = useState(false);
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
email: "",
password: "",
name: "",
},
mode: "onChange",
});
const onSubmit = async (data: FormData) => {};
return (
<View className="flex-1 bg-white px-4 justify-center items-center">
{loading ? (
<ActivityIndicator size="large" />
) : (
<View className="w-full max-w-md">
<Text className="text-3xl font-bold mb-8 text-center text-gray-800">
Create Account
</Text>
<View className="space-y-4 gap-2">
<View>
<Text className="text-gray-700 mb-2">Name</Text>
<Controller
control={control}
name="name"
render={({ field: { onChange, value } }) => (
<TextInput
className="w-full p-4 border border-gray-300 rounded-lg bg-gray-50"
placeholder="Enter name"
value={value}
onChangeText={onChange}
autoCapitalize="none"
/>
)}
/>
{errors.name && (
<Text className="text-red-500">{errors.name.message}</Text>
)}
</View>
<View>
<Text className="text-gray-700 mb-2">Email</Text>
<Controller
control={control}
name="email"
render={({ field: { onChange, value } }) => (
<TextInput
className="w-full p-4 border border-gray-300 rounded-lg bg-gray-50"
placeholder="Enter email"
value={value}
onChangeText={onChange}
keyboardType="email-address"
autoCapitalize="none"
/>
)}
/>
{errors.email && (
<Text className="text-red-500">{errors.email.message}</Text>
)}
</View>
<View>
<Text className="text-gray-700 mb-2">Password</Text>
<Controller
control={control}
name="password"
render={({ field: { onChange, value } }) => (
<TextInput
className="w-full p-4 border border-gray-300 rounded-lg bg-gray-50"
placeholder="Enter password"
value={value}
onChangeText={onChange}
secureTextEntry
/>
)}
/>
{errors.password && (
<Text className="text-red-500">{errors.password.message}</Text>
)}
</View>
<Pressable
className="w-full bg-blue-600 py-4 rounded-lg hover:bg-blue-700 duration-300"
onPress={handleSubmit(onSubmit)}
>
<Text className="text-white text-center font-semibold">
Register
</Text>
</Pressable>
<Link href="/" asChild>
<TouchableOpacity className="mt-4">
<Text className="text-blue-500 text-center">
Already have an account? Login
</Text>
</TouchableOpacity>
</Link>
</View>
</View>
)}
</View>
);
}
+41
View File
@@ -0,0 +1,41 @@
import React, { useEffect } from 'react';
import { Text, View } from 'react-native';
import { useRouter } from 'expo-router';
import * as WebBrowser from 'expo-web-browser';
import { Platform } from 'react-native';
/**
* This component handles authentication callbacks on web platforms.
* It's designed to process the authentication response and close the browser window
* after a successful authentication flow.
*/
export default function WebAuthCallback() {
const router = useRouter();
useEffect(() => {
// Handle the auth callback
const handleCallback = async () => {
try {
// Close the auth session and return to the app
if (Platform.OS === 'web') {
WebBrowser.maybeCompleteAuthSession();
}
// Navigate back to the main app after a short delay
setTimeout(() => {
router.replace('/');
}, 1000);
} catch (error) {
console.error('Error handling auth callback:', error);
}
};
handleCallback();
}, [router]);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Authentication complete. Redirecting...</Text>
</View>
);
}