Compare commits

..

1 Commits

Author SHA1 Message Date
dios.one c9bf094384 fixing login, ganti icon, add delete account 2026-05-19 10:28:31 +07:00
13 changed files with 186 additions and 65 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 934 KiB

+52 -57
View File
@@ -26,6 +26,7 @@
"expo-media-library": "~55.0.16",
"expo-print": "~55.0.14",
"expo-sharing": "~55.0.18",
"expo-splash-screen": "~55.0.20",
"expo-status-bar": "~55.0.6",
"expo-web-browser": "~55.0.15",
"react": "19.2.0",
@@ -1466,6 +1467,24 @@
"node-forge": "^1.3.3"
}
},
"node_modules/@expo/config": {
"version": "55.0.16",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.16.tgz",
"integrity": "sha512-H5dpQv5TfyZDNheZAWO3SmP10diGWZwN5QOUsArkDJih0QKNtahQBOmrV2xbhgln/nrUGoy41U/ZIY/MEx63Ug==",
"license": "MIT",
"dependencies": {
"@expo/config-plugins": "~55.0.8",
"@expo/config-types": "^55.0.5",
"@expo/json-file": "^10.0.14",
"@expo/require-utils": "^55.0.5",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4"
}
},
"node_modules/@expo/config-plugins": {
"version": "55.0.8",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.8.tgz",
@@ -1604,24 +1623,6 @@
"chalk": "^4.1.2"
}
},
"node_modules/@expo/local-build-cache-provider/node_modules/@expo/config": {
"version": "55.0.16",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.16.tgz",
"integrity": "sha512-H5dpQv5TfyZDNheZAWO3SmP10diGWZwN5QOUsArkDJih0QKNtahQBOmrV2xbhgln/nrUGoy41U/ZIY/MEx63Ug==",
"license": "MIT",
"dependencies": {
"@expo/config-plugins": "~55.0.8",
"@expo/config-types": "^55.0.5",
"@expo/json-file": "^10.0.14",
"@expo/require-utils": "^55.0.5",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4"
}
},
"node_modules/@expo/metro": {
"version": "55.1.1",
"resolved": "https://registry.npmjs.org/@expo/metro/-/metro-55.1.1.tgz",
@@ -1681,6 +1682,27 @@
"xmlbuilder": "^15.1.1"
}
},
"node_modules/@expo/prebuild-config": {
"version": "55.0.17",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.17.tgz",
"integrity": "sha512-Mcs+dg4Ripu0yCtzf66KZr18PehI1O8HxzJw+G5SUF8VWX+ic99aci1PltvmydWepLwTQL6ykmpXicAUA31IqA==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.16",
"@expo/config-plugins": "~55.0.8",
"@expo/config-types": "^55.0.5",
"@expo/image-utils": "^0.8.14",
"@expo/json-file": "^10.0.14",
"@react-native/normalize-colors": "0.83.6",
"debug": "^4.3.1",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"xml2js": "0.6.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/@expo/require-utils": {
"version": "55.0.5",
"resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.5.tgz",
@@ -4155,6 +4177,18 @@
"react-native": "*"
}
},
"node_modules/expo-splash-screen": {
"version": "55.0.20",
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-55.0.20.tgz",
"integrity": "sha512-WI5T0dutiZhxsqlF+jhEP4JRpQNILLlP8IpmKehsnV53Cncv6AQrKE7y1sOWwDyC2m2GBufZ/Vwam1RMt2EfmA==",
"license": "MIT",
"dependencies": {
"@expo/prebuild-config": "^55.0.17"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-status-bar": {
"version": "55.0.6",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-55.0.6.tgz",
@@ -4268,27 +4302,6 @@
}
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/prebuild-config": {
"version": "55.0.17",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.17.tgz",
"integrity": "sha512-Mcs+dg4Ripu0yCtzf66KZr18PehI1O8HxzJw+G5SUF8VWX+ic99aci1PltvmydWepLwTQL6ykmpXicAUA31IqA==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.16",
"@expo/config-plugins": "~55.0.8",
"@expo/config-types": "^55.0.5",
"@expo/image-utils": "^0.8.14",
"@expo/json-file": "^10.0.14",
"@react-native/normalize-colors": "0.83.6",
"debug": "^4.3.1",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"xml2js": "0.6.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": {
"version": "55.0.16",
"resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.16.tgz",
@@ -4344,24 +4357,6 @@
}
}
},
"node_modules/expo/node_modules/@expo/config": {
"version": "55.0.16",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.16.tgz",
"integrity": "sha512-H5dpQv5TfyZDNheZAWO3SmP10diGWZwN5QOUsArkDJih0QKNtahQBOmrV2xbhgln/nrUGoy41U/ZIY/MEx63Ug==",
"license": "MIT",
"dependencies": {
"@expo/config-plugins": "~55.0.8",
"@expo/config-types": "^55.0.5",
"@expo/json-file": "^10.0.14",
"@expo/require-utils": "^55.0.5",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4"
}
},
"node_modules/expo/node_modules/@expo/log-box": {
"version": "55.0.12",
"resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.12.tgz",
+1
View File
@@ -27,6 +27,7 @@
"expo-media-library": "~55.0.16",
"expo-print": "~55.0.14",
"expo-sharing": "~55.0.18",
"expo-splash-screen": "~55.0.20",
"expo-status-bar": "~55.0.6",
"expo-web-browser": "~55.0.15",
"react": "19.2.0",
+2 -2
View File
@@ -64,8 +64,8 @@ export default function PlanetCore({ size = 90, glowIntensity = 0.5 }) {
{/* Planet image */}
<Image
source={require('../../assets/icon.png')}
style={{ width: size, height: size * 0.88 }}
resizeMode="cover"
style={{ width: size, height: size }}
resizeMode="contain"
/>
</View>
);
+2 -2
View File
@@ -107,8 +107,8 @@ function PageContent({ item, isActive }) {
)}
<Image
source={require('../../assets/icon.png')}
style={{ width: item.iconSize, height: item.iconSize * 0.88 }}
resizeMode="cover"
style={{ width: item.iconSize * 1.2, height: item.iconSize * 1.2 }}
resizeMode="contain"
/>
</Animated.View>
+60
View File
@@ -54,11 +54,13 @@ export default function ProfileScreen({ navigation }) {
const resetIdentity = useIdentityStore((s) => s.reset);
const resetHabits = useHabitStore((s) => s.reset);
const resetJourney = useIdentityStore((s) => s.resetJourney);
const deleteAccount = useAuthStore((s) => s.deleteAccount);
const fadeAnim = useRef(new Animated.Value(0)).current;
const [showGuide, setShowGuide] = useState(false);
const [showReset, setShowReset] = useState(false);
const [resetReason, setResetReason] = useState('');
const [resetting, setResetting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [focusCount, setFocusCount] = useState(0);
const [profile, setProfile] = useState(null);
const [journeyCount, setJourneyCount] = useState(0);
@@ -119,6 +121,45 @@ export default function ProfileScreen({ navigation }) {
]);
};
const handleDeleteAccount = () => {
showAlert(
'Delete your account?',
'This will permanently delete your account, all your data, habits, journals, and progress. This cannot be undone.',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete Forever', style: 'destructive',
onPress: () => {
// Double confirm
showAlert(
'Last chance',
'Are you absolutely sure? Everything will be gone permanently.',
[
{ text: 'Keep my account', style: 'cancel' },
{
text: 'Yes, delete everything', style: 'destructive',
onPress: async () => {
setDeleting(true);
try {
resetIdentity();
resetHabits();
await deleteAccount();
navigation.dispatch(CommonActions.reset({ index: 0, routes: [{ name: 'Login' }] }));
} catch (e) {
showAlert('Error', e?.message || 'Failed to delete account.');
} finally {
setDeleting(false);
}
},
},
]
);
},
},
]
);
};
const phase = getDayPhase(currentDay);
const progressPct = Math.round((currentDay / 40) * 100);
const displayName = profile?.nickname || profile?.fullName || user?.email?.split('@')[0] || 'Nova';
@@ -328,6 +369,18 @@ export default function ProfileScreen({ navigation }) {
<Button title="Sign Out" onPress={handleSignOut} variant="secondary" style={st.signOutBtn} />
</FadeIn>
{/* ═══ Delete Account ═══ */}
<FadeIn delay={450} trigger={focusCount}>
<Pressable
style={({ pressed }) => [st.deleteBtn, pressed && st.deleteBtnPressed]}
onPress={handleDeleteAccount}
disabled={deleting}
>
<Text style={st.deleteIcon}>🗑</Text>
<Text style={st.deleteText}>{deleting ? 'Deleting...' : 'Delete Account'}</Text>
</Pressable>
</FadeIn>
{/* ═══ Footer ═══ */}
<FadeIn delay={500} trigger={focusCount}>
<Text style={st.version}>Nova40 v1.0.0</Text>
@@ -457,6 +510,13 @@ const st = StyleSheet.create({
legalArrow: { color: colors.textMuted, fontSize: fonts.sizes.lg },
signOutBtn: { borderColor: colors.error },
deleteBtn: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'center',
paddingVertical: spacing.md, marginTop: spacing.md,
},
deleteBtnPressed: { opacity: 0.5 },
deleteIcon: { fontSize: 14, marginRight: spacing.sm },
deleteText: { color: colors.textMuted, fontSize: fonts.sizes.sm },
// Footer
version: { color: colors.textMuted, fontSize: 9, textAlign: 'center', marginTop: spacing.xl },
+4 -4
View File
@@ -96,7 +96,7 @@ export default function SplashScreen({ navigation }) {
<Image
source={require('../../assets/icon.png')}
style={styles.logo}
resizeMode="cover"
resizeMode="contain"
/>
</Animated.View>
@@ -114,12 +114,12 @@ export default function SplashScreen({ navigation }) {
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.background },
content: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40 },
logoContainer: { alignItems: 'center', justifyContent: 'center', marginBottom: 40 },
logoContainer: { alignItems: 'center', justifyContent: 'center', marginBottom: 32 },
glow: {
width: 200, height: 200, borderRadius: 100,
width: 240, height: 240, borderRadius: 120,
backgroundColor: colors.planetGlow, opacity: 0.4, position: 'absolute',
},
logo: { width: 160, height: 140 },
logo: { width: 180, height: 180 },
title: {
color: colors.text, fontSize: fonts.sizes.hero,
fontWeight: fonts.weights.bold, letterSpacing: 8, marginBottom: 16,
+54
View File
@@ -290,6 +290,60 @@ export async function signInWithFacebook() {
}
}
export async function deleteAccount() {
if (!USE_OFFLINE) {
const { error } = await supabase.rpc('delete_user');
if (error) throw error;
return;
}
const session = await getSession();
if (!session?.user?.email) throw new Error('No active session.');
const email = session.user.email.toLowerCase();
const userId = session.user.id;
// Import offline storage
const offline = await import('./offlineStorage');
// Get all user's identities to cascade delete related data
const identities = await offline.getAll('identities', { user_id: userId });
const identityIds = identities.map((i) => i.id);
// Remove all identity-related data
for (const identityId of identityIds) {
await offline.removeAll('habits', { identity_id: identityId });
await offline.removeAll('daily_logs', { identity_id: identityId });
await offline.removeAll('habit_logs', { identity_id: identityId });
await offline.removeAll('stats', { identity_id: identityId });
await offline.removeAll('game_sessions', { identity_id: identityId });
await offline.removeAll('reset_logs', { identity_id: identityId });
}
// Remove identities & profile
await offline.removeAll('identities', { user_id: userId });
await offline.removeAll('profiles', { user_id: userId });
// Remove profile AsyncStorage key
await AsyncStorage.removeItem(`nova40_profile_${userId}`);
// Remove user from auth store
const users = await getStoredUsers();
delete users[email];
await saveUsers(users);
// Clear session
await clearSession();
// Clean up misc keys
const remembered = await AsyncStorage.getItem('remember_email');
if (remembered?.toLowerCase() === email) {
await AsyncStorage.removeItem('remember_email');
}
await AsyncStorage.removeItem('nova40_last_oauth');
await AsyncStorage.removeItem('nova40_onboarding_completed');
}
export function onAuthStateChange(callback) {
if (!USE_OFFLINE) {
return supabase.auth.onAuthStateChange(callback);
+11
View File
@@ -137,6 +137,17 @@ const useAuthStore = create((set) => ({
set({ session: null, user: null, error: null });
},
deleteAccount: async () => {
set({ loading: true, error: null });
try {
await authService.deleteAccount();
set({ session: null, user: null, loading: false, error: null });
} catch (e) {
set({ loading: false, error: e.message });
throw e;
}
},
fetchSession: async () => {
try {
const session = await authService.getSession();