@oxyhq/services
Version:
605 lines (602 loc) • 18.4 kB
JavaScript
"use strict";
import { useMemo, useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, Platform } from 'react-native';
import { Header } from "../../components/index.js";
import { Ionicons } from '@expo/vector-icons';
import { useI18n } from "../../hooks/useI18n.js";
import { useThemeStyles } from "../../hooks/useThemeStyles.js";
import { normalizeTheme, normalizeColorScheme } from "../../utils/themeUtils.js";
import { useColorScheme } from "../../hooks/useColorScheme.js";
import { Colors } from "../../constants/theme.js";
import { useOxy } from "../../context/OxyContext.js";
import { darkenColor, lightenColor } from "../../utils/colorUtils.js";
import { fontFamilies } from "../../styles/fonts.js";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const KarmaRewardsScreen = ({
goBack,
theme
}) => {
const {
t
} = useI18n();
const {
user,
oxyServices,
isAuthenticated
} = useOxy();
const [karmaTotal, setKarmaTotal] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const normalizedTheme = normalizeTheme(theme);
const baseThemeStyles = useThemeStyles(normalizedTheme);
const colorScheme = useColorScheme();
const normalizedColorScheme = normalizeColorScheme(colorScheme);
const colors = Colors[normalizedColorScheme];
const themeStyles = useMemo(() => ({
...baseThemeStyles,
primaryColor: '#d169e5'
}), [baseThemeStyles]);
useEffect(() => {
if (!user || !isAuthenticated) {
setIsLoading(false);
return;
}
setIsLoading(true);
oxyServices.getUserKarmaTotal(user.id).then(data => {
setKarmaTotal(data.total || 0);
}).catch(() => {
setKarmaTotal(0);
}).finally(() => setIsLoading(false));
}, [user, isAuthenticated, oxyServices]);
const achievements = useMemo(() => [{
id: 'first-step',
name: t('karma.achievements.firstStep') || 'First Step',
description: t('karma.achievements.firstStepDesc') || 'Earned your first karma point',
category: 'milestone',
icon: 'footsteps',
iconColor: '#8E8E93',
unlocked: karmaTotal >= 1,
rarity: 'common'
}, {
id: 'novice',
name: t('karma.achievements.novice') || 'Novice',
description: t('karma.achievements.noviceDesc') || 'Reached 10 karma points',
category: 'milestone',
icon: 'leaf',
iconColor: '#34C759',
unlocked: karmaTotal >= 10,
rarity: 'common'
}, {
id: 'contributor',
name: t('karma.achievements.contributor') || 'Contributor',
description: t('karma.achievements.contributorDesc') || 'Reached 50 karma points',
category: 'contribution',
icon: 'account-group',
iconColor: '#007AFF',
unlocked: karmaTotal >= 50,
rarity: 'common'
}, {
id: 'rising-star',
name: t('karma.achievements.risingStar') || 'Rising Star',
description: t('karma.achievements.risingStarDesc') || 'Reached 100 karma points',
category: 'milestone',
icon: 'star',
iconColor: '#FF9500',
unlocked: karmaTotal >= 100,
rarity: 'rare'
}, {
id: 'early-adopter',
name: t('karma.achievements.earlyAdopter') || 'Early Adopter',
description: t('karma.achievements.earlyAdopterDesc') || 'Been part of the community from the start',
category: 'special',
icon: 'rocket',
iconColor: '#AF52DE',
unlocked: karmaTotal >= 200,
rarity: 'rare'
}, {
id: 'community-hero',
name: t('karma.achievements.communityHero') || 'Community Hero',
description: t('karma.achievements.communityHeroDesc') || 'Reached 500 karma points',
category: 'contribution',
icon: 'shield',
iconColor: '#FF2D55',
unlocked: karmaTotal >= 500,
rarity: 'epic'
}, {
id: 'legend',
name: t('karma.achievements.legend') || 'Legend',
description: t('karma.achievements.legendDesc') || 'Reached 1000 karma points',
category: 'milestone',
icon: 'trophy',
iconColor: '#FFD700',
unlocked: karmaTotal >= 1000,
rarity: 'legendary'
}, {
id: 'phoenix',
name: t('karma.achievements.phoenix') || 'Phoenix',
description: t('karma.achievements.phoenixDesc') || 'Reached 2500 karma points',
category: 'milestone',
icon: 'flame',
iconColor: '#FF3B30',
unlocked: karmaTotal >= 2500,
rarity: 'legendary'
}, {
id: 'unstoppable',
name: t('karma.achievements.unstoppable') || 'Unstoppable',
description: t('karma.achievements.unstoppableDesc') || 'Reached 5000 karma points',
category: 'milestone',
icon: 'infinite',
iconColor: '#5E5CE6',
unlocked: karmaTotal >= 5000,
rarity: 'legendary'
}, {
id: 'bug-hunter',
name: t('karma.achievements.bugHunter') || 'Bug Hunter',
description: t('karma.achievements.bugHunterDesc') || 'Reported helpful bugs',
category: 'contribution',
icon: 'bug',
iconColor: '#FF9500',
unlocked: false,
// TODO: Check actual bug reports
rarity: 'rare'
}, {
id: 'helper',
name: t('karma.achievements.helper') || 'Helper',
description: t('karma.achievements.helperDesc') || 'Helped 10 users',
category: 'contribution',
icon: 'hand-left',
iconColor: '#34C759',
unlocked: false,
// TODO: Check help actions
rarity: 'common'
}, {
id: 'streak-master',
name: t('karma.achievements.streakMaster') || 'Streak Master',
description: t('karma.achievements.streakMasterDesc') || '7 day activity streak',
category: 'streak',
icon: 'flash',
iconColor: '#FFD700',
unlocked: false,
// TODO: Check streak
rarity: 'epic'
}], [t, karmaTotal, colors]);
const unlockedAchievements = achievements.filter(a => a.unlocked);
const lockedAchievements = achievements.filter(a => !a.unlocked);
const getRarityColor = rarity => {
switch (rarity) {
case 'legendary':
return '#FFD700';
case 'epic':
return '#AF52DE';
case 'rare':
return '#007AFF';
default:
return '#8E8E93';
}
};
const getAchievementValue = achievement => {
// Extract numeric value from achievement based on ID and unlocked state
const valueMap = {
'first-step': 1,
'novice': 10,
'contributor': 50,
'rising-star': 100,
'early-adopter': 200,
'community-hero': 500,
'legend': 1000,
'phoenix': 2500,
'unstoppable': 5000,
'helper': 10,
'streak-master': 7
};
return valueMap[achievement.id] || null;
};
const renderAchievement = achievement => {
const rarityColor = getRarityColor(achievement.rarity);
const isLocked = !achievement.unlocked;
const achievementValue = getAchievementValue(achievement);
// Two-tone colors: darker for borders/shadow, lighter for highlights
// Use achievement iconColor for unlocked badges, gray for locked
const baseColor = isLocked ? '#8E8E93' : achievement.iconColor || '#8E8E93';
const darkTone = darkenColor(baseColor, 0.45); // Darker border/shadow (more contrast)
const lightTone = lightenColor(baseColor, 0.25); // Lighter highlight
const mediumTone = baseColor; // Base color
return /*#__PURE__*/_jsxs(View, {
style: [styles.achievementCard, {
backgroundColor: colors.card
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.badgeContainer,
children: [!isLocked && /*#__PURE__*/_jsx(View, {
style: [styles.badgeGlow, styles.badgeOrganic, {
backgroundColor: darkTone,
opacity: 0.3
}]
}), /*#__PURE__*/_jsxs(View, {
style: [styles.badgeMain, styles.badgeOrganic, {
backgroundColor: isLocked ? '#E5E5EA' : mediumTone,
borderColor: darkTone,
borderWidth: 5,
shadowColor: darkTone
}],
children: [!isLocked && /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(View, {
style: [styles.badgeHighlight, {
backgroundColor: lightTone
}]
}), /*#__PURE__*/_jsx(View, {
style: [styles.badgeHighlightAccent, {
backgroundColor: lightenColor(lightTone, 0.1)
}]
})]
}), /*#__PURE__*/_jsx(View, {
style: styles.badgeIconContainer,
children: isLocked ? /*#__PURE__*/_jsx(Ionicons, {
name: "lock-closed",
size: 40,
color: "#8E8E93"
}) : /*#__PURE__*/_jsx(Ionicons, {
name: achievement.icon,
size: 40,
color: "#FFFFFF"
})
}), achievementValue !== null && !isLocked && /*#__PURE__*/_jsx(View, {
style: styles.badgeValueContainer,
children: /*#__PURE__*/_jsx(Text, {
style: [styles.badgeValueText, {
color: lightTone,
textShadowColor: darkTone
}],
children: achievementValue
})
})]
}), achievement.unlocked && /*#__PURE__*/_jsx(View, {
style: [styles.rarityBadge, {
backgroundColor: rarityColor,
borderColor: darkenColor(rarityColor, 0.4)
}],
children: /*#__PURE__*/_jsx(Text, {
style: styles.rarityText,
children: achievement.rarity[0].toUpperCase()
})
})]
}), /*#__PURE__*/_jsx(Text, {
style: [styles.achievementName, {
color: themeStyles.textColor,
opacity: isLocked ? 0.5 : 1
}],
children: achievement.name
}), /*#__PURE__*/_jsx(Text, {
style: [styles.achievementDescription, {
color: themeStyles.isDarkTheme ? '#BBBBBB' : '#888888',
opacity: isLocked ? 0.5 : 1
}],
children: achievement.description
})]
}, achievement.id);
};
if (!isAuthenticated) {
return /*#__PURE__*/_jsxs(View, {
style: [styles.container, {
backgroundColor: themeStyles.backgroundColor
}],
children: [/*#__PURE__*/_jsx(Header, {
title: t('karma.rewards.title') || 'Karma Rewards',
subtitle: t('karma.rewards.subtitle') || 'Unlock special features and recognition',
onBack: goBack,
elevation: "subtle"
}), /*#__PURE__*/_jsx(View, {
style: styles.centerContent,
children: /*#__PURE__*/_jsx(Text, {
style: [styles.message, {
color: themeStyles.textColor
}],
children: t('common.status.notSignedIn') || 'Not signed in'
})
})]
});
}
return /*#__PURE__*/_jsxs(View, {
style: [styles.container, {
backgroundColor: themeStyles.backgroundColor
}],
children: [/*#__PURE__*/_jsx(Header, {
title: t('karma.rewards.title') || 'Karma Rewards',
subtitle: t('karma.rewards.subtitle') || 'Unlock special features and recognition',
onBack: goBack,
elevation: "subtle"
}), /*#__PURE__*/_jsxs(ScrollView, {
contentContainerStyle: styles.contentContainer,
showsVerticalScrollIndicator: false,
children: [/*#__PURE__*/_jsxs(View, {
style: [styles.statsCard, {
backgroundColor: colors.card
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.statsHeader,
children: [/*#__PURE__*/_jsxs(View, {
children: [/*#__PURE__*/_jsx(Text, {
style: [styles.currentKarma, {
color: themeStyles.primaryColor
}],
children: karmaTotal
}), /*#__PURE__*/_jsx(Text, {
style: [styles.karmaLabel, {
color: themeStyles.isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: t('karma.center.balance') || 'Karma Points'
})]
}), /*#__PURE__*/_jsxs(View, {
style: styles.achievementStats,
children: [/*#__PURE__*/_jsx(Text, {
style: [styles.achievementCount, {
color: themeStyles.primaryColor
}],
children: unlockedAchievements.length
}), /*#__PURE__*/_jsx(Text, {
style: [styles.achievementCountLabel, {
color: themeStyles.isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: t('karma.achievements.unlocked') || 'Achievements'
})]
})]
}), /*#__PURE__*/_jsxs(View, {
style: styles.progressBarContainer,
children: [/*#__PURE__*/_jsx(View, {
style: [styles.progressBar, {
backgroundColor: themeStyles.borderColor
}],
children: /*#__PURE__*/_jsx(View, {
style: [styles.progressBarFill, {
width: `${unlockedAchievements.length / achievements.length * 100}%`,
backgroundColor: themeStyles.primaryColor
}]
})
}), /*#__PURE__*/_jsxs(Text, {
style: [styles.progressText, {
color: themeStyles.isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: [unlockedAchievements.length, " / ", achievements.length]
})]
})]
}), unlockedAchievements.length > 0 && /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Text, {
style: [styles.sectionTitle, {
color: themeStyles.textColor
}],
children: t('karma.achievements.unlocked') || 'Unlocked Achievements'
}), /*#__PURE__*/_jsx(View, {
style: styles.achievementsGrid,
children: unlockedAchievements.map(achievement => renderAchievement(achievement))
})]
}), lockedAchievements.length > 0 && /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Text, {
style: [styles.sectionTitle, {
color: themeStyles.textColor
}],
children: t('karma.achievements.locked') || 'Locked Achievements'
}), /*#__PURE__*/_jsx(View, {
style: styles.achievementsGrid,
children: lockedAchievements.map(achievement => renderAchievement(achievement))
})]
})]
})]
});
};
const styles = StyleSheet.create({
container: {
flex: 1
},
contentContainer: {
paddingHorizontal: 24,
paddingTop: 20,
paddingBottom: 40
},
centerContent: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
message: {
fontSize: 16,
textAlign: 'center'
},
statsCard: {
borderRadius: 18,
padding: 20,
marginBottom: 24
},
statsHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16
},
currentKarma: {
fontSize: 36,
fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
fontFamily: fontFamilies.interBold,
marginBottom: 4
},
achievementStats: {
alignItems: 'flex-end'
},
achievementCount: {
fontSize: 36,
fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
fontFamily: fontFamilies.interBold,
marginBottom: 4
},
achievementCountLabel: {
fontSize: 14
},
karmaLabel: {
fontSize: 14
},
progressBarContainer: {
marginTop: 8
},
progressBar: {
height: 8,
borderRadius: 4,
overflow: 'hidden',
marginBottom: 8
},
progressBarFill: {
height: '100%',
borderRadius: 4
},
progressText: {
fontSize: 12,
textAlign: 'right'
},
sectionTitle: {
fontSize: 20,
fontWeight: Platform.OS === 'web' ? '600' : undefined,
fontFamily: fontFamilies.interSemiBold,
marginTop: 8
},
achievementsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 20,
marginBottom: 24,
justifyContent: 'space-between'
},
achievementCard: {
width: '47%',
minWidth: 140,
borderRadius: 20,
padding: 20,
paddingTop: 24,
alignItems: 'center'
},
badgeContainer: {
alignItems: 'center',
justifyContent: 'center',
marginBottom: 12,
position: 'relative',
width: 120,
height: 120
},
badgeGlow: {
position: 'absolute',
width: 135,
height: 135,
borderRadius: 67.5,
shadowOffset: {
width: 0,
height: 6
},
shadowRadius: 15,
shadowOpacity: 0.4,
elevation: 10
},
badgeOrganic: {
width: 120,
height: 120,
// More organic blob shape with varied border radius (like Duolingo)
borderTopLeftRadius: 48,
borderTopRightRadius: 52,
borderBottomLeftRadius: 52,
borderBottomRightRadius: 48,
alignItems: 'center',
justifyContent: 'center'
},
badgeMain: {
position: 'absolute',
shadowOffset: {
width: 0,
height: 6
},
shadowRadius: 12,
shadowOpacity: 0.5,
elevation: 8
},
badgeHighlight: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '50%',
borderTopLeftRadius: 48,
borderTopRightRadius: 52,
opacity: 0.6
},
badgeHighlightAccent: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '30%',
borderTopLeftRadius: 48,
borderTopRightRadius: 52,
opacity: 0.3
},
badgeIconContainer: {
position: 'absolute',
top: 8,
left: 0,
right: 0,
alignItems: 'center',
justifyContent: 'flex-start',
zIndex: 10,
paddingTop: 12
},
badgeValueContainer: {
position: 'absolute',
bottom: 8,
left: 0,
right: 0,
alignItems: 'center',
justifyContent: 'center',
zIndex: 10
},
badgeValueText: {
fontSize: 32,
fontWeight: 'bold',
textShadowOffset: {
width: 0,
height: 2
},
textShadowRadius: 4,
letterSpacing: -1
},
rarityBadge: {
position: 'absolute',
top: -6,
right: -6,
width: 32,
height: 32,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 3,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.4,
shadowRadius: 4,
elevation: 5
},
rarityText: {
fontSize: 14,
fontWeight: 'bold',
color: '#FFFFFF'
},
achievementName: {
fontSize: 15,
fontWeight: '700',
textAlign: 'center',
marginBottom: 6,
marginTop: 4
},
achievementDescription: {
fontSize: 11,
textAlign: 'center',
lineHeight: 15
}
});
export default KarmaRewardsScreen;
//# sourceMappingURL=KarmaRewardsScreen.js.map