mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
fix(merge): accessibility, token guard, state cleanup
- Add role="radio" + aria-checked to subscription radio buttons - Add aria-hidden="true" to decorative SVG icons - Treat success=true with null tokens as error (show error toast) - Guard auto-select useEffect from overwriting user's manual choice - Clear sessionStorage on OAuth state validation failure
This commit is contained in:
@@ -54,6 +54,7 @@ export default function LinkOAuthCallback() {
|
|||||||
|
|
||||||
// Validate state match
|
// Validate state match
|
||||||
if (saved.state !== urlState) {
|
if (saved.state !== urlState) {
|
||||||
|
clearLinkOAuthState();
|
||||||
showToast({ type: 'error', message: t('profile.accounts.linkError') });
|
showToast({ type: 'error', message: t('profile.accounts.linkError') });
|
||||||
navigate('/profile/accounts', { replace: true });
|
navigate('/profile/accounts', { replace: true });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function WarningIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@@ -41,6 +42,7 @@ function ClockIcon({ className = 'h-4 w-4' }: { className?: string }) {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@@ -59,6 +61,7 @@ function CheckCircleIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@@ -221,6 +224,8 @@ function AccountCard({ account, label, isSelected, onSelect, showRadio }: Accoun
|
|||||||
{showRadio && account.subscription && (
|
{showRadio && account.subscription && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
role="radio"
|
||||||
|
aria-checked={isSelected}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
className="mt-2 flex w-full items-center gap-2.5 rounded-lg bg-dark-800/50 px-3 py-2.5 text-left transition-colors hover:bg-dark-800"
|
className="mt-2 flex w-full items-center gap-2.5 rounded-lg bg-dark-800/50 px-3 py-2.5 text-left transition-colors hover:bg-dark-800"
|
||||||
>
|
>
|
||||||
@@ -367,9 +372,11 @@ export default function MergeAccounts() {
|
|||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-select subscription when data loads
|
// Auto-select subscription when data loads (only once)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
// Don't overwrite if user already made a selection
|
||||||
|
if (selectedUserId !== null) return;
|
||||||
|
|
||||||
const primaryHasSub = !!data.primary.subscription;
|
const primaryHasSub = !!data.primary.subscription;
|
||||||
const secondaryHasSub = !!data.secondary.subscription;
|
const secondaryHasSub = !!data.secondary.subscription;
|
||||||
@@ -383,7 +390,7 @@ export default function MergeAccounts() {
|
|||||||
setSelectedUserId(data.primary.id);
|
setSelectedUserId(data.primary.id);
|
||||||
}
|
}
|
||||||
// If both have subs — null until user picks
|
// If both have subs — null until user picks
|
||||||
}, [data]);
|
}, [data, selectedUserId]);
|
||||||
|
|
||||||
// Countdown timer
|
// Countdown timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -418,14 +425,17 @@ export default function MergeAccounts() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.access_token && response.refresh_token) {
|
if (!response.access_token || !response.refresh_token) {
|
||||||
|
showToast({ type: 'error', message: t('merge.error') });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { setTokens, setUser, checkAdminStatus } = useAuthStore.getState();
|
const { setTokens, setUser, checkAdminStatus } = useAuthStore.getState();
|
||||||
setTokens(response.access_token, response.refresh_token);
|
setTokens(response.access_token, response.refresh_token);
|
||||||
if (response.user) {
|
if (response.user) {
|
||||||
setUser(response.user);
|
setUser(response.user);
|
||||||
}
|
}
|
||||||
await checkAdminStatus();
|
await checkAdminStatus();
|
||||||
}
|
|
||||||
|
|
||||||
showToast({ type: 'success', message: t('merge.success') });
|
showToast({ type: 'success', message: t('merge.success') });
|
||||||
navigate('/', { replace: true });
|
navigate('/', { replace: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user