├── assets ├── images │ ├── icon.png │ ├── favicon.png │ ├── react-logo.png │ ├── splash-icon.png │ ├── adaptive-icon.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ └── partial-react-logo.png └── fonts │ └── SpaceMono-Regular.ttf ├── shared ├── constants │ ├── entrypoint.ts │ └── Colors.ts └── types │ └── auth.ts ├── tsconfig.json ├── stores ├── slices │ └── User │ │ ├── reducer.ts │ │ └── index.ts └── index.ts ├── app ├── (auth) │ ├── _layout.tsx │ └── login.tsx ├── _layout.tsx └── (dashboard) │ ├── index.tsx │ └── _layout.tsx ├── .gitignore ├── app.json ├── services └── axiosClient.ts ├── package.json └── README.md /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/react-logo.png -------------------------------------------------------------------------------- /assets/images/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/splash-icon.png -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tienld-0801/church-teaching-app/HEAD/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /shared/constants/entrypoint.ts: -------------------------------------------------------------------------------- 1 | export const ENTRYPOINT = { 2 | login: 'api/v1/user/login', 3 | logout: 'api/v1/user/logout', 4 | }; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /stores/slices/User/reducer.ts: -------------------------------------------------------------------------------- 1 | import { IDataLoginResponse } from '@/shared/types/auth'; 2 | import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'; 3 | 4 | const updateUserInfo: CaseReducer< 5 | IDataLoginResponse, 6 | PayloadAction 7 | > = (state, action) => ({ ...state, ...action.payload }); 8 | 9 | export default { 10 | updateUserInfo, 11 | }; 12 | -------------------------------------------------------------------------------- /app/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootStatesType } from '@/stores'; 2 | import { Redirect, Slot } from 'expo-router'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | const LoginLayout = () => { 6 | const userInfo = useSelector((state: RootStatesType) => state.userInfo); 7 | return userInfo.accessToken ? : ; 8 | }; 9 | 10 | export default LoginLayout; 11 | -------------------------------------------------------------------------------- /app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { store } from '@/stores'; 2 | import { Stack, useRouter } from 'expo-router'; 3 | import { Button } from 'react-native'; 4 | import { Provider, useDispatch } from 'react-redux'; 5 | 6 | export default function Layout() { 7 | return ( 8 | 9 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | *.orig.* 14 | *.jks 15 | *.p8 16 | *.p12 17 | *.key 18 | *.mobileprovision 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | 38 | app-example 39 | 40 | .env -------------------------------------------------------------------------------- /stores/slices/User/index.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import reducers from '@/stores/slices/User/reducer'; 3 | import { IDataLoginResponse } from '@/shared/types/auth'; 4 | 5 | const initState: IDataLoginResponse = { 6 | accessToken: '', 7 | refreshToken: '', 8 | userLogin: { 9 | id: 0, 10 | account: '', 11 | name: '', 12 | roleName: '', 13 | leaderType: '', 14 | }, 15 | }; 16 | 17 | export const userSlice = createSlice({ 18 | name: 'language', 19 | initialState: initState, 20 | reducers: reducers, 21 | }); 22 | 23 | export const { updateUserInfo } = userSlice.actions; 24 | 25 | export default userSlice.reducer; 26 | -------------------------------------------------------------------------------- /shared/constants/Colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode. 3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. 4 | */ 5 | 6 | const tintColorLight = '#0a7ea4'; 7 | const tintColorDark = '#fff'; 8 | 9 | export const Colors = { 10 | light: { 11 | text: '#11181C', 12 | background: '#fff', 13 | tint: tintColorLight, 14 | icon: '#687076', 15 | tabIconDefault: '#687076', 16 | tabIconSelected: tintColorLight, 17 | }, 18 | dark: { 19 | text: '#ECEDEE', 20 | background: '#151718', 21 | tint: tintColorDark, 22 | icon: '#9BA1A6', 23 | tabIconDefault: '#9BA1A6', 24 | tabIconSelected: tintColorDark, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /shared/types/auth.ts: -------------------------------------------------------------------------------- 1 | export interface IParamsLogin { 2 | account: string; 3 | password: string; 4 | } 5 | 6 | export interface IUserLoginResponse { 7 | id: number; 8 | account: string; 9 | name: string; 10 | roleName: string; 11 | leaderType: string; 12 | } 13 | 14 | export interface IDataLoginResponse { 15 | accessToken: string; 16 | refreshToken: string; 17 | userLogin: IUserLoginResponse; 18 | } 19 | 20 | export interface IResponseLogin { 21 | status: string; 22 | message: string; 23 | timestamp: string; 24 | pageResponse: any | null; 25 | data: IDataLoginResponse; 26 | } 27 | 28 | export interface ILoginError { 29 | status: string; 30 | message: string; 31 | timestamp: string; 32 | error: string; 33 | } 34 | 35 | export interface IResponseLogout { 36 | status: string; 37 | message: string; 38 | timestamp: string; 39 | pageResponse: null; 40 | data: null; 41 | } 42 | -------------------------------------------------------------------------------- /app/(dashboard)/index.tsx: -------------------------------------------------------------------------------- 1 | import { RootStatesType } from '@/stores'; 2 | import React from 'react'; 3 | import { View, Text, StyleSheet } from 'react-native'; 4 | import { useSelector } from 'react-redux'; 5 | 6 | const HomeScreen = () => { 7 | const userInfo = useSelector((state: RootStatesType) => state.userInfo); 8 | 9 | return ( 10 | 11 | 12 | Home Screen 13 | {userInfo.userLogin ? ( 14 | Role: {userInfo.userLogin.roleName} 15 | ) : ( 16 | Loading role... 17 | )} 18 | 19 | 20 | ); 21 | }; 22 | 23 | const styles = StyleSheet.create({ 24 | container: { 25 | flex: 1, 26 | justifyContent: 'center', 27 | alignItems: 'center', 28 | }, 29 | title: { 30 | fontSize: 24, 31 | fontWeight: 'bold', 32 | }, 33 | }); 34 | 35 | export default HomeScreen; 36 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "church-teaching-app", 4 | "slug": "church-teaching-app", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "newArchEnabled": true, 11 | "ios": { 12 | "supportsTablet": true 13 | }, 14 | "android": { 15 | "adaptiveIcon": { 16 | "foregroundImage": "./assets/images/adaptive-icon.png", 17 | "backgroundColor": "#ffffff" 18 | } 19 | }, 20 | "web": { 21 | "bundler": "metro", 22 | "output": "static", 23 | "favicon": "./assets/images/favicon.png" 24 | }, 25 | "plugins": [ 26 | "expo-router", 27 | [ 28 | "expo-splash-screen", 29 | { 30 | "image": "./assets/images/splash-icon.png", 31 | "imageWidth": 200, 32 | "resizeMode": "contain", 33 | "backgroundColor": "#ffffff" 34 | } 35 | ] 36 | ], 37 | "experiments": { 38 | "typedRoutes": true 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/(dashboard)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootStatesType } from '@/stores'; 2 | import { Redirect, Slot, Stack, useRouter } from 'expo-router'; 3 | import { Button } from 'react-native'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import API from '@/services/axiosClient'; 6 | import { updateUserInfo } from '@/stores/slices/User'; 7 | 8 | const LoginLayout = () => { 9 | const navigation = useRouter(); 10 | const userInfo = useSelector((state: RootStatesType) => state.userInfo); 11 | const dispatch = useDispatch(); 12 | 13 | const handleLogout = async () => { 14 | try { 15 | const res = await API.apiLogout(); 16 | const { status } = res.data; 17 | 18 | if (status === 'success') { 19 | dispatch( 20 | updateUserInfo({ 21 | accessToken: '', 22 | refreshToken: '', 23 | userLogin: { 24 | id: 0, 25 | account: '', 26 | name: '', 27 | roleName: '', 28 | leaderType: '', 29 | }, 30 | }) 31 | ); 32 | navigation.replace('/login'); 33 | } 34 | } catch (error) { 35 | console.log('error', error); 36 | } 37 | }; 38 | 39 | return !userInfo.accessToken ? ( 40 | 41 | ) : ( 42 | 43 | ( 48 |