├── .gitignore ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── (auth) │ │ ├── _layout.tsx │ │ └── login.tsx │ ├── (tabs) │ │ ├── _layout.tsx │ │ ├── accounts.tsx │ │ └── allocations │ │ │ ├── _layout.tsx │ │ │ ├── index.tsx │ │ │ └── new.tsx │ ├── _layout.tsx │ └── index.tsx ├── components │ ├── AccountAllocationItem.tsx │ ├── AccountListItem.tsx │ ├── AccountsList.tsx │ ├── AllocationListItem.tsx │ └── AllocationsList.tsx ├── db │ ├── index.ts │ ├── migrations.ts │ ├── schema.ts │ └── sync.ts ├── lib │ └── supabase.ts ├── model │ ├── Account.ts │ ├── AccountAllocation.ts │ └── Allocation.ts └── providers │ └── AuthProvider.tsx ├── supabase ├── .gitignore ├── config.toml ├── migrations │ └── 20240426181512_remote_schema.sql └── seed.sql └── tsconfig.json /.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 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | ios 38 | android -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "ProfitFirst", 4 | "slug": "ProfitFirst", 5 | "scheme": "profitfirst", 6 | "version": "1.0.0", 7 | "orientation": "portrait", 8 | "icon": "./assets/icon.png", 9 | "userInterfaceStyle": "light", 10 | "splash": { 11 | "image": "./assets/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "assetBundlePatterns": ["**/*"], 16 | "ios": { 17 | "supportsTablet": true, 18 | "bundleIdentifier": "com.vadinsavin.ProfitFirst" 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | }, 25 | "package": "com.vadinsavin.ProfitFirst" 26 | }, 27 | "web": { 28 | "favicon": "./assets/favicon.png", 29 | "bundler": "metro" 30 | }, 31 | "plugins": [ 32 | "expo-router", 33 | [ 34 | "expo-build-properties", 35 | { 36 | "ios": { 37 | "extraPods": [ 38 | { 39 | "name": "simdjson", 40 | "configurations": ["Debug", "Release"], 41 | "path": "../node_modules/@nozbe/simdjson", 42 | "modular_headers": true 43 | } 44 | ] 45 | } 46 | // "android": { 47 | // "kotlinVersion": '' 48 | // } 49 | } 50 | ] 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notJust-dev/ProfitFirst/7e4b569c396a7a06af5bdef0d16165296cf5ad52/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notJust-dev/ProfitFirst/7e4b569c396a7a06af5bdef0d16165296cf5ad52/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notJust-dev/ProfitFirst/7e4b569c396a7a06af5bdef0d16165296cf5ad52/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notJust-dev/ProfitFirst/7e4b569c396a7a06af5bdef0d16165296cf5ad52/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "profitfirst", 3 | "version": "1.0.0", 4 | "main": "expo-router/entry", 5 | "scripts": { 6 | "start": "expo start --dev-client", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@nozbe/watermelondb": "^0.27.1", 13 | "@react-native-async-storage/async-storage": "1.21.0", 14 | "@supabase/supabase-js": "^2.42.7", 15 | "@types/react": "~18.2.45", 16 | "expo": "~50.0.14", 17 | "expo-build-properties": "~0.11.1", 18 | "expo-constants": "~15.4.6", 19 | "expo-linking": "~6.2.2", 20 | "expo-router": "~3.4.8", 21 | "expo-status-bar": "~1.11.1", 22 | "react": "18.2.0", 23 | "react-dom": "18.2.0", 24 | "react-native": "0.73.6", 25 | "react-native-safe-area-context": "4.8.2", 26 | "react-native-screens": "~3.29.0", 27 | "react-native-web": "~0.19.6", 28 | "typescript": "^5.3.0", 29 | "expo-crypto": "~12.8.1" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.20.0", 33 | "@babel/plugin-proposal-decorators": "^7.24.1" 34 | }, 35 | "private": true 36 | } 37 | -------------------------------------------------------------------------------- /src/app/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Stack } from 'expo-router'; 2 | import { useAuth } from '../../providers/AuthProvider'; 3 | 4 | const AuthLayout = () => { 5 | const { isAuthenticated } = useAuth(); 6 | 7 | if (isAuthenticated) { 8 | return ; 9 | } 10 | 11 | return ; 12 | }; 13 | 14 | export default AuthLayout; 15 | -------------------------------------------------------------------------------- /src/app/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { 3 | Alert, 4 | StyleSheet, 5 | View, 6 | AppState, 7 | Button, 8 | TextInput, 9 | } from 'react-native'; 10 | import { supabase } from '../../lib/supabase'; 11 | 12 | // Tells Supabase Auth to continuously refresh the session automatically if 13 | // the app is in the foreground. When this is added, you will continue to receive 14 | // `onAuthStateChange` events with the `TOKEN_REFRESHED` or `SIGNED_OUT` event 15 | // if the user's session is terminated. This should only be registered once. 16 | AppState.addEventListener('change', (state) => { 17 | if (state === 'active') { 18 | supabase.auth.startAutoRefresh(); 19 | } else { 20 | supabase.auth.stopAutoRefresh(); 21 | } 22 | }); 23 | 24 | export default function Auth() { 25 | const [email, setEmail] = useState(''); 26 | const [password, setPassword] = useState(''); 27 | const [loading, setLoading] = useState(false); 28 | 29 | async function signInWithEmail() { 30 | setLoading(true); 31 | const { error } = await supabase.auth.signInWithPassword({ 32 | email: email, 33 | password: password, 34 | }); 35 | 36 | if (error) Alert.alert(error.message); 37 | setLoading(false); 38 | } 39 | 40 | async function signUpWithEmail() { 41 | setLoading(true); 42 | const { 43 | data: { session }, 44 | error, 45 | } = await supabase.auth.signUp({ 46 | email: email, 47 | password: password, 48 | }); 49 | 50 | if (error) Alert.alert(error.message); 51 | if (!session) 52 | Alert.alert('Please check your inbox for email verification!'); 53 | setLoading(false); 54 | } 55 | 56 | return ( 57 | 58 | 59 | setEmail(text)} 61 | value={email} 62 | placeholder="email@address.com" 63 | autoCapitalize={'none'} 64 | style={styles.input} 65 | /> 66 | 67 | 68 | setPassword(text)} 70 | value={password} 71 | secureTextEntry={true} 72 | placeholder="Password" 73 | autoCapitalize={'none'} 74 | style={styles.input} 75 | /> 76 | 77 | 78 | 79 |