mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
fix: accessibility, query cache clear, post-merge navigation
- Add aria-hidden="true" to all decorative SVGs (TelegramIcon, EmailIcon in MergeAccounts + ConnectedAccounts, all 4 OAuthProviderIcon SVGs) - Add role="radiogroup" with aria-label to subscription choice container - Clear react-query cache after merge to prevent stale data - Wrap checkAdminStatus() in try-catch to prevent throw after token set - Navigate to /profile/accounts after merge instead of / - Import useQueryClient for cache management
This commit is contained in:
@@ -10,7 +10,7 @@ export default function OAuthProviderIcon({
|
||||
switch (provider) {
|
||||
case 'google':
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24">
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
|
||||
fill="#4285F4"
|
||||
@@ -32,7 +32,7 @@ export default function OAuthProviderIcon({
|
||||
|
||||
case 'yandex':
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24">
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12z"
|
||||
fill="#FC3F1D"
|
||||
@@ -46,7 +46,7 @@ export default function OAuthProviderIcon({
|
||||
|
||||
case 'discord':
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24">
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515.074.074 0 00-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 00-5.487 0 12.64 12.64 0 00-.617-1.25.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028 14.09 14.09 0 001.226-1.994.076.076 0 00-.041-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.292.074.074 0 01.077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 01.078.01c.12.098.246.198.373.292a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.077.077 0 00-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 00.084.028 19.839 19.839 0 006.002-3.03.077.077 0 00.032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 00-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"
|
||||
fill="#5865F2"
|
||||
@@ -56,7 +56,7 @@ export default function OAuthProviderIcon({
|
||||
|
||||
case 'vk':
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24">
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2z"
|
||||
fill="#0077FF"
|
||||
|
||||
@@ -17,7 +17,7 @@ const isOAuthProvider = (provider: string): boolean => OAUTH_PROVIDERS.includes(
|
||||
// Icons for providers not covered by OAuthProviderIcon
|
||||
function TelegramIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24">
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.2-.08-.06-.19-.04-.28-.02-.12.03-2.02 1.28-5.69 3.77-.54.37-1.03.55-1.47.54-.48-.01-1.41-.27-2.1-.5-.85-.28-1.52-.43-1.46-.91.03-.25.38-.51 1.05-.78 4.12-1.79 6.87-2.97 8.26-3.54 3.93-1.62 4.75-1.9 5.28-1.91.12 0 .37.03.54.17.14.12.18.28.2.46-.01.06.01.24 0 .37z"
|
||||
fill="#29B6F6"
|
||||
@@ -34,6 +34,7 @@ function EmailIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { motion } from 'framer-motion';
|
||||
import { authApi } from '../api/auth';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
@@ -74,7 +74,7 @@ function CheckCircleIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
||||
|
||||
function TelegramIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24">
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.2-.08-.06-.19-.04-.28-.02-.12.03-2.02 1.28-5.69 3.77-.54.37-1.03.55-1.47.54-.48-.01-1.41-.27-2.1-.5-.85-.28-1.52-.43-1.46-.91.03-.25.38-.51 1.05-.78 4.12-1.79 6.87-2.97 8.26-3.54 3.93-1.62 4.75-1.9 5.28-1.91.12 0 .37.03.54.17.14.12.18.28.2.46-.01.06.01.24 0 .37z"
|
||||
fill="#29B6F6"
|
||||
@@ -91,6 +91,7 @@ function EmailIcon({ className = 'h-5 w-5' }: { className?: string }) {
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -354,6 +355,7 @@ export default function MergeAccounts() {
|
||||
const { mergeToken } = useParams<{ mergeToken: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [selectedUserId, setSelectedUserId] = useState<number | null>(null);
|
||||
const [expiresIn, setExpiresIn] = useState(0);
|
||||
@@ -435,10 +437,15 @@ export default function MergeAccounts() {
|
||||
if (response.user) {
|
||||
setUser(response.user);
|
||||
}
|
||||
await checkAdminStatus();
|
||||
try {
|
||||
await checkAdminStatus();
|
||||
} catch {
|
||||
// Non-critical — admin status will be checked on next navigation
|
||||
}
|
||||
|
||||
queryClient.clear();
|
||||
showToast({ type: 'success', message: t('merge.success') });
|
||||
navigate('/', { replace: true });
|
||||
navigate('/profile/accounts', { replace: true });
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
@@ -512,27 +519,31 @@ export default function MergeAccounts() {
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Primary account card */}
|
||||
<motion.div variants={staggerItem}>
|
||||
<AccountCard
|
||||
account={data.primary}
|
||||
label={t('merge.currentAccount')}
|
||||
isSelected={selectedUserId === data.primary.id}
|
||||
onSelect={() => setSelectedUserId(data.primary.id)}
|
||||
showRadio={!!bothHaveSubscriptions}
|
||||
/>
|
||||
</motion.div>
|
||||
{/* Account cards */}
|
||||
<div
|
||||
role={bothHaveSubscriptions ? 'radiogroup' : undefined}
|
||||
aria-label={bothHaveSubscriptions ? t('merge.chooseSubscription') : undefined}
|
||||
>
|
||||
<motion.div variants={staggerItem}>
|
||||
<AccountCard
|
||||
account={data.primary}
|
||||
label={t('merge.currentAccount')}
|
||||
isSelected={selectedUserId === data.primary.id}
|
||||
onSelect={() => setSelectedUserId(data.primary.id)}
|
||||
showRadio={!!bothHaveSubscriptions}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Secondary account card */}
|
||||
<motion.div variants={staggerItem}>
|
||||
<AccountCard
|
||||
account={data.secondary}
|
||||
label={t('merge.foundAccount')}
|
||||
isSelected={selectedUserId === data.secondary.id}
|
||||
onSelect={() => setSelectedUserId(data.secondary.id)}
|
||||
showRadio={!!bothHaveSubscriptions}
|
||||
/>
|
||||
</motion.div>
|
||||
<motion.div variants={staggerItem} className="mt-6">
|
||||
<AccountCard
|
||||
account={data.secondary}
|
||||
label={t('merge.foundAccount')}
|
||||
isSelected={selectedUserId === data.secondary.id}
|
||||
onSelect={() => setSelectedUserId(data.secondary.id)}
|
||||
showRadio={!!bothHaveSubscriptions}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* After merge summary */}
|
||||
<motion.div variants={staggerItem}>
|
||||
|
||||
Reference in New Issue
Block a user