fix: implement custom CSS sync spinner and update all components

- Replaced broken animate-spin Tailwind class with custom weekly-spinner CSS

- Standardized loading indicators across Auth, Tasks, and Calendar settings

- Fixed JSX syntax errors in several components
This commit is contained in:
mARTin 2026-02-23 22:04:16 +01:00
parent eb5065131a
commit 83d0d0b920
8 changed files with 69 additions and 88 deletions

View File

@ -2939,4 +2939,30 @@ h3 {
/* Weekly App Spinner */
@keyframes weekly-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.weekly-spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(0, 0, 0, 0.1);
border-top-color: #3b82f6;
border-radius: 50%;
animation: weekly-spin 0.8s linear infinite;
display: inline-block;
vertical-align: middle;
}
.weekly-spinner-white {
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
}
.dark-mode .weekly-spinner {
border: 2px solid rgba(255, 255, 255, 0.1);
border-top-color: #60a5fa;
}

View File

@ -141,10 +141,7 @@ const AuthForm: React.FC<AuthFormProps> = ({ type, onSubmit, isLoading, error })
> >
{isLoading ? ( {isLoading ? (
<span className="flex items-center"> <span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <div className="weekly-spinner weekly-spinner-white -ml-1 mr-3"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing... Processing...
</span> </span>
) : type === 'signup' ? ( ) : type === 'signup' ? (
@ -179,8 +176,8 @@ const AuthForm: React.FC<AuthFormProps> = ({ type, onSubmit, isLoading, error })
</p> </p>
)} )}
</div> </div>
</div> </div >
</div> </div >
); );
}; };

View File

@ -248,10 +248,7 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
> >
{isSyncing[connection.id] ? ( {isSyncing[connection.id] ? (
<> <>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"> <div className="weekly-spinner -ml-1 mr-2 inline-block"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Syncing... Syncing...
</> </>
) : ( ) : (
@ -290,7 +287,8 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
</div> </div>
))} ))}
</div> </div>
)} )
}
<div className="connect-options mt-8 pt-6 border-t border-gray-200" role="region" aria-label="Connect more calendars"> <div className="connect-options mt-8 pt-6 border-t border-gray-200" role="region" aria-label="Connect more calendars">
<h3 className="text-xl font-semibold text-gray-800 mb-4" role="heading" aria-level={3}>Add More Calendars</h3> <h3 className="text-xl font-semibold text-gray-800 mb-4" role="heading" aria-level={3}>Add More Calendars</h3>
@ -376,10 +374,7 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
> >
{isConnectingApple ? ( {isConnectingApple ? (
<> <>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Connecting... Connecting...
</> </>
) : 'Connect'} ) : 'Connect'}
@ -429,19 +424,17 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
> >
{isConnectingApple ? ( {isConnectingApple ? (
<> <>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Verifying... Verifying...
</> </>
) : 'Verify'} ) : 'Verify'}
</button> </button>
</div> </div>
</> </>
)} )
</div> }
</div> </div >
</div >
) )
} }
</div > </div >

View File

@ -82,7 +82,7 @@ export const ImportListModal: React.FC<ImportListModalProps> = ({
<div style={{ flex: 1, overflowY: 'auto', minHeight: '200px' }}> <div style={{ flex: 1, overflowY: 'auto', minHeight: '200px' }}>
{isLoading ? ( {isLoading ? (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}> <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
<Loader className="animate-spin" /> <div className="weekly-spinner"></div>
<span style={{ marginLeft: '8px' }}>Loading lists...</span> <span style={{ marginLeft: '8px' }}>Loading lists...</span>
</div> </div>
) : lists.length === 0 ? ( ) : lists.length === 0 ? (

View File

@ -112,9 +112,8 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
name="title" name="title"
value={formData.title} value={formData.title}
onChange={handleChange} onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.title ? 'border-red-500' : 'border-gray-300'
errors.title ? 'border-red-500' : 'border-gray-300' }`}
}`}
placeholder="What needs to be done?" placeholder="What needs to be done?"
required required
/> />
@ -147,9 +146,8 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
name="startTime" name="startTime"
value={formData.startTime} value={formData.startTime}
onChange={handleChange} onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.startTime ? 'border-red-500' : 'border-gray-300'
errors.startTime ? 'border-red-500' : 'border-gray-300' }`}
}`}
/> />
{errors.startTime && <p className="mt-1 text-sm text-red-600">{errors.startTime}</p>} {errors.startTime && <p className="mt-1 text-sm text-red-600">{errors.startTime}</p>}
</div> </div>
@ -164,9 +162,8 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
name="endTime" name="endTime"
value={formData.endTime} value={formData.endTime}
onChange={handleChange} onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.endTime ? 'border-red-500' : 'border-gray-300'
errors.endTime ? 'border-red-500' : 'border-gray-300' }`}
}`}
/> />
{errors.endTime && <p className="mt-1 text-sm text-red-600">{errors.endTime}</p>} {errors.endTime && <p className="mt-1 text-sm text-red-600">{errors.endTime}</p>}
</div> </div>
@ -196,25 +193,21 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
<button <button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${ className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${isLoading
isLoading ? 'bg-blue-400 cursor-not-allowed'
? 'bg-blue-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'
: 'bg-blue-600 hover:bg-blue-700' }`}
}`}
> >
{isLoading ? ( {isLoading ? (
<span className="flex items-center justify-center"> <span className="flex items-center justify-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Adding Task... Adding Task...
</span> </span>
) : ( ) : (
'Add Task' 'Add Task'
)} )}
</button> </button>
</form> </form >
); );
}; };

View File

@ -146,10 +146,7 @@ export default function TaskItem({ task, onToggleComplete, onDelete, onEdit, onT
aria-label="Delete task" aria-label="Delete task"
> >
{isDeleting ? ( {isDeleting ? (
<svg className="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <div className="weekly-spinner"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : ( ) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" /> <path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />

View File

@ -3312,10 +3312,7 @@ export default function WeeklyView() {
{/* Week & Year */} {/* Week & Year */}
<div className="whitespace-nowrap flex items-center gap-2"> <div className="whitespace-nowrap flex items-center gap-2">
{(isLoading || isSyncing || syncStatus === "syncing") && ( {(isLoading || isSyncing || syncStatus === "syncing") && (
<div <div className="weekly-spinner" title="Syncing..."></div>
className="animate-spin rounded-full h-4 w-4 border-2 border-gray-300 border-t-blue-600 mr-2"
title="Syncing..."
></div>
)} )}
<span style={{ <span style={{
fontFamily: profile.cwFontFamily || "Inter", fontFamily: profile.cwFontFamily || "Inter",
@ -9688,26 +9685,7 @@ function SettingsSidebar({
> >
{isConnectingAppleCal ? ( {isConnectingAppleCal ? (
<> <>
<svg <div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Connecting... Connecting...
</> </>
) : ( ) : (

View File

@ -128,10 +128,7 @@ const EmailAuthForm: React.FC<EmailAuthFormProps> = ({ type, onSubmit, isLoading
> >
{isLoading ? ( {isLoading ? (
<span className="flex items-center"> <span className="flex items-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing... Processing...
</span> </span>
) : type === 'signup' ? ( ) : type === 'signup' ? (