@playbasis-ai/qwikcard-sdk 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/LICENSE +21 -0
  3. package/README.md +267 -0
  4. package/SDK_HANDOVER_GUIDE.md +129 -0
  5. package/dist/PlaybasisProvider.d.ts +19 -0
  6. package/dist/PlaybasisProvider.d.ts.map +1 -0
  7. package/dist/PlaybasisProvider.js +24 -0
  8. package/dist/QwikCardApp.d.ts +9 -0
  9. package/dist/QwikCardApp.d.ts.map +1 -0
  10. package/dist/QwikCardApp.js +210 -0
  11. package/dist/api/client.d.ts +66 -0
  12. package/dist/api/client.d.ts.map +1 -0
  13. package/dist/api/client.js +196 -0
  14. package/dist/components/Badge.d.ts +8 -0
  15. package/dist/components/Badge.d.ts.map +1 -0
  16. package/dist/components/Badge.js +34 -0
  17. package/dist/components/BadgeIcon.d.ts +10 -0
  18. package/dist/components/BadgeIcon.d.ts.map +1 -0
  19. package/dist/components/BadgeIcon.js +51 -0
  20. package/dist/components/Button.d.ts +10 -0
  21. package/dist/components/Button.d.ts.map +1 -0
  22. package/dist/components/Button.js +40 -0
  23. package/dist/components/GradientCard.d.ts +9 -0
  24. package/dist/components/GradientCard.d.ts.map +1 -0
  25. package/dist/components/GradientCard.js +28 -0
  26. package/dist/components/PointsBalance.d.ts +8 -0
  27. package/dist/components/PointsBalance.d.ts.map +1 -0
  28. package/dist/components/PointsBalance.js +85 -0
  29. package/dist/components/ProgressBar.d.ts +8 -0
  30. package/dist/components/ProgressBar.d.ts.map +1 -0
  31. package/dist/components/ProgressBar.js +41 -0
  32. package/dist/components/QuestProgress.d.ts +10 -0
  33. package/dist/components/QuestProgress.d.ts.map +1 -0
  34. package/dist/components/QuestProgress.js +94 -0
  35. package/dist/components/RadialGauge.d.ts +8 -0
  36. package/dist/components/RadialGauge.d.ts.map +1 -0
  37. package/dist/components/RadialGauge.js +53 -0
  38. package/dist/components/RewardCard.d.ts +10 -0
  39. package/dist/components/RewardCard.d.ts.map +1 -0
  40. package/dist/components/RewardCard.js +64 -0
  41. package/dist/components/RulesModal.d.ts +7 -0
  42. package/dist/components/RulesModal.d.ts.map +1 -0
  43. package/dist/components/RulesModal.js +106 -0
  44. package/dist/hooks/useBadges.d.ts +12 -0
  45. package/dist/hooks/useBadges.d.ts.map +1 -0
  46. package/dist/hooks/useBadges.js +42 -0
  47. package/dist/hooks/usePoints.d.ts +13 -0
  48. package/dist/hooks/usePoints.d.ts.map +1 -0
  49. package/dist/hooks/usePoints.js +70 -0
  50. package/dist/hooks/useQuests.d.ts +12 -0
  51. package/dist/hooks/useQuests.d.ts.map +1 -0
  52. package/dist/hooks/useQuests.js +41 -0
  53. package/dist/hooks/useQwikApp.d.ts +16 -0
  54. package/dist/hooks/useQwikApp.d.ts.map +1 -0
  55. package/dist/hooks/useQwikApp.js +42 -0
  56. package/dist/hooks/useRewards.d.ts +12 -0
  57. package/dist/hooks/useRewards.d.ts.map +1 -0
  58. package/dist/hooks/useRewards.js +56 -0
  59. package/dist/index.d.ts +27 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +29 -0
  62. package/dist/theme/context.d.ts +8 -0
  63. package/dist/theme/context.d.ts.map +1 -0
  64. package/dist/theme/context.js +8 -0
  65. package/dist/theme/tokens.d.ts +35 -0
  66. package/dist/theme/tokens.d.ts.map +1 -0
  67. package/dist/theme/tokens.js +33 -0
  68. package/dist/types/index.d.ts +119 -0
  69. package/dist/types/index.d.ts.map +1 -0
  70. package/dist/types/index.js +4 -0
  71. package/dist/web/widgetAssets.d.ts +4 -0
  72. package/dist/web/widgetAssets.d.ts.map +1 -0
  73. package/dist/web/widgetAssets.js +5 -0
  74. package/dist/web/widgetHtml.d.ts +2 -0
  75. package/dist/web/widgetHtml.d.ts.map +1 -0
  76. package/dist/web/widgetHtml.js +299 -0
  77. package/dist/web/widgetTypes.d.ts +128 -0
  78. package/dist/web/widgetTypes.d.ts.map +1 -0
  79. package/dist/web/widgetTypes.js +1 -0
  80. package/package.json +86 -0
  81. package/src/PlaybasisProvider.tsx +72 -0
  82. package/src/QwikCardApp.tsx +302 -0
  83. package/src/api/client.ts +307 -0
  84. package/src/components/Badge.tsx +51 -0
  85. package/src/components/BadgeIcon.tsx +97 -0
  86. package/src/components/Button.tsx +70 -0
  87. package/src/components/GradientCard.tsx +49 -0
  88. package/src/components/PointsBalance.tsx +122 -0
  89. package/src/components/ProgressBar.tsx +65 -0
  90. package/src/components/QuestProgress.tsx +153 -0
  91. package/src/components/RadialGauge.tsx +101 -0
  92. package/src/components/RewardCard.tsx +123 -0
  93. package/src/components/RulesModal.tsx +171 -0
  94. package/src/hooks/useBadges.ts +59 -0
  95. package/src/hooks/usePoints.ts +91 -0
  96. package/src/hooks/useQuests.ts +60 -0
  97. package/src/hooks/useQwikApp.ts +49 -0
  98. package/src/hooks/useRewards.ts +74 -0
  99. package/src/index.ts +34 -0
  100. package/src/theme/context.tsx +17 -0
  101. package/src/theme/tokens.ts +68 -0
  102. package/src/types/index.ts +176 -0
  103. package/src/web/widgetAssets.d.ts +3 -0
  104. package/src/web/widgetAssets.ts +6 -0
  105. package/src/web/widgetHtml.ts +302 -0
  106. package/src/web/widgetTypes.ts +146 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientCard.d.ts","sourceRoot":"","sources":["../../src/components/GradientCard.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAoB,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKtE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,iBAAiB,2CA8BlE"}
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View, StyleSheet } from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
4
+ import { useTheme } from '../theme/context';
5
+ export function GradientCard({ children, style }) {
6
+ const theme = useTheme();
7
+ const isGradientAvailable = LinearGradient !== undefined &&
8
+ LinearGradient !== null &&
9
+ (typeof LinearGradient === 'function' || typeof LinearGradient === 'object');
10
+ const containerStyle = [
11
+ styles.container,
12
+ {
13
+ borderRadius: theme.borderRadius.l,
14
+ borderColor: theme.colors.surface,
15
+ },
16
+ style,
17
+ ];
18
+ if (!isGradientAvailable) {
19
+ return _jsx(View, { style: containerStyle, children: children });
20
+ }
21
+ return (_jsx(LinearGradient, { colors: ['rgba(255, 255, 255, 0.12)', 'rgba(255, 255, 255, 0.04)'], start: { x: 0, y: 0 }, end: { x: 1, y: 1 }, style: containerStyle, children: children }));
22
+ }
23
+ const styles = StyleSheet.create({
24
+ container: {
25
+ borderWidth: 1,
26
+ overflow: 'hidden',
27
+ },
28
+ });
@@ -0,0 +1,8 @@
1
+ import type { PointBalance } from '../types';
2
+ interface PointsBalanceProps {
3
+ balances: PointBalance[];
4
+ style?: object;
5
+ }
6
+ export declare function PointsBalance({ balances, style }: PointsBalanceProps): import("react/jsx-runtime").JSX.Element;
7
+ export default PointsBalance;
8
+ //# sourceMappingURL=PointsBalance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PointsBalance.d.ts","sourceRoot":"","sources":["../../src/components/PointsBalance.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,kBAAkB,2CAqCpE;AAwED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import { GradientCard } from './GradientCard';
4
+ import { useTheme } from '../theme/context';
5
+ export function PointsBalance({ balances, style }) {
6
+ const theme = useTheme();
7
+ // Find primary and secondary currencies
8
+ const primary = balances.find((b) => b.currency === 'qwik-coins') || balances[0];
9
+ const secondary = balances.find((b) => b.currency !== primary?.currency);
10
+ const formattedBalance = primary?.balance.toLocaleString() || '0';
11
+ const secondaryBalance = secondary
12
+ ? `${secondary.balance.toLocaleString()} ${secondary.currency}`
13
+ : null;
14
+ return (_jsxs(GradientCard, { style: [styles.card, style], children: [_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: styles.label, children: "CURRENT BALANCE" }), _jsx(Text, { style: styles.brand, children: "QwikCard" })] }), _jsxs(View, { style: styles.mainContent, children: [_jsx(Text, { style: styles.currencySymbol, children: "$" }), _jsx(Text, { style: styles.amount, children: formattedBalance })] }), _jsxs(View, { style: styles.footer, children: [secondaryBalance && (_jsx(View, { style: styles.chip, children: _jsxs(Text, { style: styles.chipText, children: ["+ ", secondaryBalance] }) })), _jsxs(View, { style: styles.dots, children: [_jsx(View, { style: [styles.dot, { backgroundColor: '#FFD700' }] }), _jsx(View, { style: [styles.dot, { backgroundColor: '#FF69B4' }] })] })] })] }));
15
+ }
16
+ const styles = StyleSheet.create({
17
+ card: {
18
+ padding: 20,
19
+ minHeight: 150,
20
+ justifyContent: 'space-between',
21
+ },
22
+ header: {
23
+ flexDirection: 'row',
24
+ justifyContent: 'space-between',
25
+ alignItems: 'center',
26
+ marginBottom: 16,
27
+ },
28
+ label: {
29
+ color: 'rgba(255, 255, 255, 0.8)',
30
+ fontSize: 10,
31
+ fontWeight: '700',
32
+ letterSpacing: 1.2,
33
+ },
34
+ brand: {
35
+ color: '#FFFFFF',
36
+ fontSize: 12,
37
+ fontWeight: '800',
38
+ fontStyle: 'italic',
39
+ },
40
+ mainContent: {
41
+ flexDirection: 'row',
42
+ alignItems: 'flex-start',
43
+ },
44
+ currencySymbol: {
45
+ color: 'rgba(255, 255, 255, 0.9)',
46
+ fontSize: 20,
47
+ fontWeight: '600',
48
+ marginTop: 2,
49
+ marginRight: 4,
50
+ },
51
+ amount: {
52
+ color: '#FFFFFF',
53
+ fontSize: 32,
54
+ fontWeight: '800',
55
+ letterSpacing: -0.5,
56
+ },
57
+ footer: {
58
+ flexDirection: 'row',
59
+ justifyContent: 'space-between',
60
+ alignItems: 'center',
61
+ marginTop: 16,
62
+ },
63
+ chip: {
64
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
65
+ paddingHorizontal: 10,
66
+ paddingVertical: 4,
67
+ borderRadius: 16,
68
+ },
69
+ chipText: {
70
+ color: '#FFFFFF',
71
+ fontSize: 11,
72
+ fontWeight: '600',
73
+ },
74
+ dots: {
75
+ flexDirection: 'row',
76
+ gap: 5,
77
+ },
78
+ dot: {
79
+ width: 20,
80
+ height: 20,
81
+ borderRadius: 10,
82
+ opacity: 0.8,
83
+ },
84
+ });
85
+ export default PointsBalance;
@@ -0,0 +1,8 @@
1
+ interface ProgressBarProps {
2
+ label?: string;
3
+ value: number;
4
+ color?: string;
5
+ }
6
+ export declare function ProgressBar({ label, value, color }: ProgressBarProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=ProgressBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressBar.d.ts","sourceRoot":"","sources":["../../src/components/ProgressBar.tsx"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,gBAAgB,2CAyBpE"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import { useTheme } from '../theme/context';
4
+ export function ProgressBar({ label, value, color }) {
5
+ const theme = useTheme();
6
+ const barColor = color || theme.colors.primary;
7
+ return (_jsxs(View, { style: styles.container, children: [label && (_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: [styles.label, { color: theme.colors.text.primary }], children: label }), _jsxs(Text, { style: [styles.value, { color: theme.colors.text.secondary }], children: [value, "%"] })] })), _jsx(View, { style: [styles.track, { backgroundColor: 'rgba(148, 163, 184, 0.2)' }], children: _jsx(View, { style: [
8
+ styles.fill,
9
+ {
10
+ width: `${value}%`,
11
+ backgroundColor: barColor,
12
+ },
13
+ ] }) })] }));
14
+ }
15
+ const styles = StyleSheet.create({
16
+ container: {
17
+ width: '100%',
18
+ marginVertical: 4,
19
+ },
20
+ header: {
21
+ flexDirection: 'row',
22
+ justifyContent: 'space-between',
23
+ marginBottom: 6,
24
+ },
25
+ label: {
26
+ fontSize: 12,
27
+ fontWeight: '700',
28
+ },
29
+ value: {
30
+ fontSize: 12,
31
+ },
32
+ track: {
33
+ height: 8,
34
+ borderRadius: 999,
35
+ overflow: 'hidden',
36
+ },
37
+ fill: {
38
+ height: '100%',
39
+ borderRadius: 999,
40
+ },
41
+ });
@@ -0,0 +1,10 @@
1
+ import type { Quest } from '../types';
2
+ interface QuestProgressProps {
3
+ quest: Quest;
4
+ primaryColor?: string;
5
+ backgroundColor?: string;
6
+ style?: object;
7
+ }
8
+ export declare function QuestProgress({ quest, primaryColor, backgroundColor, style, }: QuestProgressProps): import("react/jsx-runtime").JSX.Element;
9
+ export default QuestProgress;
10
+ //# sourceMappingURL=QuestProgress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuestProgress.d.ts","sourceRoot":"","sources":["../../src/components/QuestProgress.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAMtC,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,YAAwB,EACxB,eAA2B,EAC3B,KAAK,GACN,EAAE,kBAAkB,2CA8CpB;AAiFD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Component
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ export function QuestProgress({ quest, primaryColor = '#6366F1', backgroundColor = '#E5E7EB', style, }) {
7
+ const progressPercent = quest.target > 0 ? (quest.progress / quest.target) * 100 : 0;
8
+ const isCompleted = quest.status === 'completed';
9
+ return (_jsxs(View, { style: [styles.container, style], children: [_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: styles.name, children: quest.name }), isCompleted && _jsx(Text, { style: styles.completedBadge, children: "\u2713" })] }), _jsx(Text, { style: styles.description, children: quest.description }), _jsx(View, { style: [styles.progressBar, { backgroundColor }], children: _jsx(View, { style: [
10
+ styles.progressFill,
11
+ {
12
+ backgroundColor: isCompleted ? '#10B981' : primaryColor,
13
+ width: `${Math.min(progressPercent, 100)}%`,
14
+ },
15
+ ] }) }), _jsxs(View, { style: styles.footer, children: [_jsxs(Text, { style: styles.progressText, children: [quest.progress, " / ", quest.target] }), _jsxs(Text, { style: styles.percentText, children: [Math.round(progressPercent), "%"] })] }), quest.rewards && quest.rewards.length > 0 && (_jsxs(View, { style: styles.rewards, children: [_jsx(Text, { style: styles.rewardLabel, children: "Rewards:" }), quest.rewards.map((reward, idx) => (_jsxs(Text, { style: styles.rewardText, children: [reward.type === 'points' && `${reward.amount} ${reward.currency}`, reward.type === 'badge' && `🏅 Badge`, reward.type === 'reward' && `🎁 Reward`] }, idx)))] }))] }));
16
+ }
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+ // Styles
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ const styles = StyleSheet.create({
21
+ container: {
22
+ backgroundColor: '#FFFFFF',
23
+ borderRadius: 12,
24
+ padding: 16,
25
+ marginVertical: 8,
26
+ shadowColor: '#000',
27
+ shadowOffset: { width: 0, height: 2 },
28
+ shadowOpacity: 0.1,
29
+ shadowRadius: 4,
30
+ elevation: 3,
31
+ },
32
+ header: {
33
+ flexDirection: 'row',
34
+ justifyContent: 'space-between',
35
+ alignItems: 'center',
36
+ marginBottom: 4,
37
+ },
38
+ name: {
39
+ fontSize: 16,
40
+ fontWeight: '600',
41
+ color: '#1F2937',
42
+ },
43
+ completedBadge: {
44
+ fontSize: 18,
45
+ color: '#10B981',
46
+ },
47
+ description: {
48
+ fontSize: 14,
49
+ color: '#6B7280',
50
+ marginBottom: 12,
51
+ },
52
+ progressBar: {
53
+ height: 8,
54
+ borderRadius: 4,
55
+ overflow: 'hidden',
56
+ },
57
+ progressFill: {
58
+ height: '100%',
59
+ borderRadius: 4,
60
+ },
61
+ footer: {
62
+ flexDirection: 'row',
63
+ justifyContent: 'space-between',
64
+ marginTop: 8,
65
+ },
66
+ progressText: {
67
+ fontSize: 12,
68
+ color: '#6B7280',
69
+ },
70
+ percentText: {
71
+ fontSize: 12,
72
+ fontWeight: '600',
73
+ color: '#1F2937',
74
+ },
75
+ rewards: {
76
+ flexDirection: 'row',
77
+ alignItems: 'center',
78
+ marginTop: 12,
79
+ paddingTop: 12,
80
+ borderTopWidth: 1,
81
+ borderTopColor: '#E5E7EB',
82
+ },
83
+ rewardLabel: {
84
+ fontSize: 12,
85
+ color: '#6B7280',
86
+ marginRight: 8,
87
+ },
88
+ rewardText: {
89
+ fontSize: 12,
90
+ color: '#1F2937',
91
+ marginRight: 8,
92
+ },
93
+ });
94
+ export default QuestProgress;
@@ -0,0 +1,8 @@
1
+ interface RadialGaugeProps {
2
+ value: number;
3
+ max: number;
4
+ label: string;
5
+ }
6
+ export declare function RadialGauge({ value, max, label }: RadialGaugeProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=RadialGauge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadialGauge.d.ts","sourceRoot":"","sources":["../../src/components/RadialGauge.tsx"],"names":[],"mappings":"AAOA,UAAU,gBAAgB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,gBAAgB,2CAgDlE"}
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, StyleSheet, Text } from 'react-native';
3
+ import Svg, { Circle } from 'react-native-svg';
4
+ import { useTheme } from '../theme/context';
5
+ import { GradientCard } from './GradientCard';
6
+ export function RadialGauge({ value, max, label }) {
7
+ const theme = useTheme();
8
+ const size = 80;
9
+ const stroke = 6;
10
+ const radius = size / 2 - stroke;
11
+ const circumference = radius * 2 * Math.PI;
12
+ const strokeDashoffset = circumference - (value / max) * circumference;
13
+ const percentage = Math.round((value / max) * 100);
14
+ return (_jsx(GradientCard, { style: styles.container, children: _jsxs(View, { style: styles.content, children: [_jsxs(View, { style: styles.gaugeContainer, children: [_jsxs(Svg, { height: size, width: size, style: { transform: [{ rotate: '-90deg' }] }, children: [_jsx(Circle, { stroke: "rgba(255,255,255,0.1)", strokeWidth: stroke, fill: "transparent", r: radius, cx: size / 2, cy: size / 2 }), _jsx(Circle, { stroke: "#FFD700", strokeWidth: stroke, strokeDasharray: `${circumference} ${circumference}`, strokeDashoffset: strokeDashoffset, strokeLinecap: "round", fill: "transparent", r: radius, cx: size / 2, cy: size / 2 })] }), _jsx(View, { style: styles.centerText, children: _jsxs(Text, { style: styles.percentText, children: [percentage, "%"] }) })] }), _jsxs(View, { style: styles.infoContainer, children: [_jsx(Text, { style: [styles.label, { color: theme.colors.text.primary }], children: label }), _jsxs(Text, { style: [styles.subText, { color: theme.colors.text.secondary }], children: ["$", value, " of $", max, " saved."] })] })] }) }));
15
+ }
16
+ const styles = StyleSheet.create({
17
+ container: {
18
+ padding: 16,
19
+ marginBottom: 16,
20
+ },
21
+ content: {
22
+ flexDirection: 'row',
23
+ alignItems: 'center',
24
+ },
25
+ gaugeContainer: {
26
+ position: 'relative',
27
+ width: 80,
28
+ height: 80,
29
+ },
30
+ centerText: {
31
+ ...StyleSheet.absoluteFillObject,
32
+ alignItems: 'center',
33
+ justifyContent: 'center',
34
+ },
35
+ percentText: {
36
+ fontSize: 16,
37
+ fontWeight: '800',
38
+ color: 'white',
39
+ },
40
+ infoContainer: {
41
+ flex: 1,
42
+ marginLeft: 16,
43
+ },
44
+ label: {
45
+ fontSize: 16,
46
+ fontWeight: '700',
47
+ marginBottom: 2,
48
+ },
49
+ subText: {
50
+ fontSize: 12,
51
+ lineHeight: 16,
52
+ },
53
+ });
@@ -0,0 +1,10 @@
1
+ import type { Reward } from '../types';
2
+ interface RewardCardProps {
3
+ reward: Reward;
4
+ onRedeem?: () => void;
5
+ disabled?: boolean;
6
+ style?: object;
7
+ }
8
+ export declare function RewardCard({ reward, onRedeem, disabled, style }: RewardCardProps): import("react/jsx-runtime").JSX.Element;
9
+ export default RewardCard;
10
+ //# sourceMappingURL=RewardCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RewardCard.d.ts","sourceRoot":"","sources":["../../src/components/RewardCard.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAMvC,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAgB,EAAE,KAAK,EAAE,EAAE,eAAe,2CA6CxF;AAsDD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, Image, StyleSheet } from 'react-native';
3
+ import { GradientCard } from './GradientCard';
4
+ import { Button } from './Button';
5
+ import { useTheme } from '../theme/context';
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // Component
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ export function RewardCard({ reward, onRedeem, disabled = false, style }) {
10
+ const theme = useTheme();
11
+ const isAvailable = reward.available && !disabled;
12
+ return (_jsxs(GradientCard, { style: [styles.container, style], children: [reward.imageUrl ? (_jsx(Image, { source: { uri: reward.imageUrl }, style: styles.image, resizeMode: "cover" })) : (_jsx(View, { style: [styles.imagePlaceholder, { backgroundColor: 'rgba(255, 69, 0, 0.1)' }], children: _jsx(Text, { style: styles.placeholderText, children: "\uD83C\uDF81" }) })), _jsxs(View, { style: styles.content, children: [_jsx(Text, { style: [styles.name, { color: theme.colors.text.primary }], children: reward.name }), _jsx(Text, { style: [styles.description, { color: theme.colors.text.secondary }], numberOfLines: 2, children: reward.description }), _jsxs(View, { style: styles.footer, children: [_jsxs(View, { style: styles.cost, children: [_jsx(Text, { style: [styles.costAmount, { color: theme.colors.text.primary }], children: reward.cost }), _jsx(Text, { style: [styles.costCurrency, { color: theme.colors.text.secondary }], children: reward.currency })] }), onRedeem && (_jsx(Button, { title: isAvailable ? 'Redeem' : 'Unavailable', onPress: onRedeem, variant: isAvailable ? 'primary' : 'secondary', style: { opacity: isAvailable ? 1 : 0.6 } }))] })] })] }));
13
+ }
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ // Styles
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ const styles = StyleSheet.create({
18
+ container: {
19
+ marginVertical: 8,
20
+ },
21
+ image: {
22
+ width: '100%',
23
+ height: 140,
24
+ },
25
+ imagePlaceholder: {
26
+ width: '100%',
27
+ height: 140,
28
+ justifyContent: 'center',
29
+ alignItems: 'center',
30
+ },
31
+ placeholderText: {
32
+ fontSize: 48,
33
+ },
34
+ content: {
35
+ padding: 16,
36
+ },
37
+ name: {
38
+ fontSize: 18,
39
+ fontWeight: '700',
40
+ marginBottom: 4,
41
+ },
42
+ description: {
43
+ fontSize: 14,
44
+ marginBottom: 16,
45
+ },
46
+ footer: {
47
+ flexDirection: 'row',
48
+ justifyContent: 'space-between',
49
+ alignItems: 'center',
50
+ },
51
+ cost: {
52
+ flexDirection: 'row',
53
+ alignItems: 'baseline',
54
+ },
55
+ costAmount: {
56
+ fontSize: 20,
57
+ fontWeight: '800',
58
+ },
59
+ costCurrency: {
60
+ fontSize: 12,
61
+ marginLeft: 4,
62
+ },
63
+ });
64
+ export default RewardCard;
@@ -0,0 +1,7 @@
1
+ interface RulesModalProps {
2
+ visible: boolean;
3
+ onClose: () => void;
4
+ }
5
+ export declare function RulesModal({ visible, onClose }: RulesModalProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=RulesModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RulesModal.d.ts","sourceRoot":"","sources":["../../src/components/RulesModal.tsx"],"names":[],"mappings":"AAcA,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,eAAe,2CAwE/D"}
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Modal, View, Text, StyleSheet, ScrollView, TouchableOpacity, SafeAreaView, } from 'react-native';
3
+ import { useTheme } from '../theme/context';
4
+ import { GradientCard } from './GradientCard';
5
+ export function RulesModal({ visible, onClose }) {
6
+ const theme = useTheme();
7
+ const rules = [
8
+ { icon: '🛍️', title: 'Spend & Earn', desc: 'Earn 1 XP for every $5 spent on your QwikCard.' },
9
+ { icon: '🧾', title: 'Pay Bills', desc: 'Get +50 XP and 1 Qwik Coin for every bill paid.' },
10
+ {
11
+ icon: '🔥',
12
+ title: 'Streaks',
13
+ desc: 'Maintain a daily login streak for massive XP milestones.',
14
+ },
15
+ {
16
+ icon: '🤝',
17
+ title: 'Refer Friends',
18
+ desc: 'Earn +500 XP and 5 Qwik Coins when friends activate.',
19
+ },
20
+ { icon: '📊', title: 'Credit Checks', desc: 'Check your credit score weekly to earn +50 XP.' },
21
+ {
22
+ icon: '💡',
23
+ title: 'Financial Literacy',
24
+ desc: 'Complete quizzes to earn +100 XP per lesson.',
25
+ },
26
+ ];
27
+ return (_jsx(Modal, { visible: visible, animationType: "slide", transparent: true, onRequestClose: onClose, children: _jsx(View, { style: styles.overlay, children: _jsx(SafeAreaView, { style: styles.safeArea, children: _jsxs(View, { style: [styles.container, { backgroundColor: theme.colors.background }], children: [_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: [styles.title, { color: theme.colors.text.primary }], children: "How to Play" }), _jsx(TouchableOpacity, { onPress: onClose, style: styles.closeButton, children: _jsx(Text, { style: { fontSize: 24, color: theme.colors.text.secondary }, children: "\u2715" }) })] }), _jsxs(ScrollView, { contentContainerStyle: styles.scrollContent, children: [_jsx(Text, { style: [styles.subtitle, { color: theme.colors.text.secondary }], children: "Level up your financial future by building healthy habits. Here is how you earn rewards:" }), rules.map((rule, index) => (_jsx(GradientCard, { style: styles.ruleCard, children: _jsxs(View, { style: styles.ruleRow, children: [_jsx(View, { style: styles.iconContainer, children: _jsx(Text, { style: { fontSize: 24 }, children: rule.icon }) }), _jsxs(View, { style: styles.textContainer, children: [_jsx(Text, { style: [styles.ruleTitle, { color: theme.colors.text.primary }], children: rule.title }), _jsx(Text, { style: [styles.ruleDesc, { color: theme.colors.text.secondary }], children: rule.desc })] })] }) }, index))), _jsx(TouchableOpacity, { onPress: onClose, style: [styles.actionButton, { backgroundColor: theme.colors.primary }], children: _jsx(Text, { style: styles.actionButtonText, children: "Got it!" }) })] })] }) }) }) }));
28
+ }
29
+ const styles = StyleSheet.create({
30
+ overlay: {
31
+ flex: 1,
32
+ backgroundColor: 'rgba(0, 0, 0, 0.85)',
33
+ },
34
+ safeArea: {
35
+ flex: 1,
36
+ justifyContent: 'flex-end',
37
+ },
38
+ container: {
39
+ borderTopLeftRadius: 32,
40
+ borderTopRightRadius: 32,
41
+ height: '90%',
42
+ padding: 24,
43
+ },
44
+ header: {
45
+ flexDirection: 'row',
46
+ justifyContent: 'space-between',
47
+ alignItems: 'center',
48
+ marginBottom: 20,
49
+ },
50
+ title: {
51
+ fontSize: 24,
52
+ fontWeight: '900',
53
+ },
54
+ closeButton: {
55
+ padding: 8,
56
+ },
57
+ scrollContent: {
58
+ paddingBottom: 40,
59
+ },
60
+ subtitle: {
61
+ fontSize: 14,
62
+ lineHeight: 20,
63
+ marginBottom: 24,
64
+ },
65
+ ruleCard: {
66
+ padding: 16,
67
+ marginBottom: 12,
68
+ borderRadius: 20,
69
+ },
70
+ ruleRow: {
71
+ flexDirection: 'row',
72
+ alignItems: 'center',
73
+ },
74
+ iconContainer: {
75
+ width: 48,
76
+ height: 48,
77
+ borderRadius: 14,
78
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
79
+ alignItems: 'center',
80
+ justifyContent: 'center',
81
+ marginRight: 16,
82
+ },
83
+ textContainer: {
84
+ flex: 1,
85
+ },
86
+ ruleTitle: {
87
+ fontSize: 16,
88
+ fontWeight: '700',
89
+ marginBottom: 2,
90
+ },
91
+ ruleDesc: {
92
+ fontSize: 12,
93
+ lineHeight: 16,
94
+ },
95
+ actionButton: {
96
+ marginTop: 24,
97
+ padding: 18,
98
+ borderRadius: 18,
99
+ alignItems: 'center',
100
+ },
101
+ actionButtonText: {
102
+ color: 'white',
103
+ fontSize: 16,
104
+ fontWeight: '800',
105
+ },
106
+ });
@@ -0,0 +1,12 @@
1
+ import type { Badge } from '../types';
2
+ interface UseBadgesReturn {
3
+ badges: Badge[];
4
+ earnedBadges: Badge[];
5
+ allBadges: Badge[];
6
+ loading: boolean;
7
+ error: Error | null;
8
+ refresh: () => Promise<void>;
9
+ }
10
+ export declare function useBadges(): UseBadgesReturn;
11
+ export default useBadges;
12
+ //# sourceMappingURL=useBadges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBadges.d.ts","sourceRoot":"","sources":["../../src/hooks/useBadges.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,UAAU,eAAe;IACvB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,YAAY,EAAE,KAAK,EAAE,CAAC;IACtB,SAAS,EAAE,KAAK,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,SAAS,IAAI,eAAe,CA0C3C;AAED,eAAe,SAAS,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { usePlaybasis } from '../PlaybasisProvider';
3
+ export function useBadges() {
4
+ const { client, playerId } = usePlaybasis();
5
+ const [badges, setBadges] = useState([]);
6
+ const [allBadges, setAllBadges] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState(null);
9
+ const fetchBadges = useCallback(async () => {
10
+ setLoading(true);
11
+ setError(null);
12
+ try {
13
+ // Fetch all available badges
14
+ const all = await client.getBadges();
15
+ setAllBadges(all);
16
+ // Fetch player's earned badges
17
+ if (playerId) {
18
+ const playerBadges = await client.getPlayerBadges(playerId);
19
+ setBadges(playerBadges);
20
+ }
21
+ }
22
+ catch (err) {
23
+ setError(err instanceof Error ? err : new Error('Failed to fetch badges'));
24
+ }
25
+ finally {
26
+ setLoading(false);
27
+ }
28
+ }, [client, playerId]);
29
+ useEffect(() => {
30
+ fetchBadges();
31
+ }, [fetchBadges]);
32
+ const earnedBadges = badges.filter((b) => b.isEarned);
33
+ return {
34
+ badges,
35
+ earnedBadges,
36
+ allBadges,
37
+ loading,
38
+ error,
39
+ refresh: fetchBadges,
40
+ };
41
+ }
42
+ export default useBadges;
@@ -0,0 +1,13 @@
1
+ import type { PointBalance, Currency } from '../types';
2
+ interface UsePointsReturn {
3
+ balances: PointBalance[];
4
+ xp: number;
5
+ qwikCoins: number;
6
+ loading: boolean;
7
+ error: Error | null;
8
+ refresh: () => Promise<void>;
9
+ earn: (currency: Currency, amount: number, reason?: string) => Promise<PointBalance | null>;
10
+ }
11
+ export declare function usePoints(): UsePointsReturn;
12
+ export default usePoints;
13
+ //# sourceMappingURL=usePoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePoints.d.ts","sourceRoot":"","sources":["../../src/hooks/usePoints.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEvD,UAAU,eAAe;IACvB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CAC7F;AAED,wBAAgB,SAAS,IAAI,eAAe,CAyE3C;AAED,eAAe,SAAS,CAAC"}