Skip to content

Commit

Permalink
Merge pull request #76 from infinite-loop-factory/feature/reaction-sp…
Browse files Browse the repository at this point in the history
…eed-test-record

reaction-speed-test 기록 페이지 supabase 연동
  • Loading branch information
JAM-PARK authored Feb 9, 2025
2 parents b014545 + 8e29403 commit 614ef40
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 63 deletions.
40 changes: 20 additions & 20 deletions apps/reaction-speed-test/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import type { ConfigContext, ExpoConfig } from "expo/config";

const validateEnv = () => {
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL || "";
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || "";
// const validateEnv = () => {
// const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL || "";
// const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || "";

if (!supabaseUrl) {
throw new Error(
"Required environment variable EXPO_PUBLIC_SUPABASE_URL is missing",
);
}
// if (!supabaseUrl) {
// throw new Error(
// "Required environment variable EXPO_PUBLIC_SUPABASE_URL is missing",
// );
// }

if (!supabaseAnonKey) {
throw new Error(
"Required environment variable EXPO_PUBLIC_SUPABASE_ANON_KEY is missing",
);
}
// if (!supabaseAnonKey) {
// throw new Error(
// "Required environment variable EXPO_PUBLIC_SUPABASE_ANON_KEY is missing",
// );
// }

return { supabaseUrl, supabaseAnonKey };
};
// return { supabaseUrl, supabaseAnonKey };
// };

export default ({ config }: ConfigContext): ExpoConfig => {
const env = validateEnv();
// const env = validateEnv();

return {
...config,
Expand Down Expand Up @@ -56,9 +56,9 @@ export default ({ config }: ConfigContext): ExpoConfig => {
typedRoutes: true,
baseUrl: "/app-factory/reaction-speed-test",
},
extra: {
supabaseUrl: env.supabaseUrl,
supabaseAnonKey: env.supabaseAnonKey,
},
// extra: {
// supabaseUrl: env.supabaseUrl,
// supabaseAnonKey: env.supabaseAnonKey,
// },
};
};
137 changes: 105 additions & 32 deletions apps/reaction-speed-test/app/(pages)/results.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Button, ButtonText } from "@/components/ui/button";
import { getRecords } from "@/services";
import { formatDateTime } from "@/utils/date";
import { supabase } from "@/utils/supabase";
import { cn } from "@infinite-loop-factory/common";
import { useAsyncEffect } from "@reactuses/core";
import { noop } from "es-toolkit";
import { Stack, useRouter } from "expo-router";
import type { FC } from "react";
import { useEffect, useState } from "react";
import { Alert, AppState, Text, View } from "react-native";
import { useState } from "react";
import { Alert, AppState, ScrollView, Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";

AppState.addEventListener("change", (state) => {
if (state === "active") {
Expand All @@ -13,43 +19,110 @@ AppState.addEventListener("change", (state) => {
}
});

const Results: FC = () => {
const router = useRouter();
// ⚠️ FIXME: test와 getTestTable 함수 수정
const [test, setTest] = useState<string>("");
interface Record {
id: string;
created_at: string;
result_value: number;
unit: string;
}

useEffect(() => {
getTestTable();
}, []);
interface RecordStatus {
isNewest: boolean;
isLowest: boolean;
}

async function getTestTable() {
try {
const { data, error, status } = await supabase.from("test").select("*");
// console.log("data", data, error, status);
const Results: FC = () => {
const router = useRouter();
const [records, setRecords] = useState<Record[]>([]);
const [loading, setLoading] = useState(true);

if (error && status !== 406) {
throw error;
useAsyncEffect(
async () => {
try {
const data = await getRecords();
setRecords(data);
} catch (error) {
if (error instanceof Error) {
Alert.alert(error.message);
} else {
Alert.alert("데이터를 불러오는데 실패했습니다");
}
console.error(error);
} finally {
setLoading(false);
}
},
noop,
[],
);

if (data) {
setTest(data[1].test);
}
} catch (error) {
if (error instanceof Error) {
Alert.alert(error.message);
}
}
}
const getResultText = (
record: Record,
{ isNewest, isLowest }: RecordStatus,
): string => {
const baseText = `결과값: ${record.result_value}${record.unit}`;

if (isNewest) return `${baseText} | 최근 기록`;
if (isLowest) return `${baseText} | 최단 기록`;
return baseText;
};

return (
<View className="flex-1 items-center justify-center">
<Stack.Screen options={{ title: "기록 페이지", headerShown: false }} />
<Text className="mb-5 text-2xl dark:text-gray-50">기록 페이지</Text>
<Button className="bg-slate-500" onPress={() => router.back()}>
<ButtonText>뒤로 가기</ButtonText>
<ButtonText>{test}</ButtonText>
</Button>
</View>
<SafeAreaView>
<ScrollView>
<View className="my-8 flex-1 items-center justify-center">
<Stack.Screen
options={{ title: "기록 페이지", headerShown: false }}
/>
<Text className="mb-5 font-mono text-2xl tracking-tighter dark:text-gray-50">
기록 페이지
</Text>
{loading ? (
<Text className="font-mono tracking-tighter dark:text-gray-50">
로딩 중...
</Text>
) : (
<View className="w-full max-w-[20rem] px-4">
{records.map((record) => {
const isNewest =
records.length > 0 &&
record.created_at === records.at(0)?.created_at;
const lowestValue = Math.min(
...records.map((r) => r.result_value),
);
const isLowest = record.result_value === lowestValue;
const isDefault = !(isNewest || isLowest);

return (
<View
key={record.id}
className={cn(
"mb-3 rounded p-3",
isNewest &&
"bg-yellow-300 dark:bg-yellow-300 dark:text-gray-950",
isLowest &&
"bg-red-300 dark:bg-red-300 dark:text-gray-950",
isDefault &&
"bg-blue-300 dark:bg-blue-300 dark:text-gray-950",
)}
>
<Text className="font-mono text-sm tracking-tighter dark:text-gray-950">
{formatDateTime(record.created_at, "Asia/Seoul")}
</Text>
<Text className="font-mono font-semibold text-lg tracking-tighter dark:text-gray-950">
{getResultText(record, { isNewest, isLowest })}
</Text>
</View>
);
})}
</View>
)}
<Button className="mt-8 bg-slate-500" onPress={() => router.back()}>
<ButtonText>뒤로 가기</ButtonText>
</Button>
</View>
</ScrollView>
</SafeAreaView>
);
};

Expand Down
10 changes: 3 additions & 7 deletions apps/reaction-speed-test/app/(pages)/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "@/components/ui/form-control";
import { Input, InputField } from "@/components/ui/input";
import { VStack } from "@/components/ui/vstack";
import { supabase } from "@/utils/supabase";
import { signUpUser } from "@/services";
import { Stack, useRouter } from "expo-router";
import { useState } from "react";
import { Text, View } from "react-native";
Expand Down Expand Up @@ -63,14 +63,10 @@ export default function SignUpForm() {

setLoading(true);
try {
const { error } = await supabase.auth.signUp({
const { error } = await signUpUser({
email: formData.email,
password: formData.password,
options: {
data: {
username: formData.username,
},
},
username: formData.username,
});

if (error) {
Expand Down
4 changes: 2 additions & 2 deletions apps/reaction-speed-test/components/login/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "@/components/ui/form-control";
import { Input, InputField } from "@/components/ui/input";
import { VStack } from "@/components/ui/vstack";
import { supabase } from "@/utils/supabase";
import { signInUser } from "@/services";
import { useRouter } from "expo-router";
import { useState } from "react";
import { Text } from "react-native";
Expand Down Expand Up @@ -54,7 +54,7 @@ export default function LoginForm() {

setLoading(true);
try {
const { error } = await supabase.auth.signInWithPassword({
const { error } = await signInUser({
email: formData.email,
password: formData.password,
});
Expand Down
17 changes: 17 additions & 0 deletions apps/reaction-speed-test/hooks/useReactionTimer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { insertRecord } from "@/services";
import { useAsyncEffect } from "@reactuses/core";
import { noop } from "es-toolkit";
import { useCallback, useRef, useState } from "react";

export interface TimeResult {
Expand All @@ -12,6 +15,20 @@ export const useReactionTimer = () => {
const startTimeRef = useRef<number>(0);
const isStartedRef = useRef<boolean>(false);

useAsyncEffect(
async () => {
if (result) {
try {
await insertRecord(result.reactionTime);
} catch (error) {
console.error("Failed to save record:", error);
}
}
},
noop,
[result],
);

const start = useCallback(() => {
startTimeRef.current = Date.now();
isStartedRef.current = true;
Expand Down
3 changes: 3 additions & 0 deletions apps/reaction-speed-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@gluestack-ui/overlay": "^0.1.15",
"@gluestack-ui/select": "^0.1.29",
"@gluestack-ui/toast": "^1.0.7",
"@infinite-loop-factory/common": "workspace:^",
"@legendapp/motion": "^2.4.0",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-navigation/native": "^6.0.2",
Expand All @@ -52,6 +53,7 @@
"expo-web-browser": "~13.0.3",
"https-browserify": "^1.0.0",
"i18n-js": "^4.4.3",
"luxon": "^3.5.0",
"nativewind": "catalog:",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",
Expand Down Expand Up @@ -83,6 +85,7 @@
"@infinite-loop-factory/config-typescript": "workspace:^",
"@testing-library/react-native": "^12.7.2",
"@types/jest": "^29.5.13",
"@types/luxon": "^3.4.2",
"@types/react": "~18.2.79",
"@types/react-test-renderer": "^18.3.0",
"cross-env": "^7.0.3",
Expand Down
1 change: 1 addition & 0 deletions apps/reaction-speed-test/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./user";
export * from "./records";
43 changes: 43 additions & 0 deletions apps/reaction-speed-test/services/records.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getCurrentUser } from "@/services";
import { supabase } from "@/utils/supabase";

export const insertRecord = async (result_value: number) => {
const user = await getCurrentUser();

if (!user) {
throw new Error("User not authenticated");
}

const { data, error } = await supabase
.from("records")
.insert([
{
user_id: user.id,
result_value,
unit: "ms",
},
])
.select()
.single();

if (error) throw error;
return data;
};

export const getRecords = async () => {
const user = await getCurrentUser();

if (!user) {
throw new Error("로그인이 필요합니다");
}

const { data, error } = await supabase
.from("records")
.select("id, created_at, result_value, unit")
.eq("user_id", user.id)
.order("created_at", { ascending: false });

if (error) throw error;

return data;
};
Loading

0 comments on commit 614ef40

Please sign in to comment.