Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
bda709d
feat: add mock data for user profile, transactions, investments, and …
Rice9547 Apr 25, 2025
1d391ad
refactor: update TransactionScreen to display monthly summary with in…
Rice9547 Apr 25, 2025
8db2608
feat: add monthly saving and current saving features to HomeScreen an…
Rice9547 Apr 25, 2025
531976b
feat: enhance user profile management by integrating AsyncStorage for…
Rice9547 Apr 25, 2025
ecaf687
feat: integrate AsyncStorage for transaction management; update Trans…
Rice9547 Apr 25, 2025
3a2507a
feat: implement mission management service with AsyncStorage integrat…
Rice9547 Apr 25, 2025
ce10962
feat: integrate Redux for state management
Rice9547 Apr 25, 2025
124a86d
refactor: replace Layout component with SafeAreaView in SetUp screen;…
Rice9547 Apr 25, 2025
99948e4
refactor: migrate mission and transaction services to use Redux for s…
Rice9547 Apr 25, 2025
365a93b
feat: implement initialization service for app setup; enhance App com…
Rice9547 Apr 26, 2025
df606f0
feat: integrate character management with Redux; implement character …
Rice9547 Apr 26, 2025
751c30c
feat: enhance BagScreen with character selection logic; integrate Red…
Rice9547 Apr 26, 2025
9ff61be
feat: implement diamond cost validation in GachaScreen; alert user fo…
Rice9547 Apr 26, 2025
dc49a16
fix: update diamond cost in GachaScreen alert and UI; change required…
Rice9547 Apr 26, 2025
a205d96
feat: add character rotation animation in GachaScreen; implement curr…
Rice9547 Apr 26, 2025
ea152d5
feat: add character images and definitions; refactor character image …
Rice9547 Apr 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect} from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import AppNavigator from './src/navigation/AppNavigator';
Expand All @@ -8,6 +8,9 @@ import LoadingScreen from './src/screens/LoadingScreen';
import {initializeApp, getApps} from '@react-native-firebase/app';
import {SWRProvider} from './src/api/swrConfig';
import Config from 'react-native-config';
import {Provider} from 'react-redux';
import {store} from './src/store';
import {initializationService} from './src/api/initializationService';

const firebaseConfig = {
apiKey: Config.FIREBASE_API_KEY || '',
Expand All @@ -27,6 +30,21 @@ const Stack = createStackNavigator();
const AppContent = () => {
const {loading, serverToken} = useAuth();

useEffect(() => {
console.log('AppContent mounted, starting initialization...');
const initializeAppData = async () => {
console.log('Calling initializeApp...');
try {
await initializationService.initializeApp();
} catch (error) {
console.error('Error initializing app:', error);
}
console.log('Initialization completed');
};

initializeAppData();
}, []);

if (loading) {
return <LoadingScreen />;
}
Expand All @@ -45,12 +63,15 @@ const AppContent = () => {
};

function App(): React.JSX.Element {
console.log('App rendering...');
return (
<SWRProvider>
<AuthProvider>
<AppContent />
</AuthProvider>
</SWRProvider>
<Provider store={store}>
<SWRProvider>
<AuthProvider>
<AppContent />
</AuthProvider>
</SWRProvider>
</Provider>
);
}

Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"@react-native-picker/picker": "^2.11.0",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.1.1",
"@reduxjs/toolkit": "^2.7.0",
"axios": "^1.8.4",
"react": "19.0.0",
"react-native": "0.78.0",
Expand All @@ -37,6 +39,7 @@
"react-native-svg": "^15.11.2",
"react-native-switch-selector": "^2.3.0",
"react-native-vector-icons": "^10.2.0",
"react-redux": "^9.2.0",
"swr": "^2.3.3"
},
"devDependencies": {
Expand All @@ -50,6 +53,7 @@
"@react-native/eslint-config": "0.78.0",
"@react-native/metro-config": "0.78.0",
"@react-native/typescript-config": "0.78.0",
"@tsconfig/react-native": "^3.0.5",
"@types/jest": "^29.5.13",
"@types/react": "^19.0.0",
"@types/react-native-vector-icons": "^6.4.18",
Expand Down
46 changes: 16 additions & 30 deletions src/api/authService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import apiClient from './client';
import {API_ENDPOINTS} from './endpoints';

interface LoginCredentials {
email: string;
password: string;
Expand All @@ -12,41 +9,30 @@ interface AuthResponse {
expires_in: number;
}

const mockAuthResponse: AuthResponse = {
access_token: 'mock-access-token',
refresh_token: 'mock-refresh-token',
expires_in: 3600,
};

export const authService = {
exchangeToken: async (firebaseToken: string): Promise<AuthResponse> => {
const response = await apiClient.post(API_ENDPOINTS.AUTH_LOGIN, {
firebase_token: firebaseToken,
});
return response.data;
exchangeToken: async (_: string): Promise<AuthResponse> => {
return mockAuthResponse;
},

login: async (credentials: LoginCredentials): Promise<AuthResponse> => {
const response = await apiClient.post(
API_ENDPOINTS.AUTH_LOGIN,
credentials,
);
return response.data;
login: async (_: LoginCredentials): Promise<AuthResponse> => {
return mockAuthResponse;
},

register: async (userData: any) => {
const response = await apiClient.post(
API_ENDPOINTS.AUTH_REGISTER,
userData,
);
return response.data;
register: async (_: any) => {
return mockAuthResponse;
},

logout: async (refreshToken: string) => {
const response = await apiClient.post(API_ENDPOINTS.AUTH_LOGOUT, {
refresh_token: refreshToken,
});
return response.data;
logout: async (_: string) => {
return {success: true};
},

refreshToken: async (refreshToken: string): Promise<AuthResponse> => {
const response = await apiClient.post(API_ENDPOINTS.AUTH_REFRESH, {
refresh_token: refreshToken,
});
return response.data;
refreshToken: async (_: string): Promise<AuthResponse> => {
return mockAuthResponse;
},
};
24 changes: 14 additions & 10 deletions src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios, {AxiosError, AxiosRequestConfig} from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Config from 'react-native-config';
import {API_ENDPOINTS} from './endpoints';
import {store} from '../store';

const API_BASE_URL = Config.API_BASE_URL || 'http://localhost:8080/api';
const SKIP_AUTH = Config.SKIP_AUTH === 'true';
Expand All @@ -16,7 +16,9 @@ const apiClient = axios.create({

apiClient.interceptors.request.use(
async config => {
const token = await AsyncStorage.getItem('userToken');
const state = store.getState();
const token = state.auth.userToken;

if (
token &&
!config.url?.includes(API_ENDPOINTS.AUTH_LOGIN) &&
Expand Down Expand Up @@ -48,7 +50,8 @@ apiClient.interceptors.response.use(
originalRequest._retry = true;

try {
const refreshToken = await AsyncStorage.getItem('refreshToken');
const state = store.getState();
const refreshToken = state.auth.refreshToken;

if (!refreshToken) {
throw new Error('No refresh token available');
Expand All @@ -63,8 +66,13 @@ apiClient.interceptors.response.use(

const {access_token, refresh_token} = response.data;

await AsyncStorage.setItem('userToken', access_token);
await AsyncStorage.setItem('refreshToken', refresh_token);
store.dispatch({
type: 'auth/setTokens',
payload: {
userToken: access_token,
refreshToken: refresh_token,
},
});

apiClient.defaults.headers.common.Authorization = `Bearer ${access_token}`;

Expand All @@ -74,12 +82,8 @@ apiClient.interceptors.response.use(

return apiClient(originalRequest);
} catch (refreshError) {
await AsyncStorage.removeItem('userToken');
await AsyncStorage.removeItem('refreshToken');
await AsyncStorage.removeItem('isDummyToken');

store.dispatch({type: 'auth/clearTokens'});
console.error('Token refresh failed, user needs to login again');

return Promise.reject(error);
}
}
Expand Down
77 changes: 77 additions & 0 deletions src/api/initializationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {store} from '../store';
import {missionService} from './missionService';
import {missionSlice} from '../store/slices/missionSlice';

export const initializationService = {
initializeApp: async () => {
try {
// Check if missions exist in store
const state = store.getState();
console.log('Current missions in store:', state.missions.missions);

if (!state.missions.missions || state.missions.missions.length === 0) {
// If no missions, set default missions
const defaultMissions = await missionService.getMissions();
console.log('Setting default missions:', defaultMissions);
store.dispatch(missionSlice.actions.setMissions(defaultMissions));
}

// Check missions completion
const {missions, transactions} = store.getState();
console.log('Missions after initialization:', missions.missions);

// Check transaction mission
const hasTransactions = transactions.transactions.length > 0;
const transactionMission = missions.missions.find(
m => m.id === 'transaction',
);
if (
hasTransactions &&
transactionMission &&
!transactionMission.isCompleted
) {
const updatedMissions = await missionService.updateMissionStatus(
'transaction',
true,
missions.missions,
);
store.dispatch(missionSlice.actions.setMissions(updatedMissions));
}

// Check income mission
const hasIncome = transactions.transactions.some(
t => t.type === 'INCOME' && t.amount >= 500,
);
const incomeMission = missions.missions.find(m => m.id === 'income');
if (hasIncome && incomeMission && !incomeMission.isCompleted) {
const updatedMissions = await missionService.updateMissionStatus(
'income',
true,
missions.missions,
);
store.dispatch(missionSlice.actions.setMissions(updatedMissions));
}
} catch (error) {
console.error('Error initializing app:', error);
}
},

// Save user data to AsyncStorage
saveUserData: async () => {
try {
const state = store.getState();
const userData = {
budget: state.settings.budget,
saving: state.settings.saving,
diamonds: state.settings.diamonds,
selectedDino: state.settings.selectedDino,
monthlyIncome: state.settings.monthlyIncome,
monthlySaving: state.settings.monthlySaving,
currentSaving: state.settings.currentSaving,
};
await AsyncStorage.setItem('userData', JSON.stringify(userData));
} catch (error) {
console.error('Error saving user data:', error);
}
},
};
80 changes: 62 additions & 18 deletions src/api/investmentService.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,81 @@
import apiClient from './client';
import {API_ENDPOINTS} from './endpoints';
import {mockInvestments, mockInvestmentOpportunities} from '../mock/data';
import useSWR from 'swr';

export interface Investment {
id: string;
name: string;
amount: number;
returnRate: number;
riskLevel: string;
status: string;
}

export interface Opportunity {
id: string;
name: string;
description: string;
date: string;
category: string;
transaction_type: 'Income' | 'Expense';
minAmount: number;
expectedReturn: number;
riskLevel: string;
}

interface CreateInvestmentRequest {
opportunity_id: string;
export interface CreateInvestmentRequest {
opportunityId: string;
amount: number;
}

export interface Opportunity {}

export const transactionService = {
export const investmentService = {
getOpportunities: async (): Promise<Opportunity[]> => {
const response = await apiClient.get(API_ENDPOINTS.INVESTMENT);
return response.data;
return mockInvestmentOpportunities;
},

getUserInvestments: async (): Promise<Investment[]> => {
const response = await apiClient.get(API_ENDPOINTS.USER_INVESTMENT);
return response.data;
return mockInvestments;
},

postUserInvestment: async (investment: CreateInvestmentRequest) => {
const response = await apiClient.post(
API_ENDPOINTS.USER_INVESTMENT,
investment,
const opportunity = mockInvestmentOpportunities.find(
o => o.id === investment.opportunityId,
);
return response.data;
if (!opportunity) {
throw new Error('Opportunity not found');
}
const newInvestment: Investment = {
id: (mockInvestments.length + 1).toString(),
name: opportunity.name,
amount: investment.amount,
returnRate: opportunity.expectedReturn,
riskLevel: opportunity.riskLevel,
status: 'ACTIVE',
};
mockInvestments.push(newInvestment);
return newInvestment;
},
};

export const useInvestments = () => {
const {data, error, isLoading, mutate} = useSWR<Investment[]>(
'mock-investments',
() => investmentService.getUserInvestments(),
);

return {
investments: data || [],
isLoading,
isError: error,
mutate,
};
};

export const useInvestmentOpportunities = () => {
const {data, error, isLoading} = useSWR<Opportunity[]>(
'mock-investment-opportunities',
() => investmentService.getOpportunities(),
);

return {
opportunities: data || [],
isLoading,
isError: error,
};
};
Loading