Part of #145. Fourteen files whose user-facing strings are generic enough to belong in `common` — loading and empty states, the confirmation modal, the shared input modals (text/date/user/attachment viewer), the mobile nav bar, the autocomplete input, the error screen and the file-upload error paths. Extends the namespace that already exists, so `src/i18n/config.js` is untouched and this cannot collide with any other extraction PR over the `ns:` array. 28 keys added to `public/locales/en/common.json`. English only — no translations, no behaviour change. Every t() value is checked against this branch's base: the string must appear character-for-character in the code it replaces (36 call sites). One value is matched loosely and worth naming: `errorScreen.hideDetails`. The base renders `{showDetails ? 'Hide' : 'Show'} error details`, so neither full phrase exists contiguously in the source — only one branch of the ternary can. Both keys hold exactly what each branch renders. The sentence is kept whole rather than split around the ternary, because a split sentence cannot be reordered by a translator.
109 lines
2.3 KiB
JavaScript
109 lines
2.3 KiB
JavaScript
import { Box, CircularProgress, Typography } from '@mui/joy'
|
|
import { styled } from '@mui/joy/styles'
|
|
|
|
// Styled components with keyframe animations
|
|
const LoadingContainer = styled(Box)({
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
backdropFilter: 'blur(8px)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
zIndex: 9999,
|
|
animation: 'fadeIn 0.3s ease-out',
|
|
'@keyframes fadeIn': {
|
|
from: {
|
|
opacity: 0,
|
|
},
|
|
to: {
|
|
opacity: 1,
|
|
},
|
|
},
|
|
})
|
|
|
|
const LoadingContent = styled(Box)({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: '24px',
|
|
animation: 'slideUp 0.5s ease-out 0.2s both',
|
|
'@keyframes slideUp': {
|
|
from: {
|
|
opacity: 0,
|
|
transform: 'translateY(20px)',
|
|
},
|
|
to: {
|
|
opacity: 1,
|
|
transform: 'translateY(0)',
|
|
},
|
|
},
|
|
})
|
|
|
|
const PulsingText = styled(Typography)({
|
|
animation: 'pulse 2s ease-in-out infinite',
|
|
'@keyframes pulse': {
|
|
'0%, 100%': {
|
|
opacity: 1,
|
|
},
|
|
'50%': {
|
|
opacity: 0.5,
|
|
},
|
|
},
|
|
})
|
|
|
|
const LogoContainer = styled(Box)({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
marginBottom: '24px',
|
|
})
|
|
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
const LoadingScreen = ({
|
|
message = null,
|
|
showLogo = true,
|
|
size = 'lg',
|
|
}) => {
|
|
const { t } = useTranslation('common')
|
|
return (
|
|
<LoadingContainer>
|
|
<LoadingContent>
|
|
{showLogo && (
|
|
<LogoContainer>
|
|
<Typography
|
|
level='h1'
|
|
sx={{
|
|
fontSize: { xs: '2rem', sm: '2.5rem' },
|
|
fontWeight: 700,
|
|
color: 'primary.500',
|
|
mb: 1,
|
|
}}
|
|
>
|
|
{t('done')}
|
|
<span style={{ color: '#06b6d4' }}>tick</span>
|
|
</Typography>
|
|
</LogoContainer>
|
|
)}
|
|
|
|
<CircularProgress
|
|
size={size}
|
|
sx={{
|
|
'--CircularProgress-size': size === 'lg' ? '60px' : '40px',
|
|
mb: 2,
|
|
}}
|
|
/>
|
|
|
|
<PulsingText level='body-md'>{message ?? t('loading')}</PulsingText>
|
|
</LoadingContent>
|
|
</LoadingContainer>
|
|
)
|
|
}
|
|
|
|
export default LoadingScreen
|