fix(frontend): Fix Zustand authStore hydration timing issue

Fix race condition where Epic form checked user authentication before
Zustand persist middleware completed hydration from localStorage.

Root cause:
- authStore uses persist middleware to restore from localStorage
- Hydration is asynchronous
- Epic form checked user state before hydration completed
- Result: "User not authenticated" error on page refresh

Changes:
- Add isHydrated state to authStore interface
- Add onRehydrateStorage callback to track hydration completion
- Update epic-form to check isHydrated before checking user
- Disable submit button until hydration completes
- Show "Loading..." button text during hydration
- Improve error messages for better UX
- Add console logging to track hydration process

Testing:
- Page refresh should now wait for hydration
- Epic form correctly identifies logged-in users
- Submit button disabled until auth state ready
- Clear user feedback during loading state

Fixes: Epic creation "User not authenticated" error on refresh

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-05 20:56:13 +01:00
parent b404fbb006
commit be69325797
2 changed files with 24 additions and 4 deletions

View File

@@ -59,6 +59,7 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
const createEpic = useCreateEpic();
const updateEpic = useUpdateEpic();
const user = useAuthStore((state) => state.user);
const isHydrated = useAuthStore((state) => state.isHydrated);
const form = useForm<EpicFormValues>({
resolver: zodResolver(epicSchema),
@@ -71,12 +72,19 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
});
async function onSubmit(data: EpicFormValues) {
console.log('[EpicForm] onSubmit triggered', { data, user: user?.id, projectId });
console.log('[EpicForm] onSubmit triggered', { data, user: user?.id, projectId, isHydrated });
try {
// Check if auth store has completed hydration
if (!isHydrated) {
console.warn('[EpicForm] Auth store not hydrated yet, waiting...');
toast.error('Loading user information, please try again in a moment');
return;
}
if (!user?.id) {
console.error('[EpicForm] User not authenticated');
toast.error('User not authenticated');
toast.error('Please log in to create an epic');
return;
}
@@ -239,9 +247,9 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
Cancel
</Button>
)}
<Button type="submit" disabled={isLoading}>
<Button type="submit" disabled={isLoading || !isHydrated}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{isEditing ? 'Update Epic' : 'Create Epic'}
{!isHydrated ? 'Loading...' : isEditing ? 'Update Epic' : 'Create Epic'}
</Button>
</div>
</form>

View File

@@ -6,6 +6,7 @@ interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
isHydrated: boolean;
setUser: (user: User) => void;
clearUser: () => void;
@@ -18,6 +19,7 @@ export const useAuthStore = create<AuthState>()(
user: null,
isAuthenticated: false,
isLoading: true,
isHydrated: false,
setUser: (user) =>
set({ user, isAuthenticated: true, isLoading: false }),
@@ -31,6 +33,16 @@ export const useAuthStore = create<AuthState>()(
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
onRehydrateStorage: () => (state) => {
console.log('[AuthStore] Hydration started');
if (state) {
state.isHydrated = true;
console.log('[AuthStore] Hydration completed', {
user: state.user?.id,
isAuthenticated: state.isAuthenticated
});
}
},
}
)
);