diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9a0bd5c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +*.pdf filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.heic filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 05647d5..b7369fe 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ yarn-error.* # typescript *.tsbuildinfo +/reference +/secrets +/android diff --git a/App.tsx b/App.tsx deleted file mode 100644 index 0329d0c..0000000 --- a/App.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, Text, View } from 'react-native'; - -export default function App() { - return ( - - Open up App.tsx to start working on your app! - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, -}); diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d1108f --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Essentory Login Mockup + +## Firebase Setup + +Create the directory `./secrets` and download `google-services.json` and +`GoogleService-Info.plist` into there. diff --git a/app.json b/app.json index f566f57..2290c59 100644 --- a/app.json +++ b/app.json @@ -1,30 +1,55 @@ { "expo": { - "name": "loginmockup2", - "slug": "loginmockup2", + "name": "loginmockup", + "slug": "loginmockup", + "scheme": "loginmockup", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", + "userInterfaceStyle": "dark", "splash": { - "image": "./assets/splash.png", + "image": "./assets/splash2.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#333333" }, "assetBundlePatterns": [ "**/*" ], "ios": { - "supportsTablet": true + "googleServicesFile": "./secrets/GoogleService-Info.plist", + "supportsTablet": true, + "package": "com.v1.client.app.essentory" }, "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" - } + "googleServicesFile": "./secrets/google-services.json", + "package": "com.estrclient" }, "web": { "favicon": "./assets/favicon.png" - } + }, + "plugins": [ + "@react-native-firebase/app", + "@react-native-firebase/auth", + "@react-native-google-signin/google-signin", + [ + "expo-font", + { + "fonts": [ + "./assets/fonts/Pretendard-Bold.ttf", + "./assets/fonts/Pretendard-Thin.ttf", + "./assets/fonts/Pretendard-Black.ttf", + "./assets/fonts/Pretendard-Light.ttf", + "./assets/fonts/Pretendard-Medium.ttf", + "./assets/fonts/Pretendard-Regular.ttf", + "./assets/fonts/Pretendard-SemiBold.ttf", + "./assets/fonts/Pretendard-ExtraBold.ttf", + "./assets/fonts/Pretendard-ExtraLight.ttf", + "./assets/fonts/Axiforma-ExtraBold.otf", + "./assets/fonts/Axiforma-ExtraBoldItalic.otf" + ] + } + ], + "expo-router" + ] } } diff --git a/app/_layout.tsx b/app/_layout.tsx new file mode 100644 index 0000000..1b9658f --- /dev/null +++ b/app/_layout.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { StatusBar } from "react-native"; + +import { ScrollView } from "react-native"; +import AuthProvider from "@components/AuthProvider"; +import * as SplashScreen from 'expo-splash-screen'; +import { Slot, Stack } from "expo-router"; + +SplashScreen.preventAutoHideAsync(); + +export const unstable_settings = { + initialRouteName: 'auth/login', +} + +function App() { + const backgroundStyle = { + backgroundColor: "#333333", + fontFamily: "Pretendard-Regular", + color: "white", + }; + SplashScreen.hideAsync(); + return ( + + + + + + + + + ); +} + +export default App; diff --git a/app/auth/createpassword.tsx b/app/auth/createpassword.tsx new file mode 100644 index 0000000..e90976f --- /dev/null +++ b/app/auth/createpassword.tsx @@ -0,0 +1,82 @@ +import React, { useMemo, useState } from "react"; +import { View } from "react-native"; +import ETextInput, { ETextInputBorder } from "@components/ETextInput"; +import EFullButton from "@components/EButton"; +import EText, { ECaptionText, ELabelText, ETitleText } from "@components/EText"; +import ENavButtons from "@components/ENavButtons"; + +// implement logic here later. +export default function CreatePassword(props: any) { + const [password, setPassword] = useState(""); + const [validation, setValidation] = useState(""); + const [visible, setVisible] = useState(true); + const border = useMemo(() => { + if (password.length === 0 && validation.length === 0) + return ETextInputBorder.Nothing; + if (validation !== password) return ETextInputBorder.Fail; + if (validation.length >= 8) return ETextInputBorder.Success; + return ETextInputBorder.Fail; + }, [password, validation]); + return ( + <> + + + + + + 비밀번호 설정하기 + + + 비밀번호를 설정해주세요. 영문, 숫자를 조합한 8자 이상으로 + 만들어주세요 + + + + 비밀번호 + + + {border === ETextInputBorder.Nothing && ( + + 영문/숫자 조합 8자 이상으로 만들어주세요 + + )} + {border === ETextInputBorder.Fail && ( + <> + + 영문/숫자 조합 8자 이상으로 만들어주세요 + + + 비밀번호가 일치하는지 확인해주세요 + + + )} + + + + + + ); +} diff --git a/app/auth/login.tsx b/app/auth/login.tsx new file mode 100644 index 0000000..4953e53 --- /dev/null +++ b/app/auth/login.tsx @@ -0,0 +1,140 @@ +import React, { useState } from "react"; +import { Button, Text, View } from "react-native"; +import ELogo from "@components/ELogo"; +import ETextInput from "@components/ETextInput"; +import EFullButton, { EIconButton } from "@components/EButton"; +import { ColorScheme } from "@components/Design"; +import { AppleIcon, GoogleIcon } from "@components/EIcons"; +import { ECaptionText, ELabelText } from "@components/EText"; +import auth from "@react-native-firebase/auth"; +import { useRouter } from "expo-router"; +import { GoogleSignin } from "@react-native-google-signin/google-signin"; +import { useAuthState } from "@components/AuthProvider"; + +GoogleSignin.configure({ + webClientId: + "406674810515-l8a40f83kl5hnvflvcjrabmjmbvda7am.apps.googleusercontent.com", +}); + +export default function LoginScreen(props: any) { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(false); + const router = useRouter(); + const [loginAttempting, setLoginAttempting] = useState(false); + const signInWithEmail = async () => { + setLoginAttempting(true); + try { + await auth().signInWithEmailAndPassword(email, password); + router.replace("/"); + } catch (e) { + setError(true); + } + setLoginAttempting(false); + }; + const signInWithGoogle = async () => { + setLoginAttempting(true); + await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true }); + const { idToken } = await GoogleSignin.signIn(); + const googleCredential = auth.GoogleAuthProvider.credential(idToken); + return auth().signInWithCredential(googleCredential); + }; + const [user, loginstate, setLoginState] = useAuthState(); + return ( + <> + + + + setError(false)} + /> + setError(false)} + /> + {error && ( + + 이메일 혹은 비밀번호가 일치하지 않습니다 + + )} + + + signInWithEmail()} + /> + { + setLoginState && + setLoginState({ ...loginstate, passwordReset: true }); + router.push("auth/passwordresetemail"); + }} + active={true} + text={"비밀번호를 모르겠어요"} + /> + + + + + { + signInWithGoogle() + .then((d) => { + console.log("worked!"); + setLoginAttempting(false); + router.replace(""); + }) + .catch((e) => { + console.error(e); + setLoginAttempting(false); + }); + }} + > + + + + + + + + 아직 회원이 아니신가요? + + { + setLoginState && + setLoginState({ ...loginstate, passwordReset: false }); + router.push("auth/registeremail"); + }} + style={{ backgroundColor: ColorScheme.frostedfill.white[10] }} + active={true} + text={"회원가입"} + /> + + + ); +} diff --git a/app/auth/passwordresetemail.tsx b/app/auth/passwordresetemail.tsx new file mode 100644 index 0000000..6ac62a8 --- /dev/null +++ b/app/auth/passwordresetemail.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import {View} from 'react-native' +import ETextInput from '@components/ETextInput'; +import EFullButton, { ESmallButton } from '@components/EButton'; +import EText, { ELabelText, ETitleText } from '@components/EText'; +import ENavButtons from '@components/ENavButtons'; + +// implement logic here later. +export default function PasswordResetEmailInput(props:any) { + const [email, setEmail] = useState(''); + const emailre = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; + return ( + <> + + + + + 비밀번호 재설정하기 + 비밀번호를 다시 설정하려면 회원가입 때 사용하신 이메일 주소로 비밀번호 재설정 링크를 보내드립니다 + + + 이메일 + + + + + + + + ); +} diff --git a/app/auth/registeremail.tsx b/app/auth/registeremail.tsx new file mode 100644 index 0000000..cb67dbc --- /dev/null +++ b/app/auth/registeremail.tsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from "react"; +import { Text, View } from "react-native"; +import ETextInput from "@components/ETextInput"; +import EFullButton from "@components/EButton"; +import { ELabelText, ETitleText } from "@components/EText"; +import ENavButtons from "@components/ENavButtons"; +import { useAuthState } from "@components/AuthProvider"; +import { useRouter } from "expo-router"; + +// implement logic here later. +export default function RegisterEmail(props: any) { + const [email, setEmail] = useState(''); + const emailre = + /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; + const [user, loginState, setLoginState] = useAuthState(); + useEffect(() => { + setEmail(loginState?.tempEmail || '') + }, []) + const router = useRouter(); + return ( + <> + + + + + 이메일을 입력해주세요 + + + 이메일 + setLoginState && setLoginState({ ...loginState, tempEmail: v })} + /> + + + router.push('auth/createpassword', )} + /> + + + ); +} diff --git a/app/index.tsx b/app/index.tsx new file mode 100644 index 0000000..ab0239e --- /dev/null +++ b/app/index.tsx @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from 'react'; +import {Button, Text, View} from 'react-native'; +import ELogo from '@components/ELogo'; +import { useAuthState } from '@components/AuthProvider'; +import { Redirect, useRouter } from 'expo-router'; +import auth from '@react-native-firebase/auth'; + +export default function LoginScreen(props:any) { + const [user, _, setLoginState, init] = useAuthState(); + const [redirect, setRedirect] = useState(false); + const router = useRouter(); + useEffect(() => { + if (!init) { + if (user === null) setRedirect(true); + } + }, [init]) + if (redirect) { + return + } + return ( + <> + + + {JSON.stringify(user)} , {JSON.stringify(init)} +