Compare commits
2 Commits
dca0d54e2e
...
9ebc55de73
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ebc55de73 | ||
|
|
8f0becb53a |
3
public/assets/icons/cloud-pending-icon.svg
Normal file
3
public/assets/icons/cloud-pending-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z" fill="#9CA3AF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 408 B |
3
public/assets/icons/cloud-saved-icon.svg
Normal file
3
public/assets/icons/cloud-saved-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM10 17l-3.5-3.5 1.41-1.41L10 14.17 15.18 9l1.41 1.41L10 17z" fill="#22C55E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 342 B |
3
public/assets/icons/cloud-saving-icon.svg
Normal file
3
public/assets/icons/cloud-saving-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z" fill="#E58625"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 311 B |
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { QuickLinks } from './components';
|
import { QuickLinks } from './components';
|
||||||
import DynamicSection from './components/DynamicSection';
|
import DynamicSection from './components/DynamicSection';
|
||||||
@@ -48,9 +49,7 @@ export default function EditProfilePage() {
|
|||||||
const repeatableDataRef = useRef<Record<string, RepeatableEntryData[]>>({});
|
const repeatableDataRef = useRef<Record<string, RepeatableEntryData[]>>({});
|
||||||
// Dirty (unsaved) field slugs; repeatable sections tracked as `${slug}_entries`
|
// Dirty (unsaved) field slugs; repeatable sections tracked as `${slug}_entries`
|
||||||
const dirtySlugsRef = useRef<Set<string>>(new Set());
|
const dirtySlugsRef = useRef<Set<string>>(new Set());
|
||||||
// Server auto-save stays off for APPROVED profiles — their field values are
|
// Set once the profile loads; guards flushes from firing before then
|
||||||
// live, so changes only go out via the explicit Save button. Local drafts
|
|
||||||
// still work for everyone.
|
|
||||||
const autoSaveEnabledRef = useRef(false);
|
const autoSaveEnabledRef = useRef(false);
|
||||||
const draftKeyRef = useRef<string | null>(null);
|
const draftKeyRef = useRef<string | null>(null);
|
||||||
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
@@ -233,12 +232,9 @@ export default function EditProfilePage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// APPROVED profiles are live — keep their changes behind the explicit
|
// Auto-save for all statuses. Note: APPROVED profiles are live, so
|
||||||
// Save button. Everyone else gets server auto-save while they build.
|
// auto-saved edits update the public profile immediately.
|
||||||
autoSaveEnabledRef.current =
|
autoSaveEnabledRef.current = true;
|
||||||
!profile.verificationStatus ||
|
|
||||||
profile.verificationStatus === 'NONE' ||
|
|
||||||
profile.verificationStatus === 'REJECTED';
|
|
||||||
draftKeyRef.current = `agent-profile-draft:${profile.userId}`;
|
draftKeyRef.current = `agent-profile-draft:${profile.userId}`;
|
||||||
|
|
||||||
if (!profile.agentTypeId) {
|
if (!profile.agentTypeId) {
|
||||||
@@ -640,25 +636,27 @@ export default function EditProfilePage() {
|
|||||||
{/* Auto-save status */}
|
{/* Auto-save status */}
|
||||||
{(autoSaveStatus !== 'idle' || lastSavedAt) && (
|
{(autoSaveStatus !== 'idle' || lastSavedAt) && (
|
||||||
<div className="flex items-center justify-end gap-2 px-1">
|
<div className="flex items-center justify-end gap-2 px-1">
|
||||||
<span
|
<Image
|
||||||
className={`inline-block w-2 h-2 rounded-full ${
|
src={
|
||||||
autoSaveStatus === 'saving'
|
autoSaveStatus === 'saving'
|
||||||
? 'bg-[#E58625] animate-pulse'
|
? '/assets/icons/cloud-saving-icon.svg'
|
||||||
: autoSaveStatus === 'saved' || (autoSaveStatus === 'idle' && lastSavedAt)
|
|
||||||
? 'bg-green-500'
|
|
||||||
: autoSaveStatus === 'error'
|
: autoSaveStatus === 'error'
|
||||||
? 'bg-red-500'
|
? '/assets/icons/caution-icon.svg'
|
||||||
: 'bg-gray-400'
|
: autoSaveStatus === 'dirty'
|
||||||
}`}
|
? '/assets/icons/cloud-pending-icon.svg'
|
||||||
></span>
|
: '/assets/icons/cloud-saved-icon.svg'
|
||||||
|
}
|
||||||
|
alt={autoSaveStatus === 'saving' ? 'Saving' : autoSaveStatus === 'dirty' ? 'Unsaved changes' : autoSaveStatus === 'error' ? 'Save failed' : 'Saved'}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className={autoSaveStatus === 'saving' ? 'animate-pulse' : ''}
|
||||||
|
/>
|
||||||
<span className="text-[12px] font-serif text-[#00293D]/60">
|
<span className="text-[12px] font-serif text-[#00293D]/60">
|
||||||
{autoSaveStatus === 'saving' && 'Saving…'}
|
{autoSaveStatus === 'saving' && 'Saving…'}
|
||||||
{autoSaveStatus === 'saved' && lastSavedAt && `All changes saved at ${formatTime(lastSavedAt)}`}
|
{autoSaveStatus === 'saved' && lastSavedAt && `All changes saved at ${formatTime(lastSavedAt)}`}
|
||||||
{autoSaveStatus === 'error' && 'Auto-save failed — retrying'}
|
{autoSaveStatus === 'error' && 'Auto-save failed — retrying'}
|
||||||
{autoSaveStatus === 'dirty' &&
|
{autoSaveStatus === 'dirty' &&
|
||||||
(autoSaveEnabledRef.current
|
(lastSavedAt ? `Last saved at ${formatTime(lastSavedAt)}` : 'Unsaved changes')}
|
||||||
? 'Unsaved changes'
|
|
||||||
: 'Unsaved changes — remember to save')}
|
|
||||||
{autoSaveStatus === 'idle' && lastSavedAt && `Last saved at ${formatTime(lastSavedAt)}`}
|
{autoSaveStatus === 'idle' && lastSavedAt && `Last saved at ${formatTime(lastSavedAt)}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -675,9 +673,7 @@ export default function EditProfilePage() {
|
|||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
})}
|
})}
|
||||||
{autoSaveEnabledRef.current
|
. Keep editing — your progress is saved automatically.
|
||||||
? '. Keep editing — your progress is saved automatically.'
|
|
||||||
: '. Review them and press Save Changes to keep them.'}
|
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => setDraftRestoredAt(null)}
|
onClick={() => setDraftRestoredAt(null)}
|
||||||
|
|||||||
Reference in New Issue
Block a user