Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .env.dev

This file was deleted.

3 changes: 0 additions & 3 deletions .env.example

This file was deleted.

10 changes: 10 additions & 0 deletions app/(auth)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SafeAreaView } from "react-native";
import { Slot } from "expo-router";

export default function AuthLayout() {
return (
<SafeAreaView className="items-left justify-top flex-1 p-4">
<Slot />
</SafeAreaView>
);
}
11 changes: 11 additions & 0 deletions app/(auth)/loginScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { View } from "react-native";

import { Login } from "@/fetaure/auth/screen/Login";

export default function LoginScreen() {
return (
<View className="w-full p-4">
<Login />
</View>
);
}
11 changes: 11 additions & 0 deletions app/(auth)/signupScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { View } from "react-native";

import { Signup } from "@/fetaure/auth/screen/Signup";

export default function SignupScreen() {
return (
<View className="w-full p-4">
<Signup />
</View>
);
}
54 changes: 54 additions & 0 deletions app/(protected)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// app/(protected)/_layout.tsx
import { useEffect, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { Slot, useRouter } from "expo-router";
import { type User } from "@supabase/supabase-js";

import supabase from "@/lib/supabase";

export default function ProtectedLayout() {
const router = useRouter();
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
// 現在のセッションを取得
const getSession = async () => {
const {
data: { session },
} = await supabase.auth.getSession();
setUser(session?.user ?? null);
setLoading(false);
};
getSession();

// 認証状態の変化を監視
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => subscription?.unsubscribe();
}, []);

useEffect(() => {
if (!loading && !user) {
router.replace("/loginScreen");
}
}, [user, loading, router]);

if (loading) {
// ローディング中はインジケーターを表示
return (
<View className="flex-1 items-center justify-center">
<ActivityIndicator size="large" />
</View>
);
}

if (!user) {
return null;
}

return <Slot />;
}
6 changes: 4 additions & 2 deletions app/index.tsx → app/(protected)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Text, View } from "react-native";

import { SignoutButton } from "@/fetaure/auth/components/SignoutButton";

export default function Index() {
return (
<View className="flex-1 items-center justify-center">
<Text className="text-2xl font-bold">Hello World, Linpe</Text>
<View className="items-left flex justify-center p-4">
<Text className="text-2xl font-bold">Hello World, Linpe</Text>
<SignoutButton />
</View>
);
}
43 changes: 41 additions & 2 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
import { Stack } from "expo-router";
// app/_layout.jsx
import { SafeAreaView, Text } from "react-native";
import { Slot, useRouter, useSegments } from "expo-router";

import "../assets/styles/global.css";

import { useEffect, useState } from "react";
import { type Session } from "@supabase/supabase-js";

import supabase from "@/lib/supabase";

export default function RootLayout() {
return <Stack />;
const [session, setSession] = useState<Session | null>(null);
const segments = useSegments();
const router = useRouter();

useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});

supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);

useEffect(() => {
const inAuthGroup = segments[0] === "(auth)";
const inProtectedGroup = segments[0] === "(protected)";

if (session && inAuthGroup) {
// ログイン済みで認証画面にいる場合は、protectedにリダイレクト
router.replace("/(protected)");
} else if (!session && inProtectedGroup) {
// 未ログインでprotected画面にいる場合は、認証画面にリダイレクト
router.replace("/(auth)/loginScreen");
}
}, [session, segments, router]);

return (
<SafeAreaView className="items-left justify-top flex-1 p-4">
<Text>{session ? "ログイン済み" : "未ログイン"}</Text>
<Slot />
</SafeAreaView>
);
}
23 changes: 23 additions & 0 deletions components/button/PrimaryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TouchableOpacity } from "react-native";

export const PrimaryButton = ({
children,
onPress,
loading = false,
}: {
children: React.ReactNode;
onPress: () => void;
loading?: boolean;
}) => {
return (
<TouchableOpacity
className={`items-center rounded-md p-2 ${
loading ? "bg-gray-500" : "bg-blue-500"
}`}
onPress={onPress}
disabled={loading}
>
{children}
</TouchableOpacity>
);
};
14 changes: 14 additions & 0 deletions components/link/DefaultLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Link } from "expo-router";

type DefaultLinkProps = Pick<
React.ComponentProps<typeof Link>,
"children" | "href"
>;

export const DefaultLink = ({ children, href }: DefaultLinkProps) => {
return (
<Link href={href} className="text-blue-500 underline">
{children}
</Link>
);
};
12 changes: 12 additions & 0 deletions fetaure/auth/components/SignoutButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Text } from "react-native";

import { PrimaryButton } from "@/components/button/PrimaryButton";
import { signout } from "../service/authService";

export const SignoutButton = () => {
return (
<PrimaryButton onPress={() => signout()}>
<Text>Signout</Text>
</PrimaryButton>
);
};
51 changes: 51 additions & 0 deletions fetaure/auth/screen/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState } from "react";
import { Text, TextInput, View } from "react-native";

import { PrimaryButton } from "@/components/button/PrimaryButton";
import { DefaultLink } from "@/components/link/DefaultLink";
import { loginWithEmail } from "../service/authService";

export const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);

return (
<View className="flex flex-col gap-4">
<Text className="text-2xl font-bold">Login</Text>
<View className="flex flex-col gap-2">
<Text className="text-lg font-bold">Email</Text>
<TextInput
className="rounded-md border border-gray-300 p-2"
autoCapitalize="none"
textContentType="emailAddress"
keyboardType="email-address"
autoComplete="email"
value={email}
onChangeText={setEmail}
/>
</View>
<View className="flex flex-col gap-2">
<Text className="text-lg font-bold">Password</Text>
<TextInput
className="rounded-md border border-gray-300 p-2"
autoCapitalize="none"
textContentType="password"
keyboardType="ascii-capable"
autoComplete="password"
value={password}
onChangeText={setPassword}
/>
</View>
<PrimaryButton
onPress={() => loginWithEmail(email, password, setLoading)}
loading={loading}
>
<Text className="text-lg font-bold text-white">Login</Text>
</PrimaryButton>
<DefaultLink href="/signupScreen">
<Text className="text-lg font-bold text-blue-500">Signup</Text>
</DefaultLink>
</View>
);
};
51 changes: 51 additions & 0 deletions fetaure/auth/screen/Signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState } from "react";
import { Text, TextInput, View } from "react-native";

import { PrimaryButton } from "@/components/button/PrimaryButton";
import { DefaultLink } from "@/components/link/DefaultLink";
import { signupWithEmail } from "../service/authService";

export const Signup = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);

return (
<View className="flex flex-col gap-4">
<Text className="text-2xl font-bold">Signup</Text>
<View className="flex flex-col gap-2">
<Text className="text-lg font-bold">Email</Text>
<TextInput
className="rounded-md border border-gray-300 p-2"
autoCapitalize="none"
textContentType="emailAddress"
keyboardType="email-address"
autoComplete="email"
value={email}
onChangeText={setEmail}
/>
</View>
<View className="flex flex-col gap-2">
<Text className="text-lg font-bold">Password</Text>
<TextInput
className="rounded-md border border-gray-300 p-2"
autoCapitalize="none"
textContentType="password"
keyboardType="ascii-capable"
autoComplete="password"
value={password}
onChangeText={setPassword}
/>
</View>
<PrimaryButton
onPress={() => signupWithEmail(email, password, setLoading)}
loading={loading}
>
<Text className="text-lg font-bold text-white">Signup</Text>
</PrimaryButton>
<DefaultLink href="/loginScreen">
<Text className="text-lg font-bold text-blue-500">Login</Text>
</DefaultLink>
</View>
);
};
42 changes: 42 additions & 0 deletions fetaure/auth/service/authService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Alert } from "react-native";

import supabase from "@/lib/supabase";

export const loginWithEmail = async (
email: string,
password: string,
setLoading: (loading: boolean) => void,
) => {
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});

if (error) Alert.alert(error.message);

setLoading(false);
};

export const signupWithEmail = async (
email: string,
password: string,
setLoading: (loading: boolean) => void,
) => {
setLoading(true);
const {
data: { session },
error,
} = await supabase.auth.signUp({
email,
password,
});
if (error) Alert.alert(error.message);
if (!session) Alert.alert("Please check your inbox for email verification!");
setLoading(false);
};

export const signout = async () => {
const { error } = await supabase.auth.signOut();
if (error) Alert.alert(error.message);
};
5 changes: 3 additions & 2 deletions lib/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ class LargeSecureStore {
}
}

const supabaseUrl = process.env.SUPABASE_URL;
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY;
const supabaseUrl = "https://lxuurozqrpthyvxvnaml.supabase.co";
const supabaseAnonKey =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imx4dXVyb3pxcnB0aHl2eHZuYW1sIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkwNzg0NjYsImV4cCI6MjA1NDY1NDQ2Nn0.m8YcHx4RNlRRi4-GsnQTg5h_X_WfRlSY7iYKVmhZCTU";

const supabase = createClient(supabaseUrl || "", supabaseAnonKey || "", {
auth: {
Expand Down
6 changes: 5 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
content: ["./app/**/*.{js,jsx,ts,tsx}", "./app/*.{js,jsx,ts,tsx}"],
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./app/*.{js,jsx,ts,tsx}",
"./fetaure/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
Expand Down