feat: 회원가입 화면 생성
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
import React from "react";
|
||||
import { Text, type TextProps, type TextStyle } from "react-native";
|
||||
import {
|
||||
Text,
|
||||
type StyleProp,
|
||||
type TextProps,
|
||||
type TextStyle,
|
||||
} from "react-native";
|
||||
import { Theme } from "../../theme/theme";
|
||||
|
||||
type Variant = "logo" | "title" | "body" | "muted" | "error";
|
||||
|
||||
type Props = TextProps & {
|
||||
variant?: Variant;
|
||||
style?: TextStyle | TextStyle[];
|
||||
style?: StyleProp<TextStyle>;
|
||||
};
|
||||
|
||||
export default function AppText({ variant = "body", style, ...props }: Props) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Pressable, type PressableProps, StyleSheet, View } from "react-native";
|
||||
import { Pressable, type PressableProps, StyleSheet } from "react-native";
|
||||
import { Theme } from "../../theme/theme";
|
||||
import AppText from "./AppText";
|
||||
|
||||
@@ -12,6 +12,7 @@ export default function Button({
|
||||
title,
|
||||
variant = "primary",
|
||||
style,
|
||||
disabled,
|
||||
...props
|
||||
}: Props) {
|
||||
const isPrimary = variant === "primary";
|
||||
@@ -19,42 +20,42 @@ export default function Button({
|
||||
return (
|
||||
<Pressable
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
style={({ pressed }) => [
|
||||
styles.base,
|
||||
isPrimary ? styles.primary : styles.secondary,
|
||||
|
||||
// pressed 스타일(비활성화면 적용 안 함)
|
||||
!disabled &&
|
||||
pressed &&
|
||||
(isPrimary ? styles.primaryPressed : styles.secondaryPressed),
|
||||
|
||||
// disabled 스타일
|
||||
disabled && styles.disabled,
|
||||
|
||||
typeof style === "function" ? style({ pressed }) : style,
|
||||
]}
|
||||
>
|
||||
<View>
|
||||
<AppText
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontWeight: Theme.FontWeight.bold,
|
||||
color: isPrimary ? Theme.Colors.text : Theme.Colors.text,
|
||||
}}
|
||||
style={[
|
||||
styles.text,
|
||||
isPrimary ? styles.textPrimary : styles.textSecondary,
|
||||
disabled && styles.textDisabled,
|
||||
]}
|
||||
>
|
||||
{title}
|
||||
</AppText>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
height: 48,
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: Theme.Spacing.lg,
|
||||
paddingVertical: Theme.Spacing.md,
|
||||
borderRadius: Theme.Radius.md,
|
||||
alignItems: "center", // 가로 가운데
|
||||
justifyContent: "center", // 세로 가운데
|
||||
},
|
||||
text: {
|
||||
textAlign: "center",
|
||||
fontWeight: Theme.FontWeight.bold,
|
||||
lineHeight: 20,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: 48,
|
||||
},
|
||||
primary: {
|
||||
backgroundColor: Theme.Colors.primary,
|
||||
@@ -70,4 +71,22 @@ const styles = StyleSheet.create({
|
||||
secondaryPressed: {
|
||||
opacity: 0.9,
|
||||
},
|
||||
|
||||
disabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
text: {
|
||||
textAlign: "center",
|
||||
fontWeight: Theme.FontWeight.bold,
|
||||
},
|
||||
textPrimary: {
|
||||
color: Theme.Colors.text,
|
||||
},
|
||||
textSecondary: {
|
||||
color: Theme.Colors.text,
|
||||
},
|
||||
textDisabled: {
|
||||
color: Theme.Colors.mutedText,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import React from "react";
|
||||
import LoginScreen from "../screens/LoginScreen";
|
||||
import SignUpScreen from "../screens/SignUpScreen";
|
||||
|
||||
export type AuthStackParamList = {
|
||||
Login: undefined;
|
||||
SignUp: undefined;
|
||||
};
|
||||
|
||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||
@@ -12,6 +14,10 @@ export default function AuthStack() {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="Login" component={LoginScreen} />
|
||||
|
||||
<Stack.Group screenOptions={{ presentation: "modal" }}>
|
||||
<Stack.Screen name="SignUp" component={SignUpScreen} />
|
||||
</Stack.Group>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { Button, StyleSheet, View } from "react-native";
|
||||
import { useAuth } from "../store/auth";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { signOut } = useAuth();
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Button
|
||||
@@ -12,6 +14,7 @@ export default function HomeScreen() {
|
||||
console.log(entries);
|
||||
}}
|
||||
/>
|
||||
<Button title="logout" onPress={() => signOut()}></Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import React, { useState } from "react";
|
||||
import { Pressable, StyleSheet, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import AppText from "../components/ui/AppText";
|
||||
import Button from "../components/ui/Button";
|
||||
import Input from "../components/ui/Input";
|
||||
import { AuthStackParamList } from "../navigation/AuthStack";
|
||||
import { useAuth } from "../store/auth";
|
||||
import { Theme } from "../theme/theme";
|
||||
|
||||
@@ -12,6 +15,8 @@ export default function LoginScreen() {
|
||||
const [loginId, setLoginId] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const canSubmit = loginId.trim().length > 0 && password.length > 0;
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.safe}>
|
||||
@@ -60,7 +65,7 @@ export default function LoginScreen() {
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
console.log("feawf");
|
||||
navigation.navigate("SignUp");
|
||||
}}
|
||||
>
|
||||
<AppText variant="muted" style={styles.signupLine}>
|
||||
|
||||
121
src/screens/SignUpScreen.tsx
Normal file
121
src/screens/SignUpScreen.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import AppText from "../components/ui/AppText";
|
||||
import Button from "../components/ui/Button";
|
||||
import Input from "../components/ui/Input";
|
||||
import { Theme } from "../theme/theme";
|
||||
|
||||
export default function SignUpScreen() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [loginId, setLoginId] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirm, setPasswordConfirm] = useState("");
|
||||
|
||||
const { canSubmit, errorText } = useMemo(() => {
|
||||
const idOk = loginId.trim().length >= 4;
|
||||
const pwOk = password.length >= 8;
|
||||
const match = password.length > 0 && password === passwordConfirm;
|
||||
|
||||
const can = idOk && pwOk && match;
|
||||
|
||||
let err = "";
|
||||
if (loginId.length > 0 && !idOk)
|
||||
err = "Username must be at least 4 characters.";
|
||||
else if (password.length > 0 && !pwOk)
|
||||
err = "Password must be at least 8 characters.";
|
||||
else if (passwordConfirm.length > 0 && !match)
|
||||
err = "Passwords do not match.";
|
||||
|
||||
return { canSubmit: can, errorText: err };
|
||||
}, [loginId, password, passwordConfirm]);
|
||||
|
||||
const onSignUp = async () => {
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.safe}>
|
||||
<View style={styles.container}>
|
||||
<AppText variant="title">Create your account</AppText>
|
||||
<AppText variant="muted">Start listening in minutes</AppText>
|
||||
|
||||
<View style={{ height: Theme.Spacing.lg }} />
|
||||
|
||||
<Input
|
||||
placeholder="Username"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
textContentType="username"
|
||||
value={loginId}
|
||||
onChangeText={setLoginId}
|
||||
returnKeyType="next"
|
||||
/>
|
||||
|
||||
<View style={{ height: Theme.Spacing.sm }} />
|
||||
|
||||
<Input
|
||||
placeholder="Password"
|
||||
secureTextEntry
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
textContentType="newPassword"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
returnKeyType="next"
|
||||
/>
|
||||
|
||||
<View style={{ height: Theme.Spacing.sm }} />
|
||||
|
||||
<Input
|
||||
placeholder="Confirm password"
|
||||
secureTextEntry
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
textContentType="newPassword"
|
||||
value={passwordConfirm}
|
||||
onChangeText={setPasswordConfirm}
|
||||
returnKeyType="done"
|
||||
onSubmitEditing={() => {
|
||||
if (canSubmit) onSignUp();
|
||||
}}
|
||||
/>
|
||||
|
||||
{errorText ? (
|
||||
<AppText variant="muted" style={styles.error}>
|
||||
{errorText}
|
||||
</AppText>
|
||||
) : null}
|
||||
|
||||
<View style={{ height: Theme.Spacing.lg }} />
|
||||
|
||||
<Button title="Sign up" disabled={!canSubmit} onPress={onSignUp} />
|
||||
<View style={{ height: Theme.Spacing.sm }} />
|
||||
<Button
|
||||
title="Back"
|
||||
variant="secondary"
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: {
|
||||
flex: 1,
|
||||
backgroundColor: Theme.Colors.bg,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: Theme.Spacing.xl,
|
||||
justifyContent: "center",
|
||||
gap: Theme.Spacing.sm,
|
||||
},
|
||||
error: {
|
||||
textAlign: "center",
|
||||
color: Theme.Colors.mutedText,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user