-
Notifications
You must be signed in to change notification settings - Fork 26
[jules] enhance: Add global Toast notification system for mobile #320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||
| import React, { createContext, useState, useContext, useCallback } from 'react'; | ||||||||||||||||||
| import { StyleSheet } from 'react-native'; | ||||||||||||||||||
| import { Snackbar, useTheme } from 'react-native-paper'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export const ToastContext = createContext(); | ||||||||||||||||||
|
|
||||||||||||||||||
| export const ToastProvider = ({ children }) => { | ||||||||||||||||||
| const [visible, setVisible] = useState(false); | ||||||||||||||||||
| const [message, setMessage] = useState(''); | ||||||||||||||||||
| const [type, setType] = useState('info'); // 'info', 'success', 'error' | ||||||||||||||||||
| const theme = useTheme(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const showToast = useCallback((msg, toastType = 'info') => { | ||||||||||||||||||
| setMessage(msg); | ||||||||||||||||||
| setType(toastType); | ||||||||||||||||||
| setVisible(true); | ||||||||||||||||||
| }, []); | ||||||||||||||||||
|
|
||||||||||||||||||
| const hideToast = useCallback(() => { | ||||||||||||||||||
| setVisible(false); | ||||||||||||||||||
| }, []); | ||||||||||||||||||
|
|
||||||||||||||||||
| const getBackgroundColor = () => { | ||||||||||||||||||
| switch (type) { | ||||||||||||||||||
| case 'success': | ||||||||||||||||||
| return '#4CAF50'; | ||||||||||||||||||
| case 'error': | ||||||||||||||||||
| return '#F44336'; | ||||||||||||||||||
| case 'info': | ||||||||||||||||||
| default: | ||||||||||||||||||
| return theme.colors.elevation.level3; | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const getTextColor = () => { | ||||||||||||||||||
| switch (type) { | ||||||||||||||||||
| case 'success': | ||||||||||||||||||
| case 'error': | ||||||||||||||||||
| return '#FFFFFF'; | ||||||||||||||||||
| case 'info': | ||||||||||||||||||
| default: | ||||||||||||||||||
| return theme.colors.onSurface; | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <ToastContext.Provider value={{ showToast }}> | ||||||||||||||||||
| {children} | ||||||||||||||||||
| <Snackbar | ||||||||||||||||||
| visible={visible} | ||||||||||||||||||
| onDismiss={hideToast} | ||||||||||||||||||
| duration={3000} | ||||||||||||||||||
| style={[styles.snackbar, { backgroundColor: getBackgroundColor() }]} | ||||||||||||||||||
| action={{ | ||||||||||||||||||
| label: 'Dismiss', | ||||||||||||||||||
| onPress: hideToast, | ||||||||||||||||||
| labelStyle: { color: getTextColor() }, | ||||||||||||||||||
| }} | ||||||||||||||||||
| theme={{ colors: { onSurface: getTextColor(), surface: getBackgroundColor() } }} | ||||||||||||||||||
| > | ||||||||||||||||||
| {message} | ||||||||||||||||||
| </Snackbar> | ||||||||||||||||||
| </ToastContext.Provider> | ||||||||||||||||||
| ); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| export const useToast = () => useContext(ToastContext); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's explore the mobile/context directory structure
find mobile/context -type f -name "*.js" -o -name "*.ts" -o -name "*.tsx" 2>/dev/null | head -20Repository: Devasy/splitwiser Length of output: 121 🏁 Script executed: # Check the exact content of ToastContext.js
cat -n mobile/context/ToastContext.jsRepository: Devasy/splitwiser Length of output: 2427 🏁 Script executed: # Search for ToastProvider to understand the context setup
rg "ToastProvider" -A 5 -B 2Repository: Devasy/splitwiser Length of output: 3168 🏁 Script executed: # Check if other context hooks in the codebase follow the guard pattern
rg "useContext\(" mobile/context web/contexts 2>/dev/null | head -20Repository: Devasy/splitwiser Length of output: 445 🏁 Script executed: # Look for other custom context hooks to see the pattern
ast-grep --pattern 'export const use$_$_ = () => useContext($_);'Repository: Devasy/splitwiser Length of output: 43 Guard At Line 67, returning raw context can cause runtime crashes ( 🛡️ Proposed fix-export const useToast = () => useContext(ToastContext);
+export const useToast = () => {
+ const context = useContext(ToastContext);
+ if (!context) {
+ throw new Error('useToast must be used within a ToastProvider');
+ }
+ return context;
+};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| const styles = StyleSheet.create({ | ||||||||||||||||||
| snackbar: { | ||||||||||||||||||
| marginBottom: 80, // Avoid bottom nav bar | ||||||||||||||||||
| }, | ||||||||||||||||||
| }); | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,9 @@ | ||||||||||
| import React, { useState, useContext } from 'react'; | ||||||||||
| import { View, StyleSheet, Alert } from 'react-native'; | ||||||||||
| import { View, StyleSheet, ScrollView } from 'react-native'; | ||||||||||
| import { Text, TextInput } from 'react-native-paper'; | ||||||||||
| import HapticButton from '../components/ui/HapticButton'; | ||||||||||
| import { AuthContext } from '../context/AuthContext'; | ||||||||||
| import { useToast } from '../context/ToastContext'; | ||||||||||
|
|
||||||||||
| const SignupScreen = ({ navigation }) => { | ||||||||||
| const [name, setName] = useState(''); | ||||||||||
|
|
@@ -11,27 +12,25 @@ const SignupScreen = ({ navigation }) => { | |||||||||
| const [confirmPassword, setConfirmPassword] = useState(''); | ||||||||||
| const [isLoading, setIsLoading] = useState(false); | ||||||||||
| const { signup } = useContext(AuthContext); | ||||||||||
| const { showToast } = useToast(); | ||||||||||
|
|
||||||||||
| const handleSignup = async () => { | ||||||||||
| if (!name || !email || !password || !confirmPassword) { | ||||||||||
| Alert.alert('Error', 'Please fill in all fields.'); | ||||||||||
| showToast('Please fill in all fields.', 'error'); | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| if (password !== confirmPassword) { | ||||||||||
| Alert.alert('Error', "Passwords don't match!"); | ||||||||||
| showToast("Passwords don't match!", 'error'); | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| setIsLoading(true); | ||||||||||
| const success = await signup(name, email, password); | ||||||||||
| setIsLoading(false); | ||||||||||
| if (success) { | ||||||||||
| Alert.alert( | ||||||||||
| 'Success', | ||||||||||
| 'Your account has been created successfully. Please log in.', | ||||||||||
| [{ text: 'OK', onPress: () => navigation.navigate('Login') }] | ||||||||||
| ); | ||||||||||
| showToast('Account created successfully! Please login.', 'success'); | ||||||||||
| navigation.navigate('Login'); | ||||||||||
|
Comment on lines
+30
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Prefer At Line 31, ♻️ Proposed fix if (success) {
showToast('Account created successfully! Please login.', 'success');
- navigation.navigate('Login');
+ navigation.replace('Login');
} else {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| } else { | ||||||||||
| Alert.alert('Signup Failed', 'An error occurred. Please try again.'); | ||||||||||
| showToast('Signup failed. An error occurred. Please try again.', 'error'); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Memoize provider value to avoid app-wide rerenders on each toast state change.
At Line 47,
value={{ showToast }}creates a new object every render, forcing alluseToast()consumers to rerender whenever toast UI state changes.♻️ Proposed fix
import React, { createContext, useState, useContext, useCallback } from 'react'; +import { useMemo } from 'react'; @@ - return ( - <ToastContext.Provider value={{ showToast }}> + const contextValue = useMemo(() => ({ showToast }), [showToast]); + + return ( + <ToastContext.Provider value={contextValue}>🤖 Prompt for AI Agents