Juni Kim
8 months ago
34 changed files with 2182 additions and 46 deletions
-
7.gitattributes
-
3.gitignore
-
20App.tsx
-
6README.md
-
47app.json
-
44app/_layout.tsx
-
82app/auth/createpassword.tsx
-
140app/auth/login.tsx
-
35app/auth/passwordresetemail.tsx
-
51app/auth/registeremail.tsx
-
35app/index.tsx
-
BINassets/fonts/Axiforma-ExtraBold.otf
-
BINassets/fonts/Axiforma-ExtraBoldItalic.otf
-
BINassets/fonts/Pretendard-Black.ttf
-
BINassets/fonts/Pretendard-Bold.ttf
-
BINassets/fonts/Pretendard-ExtraBold.ttf
-
BINassets/fonts/Pretendard-ExtraLight.ttf
-
BINassets/fonts/Pretendard-Light.ttf
-
BINassets/fonts/Pretendard-Medium.ttf
-
BINassets/fonts/Pretendard-Regular.ttf
-
BINassets/fonts/Pretendard-SemiBold.ttf
-
BINassets/fonts/Pretendard-Thin.ttf
-
BINassets/splash2.png
-
42components/AuthProvider.tsx
-
60components/Design.ts
-
93components/EButton.tsx
-
100components/EIcons.tsx
-
17components/ELogo.tsx
-
40components/ENavButtons.tsx
-
76components/EText.tsx
-
113components/ETextInput.tsx
-
1156package-lock.json
-
20package.json
-
5tsconfig.json
@ -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 |
@ -1,20 +0,0 @@ |
|||
import { StatusBar } from 'expo-status-bar'; |
|||
import { StyleSheet, Text, View } from 'react-native'; |
|||
|
|||
export default function App() { |
|||
return ( |
|||
<View style={styles.container}> |
|||
<Text>Open up App.tsx to start working on your app!</Text> |
|||
<StatusBar style="auto" /> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
flex: 1, |
|||
backgroundColor: '#fff', |
|||
alignItems: 'center', |
|||
justifyContent: 'center', |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
# Essentory Login Mockup |
|||
|
|||
## Firebase Setup |
|||
|
|||
Create the directory `./secrets` and download `google-services.json` and |
|||
`GoogleService-Info.plist` into there. |
@ -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" |
|||
] |
|||
} |
|||
} |
@ -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 ( |
|||
<SafeAreaView style={{ ...backgroundStyle, height: "100%", flex:1 }}> |
|||
<AuthProvider> |
|||
<StatusBar |
|||
barStyle={"light-content"} |
|||
backgroundColor={backgroundStyle.backgroundColor} |
|||
/> |
|||
<ScrollView |
|||
style={{ |
|||
paddingHorizontal: 16, |
|||
height: "auto", |
|||
backgroundColor: "#333333", |
|||
}} |
|||
> |
|||
<Slot /> |
|||
</ScrollView> |
|||
</AuthProvider> |
|||
</SafeAreaView> |
|||
); |
|||
} |
|||
|
|||
export default App; |
@ -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<string>(""); |
|||
const [validation, setValidation] = useState<string>(""); |
|||
const [visible, setVisible] = useState<boolean>(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 ( |
|||
<> |
|||
<View |
|||
style={{ |
|||
flex: 1, |
|||
flexDirection: "column", |
|||
}} |
|||
> |
|||
<ENavButtons back={true} /> |
|||
<View style={{ flexDirection: "column", marginTop: 0 }}> |
|||
<View style={{ marginBottom: 24 }}> |
|||
<ETitleText style={{ marginBottom: 8 }}> |
|||
비밀번호 설정하기 |
|||
</ETitleText> |
|||
<EText> |
|||
비밀번호를 설정해주세요. 영문, 숫자를 조합한 8자 이상으로 |
|||
만들어주세요 |
|||
</EText> |
|||
</View> |
|||
<View style={{ rowGap: 8 }}> |
|||
<ELabelText>비밀번호</ELabelText> |
|||
<ETextInput |
|||
border={border} |
|||
placeholder="" |
|||
value={password} |
|||
setValue={setPassword} |
|||
passwordHidden={visible} |
|||
setPasswordHidden={setVisible} |
|||
/> |
|||
<ETextInput |
|||
border={border} |
|||
placeholder="비밀번호 다시 입력하기" |
|||
value={validation} |
|||
setValue={setValidation} |
|||
passwordHidden={visible} |
|||
setPasswordHidden={setVisible} |
|||
/> |
|||
{border === ETextInputBorder.Nothing && ( |
|||
<ECaptionText> |
|||
영문/숫자 조합 8자 이상으로 만들어주세요 |
|||
</ECaptionText> |
|||
)} |
|||
{border === ETextInputBorder.Fail && ( |
|||
<> |
|||
<ECaptionText error={true}> |
|||
영문/숫자 조합 8자 이상으로 만들어주세요 |
|||
</ECaptionText> |
|||
<ECaptionText error={true}> |
|||
비밀번호가 일치하는지 확인해주세요 |
|||
</ECaptionText> |
|||
</> |
|||
)} |
|||
</View> |
|||
</View> |
|||
<EFullButton |
|||
style={{ marginTop: 24 }} |
|||
text="비밀번호 설정하기" |
|||
active={border == ETextInputBorder.Success} |
|||
/> |
|||
</View> |
|||
</> |
|||
); |
|||
} |
@ -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<string>(""); |
|||
const [password, setPassword] = useState<string>(""); |
|||
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 ( |
|||
<> |
|||
<View |
|||
style={{ |
|||
flex: 1, |
|||
rowGap: 24, |
|||
flexDirection: "column", |
|||
paddingTop: 80, |
|||
}} |
|||
> |
|||
<ELogo /> |
|||
<View style={{ rowGap: 16, flexDirection: "column" }}> |
|||
<ETextInput |
|||
placeholder={"이메일"} |
|||
value={email} |
|||
setValue={setEmail} |
|||
onChange={() => setError(false)} |
|||
/> |
|||
<ETextInput |
|||
placeholder={"비밀번호"} |
|||
value={password} |
|||
setValue={setPassword} |
|||
passwordHidden={true} |
|||
onChange={() => setError(false)} |
|||
/> |
|||
{error && ( |
|||
<ECaptionText error={true}> |
|||
이메일 혹은 비밀번호가 일치하지 않습니다 |
|||
</ECaptionText> |
|||
)} |
|||
</View> |
|||
<View style={{ rowGap: 16, flexDirection: "column" }}> |
|||
<EFullButton |
|||
text={"로그인"} |
|||
active={ |
|||
email.length !== 0 && password.length !== 0 && !loginAttempting |
|||
} |
|||
onPress={() => signInWithEmail()} |
|||
/> |
|||
<EFullButton |
|||
style={{ backgroundColor: ColorScheme.passthrough }} |
|||
onPress={() => { |
|||
setLoginState && |
|||
setLoginState({ ...loginstate, passwordReset: true }); |
|||
router.push("auth/passwordresetemail"); |
|||
}} |
|||
active={true} |
|||
text={"비밀번호를 모르겠어요"} |
|||
/> |
|||
<View |
|||
style={{ |
|||
flex: 1, |
|||
justifyContent: "center", |
|||
columnGap: 16, |
|||
flexDirection: "row", |
|||
}} |
|||
> |
|||
<EIconButton active={false}> |
|||
<AppleIcon fill={ColorScheme.fill[10]} /> |
|||
</EIconButton> |
|||
<EIconButton |
|||
active={!loginAttempting} |
|||
onPress={() => { |
|||
signInWithGoogle() |
|||
.then((d) => { |
|||
console.log("worked!"); |
|||
setLoginAttempting(false); |
|||
router.replace(""); |
|||
}) |
|||
.catch((e) => { |
|||
console.error(e); |
|||
setLoginAttempting(false); |
|||
}); |
|||
}} |
|||
> |
|||
<GoogleIcon fill={ColorScheme.fill[10]} /> |
|||
</EIconButton> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
<View style={{ flexDirection: "column", rowGap: 16, marginTop: 200 }}> |
|||
<ELabelText style={{ textAlign: "center" }}> |
|||
아직 회원이 아니신가요? |
|||
</ELabelText> |
|||
<EFullButton |
|||
onPress={() => { |
|||
setLoginState && |
|||
setLoginState({ ...loginstate, passwordReset: false }); |
|||
router.push("auth/registeremail"); |
|||
}} |
|||
style={{ backgroundColor: ColorScheme.frostedfill.white[10] }} |
|||
active={true} |
|||
text={"회원가입"} |
|||
/> |
|||
</View> |
|||
</> |
|||
); |
|||
} |
@ -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<string>(''); |
|||
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 ( |
|||
<> |
|||
<View |
|||
style={{ |
|||
flex: 1, |
|||
flexDirection: 'column', |
|||
}}> |
|||
<ENavButtons back={true} /> |
|||
<View style={{flexDirection: 'column', marginTop: 0}}> |
|||
<View style={{marginBottom: 24}}> |
|||
<ETitleText style={{marginBottom:8}}>비밀번호 재설정하기</ETitleText> |
|||
<EText>비밀번호를 다시 설정하려면 회원가입 때 사용하신 이메일 주소로 비밀번호 재설정 링크를 보내드립니다</EText> |
|||
</View> |
|||
<View style={{rowGap: 8}}> |
|||
<ELabelText>이메일</ELabelText> |
|||
<ETextInput placeholder='이메일 입력하기' value={email} setValue={setEmail} /> |
|||
<ESmallButton text={'재설정 이메일 보내기'} active={emailre.test(email)} /> |
|||
</View> |
|||
</View> |
|||
<EFullButton style={{marginTop:380}} text='다음' active={emailre.test(email)}/> |
|||
</View> |
|||
</> |
|||
); |
|||
} |
@ -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<string>(''); |
|||
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 ( |
|||
<> |
|||
<View |
|||
style={{ |
|||
flexDirection: "column", |
|||
}} |
|||
> |
|||
<ENavButtons back={true} /> |
|||
<View style={{ flexDirection: "column", marginTop: 0 }}> |
|||
<ETitleText style={{ marginBottom: 24 }}> |
|||
이메일을 입력해주세요 |
|||
</ETitleText> |
|||
<View style={{ rowGap: 8 }}> |
|||
<ELabelText>이메일</ELabelText> |
|||
<ETextInput |
|||
placeholder="이메일 입력하기" |
|||
value={email} |
|||
setValue={setEmail} |
|||
onChange={(v) => setLoginState && setLoginState({ ...loginState, tempEmail: v })} |
|||
/> |
|||
</View> |
|||
</View> |
|||
<EFullButton |
|||
style={{ marginTop: 24 }} |
|||
text="다음" |
|||
active={emailre.test(email)} |
|||
onPress={() => router.push('auth/createpassword', )} |
|||
/> |
|||
</View> |
|||
</> |
|||
); |
|||
} |
@ -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 <Redirect href="auth/login" /> |
|||
} |
|||
return ( |
|||
<> |
|||
<View |
|||
style={{ |
|||
flex: 1, |
|||
rowGap: 24, |
|||
flexDirection: 'column', |
|||
paddingTop: 345, |
|||
}}> |
|||
<ELogo /> |
|||
<Text style={{color: 'white'}}>{JSON.stringify(user)} , {JSON.stringify(init)}</Text> |
|||
<Button title="Signout" onPress={async ()=> {await auth().signOut(); router.replace("auth/login")}} /> |
|||
</View> |
|||
</> |
|||
); |
|||
} |
size 115273 |
size 117672 |
size 2665004 |
size 2661752 |
size 2669648 |
size 2838808 |
size 2813692 |
size 2701192 |
size 2725828 |
size 2671468 |
size 2868924 |
size 9995 |
@ -0,0 +1,42 @@ |
|||
import React, { createContext, useContext, useEffect, useState } from "react"; |
|||
import auth, { FirebaseAuthTypes } from "@react-native-firebase/auth"; |
|||
import { useRouter } from "expo-router"; |
|||
|
|||
interface LoginState { |
|||
tempEmail?: string; |
|||
passwordReset?: boolean |
|||
} |
|||
|
|||
const AuthContext = createContext< |
|||
[ |
|||
FirebaseAuthTypes.User | null, |
|||
LoginState | null, |
|||
React.Dispatch<React.SetStateAction<LoginState | null>> | null, |
|||
boolean |
|||
] |
|||
>([null, null, null, false]); |
|||
|
|||
export default function AuthProvider(props: { children: React.ReactNode }) { |
|||
const [user, setUser] = useState<FirebaseAuthTypes.User | null>(null); |
|||
const [init, setInit] = useState(true); |
|||
const [loginstate, setLogInState] = useState<LoginState|null>(null); |
|||
useEffect(() => { |
|||
const subscriber = auth().onAuthStateChanged((u) => { |
|||
setUser(u); |
|||
if (init) { |
|||
setInit(false); |
|||
} |
|||
return subscriber; |
|||
}); |
|||
}, []); |
|||
if (init) return null; |
|||
return ( |
|||
<AuthContext.Provider value={[user, loginstate, setLogInState, init]}> |
|||
{props.children} |
|||
</AuthContext.Provider> |
|||
); |
|||
} |
|||
|
|||
export function useAuthState() { |
|||
return useContext(AuthContext); |
|||
} |
@ -0,0 +1,60 @@ |
|||
import React from "react"; |
|||
|
|||
export const ColorScheme = { |
|||
frostedfill: { |
|||
black: { |
|||
10: 'rgba(0,0,0,0.6)', |
|||
20: 'rgba(0,0,0,0.2)', |
|||
}, |
|||
white: { |
|||
10: 'rgba(255,255,255,0.15)', |
|||
20: 'rgba(255,255,255,0.5)', |
|||
}, |
|||
}, |
|||
fill: { |
|||
10: 'rgba(255,255,255,0.85)', |
|||
20: 'rgba(255,255,255,0.5)', |
|||
30: 'rgba(255,255,255,0.3)', |
|||
40: 'rgba(255,255,255,0.15)', |
|||
50: 'rgba(255,255,255,0.08)', |
|||
60: 'rgba(255,255,255,0.05)', |
|||
black: '#000000', |
|||
accent: '#40FF4F', |
|||
background: '#090909', |
|||
}, |
|||
text: { |
|||
10: 'rgba(255,255,255,0.95)', |
|||
20: 'rgba(255,255,255,0.55)', |
|||
30: 'rgba(255,255,255,0.30)', |
|||
inverse: 'rgba(0,0,0,0.9)', |
|||
}, |
|||
divider: { |
|||
10: 'rgba(255,255,255,0.30)', |
|||
20: 'rgba(255,255,255,0.08)', |
|||
}, |
|||
success: { |
|||
fill: '#00B23B', |
|||
text: '#16D956', |
|||
10: '#00802A', |
|||
20: '#009933', |
|||
30: '#00591E', |
|||
40: '#003311', |
|||
}, |
|||
warning: { |
|||
fill: '#FF9F40', |
|||
text: '#FF9F40', |
|||
10: '#CC8033', |
|||
20: '#E58F39', |
|||
30: '#592D00', |
|||
40: '#331A00', |
|||
}, |
|||
critical: { |
|||
fill: '#FF264B', |
|||
text: '#CC0023', |
|||
10: '#99001A', |
|||
20: '#B2001E', |
|||
30: '#59000F', |
|||
40: '#330009', |
|||
}, |
|||
passthrough: '#00000000', |
|||
} |
@ -0,0 +1,93 @@ |
|||
import React from 'react'; |
|||
import {Pressable, StyleProp, ViewStyle} from 'react-native'; |
|||
import {ColorScheme} from './Design'; |
|||
import {Text} from 'react-native'; |
|||
|
|||
export default function EFullButton(props: { |
|||
text: string; |
|||
active?: boolean; |
|||
onPress?: () => void; |
|||
style?: StyleProp<ViewStyle>; |
|||
}) { |
|||
return ( |
|||
<Pressable |
|||
style={[ |
|||
{ |
|||
width: '100%', |
|||
borderRadius: 4, |
|||
backgroundColor: ColorScheme.frostedfill.black[10], |
|||
paddingHorizontal: 16, |
|||
paddingVertical: 12, |
|||
}, |
|||
props.style, |
|||
]} |
|||
onPress={() => props.active && props.onPress && props.onPress()}> |
|||
<Text |
|||
style={{ |
|||
color: props.active ? ColorScheme.fill[10] : ColorScheme.fill[40], |
|||
textAlign: 'center', |
|||
textAlignVertical: 'center', |
|||
fontSize: 17, |
|||
fontWeight: '600', |
|||
}}> |
|||
{props.text} |
|||
</Text> |
|||
</Pressable> |
|||
); |
|||
} |
|||
|
|||
export function ESmallButton(props: { |
|||
text: string; |
|||
active?: boolean; |
|||
onPress?: () => void; |
|||
style?: StyleProp<ViewStyle>; |
|||
}) { |
|||
return ( |
|||
<Pressable |
|||
style={[{ |
|||
borderRadius: 4, |
|||
backgroundColor: ColorScheme.frostedfill.white[10], |
|||
paddingHorizontal: 16, |
|||
paddingTop: 12, |
|||
paddingBottom: 13, |
|||
alignSelf: 'flex-start', |
|||
flex: 1, |
|||
},props.style]} |
|||
onPress={() => props.active && props.onPress && props.onPress()}> |
|||
<Text |
|||
style={{ |
|||
color: props.active ? ColorScheme.fill[10] : ColorScheme.fill[40], |
|||
fontFamily: 'Pretendard-SemiBold', |
|||
textAlign: 'center', |
|||
textAlignVertical: 'center', |
|||
fontSize: 15, |
|||
flex: 1, |
|||
}}> |
|||
{props.text} |
|||
</Text> |
|||
</Pressable> |
|||
); |
|||
} |
|||
|
|||
export function EIconButton(props: { |
|||
children: React.ReactNode, |
|||
active?: boolean; |
|||
onPress?: () => void; |
|||
style?: StyleProp<ViewStyle>; |
|||
}) { |
|||
return ( |
|||
<Pressable |
|||
style={[{ |
|||
borderRadius: 4, |
|||
backgroundColor: ColorScheme.frostedfill.white[10], |
|||
paddingHorizontal: 8, |
|||
paddingVertical: 8, |
|||
justifyContent: 'center', |
|||
alignContent: 'center', |
|||
height: 40, |
|||
},props.style]} |
|||
onPress={() => props.active && props.onPress && props.onPress()}> |
|||
{props.children} |
|||
</Pressable> |
|||
); |
|||
} |
@ -0,0 +1,100 @@ |
|||
import React from 'react'; |
|||
import Svg, {G, Path, Defs, ClipPath, Rect} from 'react-native-svg'; |
|||
|
|||
export const CrossIcon = (props: any) => ( |
|||
<Svg |
|||
width={24} |
|||
height={24} |
|||
viewBox="0 0 24 24" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props}> |
|||
<G clipPath="url(#clip0_226_47132)"> |
|||
<Path d="M5.721 5.72724C5.34609 6.10215 5.34611 6.70999 5.721 7.08488L10.6425 12.0064L5.72102 16.9278C5.34613 17.3027 5.34611 17.9106 5.72102 18.2855C6.09594 18.6604 6.70377 18.6604 7.07867 18.2855L12.0001 13.364L16.9216 18.2855C17.2965 18.6604 17.9043 18.6604 18.2792 18.2855C18.6541 17.9105 18.6541 17.3027 18.2792 16.9278L13.3578 12.0064L18.2792 7.0849C18.6541 6.71001 18.6542 6.10217 18.2792 5.72726C17.9043 5.35235 17.2965 5.35236 16.9216 5.72726L12.0001 10.6487L7.07865 5.72724C6.70375 5.35234 6.09592 5.35233 5.721 5.72724Z" /> |
|||
</G> |
|||
<Defs> |
|||
<ClipPath id="clip0_226_47132"> |
|||
<Rect |
|||
width={24} |
|||
height={24} |
|||
transform="translate(0 0.00634766)" |
|||
/> |
|||
</ClipPath> |
|||
</Defs> |
|||
</Svg> |
|||
); |
|||
|
|||
export const EyeIcon = (props: any) => ( |
|||
<Svg |
|||
width={24} |
|||
height={24} |
|||
viewBox="0 0 24 24" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props}> |
|||
<Path |
|||
fillRule="evenodd" |
|||
clipRule="evenodd" |
|||
d="M12 5.006c3.69 0 6.974 2.19 9.83 6.443a1 1 0 0 1 0 1.115c-2.856 4.253-6.14 6.442-9.83 6.442s-6.974-2.19-9.83-6.442a1 1 0 0 1 0-1.115C5.026 7.196 8.31 5.006 12 5.006m0 2c-2.76 0-5.323 1.598-7.71 4.898l-.074.102.074.103C6.6 15.302 9.075 16.9 11.733 17l.267.005c2.76 0 5.323-1.597 7.71-4.897l.073-.103-.073-.102C17.4 8.71 14.925 7.11 12.267 7.01zm0 1a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4" |
|||
/> |
|||
</Svg> |
|||
); |
|||
|
|||
export const SuccessIcon = (props: any) => ( |
|||
<Svg |
|||
width={24} |
|||
height={24} |
|||
viewBox="0 0 24 24" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props}> |
|||
<Path |
|||
d="M15.743 9.264a.906.906 0 0 0-1.267 0l-3.745 3.635-1.2-1.179a.92.92 0 0 0-1.264-.006.86.86 0 0 0-.01 1.235l1.832 1.8a.907.907 0 0 0 1.266.003l4.383-4.25a.865.865 0 0 0 .007-1.234z" |
|||
/> |
|||
<Path |
|||
d="M21.214 8.118a10 10 0 0 0-2.144-3.18 10 10 0 0 0-3.18-2.143A9.9 9.9 0 0 0 12 2.007c-5.514.002-10 4.488-10 10 0 5.514 4.486 10 10 10s10-4.486 10-10a9.9 9.9 0 0 0-.786-3.889M12 20.353c-4.602 0-8.346-3.744-8.346-8.345S7.398 3.662 12 3.662s8.346 3.744 8.346 8.346c0 4.601-3.744 8.345-8.346 8.345" |
|||
/> |
|||
</Svg> |
|||
); |
|||
|
|||
export const FailIcon = (props: any) => ( |
|||
<Svg |
|||
width={24} |
|||
height={24} |
|||
viewBox="0 0 24 24" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props}> |
|||
<Path d="M12.036 2.006a9.9 9.9 0 0 1 7.055 2.91C20.909 6.733 22 9.278 22 12.042a9.9 9.9 0 0 1-2.91 7.054 9.9 9.9 0 0 1-7.054 2.91c-2.763 0-5.309-1.092-7.127-2.91A9.9 9.9 0 0 1 2 12.043c0-2.764 1.09-5.31 2.91-7.128 1.817-1.818 4.363-2.909 7.126-2.909m6.037 4c-1.527-1.6-3.71-2.545-6.037-2.545-2.4 0-4.509.945-6.036 2.545-1.6 1.528-2.545 3.637-2.545 6.037 0 2.327.945 4.509 2.545 6.036 1.527 1.527 3.636 2.473 6.036 2.473 2.328 0 4.51-.946 6.037-2.473s2.473-3.709 2.473-6.036c0-2.4-.946-4.51-2.473-6.037" /> |
|||
<Path d="M14.8 8.188a.703.703 0 0 1 1.018 0 .703.703 0 0 1 0 1.018l-2.836 2.837 2.837 2.763c.29.291.29.8 0 1.091a.703.703 0 0 1-1.019 0l-2.836-2.836-2.691 2.69a.79.79 0 0 1-1.09 0 .87.87 0 0 1 0-1.018l2.763-2.69-2.764-2.691c-.218-.291-.218-.8 0-1.091a.79.79 0 0 1 1.091 0l2.69 2.69z" /> |
|||
</Svg> |
|||
); |
|||
|
|||
export const AppleIcon = (props:any) => ( |
|||
<Svg |
|||
width={24} |
|||
height={24} |
|||
viewBox="0 0 24 24" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props} |
|||
> |
|||
<Path |
|||
d="M20.3172 8.50055C20.1898 8.59956 17.9397 9.86909 17.9397 12.692C17.9397 15.9571 20.8028 17.1122 20.8885 17.1408C20.8753 17.2112 20.4337 18.7228 19.3789 20.2629C18.4385 21.6183 17.4563 22.9714 15.9621 22.9714C14.4679 22.9714 14.0834 22.1023 12.3585 22.1023C10.6775 22.1023 10.0798 23 8.71307 23C7.34633 23 6.39269 21.7459 5.29622 20.2057C4.02616 18.3971 3 15.5875 3 12.9208C3 8.64356 5.77743 6.37514 8.51092 6.37514C9.96336 6.37514 11.1741 7.33003 12.086 7.33003C12.9539 7.33003 14.3075 6.31793 15.9599 6.31793C16.5861 6.31793 18.8362 6.37514 20.3172 8.50055ZM15.1754 4.50715C15.8588 3.69527 16.3422 2.56876 16.3422 1.44224C16.3422 1.28603 16.329 1.12761 16.3005 1C15.1886 1.0418 13.8658 1.74147 13.0682 2.66777C12.442 3.38064 11.8575 4.50715 11.8575 5.64907C11.8575 5.82068 11.886 5.9923 11.8992 6.0473C11.9695 6.06051 12.0838 6.07591 12.198 6.07591C13.1956 6.07591 14.4503 5.40704 15.1754 4.50715Z" |
|||
/> |
|||
</Svg> |
|||
); |
|||
|
|||
export const GoogleIcon = (props:any) => ( |
|||
<Svg |
|||
width={24} |
|||
height={24} |
|||
viewBox="0 0 28 24" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props} |
|||
> |
|||
<Path |
|||
d="M14.0031 2.40625C8.69995 2.40625 4.40002 6.70465 4.40002 12.0063C4.40002 17.3078 8.69995 21.6063 14.0031 21.6063C22.0111 21.6063 23.8153 14.1719 23.0641 10.4062H22H20.186H14V13.6062H20.1906C19.479 16.3649 16.9808 18.4062 14 18.4062C10.4656 18.4062 7.60002 15.5407 7.60002 12.0063C7.60002 8.47185 10.4656 5.60625 14 5.60625C15.6072 5.60625 17.0713 6.20285 18.1953 7.18125L20.4688 4.90937C18.7616 3.35418 16.4935 2.40625 14.0031 2.40625Z" |
|||
/> |
|||
</Svg> |
|||
); |
|||
|
|||
|
|||
|
|||
export default {CrossIcon, EyeIcon}; |
@ -0,0 +1,17 @@ |
|||
import React from 'react'; |
|||
import {Text} from 'react-native'; |
|||
import {ColorScheme} from './Design'; |
|||
|
|||
export default function ELogo() { |
|||
return ( |
|||
<Text |
|||
style={{ |
|||
fontFamily: 'Axiforma-ExtraBold', |
|||
fontSize: 36, |
|||
color: ColorScheme.fill[10], |
|||
textAlign: 'center', |
|||
}}> |
|||
essentory |
|||
</Text> |
|||
); |
|||
} |
@ -0,0 +1,40 @@ |
|||
import React from 'react'; |
|||
import {Pressable, StyleProp, Text, View, ViewStyle} from 'react-native'; |
|||
import {Svg, Path} from 'react-native-svg'; |
|||
import {ColorScheme} from './Design'; |
|||
import { useRouter } from 'expo-router'; |
|||
|
|||
const BackButton = (props: any) => ( |
|||
<Svg |
|||
width={64} |
|||
height={64} |
|||
viewBox="0 0 64 64" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
{...props}> |
|||
<Path |
|||
fillRule="evenodd" |
|||
clipRule="evenodd" |
|||
d="M36.0002 38.3939L25.5862 27.9799L36.0002 17.5659L37.4142 18.9799L28.4142 27.9799L37.4142 36.9799L36.0002 38.3939Z" |
|||
fill="white" |
|||
fillOpacity={0.95} |
|||
/> |
|||
</Svg> |
|||
); |
|||
|
|||
// fill in logic for moving back later.
|
|||
export default function ENavButtons(props: { |
|||
back?: boolean; |
|||
style?: StyleProp<ViewStyle>, |
|||
}) { |
|||
const router = useRouter(); |
|||
return ( |
|||
<View |
|||
style={[{ |
|||
paddingHorizontal: 0, |
|||
margin:0, |
|||
left:-24 |
|||
}]}> |
|||
{props.back && <Pressable onPress={() => router.back()}><BackButton fill={ColorScheme.text[10]} /></Pressable>} |
|||
</View> |
|||
); |
|||
} |
@ -0,0 +1,76 @@ |
|||
import React from 'react'; |
|||
import {StyleProp, Text, TextStyle, ViewStyle} from 'react-native'; |
|||
import {ColorScheme} from './Design'; |
|||
|
|||
export default function EText(props: { |
|||
children: React.ReactNode; |
|||
style?: StyleProp<TextStyle>; |
|||
}) { |
|||
return ( |
|||
<Text |
|||
style={[ |
|||
{ |
|||
fontFamily: 'Pretendard-Regular', |
|||
fontSize: 15, |
|||
color: ColorScheme.fill[10], |
|||
}, |
|||
props.style, |
|||
]}> |
|||
{props.children} |
|||
</Text> |
|||
); |
|||
} |
|||
|
|||
export function ETitleText(props: { |
|||
children: React.ReactNode; |
|||
style?: StyleProp<TextStyle>; |
|||
}) { |
|||
return ( |
|||
<Text |
|||
style={[ |
|||
{ |
|||
fontFamily: 'Pretendard-SemiBold', |
|||
fontSize: 24, |
|||
color: ColorScheme.fill[10], |
|||
}, |
|||
props.style, |
|||
]}> |
|||
{props.children} |
|||
</Text> |
|||
); |
|||
} |
|||
|
|||
export function ELabelText(props: { |
|||
children: React.ReactNode; |
|||
style?: StyleProp<TextStyle>; |
|||
}) { |
|||
return ( |
|||
<Text |
|||
style={[ |
|||
{fontFamily: 'Pretendard-SemiBold', fontSize: 15, color: 'white'}, |
|||
props.style, |
|||
]}> |
|||
{props.children} |
|||
</Text> |
|||
); |
|||
} |
|||
|
|||
export function ECaptionText(props: { |
|||
children: React.ReactNode; |
|||
error?: boolean; |
|||
style?: StyleProp<TextStyle>; |
|||
}) { |
|||
return ( |
|||
<Text |
|||
style={[ |
|||
{ |
|||
fontFamily: 'Pretendard-Regular', |
|||
fontSize: 12, |
|||
color: props.error ? ColorScheme.critical.text : ColorScheme.fill[20], |
|||
}, |
|||
props.style, |
|||
]}> |
|||
{props.children} |
|||
</Text> |
|||
); |
|||
} |
@ -0,0 +1,113 @@ |
|||
import React, {useState} from 'react'; |
|||
import {StyleProp, Text, TextInput, View, ViewStyle} from 'react-native'; |
|||
import {ColorScheme} from './Design'; |
|||
import {CrossIcon, EyeIcon, FailIcon, SuccessIcon} from './EIcons'; |
|||
|
|||
export enum ETextInputBorder { |
|||
Nothing = 0, |
|||
Border = 1, |
|||
Success = 2, |
|||
Fail = 3, |
|||
} |
|||
|
|||
const borderColorScheme = [ |
|||
'rgba(0,0,0,0)', |
|||
ColorScheme.fill[30], |
|||
ColorScheme.success.fill, |
|||
ColorScheme.critical.fill, |
|||
]; |
|||
|
|||
export default function ETextInput(props: { |
|||
placeholder: string; |
|||
value: string; |
|||
setValue: any; |
|||
passwordHidden?: boolean; |
|||
setPasswordHidden?: (newState: boolean) => void; |
|||
checkFailStatus?: boolean; |
|||
border?: ETextInputBorder; |
|||
style?: StyleProp<ViewStyle>; |
|||
onChange?: (value: string) => void; |
|||
}) { |
|||
const border = |
|||
props.border === undefined ? ETextInputBorder.Nothing : props.border; |
|||
const [focused, setFocused] = useState(false); |
|||
|
|||
const clear = () => { |
|||
props.setValue(''); |
|||
props.onChange !== undefined && props.onChange(''); |
|||
}; |
|||
|
|||
const sideIcon = () => { |
|||
if (props.setPasswordHidden !== undefined) { |
|||
return ( |
|||
<EyeIcon |
|||
fill={ |
|||
props.passwordHidden ? ColorScheme.fill[20] : ColorScheme.fill[10] |
|||
} |
|||
onPress={() => |
|||
props.setPasswordHidden && |
|||
props.setPasswordHidden(!props.passwordHidden) |
|||
} |
|||
/> |
|||
); |
|||
} else if (props.checkFailStatus !== undefined) { |
|||
return props.checkFailStatus ? ( |
|||
<SuccessIcon fill={ColorScheme.success.text} /> |
|||
) : ( |
|||
<FailIcon fill={ColorScheme.critical.text} /> |
|||
); |
|||
} else { |
|||
return ( |
|||
props.value && <CrossIcon fill={ColorScheme.fill[20]} onPress={clear} /> |
|||
); |
|||
} |
|||
}; |
|||
|
|||
return ( |
|||
<View |
|||
style={[ |
|||
props.style, |
|||
{ |
|||
borderColor: |
|||
borderColorScheme[ |
|||
focused && border == ETextInputBorder.Nothing |
|||
? ETextInputBorder.Border |
|||
: border |
|||
], |
|||
borderWidth: 1, |
|||
borderRadius: 8, |
|||
backgroundColor: ColorScheme.frostedfill.black[20], |
|||
paddingHorizontal: 16, |
|||
paddingVertical: 12, |
|||
flexDirection: 'row', |
|||
justifyContent: 'space-between', |
|||
alignItems: 'center', |
|||
// these two are sketch.
|
|||
shadowRadius: 30, |
|||
shadowColor: '#000000', |
|||
}, |
|||
]}> |
|||
<TextInput |
|||
onFocus={() => setFocused(true)} |
|||
onBlur={() => setFocused(false)} |
|||
onChangeText={text => { |
|||
props.setValue(text); |
|||
props.onChange !== undefined && props.onChange(text); |
|||
}} |
|||
value={props.value} |
|||
placeholder={props.placeholder} |
|||
placeholderTextColor={ColorScheme.fill[30]} |
|||
secureTextEntry={props.passwordHidden} |
|||
style={{ |
|||
fontFamily: 'Pretendard-SemiBold', |
|||
fontSize: 17, |
|||
fontWeight: '300', |
|||
color: ColorScheme.fill[10], |
|||
margin: 0, |
|||
padding: 0, |
|||
flex: 1, |
|||
}}></TextInput> |
|||
{sideIcon()} |
|||
</View> |
|||
); |
|||
} |
1156
package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,6 +1,9 @@ |
|||
{ |
|||
"extends": "expo/tsconfig.base", |
|||
"compilerOptions": { |
|||
"strict": true |
|||
"strict": true, |
|||
"paths": { |
|||
"@components/*": ["components/*"] |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue