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>
167 lines
6.2 KiB
TypeScript
167 lines
6.2 KiB
TypeScript
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>
|
||
);
|
||
}
|