mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 06:51:13 -05:00
feat: Implement button loading states
This commit is contained in:
@@ -107,11 +107,11 @@ export function NovuMigrationUtility(): React.JSX.Element {
|
||||
|
||||
<Button
|
||||
onClick={() => void runMigration()}
|
||||
disabled={isRunning}
|
||||
loading={isRunning}
|
||||
loadingText="Migrating Users..."
|
||||
className="w-full"
|
||||
>
|
||||
{isRunning && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isRunning ? 'Migrating Users...' : 'Start Migration'}
|
||||
Start Migration
|
||||
</Button>
|
||||
|
||||
{isRunning && totalUsers > 0 && (
|
||||
|
||||
@@ -140,6 +140,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
}, [onSubmit]);
|
||||
|
||||
const { user } = useAuth();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Operator state
|
||||
const [selectedOperatorId, setSelectedOperatorId] = useState<string>(initialData?.operator_id || '');
|
||||
@@ -199,6 +200,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
|
||||
|
||||
const handleFormSubmit = async (data: ParkFormData) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
// CRITICAL: Block new photo uploads on edits
|
||||
if (isEditing && data.images?.uploaded) {
|
||||
@@ -277,6 +279,8 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
|
||||
// Re-throw so parent can handle modal closing
|
||||
throw error;
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -647,9 +651,17 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
{isEditing ? 'Update Park' : 'Create Park'}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
loadingText="Saving..."
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
Save Park
|
||||
</Button>
|
||||
|
||||
{onCancel && (
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -158,6 +158,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
const { isModerator } = useUserRole();
|
||||
const { preferences } = useUnitPreferences();
|
||||
const measurementSystem = preferences.measurement_system;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Validate that onSubmit uses submission helpers (dev mode only)
|
||||
useEffect(() => {
|
||||
@@ -263,6 +264,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
|
||||
|
||||
const handleFormSubmit = async (data: RideFormData) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
// CRITICAL: Block new photo uploads on edits
|
||||
if (isEditing && data.images?.uploaded) {
|
||||
@@ -355,6 +357,8 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
|
||||
// Re-throw so parent can handle modal closing
|
||||
throw error;
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1355,13 +1359,15 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
<Button
|
||||
type="submit"
|
||||
className="flex-1"
|
||||
loading={isSubmitting}
|
||||
loadingText="Saving..."
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
{isEditing ? 'Update Ride' : 'Create Ride'}
|
||||
</Button>
|
||||
|
||||
{onCancel && (
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -31,8 +31,11 @@ interface AuthDiagnosticsData {
|
||||
export function AuthDiagnostics() {
|
||||
const [diagnostics, setDiagnostics] = useState<AuthDiagnosticsData | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const runDiagnostics = async () => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
const storageStatus = authStorage.getStorageStatus();
|
||||
const { data: { session }, error: sessionError } = await supabase.auth.getSession();
|
||||
|
||||
@@ -57,6 +60,9 @@ export function AuthDiagnostics() {
|
||||
|
||||
setDiagnostics(results);
|
||||
logger.debug('Auth diagnostics', { results });
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -119,7 +125,7 @@ export function AuthDiagnostics() {
|
||||
⚠️ Running in iframe - storage may be restricted
|
||||
</div>
|
||||
)}
|
||||
<Button onClick={runDiagnostics} variant="outline" size="sm" className="w-full mt-2">
|
||||
<Button onClick={runDiagnostics} loading={isRefreshing} loadingText="Refreshing..." variant="outline" size="sm" className="w-full mt-2">
|
||||
Refresh Diagnostics
|
||||
</Button>
|
||||
</CardContent>
|
||||
|
||||
@@ -25,6 +25,7 @@ export function ConflictResolutionDialog({
|
||||
onResolve,
|
||||
}: ConflictResolutionDialogProps) {
|
||||
const [resolutions, setResolutions] = useState<Record<string, string>>({});
|
||||
const [isApplying, setIsApplying] = useState(false);
|
||||
const { user } = useAuth();
|
||||
|
||||
const handleResolutionChange = (itemId: string, action: string) => {
|
||||
@@ -44,6 +45,7 @@ export function ConflictResolutionDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
setIsApplying(true);
|
||||
const { resolveConflicts } = await import('@/lib/conflictResolutionService');
|
||||
|
||||
try {
|
||||
@@ -67,6 +69,8 @@ export function ConflictResolutionDialog({
|
||||
userId: user.id,
|
||||
metadata: { conflictCount: conflicts.length }
|
||||
});
|
||||
} finally {
|
||||
setIsApplying(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,10 +123,10 @@ export function ConflictResolutionDialog({
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isApplying}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleApply} disabled={!allConflictsResolved}>
|
||||
<Button onClick={handleApply} loading={isApplying} loadingText="Applying..." disabled={!allConflictsResolved}>
|
||||
Apply & Approve
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -182,8 +182,8 @@ export function AddRideCreditDialog({ userId, open, onOpenChange, onSuccess }: A
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={submitting || !selectedRideId}>
|
||||
{submitting ? 'Adding...' : 'Add Credit'}
|
||||
<Button type="submit" loading={submitting} loadingText="Adding..." disabled={!selectedRideId}>
|
||||
Add Credit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -199,9 +199,9 @@ export function ReviewForm({
|
||||
{/* Photo Upload */}
|
||||
|
||||
|
||||
<Button type="submit" disabled={submitting} className="w-full">
|
||||
<Button type="submit" loading={submitting} loadingText="Submitting..." className="w-full">
|
||||
<Send className="w-4 h-4 mr-2" />
|
||||
{submitting ? 'Submitting...' : 'Submit Review'}
|
||||
Submit Review
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
|
||||
@@ -363,8 +363,7 @@ export function EmailChangeDialog({ open, onOpenChange, currentEmail, userId }:
|
||||
<Button type="button" variant="outline" onClick={handleClose} disabled={loading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading || !captchaToken}>
|
||||
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
<Button type="submit" loading={loading} loadingText="Changing Email..." disabled={!captchaToken}>
|
||||
Change Email
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -558,8 +558,8 @@ export function LocationTab() {
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={saving}>
|
||||
{saving ? 'Saving...' : 'Save Settings'}
|
||||
<Button type="submit" loading={saving} loadingText="Saving...">
|
||||
Save Settings
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -493,8 +493,7 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={verifyMFAAndUpdate} disabled={loading || totpCode.length !== 6}>
|
||||
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
<Button onClick={verifyMFAAndUpdate} loading={loading} loadingText="Verifying..." disabled={totpCode.length !== 6}>
|
||||
Verify & Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -450,8 +450,8 @@ export function PrivacyTab() {
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Saving...' : 'Save Privacy Settings'}
|
||||
<Button type="submit" loading={loading} loadingText="Saving...">
|
||||
Save Privacy Settings
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user