feat(users): implement inspector tabs and server actions
This commit is contained in:
76
src/features/users/components/shared/AdminNote.tsx
Normal file
76
src/features/users/components/shared/AdminNote.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Loader2, Check } from 'lucide-react';
|
||||
import { saveUserNote } from '@/lib/actions/user-tabs';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface AdminNoteProps {
|
||||
userId: string;
|
||||
initialNote?: string;
|
||||
}
|
||||
|
||||
export function AdminNote({ userId, initialNote = '' }: AdminNoteProps) {
|
||||
const [content, setContent] = useState(initialNote);
|
||||
const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
|
||||
const timeoutRef = useRef<NodeJS.Timeout>(null);
|
||||
|
||||
// Sync remote changes if any (unlikely in single user flow but good practice)
|
||||
useEffect(() => {
|
||||
setContent(initialNote || '');
|
||||
}, [initialNote]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setContent(newValue);
|
||||
setStatus('idle');
|
||||
|
||||
// Debounce save
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
|
||||
timeoutRef.current = setTimeout(async () => {
|
||||
if (newValue.trim() === initialNote.trim()) return;
|
||||
|
||||
setStatus('saving');
|
||||
const result = await saveUserNote(userId, newValue);
|
||||
|
||||
if (result.success) {
|
||||
setStatus('saved');
|
||||
// Reset saved status after 2 seconds
|
||||
setTimeout(() => setStatus('idle'), 2000);
|
||||
} else {
|
||||
setStatus('error');
|
||||
toast.error("Failed to save note");
|
||||
}
|
||||
}, 1000); // 1s delay
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Textarea
|
||||
value={content}
|
||||
onChange={handleChange}
|
||||
placeholder="Add private notes about this user..."
|
||||
className="min-h-[120px] bg-amber-50/50 border-amber-200/50 resize-y text-sm focus-visible:ring-amber-500/20"
|
||||
/>
|
||||
<div className="absolute bottom-2 right-2 flex items-center gap-1.5 pointer-events-none">
|
||||
{status === 'saving' && (
|
||||
<span className="text-[10px] text-amber-600 flex items-center gap-1 bg-white/50 px-1.5 rounded-full">
|
||||
<Loader2 className="h-3 w-3 animate-spin" /> Saving...
|
||||
</span>
|
||||
)}
|
||||
{status === 'saved' && (
|
||||
<span className="text-[10px] text-emerald-600 flex items-center gap-1 bg-white/50 px-1.5 rounded-full font-medium">
|
||||
<Check className="h-3 w-3" /> Saved
|
||||
</span>
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<span className="text-[10px] text-red-600 bg-white/50 px-1.5 rounded-full">
|
||||
Not Saved
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user