I'll redesign the notification preferences to use badges that users can add/remove for each notification channel.
I've redesigned the notification preferences to use badges instead of toggle switches:
Badge-Based Channel Selection:
Visual Distinction:
Improved Interaction:
Maintained Organization:
Responsive Design:
The component allows users to easily see which notification channels are active for each event type and toggle them with a simple click. The badge-based approach provides a more visual and intuitive way to manage notification preferences.
ok can you make this work with what I have so far?
import React, { useState, useEffect } from 'react';
import { useNotificationPreferences, useUpdateNotificationPreferences } from '../hooks';
import { NotificationPreferences, NotificationItem } from '../types';
import {
FileChartColumnIcon,
MegaphoneIcon,
UserIcon,
} from 'lucide-react';
import NotificationSkeleton from './NotificationSkeleton';
const NotificationSettings = () => {
const { data: preferences, isLoading, error } = useNotificationPreferences();
const { mutate: updatePreferences } = useUpdateNotificationPreferences();
const [activeTab, setActiveTab] = useState<keyof NotificationPreferences | ''>('');
// Set the first app as the active tab when preferences load
useEffect(() => {
if (preferences) {
const firstKey = Object.keys(preferences)[0] as keyof NotificationPreferences;
setActiveTab(firstKey);
}
}, [preferences]);
// Render loading state
if (isLoading) {
return <NotificationSkeleton />;
}
// Render error state
if (error) {
return (
<div className="p-4 rounded-md bg-red-50 border border-red-200">
<p className="text-red-700">Failed to load notification preferences: {error.message}</p>
</div>
);
}
if (!preferences) {
return null;
}
// Get app names (keys) from the preferences object
const appNames = Object.keys(preferences) as Array<keyof NotificationPreferences>;
// Get icon for each app type
const getAppIcon = (appName: string) => {
switch (appName) {
case 'announce':
return <MegaphoneIcon className="w-5 h-5" />;
case 'user':
return <UserIcon className="w-5 h-5" />;
case 'perform':
return <FileChartColumnIcon className="w-5 h-5" />;
default:
return null;
}
};
// Format app name for display (capitalize first letter)
const formatAppName = (name: string) => {
return name.charAt(0).toUpperCase() + name.slice(1);
};
// Format title for display (capitalize first letter)
const formatTitle = (title: string) => {
return title.charAt(0).toUpperCase() + title.slice(1);
};
useNotificationPreferences makes a request that returns a json like this:
{
"announce": [
{
"title": "pending",
"description": "When an announcement is waiting for your approval",
"channels": {
"push": false,
"email": false
}
},
{
"title": "approved",
"description": "When an announcement is approved",
"channels": {
"push": false,
"email": false
}
},
{
"title": "rejected",
"description": "When an announcement is rejected",
"channels": {
"push": false,
"email": false
}
},
{
"title": "sent",
"description": "When you send an announcement",
"channels": {
"push": false,
"email": false
}
}
],
"perform": [
{
"title": "published",
"description": "When an Observation is published",
"channels": {
"push": true,
"email": true
}
}
],
"user": [
{
"title": "announcements",
"description": "Notification settings for announcements",
"channels": {
"push": false,
"email": false,
"sms": false
}
},
{
"title": "passwords",
"description": "Password-related notifications",
"channels": {
"email": false
}
}
]
}
then keep the badges but when a user presses the badge, call useUpdateNotificationPreferences and send the whole json back
I'll integrate the badge-based notification preferences UI with your existing code structure:
I've integrated the badge-based notification UI with your existing code structure. Here's what I've done:
Maintained Your Existing Structure:
useNotificationPreferences, useUpdateNotificationPreferences)Added Badge-Based UI:
Implemented Toggle Functionality:
Added a toggleChannel function that:
updatePreferences with the updated dataImproved Visual Organization:
Channel Display Logic:
When a user clicks a badge:
updatePreferencesThis implementation preserves all your existing functionality while adding the badge-based UI for a more intuitive notification preference management experience.
ok here is more context:
export interface BaseChannels {
push: boolean;
email: boolean;
}
export interface ExtendedChannels extends BaseChannels {
sms?: boolean;
}
export interface NotificationItem {
title: string;
description: string;
channels: BaseChannels | ExtendedChannels;
}
export interface NotificationPreferences {
announce: NotificationItem[];
perform: NotificationItem[];
user: NotificationItem[];
}
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { fetchNotificationPreferences, updateNotificationPreferences } from '../api';
import { NotificationPreferences } from '../types'
import { toast } from 'sonner';
export function useNotificationPreferences() {
return useQuery({
queryKey: ['preferences'],
queryFn: fetchNotificationPreferences,
});
}
export function useUpdateNotificationPreferences() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ payload }: { payload: NotificationPreferences }) =>
updateNotificationPreferences(payload),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['preferences'],
});
toast.success('Successfully updated preferences', { description: new Date().toLocaleString() });
},
onError: (error) => {
toast.error(error.message);
},
});
}
import callAPI from '@/services';
import { settingsEndpoints } from '@/services/api';
import { NotificationPreferences } from '../types';
export async function fetchNotificationPreferences() {
return await callAPI(settingsEndpoints.notificationPreferences());
}
export async function updateNotificationPreferences(data: NotificationPreferences): Promise<NotificationPreferences> {
return await callAPI(settingsEndpoints.notificationPreferences(), {method: 'PATCH', data: data})
}