Parent Rewards Management
Sunschool’s rewards system allows parents to create custom incentives that children can earn through learning. Children earn points by completing lessons and can save toward rewards they want.
Overview
The rewards system has three main components:
Reward Catalog - Rewards you create and manage
Redemption Requests - When children want to cash out
Settings - Special scoring modes and configurations
Access the rewards center at /rewards from your parent dashboard.
From parent-rewards-page.tsx:306-406:
const ParentRewardsPage : React . FC = () => {
const [ activeTab , setActiveTab ] = useState < Tab >( 'rewards' );
// Three tabs: rewards, redemptions, settings
return (
< SafeAreaView >
< View style = {styles. header } >
< Gift size = { 22 } color = {theme.colors. primary } />
< Text style = {styles. headerTitle } > Rewards Center </ Text >
</ View >
< View style = {styles. tabs } >
{ /* Tab navigation */ }
</ View >
</ SafeAreaView >
);
};
Creating Rewards
Click “Add Reward” to open the creation form.
From parent-rewards-page.tsx:38-135:
const RewardForm : React . FC < RewardFormProps > = ({ initial , onSave , onCancel , isSaving }) => {
const [ title , setTitle ] = useState ( initial ?. title ?? '' );
const [ description , setDescription ] = useState ( initial ?. description ?? '' );
const [ tokenCost , setTokenCost ] = useState ( String ( initial ?. tokenCost ?? 10 ));
const [ category , setCategory ] = useState ( initial ?. category ?? 'GENERAL' );
const [ emoji , setEmoji ] = useState ( initial ?. imageEmoji ?? '🎁' );
const [ color , setColor ] = useState ( initial ?. color ?? '#4A90D9' );
const [ isActive , setIsActive ] = useState ( initial ?. isActive ?? true );
const handleSave = () => {
if ( ! title . trim ()) return ;
onSave ({
title: title . trim (),
description: description . trim () || null ,
tokenCost: Number ( tokenCost ) || 10 ,
category ,
imageEmoji: emoji ,
color ,
isActive
});
};
};
Required Fields
Choose an Icon
Select an emoji to represent the reward Available emojis from parent-rewards-page.tsx:32: const EMOJIS = [ '🎁' , '⭐' , '🏆' , '🎮' , '🍦' , '🎬' , '🎨' , '📚' , '🚀' , '🦋' , '🌟' , '🎪' , '🎯' , '🛹' , '🎸' , '🎀' ];
Select a Color
Pick a color theme for the reward card From parent-rewards-page.tsx:33: const COLORS = [ '#4A90D9' , '#6BCB77' , '#FF8F00' , '#EF5350' , '#AB47BC' , '#00ACC1' , '#FF6B6B' , '#FFD93D' ];
Enter Title
Give the reward a descriptive name Examples:
“Movie Night”
“30 Minutes Screen Time”
“Ice Cream Trip”
“Choose Dinner”
Set Point Cost
Determine how many points are required tokenCost : Number ( tokenCost ) || 10
Typical point costs range from 10-100 depending on the reward value. Children earn 1 point per correct quiz answer by default.
Choose Category
Classify the reward type From parent-rewards-page.tsx:34: const CATEGORIES = [
'GENERAL' ,
'SCREEN_TIME' ,
'FOOD_TREAT' ,
'OUTING' ,
'TOY_GAME' ,
'EXPERIENCE' ,
'OTHER'
];
Optional Settings
Description - Add details about the reward
Active Status - Toggle whether children can see and save for this reward
From parent-rewards-page.tsx:119-122:
< View style = {styles. switchRow } >
< Text style = {styles. label } > Active </ Text >
< Switch value = { isActive } onValueChange = { setIsActive } />
</ View >
Managing the Reward Catalog
Reward Cards
Each reward displays as a card with:
From parent-rewards-page.tsx:138-199:
const RewardCard : React . FC <{ reward : Reward }> = ({ reward , onEdit , onDelete , learnerProgress }) => {
const [ expanded , setExpanded ] = useState ( false );
return (
< View style = { [styles.card, { borderLeftColor: reward.color, borderLeftWidth: 4 }]}>
<View style={styles.cardHeader}>
<View style={[styles.emojiCircle, { backgroundColor: reward.color + '20' }]}>
<Text style={{ fontSize: 28 }} > {reward. imageEmoji } </ Text >
</ View >
< View style = {{ flex : 1 , marginLeft : 12 }} >
< Text style = {styles. cardTitle } > {reward. title } </ Text >
< Text style = {styles. cardSub } >
{ reward . tokenCost } pts · { reward . category . replace (' _ ', ' ')} · { reward . currentRedemptions } redeemed
</ Text >
{! reward . isActive && (
< View style = {styles. inactiveBadge } >
< Text style = {styles. inactiveBadgeText } > Inactive </ Text >
</ View >
)}
</ View >
< TouchableOpacity onPress = { onEdit } style = {styles. iconBtn } >
< Edit2 size = { 16 } color = {theme.colors. textSecondary } />
</ TouchableOpacity >
< TouchableOpacity onPress = { onDelete } style = {styles. iconBtn } >
< Trash2 size = { 16 } color = "#EF5350" />
</ TouchableOpacity >
</ View >
</ View >
);
};
Learner Progress Tracking
Reward cards can show how much each child has saved:
From parent-rewards-page.tsx:174-196:
{ learnerProgress && learnerProgress . length > 0 && (
< TouchableOpacity style = {styles. progressToggle } onPress = {() => setExpanded ( e => ! e )} >
< Text style = {styles. progressToggleText } >
Learner progress ({learnerProgress. length })
</ Text >
< ChevronDown size = { 14 } color = {theme.colors. primary } />
</ TouchableOpacity >
)}
{ expanded && learnerProgress ?. map (( lp , i ) => (
< View key = { i } style = {styles. progressRow } >
< Text style = {styles. progressName } > {lp. name } </ Text >
< View style = {styles. progressBar } >
< View style = { [styles.progressFill, {
width: ` ${ Math . min ( 100 , Math . round (( lp . saved / reward . tokenCost ) * 100 )) } %` ,
backgroundColor: reward.color,
}]} />
</View>
<Text style={styles.progressPts}>
{lp.saved}/{reward.tokenCost}
</Text>
</View>
))}
Expand a reward card to see how close each child is to earning it. This helps you understand what motivates each child.
Handling Redemption Requests
When a child has enough points saved, they can request to redeem a reward.
The Redemptions Tab
From parent-rewards-page.tsx:440-458:
{ activeTab === 'redemptions' && (
<>
{ loadingRedemptions ? (
< ActivityIndicator color = {theme.colors. primary } />
) : redemptions . length === 0 ? (
< View style = { styles . empty }>
< Text style = {{ fontSize : 48 }}>📭</ Text >
< Text style = { styles . emptyText }> No redemption requests yet .</ Text >
</ View >
) : (
redemptions . map ( r => (
< RedemptionCard key = {r. id } r = { r }
onApprove = { notes => approveMutation . mutate ({ id: r . id , notes })}
onReject = { notes => rejectMutation . mutate ({ id: r . id , notes })}
isProcessing = {approveMutation.isPending || rejectMutation. isPending }
/>
))
)}
</>
)}
Redemption Cards
From parent-rewards-page.tsx:202-263:
const RedemptionCard : React . FC <{ r : Redemption }> = ({ r , onApprove , onReject , isProcessing }) => {
const [ notes , setNotes ] = useState ( '' );
return (
< View style = { [styles.card, { borderLeftColor: r.rewardColor, borderLeftWidth: 4 }]}>
<View style={styles.cardHeader}>
<Text style={{ fontSize: 28 }} > {r. rewardEmoji } </ Text >
< View style = {{ flex : 1 }} >
< Text style = {styles. cardTitle } >
{ r . learnerName } → { r . rewardTitle }
</ Text >
< Text style = {styles. cardSub } >
{ r . tokensSpent } pts · { new Date ( r . requestedAt ). toLocaleDateString ()}
</ Text >
</ View >
< View style = { [styles.statusBadge, { backgroundColor:
r.status === 'APPROVED' ? '#E8F5E9' :
r.status === 'REJECTED' ? '#FFEBEE' : '#FFF8E1' }]}>
<Text style={{ color:
r . status === 'APPROVED' ? '#2E7D32' :
r . status === 'REJECTED' ? '#C62828' : '#F57F17' }} >
{ r . status }
</ Text >
</ View >
</ View >
{ r . status === ' PENDING ' && (
<>
< TextInput
style = {styles. input }
value = { notes }
onChangeText = { setNotes }
placeholder = "Optional note for learner"
/>
< View style = {styles. redemptionActions } >
< TouchableOpacity
style = { [styles.btn, { backgroundColor: '#E8F5E9' }]}
onPress={() => onApprove(notes)}
disabled={isProcessing}
>
<CheckCircle size={16} color="#2E7D32" />
<Text style={{ color: '#2E7D32' }} > Approve </ Text >
</ TouchableOpacity >
< TouchableOpacity
style = { [styles.btn, { backgroundColor: '#FFEBEE' }]}
onPress={() => onReject(notes)}
disabled={isProcessing}
>
<XCircle size={16} color="#C62828" />
<Text style={{ color: '#C62828' }} > Reject </ Text >
</ TouchableOpacity >
</ View >
</>
)}
</ View >
);
};
Approving Redemptions
Review the Request
Check which child is requesting which reward and how many points they’re spending.
Add a Note (Optional)
Include a message to your child:
“Great job saving up!”
“We’ll do this on Saturday”
“Pick a movie you’d like to watch”
Approve or Reject
Click the appropriate button. Approved redemptions:
Deduct the points from saved balance
Mark the redemption as approved
Notify the child
Rejecting a redemption refunds the points back to the child’s available balance, so they can save for a different reward or try again later.
Advanced Settings
Double-or-Loss Mode
From parent-rewards-page.tsx:266-300:
const LearnerSettingsCard : React . FC <{ learner : Learner }> = ({ learner }) => {
const { data : settings } = useQuery ({
queryKey: [ `/api/learner-settings/ ${ learner . id } ` ],
queryFn : () => apiRequest ( 'GET' , `/api/learner-settings/ ${ learner . id } ` ). then ( r => r . data ),
});
const toggleMutation = useMutation ({
mutationFn : ( enabled : boolean ) =>
apiRequest ( 'PUT' , `/api/learner-settings/ ${ learner . id } /double-or-loss` , { enabled }). then ( r => r . data ),
onSuccess : () => queryClient . invalidateQueries ({ queryKey: [ `/api/learner-settings/ ${ learner . id } ` ] }),
});
const enabled = settings ?. doubleOrLossEnabled ?? false ;
return (
< View style = {styles. settingCard } >
< View style = {{ flex : 1 }} >
< Text style = {styles. cardTitle } > {learner. name } </ Text >
< Text style = {styles. cardSub } >
Double - or - Loss mode : { enabled ? '⚡ ON' : 'Off' }
</ Text >
< Text style = {styles. cardDesc } >
{ enabled ? '2× points for correct, -1 for wrong answers' : 'Standard scoring (1 pt per correct answer)' }
</ Text >
</ View >
< Switch
value = { enabled }
onValueChange = { v => toggleMutation . mutate ( v )}
trackColor = {{ true : '#FF8F00' }}
/>
</ View >
);
};
How Double-or-Loss Works
Correct Answers Earn 2× points (2 points instead of 1) Rewards faster progress for high performers
Wrong Answers Lose 1 point per incorrect answer Adds stakes and encourages careful thinking
Double-or-Loss mode can be demotivating for struggling learners. Use it only for:
Older children who enjoy challenges
Subjects where the child is already confident
Short-term motivation boosts
Children who explicitly request it
Data and Mutations
Fetching Rewards
From parent-rewards-page.tsx:313-316:
const { data : rewards = [], isLoading : loadingRewards } = useQuery < Reward []>({
queryKey: [ '/api/rewards' ],
queryFn : () => apiRequest ( 'GET' , '/api/rewards' ). then ( r => r . data ),
});
Creating/Updating Rewards
From parent-rewards-page.tsx:350-359:
const createMutation = useMutation ({
mutationFn : ( data : any ) => apiRequest ( 'POST' , '/api/rewards' , data ). then ( r => r . data ),
onSuccess : () => {
queryClient . invalidateQueries ({ queryKey: [ '/api/rewards' ] });
setShowForm ( false );
},
});
const updateMutation = useMutation ({
mutationFn : ({ id , ... data } : any ) => apiRequest ( 'PUT' , `/api/rewards/ ${ id } ` , data ). then ( r => r . data ),
onSuccess : () => {
queryClient . invalidateQueries ({ queryKey: [ '/api/rewards' ] });
setEditingReward ( null );
},
});
Deleting Rewards
From parent-rewards-page.tsx:361-364:
const deleteMutation = useMutation ({
mutationFn : ( id : string ) => apiRequest ( 'DELETE' , `/api/rewards/ ${ id } ` ). then ( r => r . data ),
onSuccess : () => queryClient . invalidateQueries ({ queryKey: [ '/api/rewards' ] }),
});
When you delete a reward, any points children have saved toward it are automatically refunded to their available balance.
Best Practices
Begin with inexpensive rewards (10-25 points) so children experience success early. Add bigger rewards as they get used to the system.
Offer variety:
Quick wins (ice cream, extra screen time)
Medium goals (toy, game, outing)
Long-term prizes (special trip, big purchase)
Ask your children what rewards motivate them. The system works best when rewards align with their interests.
Approve redemptions promptly and follow through on rewards. This builds trust in the system.
Use the progress tracking feature to recognize when children are getting close to their goals.
Troubleshooting
Check that:
The reward is marked as “Active”
The child has refreshed their page
The reward wasn’t just created (may take a moment to sync)
Points not saving correctly
Verify:
The child actually completed the save action
They had enough available points
The reward is still active
No errors occurred (check console)
If a redemption won’t approve:
Refresh the page
Check your internet connection
Try approving again
Contact support if issue persists
Next Steps