From 55ef3e05ef5e6c1be4607d8d366e93498c697e18 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Tue, 28 Oct 2025 19:40:37 +0000
Subject: [PATCH] feat: Add automatic email signature
---
src/App.tsx | 2 +
src/components/layout/AdminSidebar.tsx | 6 +-
src/pages/admin/AdminEmailSettings.tsx | 201 ++++++++++++++++++
.../functions/send-admin-email-reply/index.ts | 18 +-
4 files changed, 224 insertions(+), 3 deletions(-)
create mode 100644 src/pages/admin/AdminEmailSettings.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 717298c6..ab3c85d2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -54,6 +54,7 @@ const AdminUsers = lazy(() => import("./pages/AdminUsers"));
const AdminBlog = lazy(() => import("./pages/AdminBlog"));
const AdminSettings = lazy(() => import("./pages/AdminSettings"));
const AdminContact = lazy(() => import("./pages/admin/AdminContact"));
+const AdminEmailSettings = lazy(() => import("./pages/admin/AdminEmailSettings"));
// User routes (lazy-loaded)
const Profile = lazy(() => import("./pages/Profile"));
@@ -136,6 +137,7 @@ function AppContent() {
} />
} />
} />
+ } />
{/* Utility routes - lazy loaded */}
} />
diff --git a/src/components/layout/AdminSidebar.tsx b/src/components/layout/AdminSidebar.tsx
index e159d218..db51ead4 100644
--- a/src/components/layout/AdminSidebar.tsx
+++ b/src/components/layout/AdminSidebar.tsx
@@ -1,4 +1,4 @@
-import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft, ScrollText, BookOpen, Inbox } from 'lucide-react';
+import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft, ScrollText, BookOpen, Inbox, Mail } from 'lucide-react';
import { NavLink } from 'react-router-dom';
import { useUserRole } from '@/hooks/useUserRole';
import { useSidebar } from '@/hooks/useSidebar';
@@ -62,6 +62,10 @@ export function AdminSidebar() {
title: 'Settings',
url: '/admin/settings',
icon: Settings,
+ }, {
+ title: 'Email Settings',
+ url: '/admin/email-settings',
+ icon: Mail,
}] : []),
];
diff --git a/src/pages/admin/AdminEmailSettings.tsx b/src/pages/admin/AdminEmailSettings.tsx
new file mode 100644
index 00000000..26816ede
--- /dev/null
+++ b/src/pages/admin/AdminEmailSettings.tsx
@@ -0,0 +1,201 @@
+import { useState, useEffect } from 'react';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { Save, Loader2, Mail } from 'lucide-react';
+import { supabase } from '@/integrations/supabase/client';
+import { Button } from '@/components/ui/button';
+import { Textarea } from '@/components/ui/textarea';
+import { Label } from '@/components/ui/label';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { useUserRole } from '@/hooks/useUserRole';
+import { handleError, handleSuccess } from '@/lib/errorHandler';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+
+export default function AdminEmailSettings() {
+ const queryClient = useQueryClient();
+ const { isSuperuser, loading: rolesLoading } = useUserRole();
+ const [signature, setSignature] = useState('');
+
+ // Fetch email signature
+ const { data: signatureSetting, isLoading } = useQuery({
+ queryKey: ['admin-email-signature'],
+ queryFn: async () => {
+ const { data, error } = await supabase
+ .from('admin_settings')
+ .select('setting_value')
+ .eq('setting_key', 'email.signature')
+ .single();
+
+ if (error && error.code !== 'PGRST116') { // PGRST116 = no rows returned
+ throw error;
+ }
+
+ // Type guard for the setting value
+ const settingValue = data?.setting_value as { signature?: string } | null;
+ return settingValue?.signature || '';
+ },
+ });
+
+ useEffect(() => {
+ if (signatureSetting !== undefined) {
+ setSignature(signatureSetting);
+ }
+ }, [signatureSetting]);
+
+ // Update email signature mutation
+ const updateSignatureMutation = useMutation({
+ mutationFn: async (newSignature: string) => {
+ const { data: existing } = await supabase
+ .from('admin_settings')
+ .select('id')
+ .eq('setting_key', 'email.signature')
+ .single();
+
+ if (existing) {
+ // Update existing
+ const { error } = await supabase
+ .from('admin_settings')
+ .update({
+ setting_value: { signature: newSignature },
+ updated_at: new Date().toISOString(),
+ })
+ .eq('setting_key', 'email.signature');
+
+ if (error) throw error;
+ } else {
+ // Insert new
+ const { error } = await supabase
+ .from('admin_settings')
+ .insert({
+ setting_key: 'email.signature',
+ setting_value: { signature: newSignature },
+ category: 'email',
+ description: 'Email signature automatically appended to all contact submission replies',
+ });
+
+ if (error) throw error;
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['admin-email-signature'] });
+ handleSuccess('Saved', 'Email signature has been updated successfully');
+ },
+ onError: (error) => {
+ handleError(error, { action: 'update_email_signature' });
+ },
+ });
+
+ const handleSave = () => {
+ updateSignatureMutation.mutate(signature);
+ };
+
+ // Show loading state while roles are being fetched
+ if (rolesLoading) {
+ return (
+
+
+
+ );
+ }
+
+ // Superuser-only access check
+ if (!isSuperuser()) {
+ return (
+
+
+
+
+
+ Access Denied
+
+ Email settings can only be managed by superusers.
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
Email Settings
+
+ Configure automatic email signature for contact submission replies
+
+
+
+
+
+
+
+ Email Signature
+
+
+ This signature will be automatically appended to all email replies sent to users from the Contact Submissions page.
+ The signature will be added after a separator line.
+
+
+
+
+
+ Preview: The signature will appear as:
+
+ [Your reply message]
+
+
+ ---
+
+ {signature || '[No signature set]'}
+
+
+
+
+
+
+
+
+ {updateSignatureMutation.isPending && (
+
+ )}
+
+ Save Signature
+
+ {signature !== signatureSetting && (
+ setSignature(signatureSetting || '')}
+ disabled={updateSignatureMutation.isPending}
+ >
+ Reset Changes
+
+ )}
+
+
+
+
+ );
+}
diff --git a/supabase/functions/send-admin-email-reply/index.ts b/supabase/functions/send-admin-email-reply/index.ts
index 54130540..80321c56 100644
--- a/supabase/functions/send-admin-email-reply/index.ts
+++ b/supabase/functions/send-admin-email-reply/index.ts
@@ -78,6 +78,20 @@ const handler = async (req: Request): Promise => {
return createErrorResponse({ message: 'Submission not found' }, 404, corsHeaders);
}
+ // Fetch email signature from admin settings
+ const { data: signatureSetting } = await supabase
+ .from('admin_settings')
+ .select('setting_value')
+ .eq('setting_key', 'email.signature')
+ .single();
+
+ const emailSignature = signatureSetting?.setting_value?.signature || '';
+
+ // Append signature to reply body if it exists
+ const finalReplyBody = emailSignature
+ ? `${replyBody}\n\n---\n${emailSignature}`
+ : replyBody;
+
// Rate limiting: max 10 replies per hour
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const { count } = await supabase
@@ -124,7 +138,7 @@ const handler = async (req: Request): Promise => {
from: 'ThrillWiki Admin ',
to: `${submission.name} <${submission.email}>`,
subject: finalSubject,
- text: replyBody,
+ text: finalReplyBody,
headers: {
'Message-ID': messageId,
'In-Reply-To': inReplyTo,
@@ -156,7 +170,7 @@ const handler = async (req: Request): Promise => {
from_email: 'admin@thrillwiki.com',
to_email: submission.email,
subject: finalSubject,
- body_text: replyBody,
+ body_text: finalReplyBody,
direction: 'outbound',
sent_by: user.id,
metadata: {