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 ? (
<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">
<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>
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-3"></div>
Processing...
</span>
) : type === 'signup' ? (
@ -179,8 +176,8 @@ const AuthForm: React.FC<AuthFormProps> = ({ type, onSubmit, isLoading, error })
</p>
)}
</div>
</div>
</div>
</div >
</div >
);
};

View File

@ -248,10 +248,7 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
>
{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">
<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>
<div className="weekly-spinner -ml-1 mr-2 inline-block"></div>
Syncing...
</>
) : (
@ -290,7 +287,8 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
</div>
))}
</div>
)}
)
}
<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>
@ -376,10 +374,7 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
>
{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">
<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>
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
Connecting...
</>
) : 'Connect'}
@ -429,19 +424,17 @@ const CalendarSettings: React.FC<CalendarSettingsProps> = ({
>
{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">
<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>
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
Verifying...
</>
) : 'Verify'}
</button>
</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' }}>
{isLoading ? (
<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>
</div>
) : lists.length === 0 ? (

View File

@ -27,20 +27,20 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
if (!formData.title.trim()) {
newErrors.title = 'Title is required';
}
// Validate time format if provided
if (formData.startTime && !isValidTime(formData.startTime)) {
newErrors.startTime = 'Invalid time format';
}
if (formData.endTime && !isValidTime(formData.endTime)) {
newErrors.endTime = 'Invalid time format';
}
// Check if start time is before end time if both are provided
if (formData.startTime && formData.endTime) {
const start = parseTime(formData.startTime);
@ -49,7 +49,7 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
newErrors.endTime = 'End time must be after start time';
}
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
@ -70,7 +70,7 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
...formData,
[name]: value
});
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => {
@ -83,7 +83,7 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validateForm()) {
onSubmit(formData);
// Reset form
@ -101,7 +101,7 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
return (
<form onSubmit={handleSubmit} className="task-form bg-white rounded-lg shadow-md p-6 mb-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">Add New Task</h3>
<div className="form-group mb-4">
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
Task Title *
@ -112,9 +112,8 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
name="title"
value={formData.title}
onChange={handleChange}
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'
}`}
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'
}`}
placeholder="What needs to be done?"
required
/>
@ -147,13 +146,12 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
name="startTime"
value={formData.startTime}
onChange={handleChange}
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'
}`}
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 && <p className="mt-1 text-sm text-red-600">{errors.startTime}</p>}
</div>
<div className="form-group">
<label htmlFor="endTime" className="block text-sm font-medium text-gray-700 mb-1">
End Time
@ -164,9 +162,8 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
name="endTime"
value={formData.endTime}
onChange={handleChange}
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'
}`}
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 && <p className="mt-1 text-sm text-red-600">{errors.endTime}</p>}
</div>
@ -193,28 +190,24 @@ const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
</select>
</div>
<button
type="submit"
<button
type="submit"
disabled={isLoading}
className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${
isLoading
? 'bg-blue-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`}
className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${isLoading
? 'bg-blue-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
{isLoading ? (
<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">
<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>
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
Adding Task...
</span>
) : (
'Add Task'
)}
</button>
</form>
</form >
);
};

View File

@ -146,10 +146,7 @@ export default function TaskItem({ task, onToggleComplete, onDelete, onEdit, onT
aria-label="Delete task"
>
{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">
<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>
<div className="weekly-spinner"></div>
) : (
<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" />

View File

@ -3312,10 +3312,7 @@ export default function WeeklyView() {
{/* Week & Year */}
<div className="whitespace-nowrap flex items-center gap-2">
{(isLoading || isSyncing || syncStatus === "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>
<div className="weekly-spinner" title="Syncing..."></div>
)}
<span style={{
fontFamily: profile.cwFontFamily || "Inter",
@ -9688,26 +9685,7 @@ function SettingsSidebar({
>
{isConnectingAppleCal ? (
<>
<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"
>
<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>
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
Connecting...
</>
) : (

View File

@ -32,7 +32,7 @@ const EmailAuthForm: React.FC<EmailAuthFormProps> = ({ type, onSubmit, isLoading
{type === 'signup' ? 'Join us today and organize your week' : 'Welcome back! Please enter your details'}
</p>
</div>
{error && (
<div className="rounded-lg bg-red-50 p-4 border border-red-200 mb-6">
<div className="flex">
@ -47,7 +47,7 @@ const EmailAuthForm: React.FC<EmailAuthFormProps> = ({ type, onSubmit, isLoading
</div>
</div>
)}
<form className="space-y-6" onSubmit={handleSubmit}>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
@ -73,7 +73,7 @@ const EmailAuthForm: React.FC<EmailAuthFormProps> = ({ type, onSubmit, isLoading
/>
</div>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
Password
@ -128,10 +128,7 @@ const EmailAuthForm: React.FC<EmailAuthFormProps> = ({ type, onSubmit, isLoading
>
{isLoading ? (
<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">
<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>
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
Processing...
</span>
) : type === 'signup' ? (