standardize loading indicators with shared spinner asset
Replace ad-hoc Loader2 loading indicators with SpinnerIcon so loading states across content and notifications pages use the same Circular-indeterminate progress indicator. Made-with: Cursor
This commit is contained in:
parent
d33bacf628
commit
1f0046a8ee
|
|
@ -1,5 +1,5 @@
|
|||
import { useMemo, useState, type ChangeEvent } from "react"
|
||||
import { ArrowLeft, ArrowRight, Check, GripVertical, Loader2, Plus, Rocket, Trash2, Upload } from "lucide-react"
|
||||
import { ArrowLeft, ArrowRight, Check, GripVertical, Plus, Rocket, Trash2, Upload } from "lucide-react"
|
||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
import { addQuestionToSet, createLesson, createQuestion } from "../../api/courses.api"
|
||||
|
|
@ -8,6 +8,7 @@ import { PracticeQuestionEditorFields } from "../../components/content-managemen
|
|||
import { Button } from "../../components/ui/button"
|
||||
import { Card } from "../../components/ui/card"
|
||||
import { Input } from "../../components/ui/input"
|
||||
import { SpinnerIcon } from "../../components/ui/spinner-icon"
|
||||
import type { QuestionOption } from "../../types/course.types"
|
||||
|
||||
type Step = 1 | 2 | 3 | 4
|
||||
|
|
@ -356,7 +357,7 @@ export function AddNewLessonPage() {
|
|||
/>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<label className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-grayScale-200 px-3 py-2 text-xs text-grayScale-700 hover:bg-grayScale-50">
|
||||
{uploadingIntroVideo ? <Loader2 className="h-4 w-4 animate-spin" /> : <Upload className="h-4 w-4" />}
|
||||
{uploadingIntroVideo ? <SpinnerIcon className="h-4 w-4" alt="" /> : <Upload className="h-4 w-4" />}
|
||||
{uploadingIntroVideo ? "Uploading..." : "Upload video from computer"}
|
||||
<input
|
||||
type="file"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMemo, useRef, useState, type ChangeEvent } from "react"
|
||||
import { Link, useLocation, useParams, useNavigate } from "react-router-dom"
|
||||
import { ArrowLeft, ArrowRight, ChevronDown, Grid3X3, Check, Plus, Trash2, GripVertical, Edit, Rocket, Loader2, Upload } from "lucide-react"
|
||||
import { ArrowLeft, ArrowRight, ChevronDown, Grid3X3, Check, Plus, Trash2, GripVertical, Edit, Rocket, Upload } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import { Card } from "../../components/ui/card"
|
||||
import { Button } from "../../components/ui/button"
|
||||
|
|
@ -9,6 +9,7 @@ import { PracticeQuestionEditorFields } from "../../components/content-managemen
|
|||
import { createQuestionSet, createQuestion, addQuestionToSet } from "../../api/courses.api"
|
||||
import { uploadVideoFile } from "../../api/files.api"
|
||||
import { Select } from "../../components/ui/select"
|
||||
import { SpinnerIcon } from "../../components/ui/spinner-icon"
|
||||
import type { QuestionOption } from "../../types/course.types"
|
||||
|
||||
type Step = 1 | 2 | 3 | 4 | 5
|
||||
|
|
@ -526,7 +527,7 @@ export function AddNewPracticePage() {
|
|||
className="gap-1.5"
|
||||
>
|
||||
{uploadingIntroVideo ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<SpinnerIcon className="h-4 w-4" alt="" />
|
||||
) : (
|
||||
<Upload className="h-4 w-4" />
|
||||
)}
|
||||
|
|
@ -541,7 +542,7 @@ export function AddNewPracticePage() {
|
|||
>
|
||||
{importingIntroVideoUrl ? (
|
||||
<>
|
||||
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
|
||||
<SpinnerIcon className="mr-1.5 h-4 w-4" alt="" />
|
||||
Importing URL…
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
Languages,
|
||||
Lightbulb,
|
||||
Link2,
|
||||
Loader2,
|
||||
Mic,
|
||||
Plus,
|
||||
Search,
|
||||
|
|
@ -276,7 +275,7 @@ function MediaPreviewCard({
|
|||
) : null}
|
||||
{resolving ? (
|
||||
<div className="flex items-center gap-2 rounded-md border border-grayScale-100 bg-grayScale-50 px-3 py-2 text-xs text-grayScale-500">
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
<SpinnerIcon className="h-3.5 w-3.5" alt="" />
|
||||
Resolving media URL...
|
||||
</div>
|
||||
) : mediaType === "image" ? (
|
||||
|
|
@ -460,8 +459,11 @@ export function HumanLanguagePage() {
|
|||
const saved = sessionStorage.getItem(HUMAN_LANGUAGE_SCROLL_KEY)
|
||||
const targetY = saved ? Number(saved) : 0
|
||||
if (Number.isFinite(targetY) && targetY > 0) {
|
||||
window.requestAnimationFrame(() => window.scrollTo({ top: targetY, behavior: "auto" }))
|
||||
setTimeout(() => window.scrollTo({ top: targetY, behavior: "auto" }), 250)
|
||||
const restoreBehavior = window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
? "auto"
|
||||
: "smooth"
|
||||
window.requestAnimationFrame(() => window.scrollTo({ top: targetY, behavior: restoreBehavior }))
|
||||
setTimeout(() => window.scrollTo({ top: targetY, behavior: restoreBehavior }), 250)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load human-language hierarchy:", error)
|
||||
|
|
@ -1625,7 +1627,7 @@ export function HumanLanguagePage() {
|
|||
disabled={creatingKey === `module-${course.course_id}-${level}`}
|
||||
>
|
||||
{creatingKey === `module-${course.course_id}-${level}` ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
<SpinnerIcon className="h-3.5 w-3.5" alt="" />
|
||||
) : (
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
)}
|
||||
|
|
@ -1674,7 +1676,7 @@ export function HumanLanguagePage() {
|
|||
disabled={creatingKey === `submodule-${course.course_id}-${level}-${parseModuleNumber(module.title) ?? 0}`}
|
||||
>
|
||||
{creatingKey === `submodule-${course.course_id}-${level}-${parseModuleNumber(module.title) ?? 0}` ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
<SpinnerIcon className="h-3.5 w-3.5" alt="" />
|
||||
) : (
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
)}
|
||||
|
|
@ -2152,7 +2154,7 @@ export function HumanLanguagePage() {
|
|||
<div className="p-4">
|
||||
{!practiceFetch || practiceFetch.status === "loading" ? (
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-12 text-sm text-grayScale-500">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-brand-500" aria-hidden />
|
||||
<SpinnerIcon className="h-5 w-5 text-brand-500" alt="" />
|
||||
Loading questions…
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -2245,7 +2247,7 @@ export function HumanLanguagePage() {
|
|||
>
|
||||
{loadingQuestionEditId ===
|
||||
(q.question_id ?? q.id) ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" aria-hidden />
|
||||
<SpinnerIcon className="h-3 w-3" alt="" />
|
||||
) : null}
|
||||
Edit
|
||||
</Button>
|
||||
|
|
@ -2422,7 +2424,7 @@ export function HumanLanguagePage() {
|
|||
</DialogHeader>
|
||||
{loadingPracticeForm ? (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-grayScale-200 bg-grayScale-50 px-3 py-3 text-sm text-grayScale-600">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<SpinnerIcon className="h-4 w-4" alt="" />
|
||||
Loading practice details...
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -2487,7 +2489,7 @@ export function HumanLanguagePage() {
|
|||
/>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<label className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-grayScale-200 px-3 py-2 text-xs text-grayScale-700 hover:bg-grayScale-50">
|
||||
{uploadingPracticeIntroVideo ? <Loader2 className="h-4 w-4 animate-spin" /> : <Video className="h-4 w-4" />}
|
||||
{uploadingPracticeIntroVideo ? <SpinnerIcon className="h-4 w-4" alt="" /> : <Video className="h-4 w-4" />}
|
||||
{uploadingPracticeIntroVideo ? "Uploading..." : "Upload intro video"}
|
||||
<input
|
||||
type="file"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { ArrowLeft, ChevronDown, ChevronRight, Image as ImageIcon, Loader2, Mic, Plus, Trash2, Upload } from "lucide-react"
|
||||
import { ArrowLeft, ChevronDown, ChevronRight, Image as ImageIcon, Mic, Plus, Trash2, Upload } from "lucide-react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card"
|
||||
import { Button } from "../../components/ui/button"
|
||||
import { Input } from "../../components/ui/input"
|
||||
|
|
@ -1926,7 +1926,7 @@ export function SpeakingPage() {
|
|||
className="gap-1.5"
|
||||
>
|
||||
{uploadingIntroVideo ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<SpinnerIcon className="h-4 w-4" alt="" />
|
||||
) : (
|
||||
<Upload className="h-4 w-4" />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { Bell, Loader2, Mail, MailOpen, Megaphone } from "lucide-react"
|
||||
import { Bell, Mail, MailOpen, Megaphone } from "lucide-react"
|
||||
import { Card, CardContent } from "../../components/ui/card"
|
||||
import { Button } from "../../components/ui/button"
|
||||
import { Input } from "../../components/ui/input"
|
||||
import { Textarea } from "../../components/ui/textarea"
|
||||
import { FileUpload } from "../../components/ui/file-upload"
|
||||
import { SpinnerIcon } from "../../components/ui/spinner-icon"
|
||||
import { cn } from "../../lib/utils"
|
||||
import { getTeamMembers } from "../../api/team.api"
|
||||
import type { TeamMember } from "../../types/team.types"
|
||||
|
|
@ -282,7 +283,7 @@ export function CreateNotificationPage() {
|
|||
>
|
||||
{sending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" />
|
||||
<SpinnerIcon className="mr-2 h-3.5 w-3.5" alt="" />
|
||||
Sending…
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -347,7 +348,7 @@ export function CreateNotificationPage() {
|
|||
<div className="max-h-64 space-y-1.5 overflow-y-auto rounded-lg border border-grayScale-100 bg-grayScale-50/60 p-2">
|
||||
{recipientsLoading && (
|
||||
<div className="flex items-center justify-center py-6 text-xs text-grayScale-400">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<SpinnerIcon className="mr-2 h-4 w-4" alt="" />
|
||||
Loading users…
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user