feat: replace Loader2 with Spinner component for consistent loading indicators

This commit is contained in:
Anish Sarkar 2026-01-25 15:23:45 +05:30
parent 9215118bab
commit 2d17d1a1b6
34 changed files with 113 additions and 105 deletions

View file

@ -1,6 +1,6 @@
"use client";
import { ChevronDown, ChevronUp, FileX, Loader2, Plus } from "lucide-react";
import { ChevronDown, ChevronUp, FileX, Plus } from "lucide-react";
import { motion } from "motion/react";
import { useParams } from "next/navigation";
import { useTranslations } from "next-intl";
@ -8,6 +8,7 @@ import React from "react";
import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup";
import { DocumentViewer } from "@/components/document-viewer";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Checkbox } from "@/components/ui/checkbox";
import {
Table,
@ -114,7 +115,7 @@ export function DocumentsTableShell({
{loading ? (
<div className="flex h-[400px] w-full items-center justify-center">
<div className="flex flex-col items-center gap-2">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<Spinner size="lg" className="text-primary" />
<p className="text-sm text-muted-foreground">{t("loading")}</p>
</div>
</div>

View file

@ -1,8 +1,7 @@
"use client";
import { useQueryClient } from "@tanstack/react-query";
import { useAtom } from "jotai";
import { AlertCircle, ArrowLeft, FileText, Loader2, Save } from "lucide-react";
import { AlertCircle, ArrowLeft, FileText, Save } from "lucide-react";
import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
@ -21,6 +20,7 @@ import {
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import { notesApiService } from "@/lib/apis/notes-api.service";
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
@ -78,7 +78,6 @@ function extractTitleFromBlockNote(blocknoteDocument: BlockNoteDocument): string
export default function EditorPage() {
const params = useParams();
const router = useRouter();
const queryClient = useQueryClient();
const documentId = params.documentId as string;
const searchSpaceId = Number(params.search_space_id);
const isNewNote = documentId === "new";
@ -349,8 +348,8 @@ export default function EditorPage() {
<div className="flex items-center justify-center min-h-[400px] p-6">
<Card className="w-full max-w-md">
<CardContent className="flex flex-col items-center justify-center py-12">
<Loader2 className="h-12 w-12 text-primary animate-spin mb-4" />
<p className="text-muted-foreground">Loading editor...</p>
<Spinner size="xl" className="text-primary mb-4" />
<p className="text-muted-foreground">Loading editor</p>
</CardContent>
</Card>
</div>
@ -437,7 +436,7 @@ export default function EditorPage() {
>
{saving ? (
<>
<Loader2 className="h-3.5 w-3.5 md:h-4 md:w-4 animate-spin" />
<Spinner size="sm" className="h-3.5 w-3.5 md:h-4 md:w-4" />
<span className="text-xs md:text-sm">{isNewNote ? "Creating" : "Saving"}</span>
</>
) : (

View file

@ -1,7 +1,6 @@
"use client";
import { useAtomValue } from "jotai";
import { Loader2 } from "lucide-react";
import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
@ -17,6 +16,7 @@ import {
import { Logo } from "@/components/Logo";
import { LLMConfigForm, type LLMConfigFormData } from "@/components/shared/llm-config-form";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
export default function OnboardPage() {
@ -156,7 +156,7 @@ export default function OnboardPage() {
<div className="relative">
<div className="absolute inset-0 blur-3xl bg-gradient-to-r from-violet-500/20 to-cyan-500/20 rounded-full" />
<div className="relative flex items-center justify-center w-24 h-24 mx-auto rounded-2xl bg-gradient-to-br from-violet-500 to-purple-600 shadow-2xl shadow-violet-500/25">
<Loader2 className="h-12 w-12 text-white animate-spin" />
<Spinner size="xl" className="text-white" />
</div>
</div>
<div className="space-y-2">

View file

@ -14,7 +14,6 @@ import {
Hash,
Link2,
LinkIcon,
Loader2,
Logs,
type LucideIcon,
MessageCircle,
@ -106,6 +105,7 @@ import {
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { Spinner } from "@/components/ui/spinner";
import type {
CreateInviteRequest,
DeleteInviteRequest,
@ -321,7 +321,7 @@ export default function TeamManagementPage() {
animate={{ opacity: 1, scale: 1 }}
className="flex flex-col items-center gap-4"
>
<Loader2 className="h-10 w-10 text-primary animate-spin" />
<Spinner size="lg" className="text-primary" />
<p className="text-muted-foreground">Loading team data...</p>
</motion.div>
</div>
@ -571,7 +571,7 @@ function MembersTab({
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 text-primary animate-spin" />
<Spinner size="md" className="text-primary" />
</div>
);
}
@ -911,7 +911,7 @@ function RolesTab({
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 text-primary animate-spin" />
<Spinner size="md" className="text-primary" />
</div>
);
}
@ -1068,7 +1068,7 @@ function InvitesTab({
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 text-primary animate-spin" />
<Spinner size="md" className="text-primary" />
</div>
);
}
@ -1446,7 +1446,7 @@ function CreateInviteDialog({
<Button onClick={handleCreate} disabled={creating}>
{creating ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
<Spinner size="sm" className="mr-2" />
Creating
</>
) : (
@ -1699,7 +1699,7 @@ function CreateRoleDialog({
<Button onClick={handleCreate} disabled={creating || !name.trim()}>
{creating ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
<Spinner size="sm" className="mr-2" />
Creating
</>
) : (

View file

@ -1,7 +1,7 @@
"use client";
import { useAtomValue } from "jotai";
import { Loader2, Menu, User } from "lucide-react";
import { Menu, User } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
@ -11,6 +11,7 @@ import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/spinner";
interface ProfileContentProps {
onMenuClick: () => void;
@ -129,7 +130,7 @@ export function ProfileContent({ onMenuClick }: ProfileContentProps) {
>
{isUserLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-6">
@ -166,7 +167,7 @@ export function ProfileContent({ onMenuClick }: ProfileContentProps) {
<div className="flex justify-end">
<Button type="submit" disabled={isPending || !hasChanges}>
{isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{isPending && <Spinner size="sm" className="mr-2" />}
{t("profile_save")}
</Button>
</div>

View file

@ -6,8 +6,6 @@ import {
AlertCircle,
ArrowRight,
CheckCircle2,
Clock,
Loader2,
LogIn,
Shield,
Sparkles,
@ -30,6 +28,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import type { AcceptInviteResponse } from "@/contracts/types/invites.types";
import { invitesApiService } from "@/lib/apis/invites-api.service";
import { getBearerToken } from "@/lib/auth-utils";
@ -164,7 +163,7 @@ export default function InviteAcceptPage() {
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
>
<Loader2 className="h-12 w-12 text-primary" />
<Spinner size="xl" className="text-primary" />
</motion.div>
<p className="mt-4 text-muted-foreground">Loading invite details...</p>
</CardContent>
@ -353,7 +352,7 @@ export default function InviteAcceptPage() {
<Button className="flex-1 gap-2" onClick={handleAccept} disabled={accepting}>
{accepting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
<Spinner size="sm" />
Accepting...
</>
) : (

View file

@ -7,12 +7,13 @@ import {
useAssistantApi,
useAssistantState,
} from "@assistant-ui/react";
import { FileText, Loader2, Paperclip, PlusIcon, Upload, XIcon } from "lucide-react";
import { FileText, Paperclip, PlusIcon, Upload, XIcon } from "lucide-react";
import Image from "next/image";
import { type FC, type PropsWithChildren, useEffect, useRef, useState } from "react";
import { useShallow } from "zustand/shallow";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Spinner } from "@/components/ui/spinner";
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import {
DropdownMenu,
@ -135,7 +136,7 @@ const AttachmentThumb: FC = () => {
if (isProcessing) {
return (
<div className="flex h-full w-full items-center justify-center bg-muted">
<Loader2 className="size-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
);
}
@ -213,7 +214,7 @@ const AttachmentUI: FC = () => {
>
{isProcessing ? (
<span className="flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Processing...
</span>
) : (

View file

@ -1,8 +1,8 @@
"use client";
import { Loader2 } from "lucide-react";
import type { FC } from "react";
import { cn } from "@/lib/utils";
import { Spinner } from "@/components/ui/spinner";
interface ChatSessionStatusProps {
isAiResponding: boolean;
@ -43,7 +43,7 @@ export const ChatSessionStatus: FC<ChatSessionStatusProps> = ({
className
)}
>
<Loader2 className="size-3.5 animate-spin" />
<Spinner size="xs" />
<span>Currently responding to {displayName}</span>
</div>
);

View file

@ -1,13 +1,14 @@
"use client";
import { useAtomValue } from "jotai";
import { Cable, Loader2 } from "lucide-react";
import { Cable } from "lucide-react";
import { useSearchParams } from "next/navigation";
import type { FC } from "react";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { Spinner } from "@/components/ui/spinner";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { useConnectorsElectric } from "@/hooks/use-connectors-electric";
@ -174,7 +175,7 @@ export const ConnectorIndicator: FC = () => {
onClick={() => handleOpenChange(true)}
>
{isLoading ? (
<Loader2 className="size-4 animate-spin" />
<Spinner size="sm" />
) : (
<>
<Cable className="size-4 stroke-[1.5px]" />

View file

@ -1,9 +1,10 @@
"use client";
import { IconBrandYoutube } from "@tabler/icons-react";
import { FileText, Loader2 } from "lucide-react";
import { FileText } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { cn } from "@/lib/utils";
@ -111,7 +112,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
</div>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : isConnected ? (
@ -151,7 +152,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
disabled={isConnecting || !isEnabled}
>
{isConnecting ? (
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
) : !isEnabled ? (
"Unavailable"
) : isConnected ? (

View file

@ -1,8 +1,9 @@
"use client";
import { ArrowLeft, Loader2 } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { type FC, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import type { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { getConnectorTypeDisplay } from "@/lib/connectors/utils";
@ -139,7 +140,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Connecting
</>
) : connectorType === "MCP_CONNECTOR" ? (

View file

@ -1,8 +1,9 @@
"use client";
import { ArrowLeft, Info, Loader2, RefreshCw, Trash2 } from "lucide-react";
import { ArrowLeft, Info, RefreshCw, Trash2 } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { cn } from "@/lib/utils";
@ -311,7 +312,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
>
{isDisconnecting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Disconnecting
</>
) : (
@ -347,7 +348,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
>
{isSaving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Saving
</>
) : (

View file

@ -1,9 +1,10 @@
"use client";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { ArrowLeft, Check, Info } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { getConnectorTypeDisplay } from "@/lib/connectors/utils";
import { cn } from "@/lib/utils";
@ -216,7 +217,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
>
{isStartingIndexing ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Starting...
</>
) : (

View file

@ -1,11 +1,12 @@
"use client";
import { ArrowRight, Cable, Loader2 } from "lucide-react";
import { ArrowRight, Cable } from "lucide-react";
import { useRouter } from "next/navigation";
import type { FC } from "react";
import { useState } from "react";
import { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import { TabsContent } from "@/components/ui/tabs";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
@ -209,7 +210,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
<p className="text-[14px] font-semibold leading-tight truncate">{title}</p>
{isAnyIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : (
@ -270,7 +271,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
</div>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : !isMCPConnector ? (

View file

@ -1,9 +1,10 @@
"use client";
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
import { ArrowLeft, Loader2, Plus, Server } from "lucide-react";
import { ArrowLeft, Plus, Server } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
@ -143,7 +144,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
>
<div className="flex h-5 w-5 items-center justify-center rounded-md bg-primary/10 shrink-0">
{isConnecting ? (
<Loader2 className="size-3 animate-spin text-primary" />
<Spinner size="xs" className="text-primary" />
) : (
<Plus className="size-3 text-primary" />
)}
@ -207,7 +208,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
</p>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
<Spinner size="xs" />
Syncing
</p>
) : (

View file

@ -2,13 +2,14 @@
import { TagInput, type Tag as TagType } from "emblor";
import { useAtom } from "jotai";
import { ArrowLeft, Loader2 } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { type FC, useState } from "react";
import { toast } from "sonner";
import { createDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Label } from "@/components/ui/label";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
@ -222,7 +223,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
{t("processing")}
</>
) : (

View file

@ -6,6 +6,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { CommentActionsProps } from "./types";
@ -23,7 +24,7 @@ export function CommentActions({ canEdit, canDelete, onEdit, onDelete }: Comment
size="icon"
className="size-7 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity"
>
<MoreHorizontal className="size-4" />
<MoreHorizontal className="size-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
@ -33,8 +34,9 @@ export function CommentActions({ canEdit, canDelete, onEdit, onDelete }: Comment
Edit
</DropdownMenuItem>
)}
{canEdit && canDelete && <DropdownMenuSeparator />}
{canDelete && (
<DropdownMenuItem onClick={onDelete} className="text-destructive">
<DropdownMenuItem onClick={onDelete} className="text-destructive focus:text-destructive">
<Trash2 className="mr-2 size-4" />
Delete
</DropdownMenuItem>

View file

@ -1,7 +1,7 @@
"use client";
import { Loader2 } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Spinner } from "@/components/ui/spinner";
import { MemberMentionItem } from "./member-mention-item";
import type { MemberMentionPickerProps } from "./types";
@ -24,7 +24,7 @@ export function MemberMentionPicker({
if (isLoading) {
return (
<div className="flex items-center justify-center py-6">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
);
}

View file

@ -10,12 +10,12 @@ import {
FolderOpen,
HardDrive,
Image,
Loader2,
Presentation,
} from "lucide-react";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Spinner } from "@/components/ui/spinner";
import { useComposioDriveFolders } from "@/hooks/use-composio-drive-folders";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cn } from "@/lib/utils";
@ -253,7 +253,7 @@ export function ComposioDriveFolderTree({
aria-label={isExpanded ? `Collapse ${item.name}` : `Expand ${item.name}`}
>
{isLoading ? (
<Loader2 className="h-2.5 w-2.5 sm:h-3 sm:w-3 animate-spin" />
<Spinner size="xs" className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
) : isExpanded ? (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4" />
) : (
@ -344,7 +344,7 @@ export function ComposioDriveFolderTree({
{isLoadingRoot && (
<div className="flex items-center justify-center py-4 sm:py-8">
<Loader2 className="h-4 w-4 sm:h-6 sm:w-6 animate-spin text-muted-foreground" />
<Spinner size="sm" className="sm:h-6 sm:w-6 text-muted-foreground" />
</div>
)}

View file

@ -10,12 +10,12 @@ import {
FolderOpen,
HardDrive,
Image,
Loader2,
Presentation,
} from "lucide-react";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Spinner } from "@/components/ui/spinner";
import { useGoogleDriveFolders } from "@/hooks/use-google-drive-folders";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cn } from "@/lib/utils";
@ -253,7 +253,7 @@ export function GoogleDriveFolderTree({
aria-label={isExpanded ? `Collapse ${item.name}` : `Expand ${item.name}`}
>
{isLoading ? (
<Loader2 className="h-2.5 w-2.5 sm:h-3 sm:w-3 animate-spin" />
<Spinner size="xs" className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
) : isExpanded ? (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4" />
) : (
@ -344,7 +344,7 @@ export function GoogleDriveFolderTree({
{isLoadingRoot && (
<div className="flex items-center justify-center py-4 sm:py-8">
<Loader2 className="h-4 w-4 sm:h-6 sm:w-6 animate-spin text-muted-foreground" />
<Spinner size="sm" className="sm:h-6 sm:w-6 text-muted-foreground" />
</div>
)}

View file

@ -2,7 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { Loader2, Plus, Search } from "lucide-react";
import { Plus, Search } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { useForm } from "react-hook-form";
@ -26,6 +26,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import { trackSearchSpaceCreated } from "@/lib/posthog/events";
const formSchema = z.object({
@ -145,7 +146,7 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac
<Button type="submit" disabled={isSubmitting} className="w-full sm:w-auto h-9 sm:h-10 text-sm">
{isSubmitting ? (
<>
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-1.5" />
{t("creating")}
</>
) : (

View file

@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import {
ArchiveIcon,
Loader2,
MessageCircleMore,
MoreHorizontal,
RotateCcwIcon,
@ -30,6 +29,7 @@ import {
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import {
deleteThread,
@ -304,7 +304,7 @@ export function AllPrivateChatsSidebar({
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : error ? (
<div className="text-center py-8 text-sm text-destructive">
@ -365,7 +365,7 @@ export function AllPrivateChatsSidebar({
disabled={isBusy}
>
{isDeleting ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Spinner size="xs" />
) : (
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
)}

View file

@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import {
ArchiveIcon,
Loader2,
MessageCircleMore,
MoreHorizontal,
RotateCcwIcon,
@ -30,6 +29,7 @@ import {
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import {
deleteThread,
@ -304,7 +304,7 @@ export function AllSharedChatsSidebar({
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : error ? (
<div className="text-center py-8 text-sm text-destructive">
@ -365,7 +365,7 @@ export function AllSharedChatsSidebar({
disabled={isBusy}
>
{isDeleting ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Spinner size="xs" />
) : (
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
)}

View file

@ -1,6 +1,5 @@
"use client";
import { Check } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { NavItem } from "../../types/layout.types";
@ -40,9 +39,6 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
{...joyrideAttr}
>
<Icon className="h-4 w-4" />
{item.isActive && (
<Check className="absolute bottom-0.5 right-0.5 h-3 w-3 text-primary" />
)}
{item.badge && (
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center min-w-[14px] h-[14px] px-0.5 rounded-full bg-red-500 text-white text-[9px] font-medium">
{item.badge}
@ -73,9 +69,6 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
>
<Icon className="h-4 w-4 shrink-0" />
<span className="flex-1 truncate">{item.title}</span>
{item.isActive && (
<Check className="h-4 w-4 shrink-0 text-primary" />
)}
{item.badge && (
<span className="inline-flex items-center justify-center min-w-4 h-4 px-1 rounded-full bg-red-500 text-white text-[10px] font-medium">
{item.badge}

View file

@ -8,7 +8,6 @@ import {
Cloud,
Edit3,
Globe,
Loader2,
Plus,
Settings2,
Sparkles,
@ -36,6 +35,7 @@ import {
CommandSeparator,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Spinner } from "@/components/ui/spinner";
import type {
GlobalNewLLMConfig,
NewLLMConfigPublic,
@ -179,7 +179,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
>
{isLoading ? (
<>
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
<Spinner size="sm" className="text-muted-foreground" />
<span className="text-muted-foreground hidden md:inline">Loading</span>
</>
) : currentConfig ? (

View file

@ -6,7 +6,6 @@ import {
Bot,
CheckCircle,
FileText,
Loader2,
RefreshCw,
RotateCcw,
Save,
@ -32,6 +31,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Spinner } from "@/components/ui/spinner";
const ROLE_DESCRIPTIONS = {
agent: {
@ -206,7 +206,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
<Card>
<CardContent className="flex items-center justify-center py-8 md:py-12">
<div className="flex items-center gap-2 text-muted-foreground">
<Loader2 className="w-4 h-4 md:w-5 md:h-5 animate-spin" />
<Spinner size="sm" className="md:h-5 md:w-5" />
<span className="text-xs md:text-sm">
{configsLoading && preferencesLoading
? "Loading configurations and preferences..."

View file

@ -7,7 +7,6 @@ import {
Clock,
Edit3,
FileText,
Loader2,
MessageSquareQuote,
Plus,
RefreshCw,
@ -49,6 +48,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import type { NewLLMConfig } from "@/contracts/types/new-llm-config.types";
import { cn } from "@/lib/utils";
@ -211,7 +211,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
<Card>
<CardContent className="flex items-center justify-center py-10 md:py-16">
<div className="flex flex-col items-center gap-2 md:gap-3">
<Loader2 className="h-6 w-6 md:h-8 md:w-8 animate-spin text-muted-foreground" />
<Spinner size="md" className="md:h-8 md:w-8 text-muted-foreground" />
<span className="text-xs md:text-sm text-muted-foreground">
Loading configurations...
</span>
@ -484,7 +484,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
>
{isDeleting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Spinner size="sm" className="mr-2" />
Deleting
</>
) : (

View file

@ -8,7 +8,6 @@ import {
ChevronDown,
ChevronsUpDown,
Key,
Loader2,
MessageSquareQuote,
Rocket,
Sparkles,
@ -48,6 +47,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { getModelsByProvider } from "@/contracts/enums/llm-models";
@ -592,7 +592,7 @@ export function LLMConfigForm({
>
{isSubmitting ? (
<>
<Loader2 className="h-3.5 w-3.5 sm:h-4 sm:w-4 animate-spin" />
<Spinner size="sm" />
{mode === "edit" ? "Updating..." : "Creating"}
</>
) : (

View file

@ -1,13 +1,14 @@
"use client";
import { useAtom } from "jotai";
import { CheckCircle2, FileType, Info, Loader2, Tag, Upload, X } from "lucide-react";
import { CheckCircle2, FileType, Info, Tag, Upload, X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useTranslations } from "next-intl";
import { useCallback, useMemo, useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { toast } from "sonner";
import { uploadDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Spinner } from "@/components/ui/spinner";
import {
Accordion,
AccordionContent,
@ -424,7 +425,7 @@ export function DocumentUploadTab({
>
{isUploading ? (
<span className="flex items-center gap-2">
<Loader2 className="h-4 w-4 sm:h-5 sm:w-5 animate-spin" />
<Spinner size="sm" />
{t("uploading")}
</span>
) : (

View file

@ -1,10 +1,11 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import { AlertCircleIcon, Loader2Icon, MicIcon } from "lucide-react";
import { AlertCircleIcon, MicIcon } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { z } from "zod";
import { Audio } from "@/components/tool-ui/audio";
import { Spinner } from "@/components/ui/spinner";
import { baseApiService } from "@/lib/apis/base-api.service";
import { authenticatedFetch } from "@/lib/auth-utils";
import { clearActivePodcastTaskId, setActivePodcastTaskId } from "@/lib/chat/podcast-state";
@ -97,8 +98,8 @@ function PodcastGeneratingState({ title }: { title: string }) {
<div className="flex-1">
<h3 className="font-semibold text-foreground text-lg">{title}</h3>
<div className="mt-2 flex items-center gap-2 text-muted-foreground">
<Loader2Icon className="size-4 animate-spin" />
<span className="text-sm">Generating podcast. This may take a few minutes</span>
<Spinner size="sm" />
<span className="text-sm">Generating podcast. This may take a few minutes.</span>
</div>
<div className="mt-3">
<div className="h-1.5 w-full overflow-hidden rounded-full bg-primary/10">
@ -144,7 +145,7 @@ function AudioLoadingState({ title }: { title: string }) {
<div className="flex-1">
<h3 className="font-semibold text-foreground">{title}</h3>
<div className="mt-2 flex items-center gap-2 text-muted-foreground">
<Loader2Icon className="size-4 animate-spin" />
<Spinner size="sm" />
<span className="text-sm">Loading audio...</span>
</div>
</div>

View file

@ -1,11 +1,12 @@
"use client";
import { ExternalLinkIcon, ImageIcon, Loader2 } from "lucide-react";
import { ExternalLinkIcon, ImageIcon } from "lucide-react";
import NextImage from "next/image";
import { Component, type ReactNode, useState } from "react";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
/**
@ -184,7 +185,7 @@ export function ImageLoading({ title = "Loading image..." }: { title?: string })
<Card className="w-full max-w-md overflow-hidden">
<div className="aspect-[4/3] bg-muted flex items-center justify-center">
<div className="flex flex-col items-center gap-3">
<Loader2 className="size-8 text-muted-foreground animate-spin" />
<Spinner size="lg" className="text-muted-foreground" />
<p className="text-muted-foreground text-sm">{title}</p>
</div>
</div>

View file

@ -1,12 +1,13 @@
"use client";
import { ExternalLinkIcon, Globe, ImageIcon, LinkIcon, Loader2 } from "lucide-react";
import { ExternalLinkIcon, Globe, ImageIcon, LinkIcon } from "lucide-react";
import Image from "next/image";
import { Component, type ReactNode } from "react";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Spinner } from "@/components/ui/spinner";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
@ -299,18 +300,17 @@ export function MediaCard({
{/* Response Actions */}
{responseActions && responseActions.length > 0 && (
<div
className="mt-4 flex items-center justify-end gap-2 border-t pt-3"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<div className="mt-4 flex items-center justify-end gap-2 border-t pt-3">
{responseActions.map((action) => (
<Tooltip key={action.id}>
<TooltipTrigger asChild>
<Button
variant={action.variant || "secondary"}
size="sm"
onClick={() => onResponseAction?.(action.id)}
onClick={(e) => {
e.stopPropagation();
onResponseAction?.(action.id);
}}
>
{action.label}
</Button>
@ -337,7 +337,7 @@ export function MediaCardLoading({ title = "Loading preview..." }: { title?: str
return (
<Card className="w-full max-w-md overflow-hidden">
<div className="aspect-[2/1] bg-muted animate-pulse flex items-center justify-center">
<Loader2 className="size-8 text-muted-foreground animate-spin" />
<Spinner size="lg" className="text-muted-foreground" />
</div>
<CardContent className="p-4">
<div className="flex items-center gap-3">

View file

@ -2,7 +2,6 @@
import { makeAssistantToolUI, useAssistantState } from "@assistant-ui/react";
import { useAtomValue, useSetAtom } from "jotai";
import { Loader2 } from "lucide-react";
import { useEffect, useMemo } from "react";
import { z } from "zod";
import {
@ -11,6 +10,7 @@ import {
registerPlanOwner,
updatePlanStateAtom,
} from "@/atoms/chat/plan-state.atom";
import { Spinner } from "@/components/ui/spinner";
import { Plan, PlanErrorBoundary, parseSerializablePlan, TodoStatusSchema } from "./plan";
// ============================================================================
@ -46,7 +46,7 @@ function WriteTodosLoading() {
return (
<div className="my-4 w-full max-w-xl rounded-2xl border bg-card/60 px-5 py-4 shadow-sm">
<div className="flex items-center gap-3">
<Loader2 className="size-5 animate-spin text-primary" />
<Spinner size="md" className="text-primary" />
<span className="text-sm text-muted-foreground">Creating plan...</span>
</div>
</div>

View file

@ -321,10 +321,10 @@
"columns": "Columns",
"confirm_delete": "Confirm Delete",
"confirm_delete_desc": "Are you sure you want to delete {count} document(s)? This action cannot be undone.",
"uploading": "Uploading...",
"uploading": "Uploading",
"upload_success": "Document uploaded successfully",
"upload_failed": "Failed to upload document",
"loading": "Loading documents...",
"loading": "Loading documents",
"error_loading": "Error loading documents",
"retry": "Retry",
"no_documents": "No documents found",
@ -391,8 +391,8 @@
"selected_files": "Selected Files ({count})",
"total_size": "Total size",
"clear_all": "Clear all",
"uploading_files": "Uploading files...",
"uploading": "Uploading...",
"uploading_files": "Uploading files",
"uploading": "Uploading",
"upload_button": "Upload {count} {count, plural, one {file} other {files}}",
"upload_initiated": "Upload Task Initiated",
"upload_initiated_desc": "Files Uploading Initiated",