removed orphaned components

This commit is contained in:
John Overton
2026-04-02 14:36:57 -05:00
parent 90dea8a4dd
commit 63e8f3e021
3 changed files with 0 additions and 554 deletions
-128
View File
@@ -1,128 +0,0 @@
# FeedbackForm Component
A form component for collecting user feedback and support requests. This component follows the form-page pattern used throughout the application and automatically captures user context information.
## Features
- Collects feedback with subject and message
- Automatically captures submitter information (name, email, family context)
- Form validation for required fields
- Responsive design with dark mode support
- Multi-family support with family ID association
- Success/error handling with user feedback
## Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `isOpen` | boolean | Yes | Controls whether the form is visible |
| `onClose` | () => void | Yes | Function to call when the form should be closed |
| `onSuccess` | () => void | No | Optional callback function called after successful submission |
## Usage
```tsx
import FeedbackForm from '@/src/components/forms/FeedbackForm';
function MyComponent() {
const [showFeedbackForm, setShowFeedbackForm] = useState(false);
return (
<>
<Button onClick={() => setShowFeedbackForm(true)}>
Send Feedback
</Button>
<FeedbackForm
isOpen={showFeedbackForm}
onClose={() => setShowFeedbackForm(false)}
onSuccess={() => {
// Optional: Handle successful feedback submission
console.log('Feedback submitted successfully');
}}
/>
</>
);
}
```
## Form Fields
The component includes the following fields:
- **Subject**: Brief description of the feedback (required)
- **Message**: Detailed feedback, suggestions, or issue report (required)
## Automatic Context Capture
The component automatically captures and displays:
- **Submitter Name**: Extracted from authentication token (account email prefix or caretaker name)
- **Submitter Email**: Account email if available (account users only)
- **Family Context**: Current family name and ID from family context
## Implementation Details
- Uses the FormPage component for consistent UI across the application
- Automatically extracts user information from JWT authentication token
- Supports both account-based and caretaker-based authentication
- Includes form validation for required fields
- Handles API calls for submitting feedback
- Provides user feedback through alerts for success/error states
- Resets form after successful submission
- Uses emerald/green theme colors to indicate positive action (feedback submission)
### Authentication Context
The component handles different authentication scenarios:
1. **Account Authentication**: Extracts email and uses email prefix as name
2. **Caretaker Authentication**: Extracts caretaker name from token
3. **No Authentication**: Falls back to generic "User" name
### Multi-Family Support
The component supports multi-family functionality by:
- Automatically using the current family context from `useFamily()` hook
- Including the family ID in the API request payload
- Displaying the current family name in the submitter information
### API Integration
The component sends feedback data to `/api/feedback` with the following payload:
```json
{
"subject": "string",
"message": "string",
"familyId": "string|null",
"submitterName": "string",
"submitterEmail": "string|null"
}
```
## Styling
The component uses:
- Light mode styles defined in the component and CSS classes
- Dark mode overrides in `feedback-form.css`
- Consistent styling with other form components
- Emerald/green accent colors for the submit button
- Info box styling for displaying submitter context
## Accessibility
- Proper form labels and required field indicators
- Keyboard navigation support
- Screen reader friendly structure
- Clear error and success messaging
- Disabled states during form submission
## Error Handling
The component handles various error scenarios:
- Network errors during submission
- API validation errors
- Authentication token parsing errors
- Missing required fields
All errors are displayed to the user through alert dialogs with descriptive messages.
@@ -1,115 +0,0 @@
/* Dark mode overrides for FeedbackForm component */
/* Submitter info section in dark mode */
html.dark .feedback-form-submitter-info {
color: #9ca3af !important; /* gray-400 */
}
/* Info container styling in dark mode */
html.dark .feedback-form-info {
background-color: #374151 !important; /* gray-700 */
border: 1px solid #4b5563 !important; /* gray-600 */
border-radius: 0.5rem;
padding: 0.75rem;
}
/* Input field styling in dark mode */
html.dark .feedback-form-input {
background-color: #1f2937 !important; /* gray-800 */
border-color: #4b5563 !important; /* gray-600 */
color: #e5e7eb !important; /* gray-200 */
}
html.dark .feedback-form-input:hover {
background-color: #374151 !important; /* gray-700 */
border-color: #6b7280 !important; /* gray-500 */
}
html.dark .feedback-form-input:focus {
border-color: #10b981 !important; /* emerald-500 */
background-color: #1f2937 !important; /* gray-800 */
ring-color: rgba(16, 185, 129, 0.2) !important; /* emerald-500 with opacity */
}
html.dark .feedback-form-input::placeholder {
color: #9ca3af !important; /* gray-400 */
}
/* Textarea styling in dark mode */
html.dark .feedback-form-textarea {
background-color: #1f2937 !important; /* gray-800 */
border-color: #4b5563 !important; /* gray-600 */
color: #e5e7eb !important; /* gray-200 */
}
html.dark .feedback-form-textarea:hover {
background-color: #374151 !important; /* gray-700 */
border-color: #6b7280 !important; /* gray-500 */
}
html.dark .feedback-form-textarea:focus {
border-color: #10b981 !important; /* emerald-500 */
background-color: #1f2937 !important; /* gray-800 */
ring-color: rgba(16, 185, 129, 0.2) !important; /* emerald-500 with opacity */
}
html.dark .feedback-form-textarea::placeholder {
color: #9ca3af !important; /* gray-400 */
}
/* Help text styling in dark mode */
html.dark .feedback-form-help-text {
color: #9ca3af !important; /* gray-400 */
}
/* Light mode styles for info container */
.feedback-form-info {
background-color: #f8fafc; /* slate-50 */
border: 1px solid #e2e8f0; /* slate-200 */
border-radius: 0.5rem;
padding: 0.75rem;
}
.feedback-form-submitter-info {
color: #64748b; /* slate-500 */
}
.feedback-form-help-text {
color: #64748b; /* slate-500 */
}
/* Success toast styling */
.feedback-success-toast {
background-color: #ecfdf5; /* emerald-50 */
border-color: #a7f3d0; /* emerald-200 */
}
.feedback-success-toast-icon {
color: #6ee7b7; /* emerald-300 */
}
.feedback-success-toast-title {
color: #065f46; /* emerald-800 */
}
.feedback-success-toast-message {
color: #047857; /* emerald-700 */
}
/* Dark mode success toast styling */
html.dark .feedback-success-toast {
background-color: #064e3b !important; /* emerald-900 */
border-color: #065f46 !important; /* emerald-800 */
}
html.dark .feedback-success-toast-icon {
color: #6ee7b7 !important; /* emerald-300 */
}
html.dark .feedback-success-toast-title {
color: #a7f3d0 !important; /* emerald-200 */
}
html.dark .feedback-success-toast-message {
color: #6ee7b7 !important; /* emerald-300 */
}
-311
View File
@@ -1,311 +0,0 @@
'use client';
import React, { useState, useEffect } from 'react';
import { Button } from '@/src/components/ui/button';
import { Input } from '@/src/components/ui/input';
import { Textarea } from '@/src/components/ui/textarea';
import {
FormPage,
FormPageContent,
FormPageFooter
} from '@/src/components/ui/form-page';
import { useTheme } from '@/src/context/theme';import { useLocalization } from '@/src/context/localization';
import './feedback-form.css';
interface FeedbackFormProps {
isOpen: boolean;
onClose: () => void;
onSuccess?: () => void;
embedded?: boolean; // If true, renders without FormPage wrapper
}
export default function FeedbackForm({
isOpen,
onClose,
onSuccess,
embedded = false,
}: FeedbackFormProps) {
const { t } = useLocalization();
const { theme } = useTheme();
const [formData, setFormData] = useState({
subject: '',
message: '',
});
const [loading, setLoading] = useState(false);
const [submitterInfo, setSubmitterInfo] = useState({
name: '',
email: '',
});
const [family, setFamily] = useState<{ id: string; name: string } | null>(null);
const [showSuccessToast, setShowSuccessToast] = useState(false);
// Get submitter information from authentication context
useEffect(() => {
const getSubmitterInfo = async () => {
try {
const authToken = localStorage.getItem('authToken');
if (!authToken) return;
// Parse JWT token to get user info
const payload = authToken.split('.')[1];
const decodedPayload = JSON.parse(atob(payload));
// Check if this is account authentication
if (decodedPayload.isAccountAuth) {
// For account users, we can get email from the token
setSubmitterInfo({
name: decodedPayload.accountEmail ? decodedPayload.accountEmail.split('@')[0] : 'Account User',
email: decodedPayload.accountEmail || '',
});
} else {
// For caretaker users, get name from token
setSubmitterInfo({
name: decodedPayload.name || 'User',
email: '', // Caretakers don't have email in the token
});
}
// Try to get family information if available
if (decodedPayload.familyId && decodedPayload.familySlug) {
// We have family info in the token, try to get family name
try {
const familyResponse = await fetch(`/api/family/by-slug/${decodedPayload.familySlug}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (familyResponse.ok) {
const familyData = await familyResponse.json();
if (familyData.success && familyData.data) {
setFamily({
id: familyData.data.id,
name: familyData.data.name
});
}
}
} catch (familyError) {
console.log('Could not fetch family info:', familyError);
// Not a critical error, continue without family info
}
}
} catch (error) {
console.error('Error parsing auth token:', error);
setSubmitterInfo({
name: 'User',
email: '',
});
}
};
if (isOpen) {
getSubmitterInfo();
}
}, [isOpen]);
// Reset form when opening
useEffect(() => {
if (isOpen) {
setFormData({
subject: '',
message: '',
});
}
}, [isOpen]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Validate required fields
if (!formData.subject.trim() || !formData.message.trim()) {
alert(t('Please fill in both subject and message fields.'));
return;
}
setLoading(true);
try {
const authToken = localStorage.getItem('authToken');
const payload = {
subject: formData.subject.trim(),
message: formData.message.trim(),
familyId: family?.id || null,
submitterName: submitterInfo.name,
submitterEmail: submitterInfo.email || null,
};
const response = await fetch('/api/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': authToken ? `Bearer ${authToken}` : '',
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (data.success) {
// Show success toast
setShowSuccessToast(true);
// Auto-close after 3 seconds
setTimeout(() => {
onClose();
if (onSuccess) onSuccess();
}, 3000);
} else {
console.error('Error submitting feedback:', data.error);
alert(`Error: ${data.error || 'Failed to submit feedback'}`);
}
} catch (error) {
console.error('Error submitting feedback:', error);
alert(t('An unexpected error occurred. Please try again.'));
} finally {
setLoading(false);
}
};
const formContent = (
<>
{/* Success Toast */}
{showSuccessToast && (
<div className="mb-4 p-4 rounded-lg feedback-success-toast">
<div className="flex items-center">
<div className="flex-shrink-0">
<svg className="h-5 w-5 feedback-success-toast-icon" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<p className="text-sm font-medium feedback-success-toast-title">
{t('Thank you for your feedback!')}
</p>
<p className="text-sm mt-1 feedback-success-toast-message">
{t('We appreciate your input and will review your message.')}
{submitterInfo.email && ' A confirmation email has been sent to you.'}
</p>
</div>
</div>
</div>
)}
<form onSubmit={handleSubmit}>
<div className="space-y-4">
{/* Submitter Info Display */}
<div className="feedback-form-info">
<div className="text-sm text-gray-600 feedback-form-submitter-info">
<p><strong>{t('From:')}</strong> {submitterInfo.name}</p>
{submitterInfo.email && (
<p><strong>{t('Email:')}</strong> {submitterInfo.email}</p>
)}
{family && (
<p><strong>{t('Family:')}</strong> {family.name}</p>
)}
</div>
</div>
{/* Subject */}
<div className="space-y-2">
<label htmlFor="subject" className="form-label">
{t('Subject')} <span className="text-red-500">*</span>
</label>
<Input
id="subject"
name="subject"
type="text"
placeholder={t("Brief description of your feedback")}
value={formData.subject}
onChange={handleInputChange}
required
disabled={loading}
className="feedback-form-input"
/>
</div>
{/* Message */}
<div className="space-y-2">
<label htmlFor="message" className="form-label">
{t('Message')} <span className="text-red-500">*</span>
</label>
<Textarea
id="message"
name="message"
placeholder={t("Please share your detailed feedback, suggestions, or report any issues you've encountered...")}
value={formData.message}
onChange={handleInputChange}
required
disabled={loading}
rows={6}
className="feedback-form-textarea"
/>
</div>
{/* Help Text */}
<div className="feedback-form-help-text">
<p className="text-sm text-gray-500">
{t('Your feedback helps us improve the app. Please be as specific as possible about any issues or suggestions you have.')}
</p>
</div>
</div>
</form>
</>
);
const formFooter = (
<div className="flex justify-end space-x-2">
<Button
type="button"
variant="outline"
onClick={onClose}
disabled={loading}
>
{t('Cancel')}
</Button>
<Button
onClick={handleSubmit}
disabled={loading || !formData.subject.trim() || !formData.message.trim()}
variant="success"
>
{loading ? t('Sending...') : t('Send Feedback')}
</Button>
</div>
);
if (embedded) {
return (
<>
<FormPageContent>
{formContent}
</FormPageContent>
<FormPageFooter>
{formFooter}
</FormPageFooter>
</>
);
}
return (
<FormPage
isOpen={isOpen}
onClose={onClose}
title={t("Send Feedback")}
description={t("Help us improve by sharing your thoughts and suggestions")}
>
<FormPageContent>
{formContent}
</FormPageContent>
<FormPageFooter>
{formFooter}
</FormPageFooter>
</FormPage>
);
}