Skip to content

Commit 0d9524a

Browse files
authored
feat(712): add ToastContext provider and useToast hook (#869)
1 parent 859c3f9 commit 0d9524a

2 files changed

Lines changed: 83 additions & 4 deletions

File tree

mobileapp/app/_layout.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
Outfit_700Bold,
2424
} from "@expo-google-fonts/outfit";
2525
import { ErrorBoundary } from "../src/components/ErrorBoundary";
26-
import { ToastManager } from "../src/components/Toast";
26+
import { ToastProvider } from "../src/components/Toast";
2727
import { useOfflineDetection } from "../src/hooks/useNetworkStatus";
2828
import {
2929
getStoredNotificationPreference,
@@ -246,7 +246,6 @@ function LayoutContent() {
246246
<Stack.Screen name="about-zaps" options={{ animation: "fade" }} />
247247
<Stack.Screen name="help-support" options={{ animation: "fade" }} />
248248
</Stack>
249-
<ToastManager />
250249
<Modal
251250
visible={forceUpdate}
252251
transparent
@@ -338,7 +337,9 @@ export default function Layout() {
338337
}}
339338
>
340339
<ErrorBoundary>
341-
<LayoutContent />
340+
<ToastProvider>
341+
<LayoutContent />
342+
</ToastProvider>
342343
</ErrorBoundary>
343344
</PrivyProvider>
344345
);

mobileapp/src/components/Toast.tsx

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useEffect, useState, useCallback } from "react";
1+
import React, {
2+
createContext,
3+
useContext,
4+
useEffect,
5+
useState,
6+
useCallback,
7+
} from "react";
28
import {
39
View,
410
Text,
@@ -199,6 +205,78 @@ export const ToastManager: React.FC = () => {
199205
);
200206
};
201207

208+
// ── Context-based toast API (Issue #712) ──────────────────────────────────────
209+
210+
type ToastContextValue = {
211+
showToast: (message: string, type: ToastType) => void;
212+
};
213+
214+
const ToastContext = createContext<ToastContextValue | undefined>(undefined);
215+
216+
export function useToast(): ToastContextValue {
217+
const context = useContext(ToastContext);
218+
if (!context) {
219+
throw new Error("useToast must be used within ToastProvider");
220+
}
221+
return context;
222+
}
223+
224+
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({
225+
children,
226+
}) => {
227+
const [toasts, setToasts] = useState<{ id: string; props: ToastProps }[]>([]);
228+
229+
const removeToast = useCallback((id: string) => {
230+
setToasts((prev) => prev.filter((t) => t.id !== id));
231+
}, []);
232+
233+
const showToast = useCallback((message: string, type: ToastType) => {
234+
const id = Date.now().toString();
235+
setToasts((prev) => [...prev, { id, props: { message, type } }]);
236+
}, []);
237+
238+
// Bridge to global.toast for existing consumers
239+
useEffect(() => {
240+
(global as any).toast = {
241+
show: (props: Omit<ToastProps, "onHide">) => {
242+
const id = Date.now().toString();
243+
setToasts((prev) => [...prev, { id, props }]);
244+
},
245+
success: (message: string, action?: ToastProps["action"]) => {
246+
const id = Date.now().toString();
247+
setToasts((prev) => [...prev, { id, props: { message, type: "success", action } }]);
248+
},
249+
error: (message: string, action?: ToastProps["action"]) => {
250+
const id = Date.now().toString();
251+
setToasts((prev) => [...prev, { id, props: { message, type: "error", action } }]);
252+
},
253+
warning: (message: string, action?: ToastProps["action"]) => {
254+
const id = Date.now().toString();
255+
setToasts((prev) => [...prev, { id, props: { message, type: "warning", action } }]);
256+
},
257+
info: (message: string, action?: ToastProps["action"]) => {
258+
const id = Date.now().toString();
259+
setToasts((prev) => [...prev, { id, props: { message, type: "info", action } }]);
260+
},
261+
};
262+
263+
return () => {
264+
delete (global as any).toast;
265+
};
266+
}, []);
267+
268+
return (
269+
<ToastContext.Provider value={{ showToast }}>
270+
{children}
271+
<View style={styles.toastContainer}>
272+
{toasts.map(({ id, props }) => (
273+
<Toast key={id} {...props} onHide={() => removeToast(id)} />
274+
))}
275+
</View>
276+
</ToastContext.Provider>
277+
);
278+
};
279+
202280
const styles = StyleSheet.create({
203281
container: {
204282
marginHorizontal: 16,

0 commit comments

Comments
 (0)