feat: send email notifications for missed messages
- New cron job in cluster - Handle presence and other concerns - Toggle in app to disable email notifications - Handles other edge cases such as cooldown period - Simple html email with simple message Closes #117
This commit is contained in:
@@ -125,6 +125,12 @@ class ApiClient {
|
||||
return data.url;
|
||||
}
|
||||
|
||||
// --- Settings ---
|
||||
|
||||
async updateSettings(data: { email_notifications_enabled?: boolean }): Promise<void> {
|
||||
await this.requestVoid("PATCH", "/humans/me/settings", data);
|
||||
}
|
||||
|
||||
// --- Depot ---
|
||||
|
||||
async prepareUpload(data: PrepareUploadRequest) {
|
||||
|
||||
@@ -5,6 +5,7 @@ export const HumanSchema = z.object({
|
||||
created_at: z.coerce.date(),
|
||||
email: z.string().email(),
|
||||
email_prefix: z.string(),
|
||||
email_notifications_enabled: z.boolean(),
|
||||
});
|
||||
|
||||
export type Human = z.infer<typeof HumanSchema>;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChevronRight, LogOut, User, Info, Shield } from "lucide-react";
|
||||
import { ChevronRight, LogOut, User, Info, Shield, Mail } from "lucide-react";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { WindowControls } from "@/components/window-controls";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Muted } from "@/components/ui/typography";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { apiClient } from "@/api/client";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
interface SettingsRowProps {
|
||||
@@ -63,6 +66,25 @@ export default function SettingsPage() {
|
||||
const navigate = useNavigate();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const signOut = useAuthStore((s) => s.signOut);
|
||||
const [emailNotifications, setEmailNotifications] = useState(
|
||||
user?.email_notifications_enabled ?? true,
|
||||
);
|
||||
|
||||
const handleToggleEmailNotifications = async (checked: boolean) => {
|
||||
setEmailNotifications(checked);
|
||||
useAuthStore.setState((state) => ({
|
||||
user: state.user ? { ...state.user, email_notifications_enabled: checked } : null,
|
||||
}));
|
||||
try {
|
||||
await apiClient.updateSettings({ email_notifications_enabled: checked });
|
||||
} catch {
|
||||
// Revert on failure
|
||||
setEmailNotifications(!checked);
|
||||
useAuthStore.setState((state) => ({
|
||||
user: state.user ? { ...state.user, email_notifications_enabled: !checked } : null,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const initials = user?.email_prefix?.slice(0, 2).toUpperCase() ?? "?";
|
||||
|
||||
@@ -115,6 +137,24 @@ export default function SettingsPage() {
|
||||
|
||||
<Separator className="mt-4" />
|
||||
|
||||
<SettingsGroup title="Notifications">
|
||||
<div className="flex w-full items-center gap-3 px-4 py-3">
|
||||
<span className="text-muted-foreground flex size-5 items-center justify-center">
|
||||
<Mail className="size-4" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 text-sm font-medium">
|
||||
Email notifications
|
||||
</span>
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={emailNotifications}
|
||||
onCheckedChange={handleToggleEmailNotifications}
|
||||
/>
|
||||
</div>
|
||||
</SettingsGroup>
|
||||
|
||||
<Separator className="mt-4" />
|
||||
|
||||
<SettingsGroup title="About">
|
||||
<SettingsRow
|
||||
icon={<Info className="size-4" />}
|
||||
|
||||
Reference in New Issue
Block a user