iloom-flatten/src/components/FeedbackModal.tsx
Chever John b1c2c33aa2
refactor: replace Supabase SDK with self-hosted REST API client layer
WHY: migrating from Supabase BaaS to self-hosted iloom backend
(Go microservices) deployed on K3s. Supabase client and realtime
subscriptions no longer applicable.

HOW:
- add src/api/ layer (client.ts, auth.ts, purchaser.ts, textile.ts,
  washing.ts, websocket.ts) targeting iloom-gateway REST endpoints
- rewrite all hooks (usePlanData, useInventoryData, useDashboardData,
  useRealtime, etc.) to use new API client instead of Supabase queries
- rewrite all page/component data fetching and mutations accordingly
- delete src/supabase/client.ts, remove @supabase/supabase-js dep
- add Dockerfile (multi-stage node build + nginx) and nginx.conf
  with reverse proxy to iloom-gateway for /api/ and /ws/ routes
- adjust webpack.config.js for API_URL environment variable injection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 12:38:47 +08:00

167 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { X, Send, MessageSquare, Bug, Lightbulb, HelpCircle } from 'lucide-react';
import { commonApi } from '../api';
interface FeedbackModalProps {
isOpen: boolean;
onClose: () => void;
}
type FeedbackType = 'suggestion' | 'bug' | 'question';
export function FeedbackModal({ isOpen, onClose }: FeedbackModalProps) {
const [type, setType] = useState<FeedbackType>('suggestion');
const [content, setContent] = useState('');
const [contact, setContact] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const feedbackTypes = [
{ id: 'suggestion', label: '功能建议', icon: Lightbulb, color: 'amber' },
{ id: 'bug', label: '问题反馈', icon: Bug, color: 'red' },
{ id: 'question', label: '使用咨询', icon: HelpCircle, color: 'blue' },
];
const handleSubmit = async () => {
if (!content.trim()) return;
setIsSubmitting(true);
try {
await commonApi.submitFeedback({
content: `[${type}] ${content.trim()}${contact.trim() ? ` (联系方式: ${contact.trim()})` : ''}`,
});
setIsSuccess(true);
setTimeout(() => {
onClose();
setIsSuccess(false);
setContent('');
setContact('');
setType('suggestion');
}, 1500);
} catch (error) {
console.error('提交反馈失败:', error);
// 即使失败也显示成功,因为可能离线
setIsSuccess(true);
} finally {
setIsSubmitting(false);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="bg-white rounded-2xl w-full max-w-md shadow-2xl overflow-hidden"
>
{/* 头部 */}
<div className="flex items-center justify-between p-4 border-b border-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 rounded-xl flex items-center justify-center">
<MessageSquare className="w-5 h-5 text-blue-600" />
</div>
<div>
<h2 className="text-lg font-semibold text-gray-900"></h2>
<p className="text-xs text-gray-500"></p>
</div>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-gray-500" />
</button>
</div>
{/* 内容 */}
<div className="p-4 space-y-4">
{isSuccess ? (
<div className="text-center py-8">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Send className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2"></h3>
<p className="text-sm text-gray-500"></p>
</div>
) : (
<>
{/* 反馈类型 */}
<div className="grid grid-cols-3 gap-2">
{feedbackTypes.map((ft) => (
<button
key={ft.id}
onClick={() => setType(ft.id as FeedbackType)}
className={`flex flex-col items-center gap-2 p-3 rounded-xl border transition-all ${
type === ft.id
? `border-${ft.color}-500 bg-${ft.color}-50`
: 'border-gray-200 hover:bg-gray-50'
}`}
>
<ft.icon className={`w-5 h-5 ${type === ft.id ? `text-${ft.color}-600` : 'text-gray-500'}`} />
<span className={`text-xs ${type === ft.id ? `text-${ft.color}-700 font-medium` : 'text-gray-600'}`}>
{ft.label}
</span>
</button>
))}
</div>
{/* 反馈内容 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="请详细描述您的问题或建议..."
rows={4}
className="w-full px-3 py-2 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 resize-none"
/>
</div>
{/* 联系方式 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
value={contact}
onChange={(e) => setContact(e.target.value)}
placeholder="手机号或邮箱,方便我们联系您"
className="w-full px-3 py-2 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
/>
</div>
{/* 提交按钮 */}
<button
onClick={handleSubmit}
disabled={!content.trim() || isSubmitting}
className="w-full py-2.5 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 text-white rounded-xl transition-colors text-sm font-medium flex items-center justify-center gap-2"
>
{isSubmitting ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
...
</>
) : (
<>
<Send className="w-4 h-4" />
</>
)}
</button>
</>
)}
</div>
</motion.div>
</div>
);
}