wepee commited on
Commit
8a737a9
·
1 Parent(s): ab45a50

delete: delete trash logo on /dosen/course_id page

Browse files
resources/css/app.css CHANGED
@@ -320,7 +320,54 @@ @layer base {
320
  }
321
  }
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  @layer components {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  .welcome-shell {
325
  @apply relative flex min-h-svh items-center justify-center px-4 py-8 sm:px-6;
326
  background-color: var(--welcome-shell-bg);
@@ -1884,29 +1931,6 @@ @layer components {
1884
  color: var(--lecturer-primary);
1885
  }
1886
 
1887
- .lecturer-document-actions {
1888
- @apply ml-auto flex shrink-0 items-center gap-3;
1889
- }
1890
-
1891
- .lecturer-document-delete {
1892
- @apply inline-flex size-8 items-center justify-center rounded-[7px] transition-colors outline-none focus-visible:ring-2;
1893
- color: var(--lecturer-muted);
1894
- --tw-ring-color: color-mix(
1895
- in oklab,
1896
- var(--lecturer-primary) 45%,
1897
- transparent
1898
- );
1899
- }
1900
-
1901
- .lecturer-document-delete:hover {
1902
- background-color: color-mix(
1903
- in oklab,
1904
- var(--lecturer-danger) 10%,
1905
- var(--lecturer-surface)
1906
- );
1907
- color: var(--lecturer-danger);
1908
- }
1909
-
1910
  .lecturer-detail-empty {
1911
  @apply rounded-[10px] border px-5 py-8 text-center text-sm;
1912
  background-color: var(--lecturer-surface);
@@ -1971,9 +1995,5 @@ @layer components {
1971
  .lecturer-document-content {
1972
  @apply flex-col;
1973
  }
1974
-
1975
- .lecturer-document-actions {
1976
- @apply ml-0 w-full justify-between;
1977
- }
1978
  }
1979
  }
 
320
  }
321
  }
322
 
323
+ @keyframes app-loading-dot {
324
+ 0%,
325
+ 80%,
326
+ 100% {
327
+ opacity: 0.24;
328
+ transform: translateY(0);
329
+ }
330
+
331
+ 40% {
332
+ opacity: 1;
333
+ transform: translateY(-0.12rem);
334
+ }
335
+ }
336
+
337
+ @media (prefers-reduced-motion: reduce) {
338
+ .app-loading-dot {
339
+ animation: none !important;
340
+ opacity: 1;
341
+ transform: none;
342
+ }
343
+ }
344
+
345
  @layer components {
346
+ .app-loading-indicator {
347
+ @apply inline-flex items-center justify-center gap-2 whitespace-nowrap;
348
+ }
349
+
350
+ .app-loading-spinner {
351
+ @apply shrink-0;
352
+ }
353
+
354
+ .app-loading-dots {
355
+ @apply inline-flex min-w-[1.05em] items-center justify-start;
356
+ }
357
+
358
+ .app-loading-dot {
359
+ animation: app-loading-dot 1.2s infinite ease-in-out both;
360
+ line-height: 1;
361
+ }
362
+
363
+ .app-loading-dot:nth-child(2) {
364
+ animation-delay: 0.16s;
365
+ }
366
+
367
+ .app-loading-dot:nth-child(3) {
368
+ animation-delay: 0.32s;
369
+ }
370
+
371
  .welcome-shell {
372
  @apply relative flex min-h-svh items-center justify-center px-4 py-8 sm:px-6;
373
  background-color: var(--welcome-shell-bg);
 
1931
  color: var(--lecturer-primary);
1932
  }
1933
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1934
  .lecturer-detail-empty {
1935
  @apply rounded-[10px] border px-5 py-8 text-center text-sm;
1936
  background-color: var(--lecturer-surface);
 
1995
  .lecturer-document-content {
1996
  @apply flex-col;
1997
  }
 
 
 
 
1998
  }
1999
  }
resources/js/components/auth/register-role-form.tsx CHANGED
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
7
  import { Card, CardContent } from '@/components/ui/card';
8
  import { Input } from '@/components/ui/input';
9
  import { Label } from '@/components/ui/label';
 
10
  import { authApi, getAuthErrorMessage } from '@/lib/auth';
11
  import { register } from '@/routes';
12
 
@@ -357,7 +358,14 @@ export default function RegisterRoleForm({ role }: RegisterRoleFormProps) {
357
  disabled={isProcessing}
358
  data-test="register-role-button"
359
  >
360
- Register
 
 
 
 
 
 
 
361
  </Button>
362
  </form>
363
 
 
7
  import { Card, CardContent } from '@/components/ui/card';
8
  import { Input } from '@/components/ui/input';
9
  import { Label } from '@/components/ui/label';
10
+ import { LoadingIndicator } from '@/components/ui/loading-indicator';
11
  import { authApi, getAuthErrorMessage } from '@/lib/auth';
12
  import { register } from '@/routes';
13
 
 
358
  disabled={isProcessing}
359
  data-test="register-role-button"
360
  >
361
+ {isProcessing ? (
362
+ <LoadingIndicator
363
+ label="Mendaftar"
364
+ spinnerClassName="size-3.5"
365
+ />
366
+ ) : (
367
+ 'Register'
368
+ )}
369
  </Button>
370
  </form>
371
 
resources/js/components/student/question-composer.tsx CHANGED
@@ -10,6 +10,7 @@ import {
10
  SelectTrigger,
11
  SelectValue,
12
  } from '@/components/ui/select';
 
13
  import type { CourseResponse } from '@/lib/rag';
14
  import { cn } from '@/lib/utils';
15
 
@@ -118,7 +119,11 @@ export function QuestionComposer({
118
  size="icon"
119
  type="submit"
120
  >
121
- <Send className="size-5" />
 
 
 
 
122
  </Button>
123
  </form>
124
  </CardContent>
 
10
  SelectTrigger,
11
  SelectValue,
12
  } from '@/components/ui/select';
13
+ import { Spinner } from '@/components/ui/spinner';
14
  import type { CourseResponse } from '@/lib/rag';
15
  import { cn } from '@/lib/utils';
16
 
 
119
  size="icon"
120
  type="submit"
121
  >
122
+ {isProcessing ? (
123
+ <Spinner className="size-5" />
124
+ ) : (
125
+ <Send className="size-5" />
126
+ )}
127
  </Button>
128
  </form>
129
  </CardContent>
resources/js/components/student/student-shell.tsx CHANGED
@@ -20,6 +20,7 @@ import {
20
  DialogHeader,
21
  DialogTitle,
22
  } from '@/components/ui/dialog';
 
23
  import {
24
  Sidebar,
25
  SidebarContent,
@@ -250,7 +251,10 @@ function StudentSidebar({
250
  <Clock3 className="student-history-menu-icon size-4" />
251
  <span className="student-history-copy">
252
  <span className="student-history-title">
253
- Memuat riwayat
 
 
 
254
  </span>
255
  </span>
256
  </SidebarMenuButton>
@@ -421,7 +425,14 @@ function StudentSidebar({
421
  }}
422
  type="button"
423
  >
424
- {isDeletingSession ? 'Menghapus...' : 'Hapus'}
 
 
 
 
 
 
 
425
  </Button>
426
  </DialogFooter>
427
  </DialogContent>
 
20
  DialogHeader,
21
  DialogTitle,
22
  } from '@/components/ui/dialog';
23
+ import { LoadingIndicator } from '@/components/ui/loading-indicator';
24
  import {
25
  Sidebar,
26
  SidebarContent,
 
251
  <Clock3 className="student-history-menu-icon size-4" />
252
  <span className="student-history-copy">
253
  <span className="student-history-title">
254
+ <LoadingIndicator
255
+ label="Memuat riwayat"
256
+ spinnerClassName="size-3"
257
+ />
258
  </span>
259
  </span>
260
  </SidebarMenuButton>
 
425
  }}
426
  type="button"
427
  >
428
+ {isDeletingSession ? (
429
+ <LoadingIndicator
430
+ label="Menghapus"
431
+ spinnerClassName="size-3.5"
432
+ />
433
+ ) : (
434
+ 'Hapus'
435
+ )}
436
  </Button>
437
  </DialogFooter>
438
  </DialogContent>
resources/js/components/ui/loading-indicator.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { Spinner } from '@/components/ui/spinner';
4
+ import { cn } from '@/lib/utils';
5
+
6
+ type LoadingIndicatorProps = ComponentProps<'span'> & {
7
+ label?: string;
8
+ showSpinner?: boolean;
9
+ spinnerClassName?: string;
10
+ };
11
+
12
+ function LoadingDots({ className }: { className?: string }) {
13
+ return (
14
+ <span aria-hidden="true" className={cn('app-loading-dots', className)}>
15
+ <span className="app-loading-dot">.</span>
16
+ <span className="app-loading-dot">.</span>
17
+ <span className="app-loading-dot">.</span>
18
+ </span>
19
+ );
20
+ }
21
+
22
+ function LoadingIndicator({
23
+ className,
24
+ label = 'Memuat',
25
+ showSpinner = true,
26
+ spinnerClassName,
27
+ ...props
28
+ }: LoadingIndicatorProps) {
29
+ return (
30
+ <span
31
+ aria-live="polite"
32
+ className={cn('app-loading-indicator', className)}
33
+ role="status"
34
+ {...props}
35
+ >
36
+ {showSpinner && (
37
+ <Spinner
38
+ aria-hidden="true"
39
+ className={cn('app-loading-spinner', spinnerClassName)}
40
+ role="presentation"
41
+ />
42
+ )}
43
+ {label && <span>{label}</span>}
44
+ <LoadingDots />
45
+ </span>
46
+ );
47
+ }
48
+
49
+ export { LoadingDots, LoadingIndicator };
resources/js/pages/auth/login.tsx CHANGED
@@ -7,6 +7,7 @@ import { Card, CardContent } from '@/components/ui/card';
7
  import { Checkbox } from '@/components/ui/checkbox';
8
  import { Input } from '@/components/ui/input';
9
  import { Label } from '@/components/ui/label';
 
10
  import { authApi, getAuthErrorMessage, saveAuthSession } from '@/lib/auth';
11
  import { dashboard, dosen, home, mahasiswa, register } from '@/routes';
12
 
@@ -137,7 +138,14 @@ export default function Login({ canRegister = true, status }: LoginProps) {
137
  disabled={isProcessing}
138
  data-test="login-button"
139
  >
140
- Sign In
 
 
 
 
 
 
 
141
  </Button>
142
  </form>
143
 
 
7
  import { Checkbox } from '@/components/ui/checkbox';
8
  import { Input } from '@/components/ui/input';
9
  import { Label } from '@/components/ui/label';
10
+ import { LoadingIndicator } from '@/components/ui/loading-indicator';
11
  import { authApi, getAuthErrorMessage, saveAuthSession } from '@/lib/auth';
12
  import { dashboard, dosen, home, mahasiswa, register } from '@/routes';
13
 
 
138
  disabled={isProcessing}
139
  data-test="login-button"
140
  >
141
+ {isProcessing ? (
142
+ <LoadingIndicator
143
+ label="Masuk"
144
+ spinnerClassName="size-3.5"
145
+ />
146
+ ) : (
147
+ 'Sign In'
148
+ )}
149
  </Button>
150
  </form>
151
 
resources/js/pages/dosen-detail.tsx CHANGED
@@ -25,6 +25,10 @@ import {
25
  } from '@/components/ui/dialog';
26
  import { Input } from '@/components/ui/input';
27
  import { Label } from '@/components/ui/label';
 
 
 
 
28
  import { getStoredAuthToken } from '@/lib/auth';
29
  import type {
30
  CourseResponse,
@@ -142,19 +146,16 @@ function DocumentCard({ document }: { document: DocumentResponse }) {
142
  data-status={statusTone}
143
  >
144
  <DocumentStatusIcon status={document.status} />
145
- {getStatusLabel(document.status)}
 
 
 
 
 
 
 
146
  </p>
147
  </div>
148
-
149
- <div className="lecturer-document-actions">
150
- <button
151
- aria-label={`Hapus ${document.filename}`}
152
- className="lecturer-document-delete"
153
- type="button"
154
- >
155
- <Trash2 className="size-4" />
156
- </button>
157
- </div>
158
  </CardContent>
159
  </Card>
160
  );
@@ -338,11 +339,6 @@ export default function DosenDetail({ courseId }: DosenDetailProps) {
338
  const title =
339
  course?.title ??
340
  (isLoading ? 'Memuat mata kuliah...' : 'Detail mata kuliah');
341
- const description =
342
- course?.description ??
343
- (isLoading
344
- ? 'Mengambil detail mata kuliah dari backend.'
345
- : 'Tidak ada deskripsi mata kuliah.');
346
 
347
  return (
348
  <>
@@ -362,9 +358,26 @@ export default function DosenDetail({ courseId }: DosenDetailProps) {
362
 
363
  <div className="lecturer-detail-heading">
364
  <div className="lecturer-detail-title-row">
365
- <h1>{title}</h1>
 
 
 
 
 
 
 
366
  </div>
367
- <p>{description}</p>
 
 
 
 
 
 
 
 
 
 
368
  </div>
369
 
370
  <div className="lecturer-detail-actions">
@@ -417,9 +430,14 @@ export default function DosenDetail({ courseId }: DosenDetailProps) {
417
  )}
418
  </span>
419
  <span className="lecturer-upload-title">
420
- {isUploading
421
- ? 'Mengupload dokumen...'
422
- : 'Upload Dokumen Baru'}
 
 
 
 
 
423
  </span>
424
  <span className="lecturer-upload-description">
425
  Drag & drop file PDF, PPT, atau TXT di sini.
@@ -440,7 +458,7 @@ export default function DosenDetail({ courseId }: DosenDetailProps) {
440
  <div className="lecturer-document-list">
441
  {isLoading && (
442
  <p className="lecturer-detail-empty">
443
- Memuat dokumen...
444
  </p>
445
  )}
446
 
@@ -548,9 +566,14 @@ export default function DosenDetail({ courseId }: DosenDetailProps) {
548
  disabled={isUpdatingCourse}
549
  type="submit"
550
  >
551
- {isUpdatingCourse
552
- ? 'Menyimpan...'
553
- : 'Simpan'}
 
 
 
 
 
554
  </Button>
555
  </DialogFooter>
556
  </form>
@@ -604,9 +627,14 @@ export default function DosenDetail({ courseId }: DosenDetailProps) {
604
  }}
605
  type="button"
606
  >
607
- {isDeletingCourse
608
- ? 'Menghapus...'
609
- : 'Hapus'}
 
 
 
 
 
610
  </Button>
611
  </DialogFooter>
612
  </div>
 
25
  } from '@/components/ui/dialog';
26
  import { Input } from '@/components/ui/input';
27
  import { Label } from '@/components/ui/label';
28
+ import {
29
+ LoadingDots,
30
+ LoadingIndicator,
31
+ } from '@/components/ui/loading-indicator';
32
  import { getStoredAuthToken } from '@/lib/auth';
33
  import type {
34
  CourseResponse,
 
146
  data-status={statusTone}
147
  >
148
  <DocumentStatusIcon status={document.status} />
149
+ {document.status === 'processing' ? (
150
+ <>
151
+ <span>Sedang diproses</span>
152
+ <LoadingDots />
153
+ </>
154
+ ) : (
155
+ getStatusLabel(document.status)
156
+ )}
157
  </p>
158
  </div>
 
 
 
 
 
 
 
 
 
 
159
  </CardContent>
160
  </Card>
161
  );
 
339
  const title =
340
  course?.title ??
341
  (isLoading ? 'Memuat mata kuliah...' : 'Detail mata kuliah');
 
 
 
 
 
342
 
343
  return (
344
  <>
 
358
 
359
  <div className="lecturer-detail-heading">
360
  <div className="lecturer-detail-title-row">
361
+ <h1>
362
+ {course?.title ??
363
+ (isLoading ? (
364
+ <LoadingIndicator label="Memuat mata kuliah" />
365
+ ) : (
366
+ 'Detail mata kuliah'
367
+ ))}
368
+ </h1>
369
  </div>
370
+ <p>
371
+ {course?.description ??
372
+ (isLoading ? (
373
+ <LoadingIndicator
374
+ label="Mengambil detail mata kuliah"
375
+ showSpinner={false}
376
+ />
377
+ ) : (
378
+ 'Tidak ada deskripsi mata kuliah.'
379
+ ))}
380
+ </p>
381
  </div>
382
 
383
  <div className="lecturer-detail-actions">
 
430
  )}
431
  </span>
432
  <span className="lecturer-upload-title">
433
+ {isUploading ? (
434
+ <LoadingIndicator
435
+ label="Mengupload dokumen"
436
+ showSpinner={false}
437
+ />
438
+ ) : (
439
+ 'Upload Dokumen Baru'
440
+ )}
441
  </span>
442
  <span className="lecturer-upload-description">
443
  Drag & drop file PDF, PPT, atau TXT di sini.
 
458
  <div className="lecturer-document-list">
459
  {isLoading && (
460
  <p className="lecturer-detail-empty">
461
+ <LoadingIndicator label="Memuat dokumen" />
462
  </p>
463
  )}
464
 
 
566
  disabled={isUpdatingCourse}
567
  type="submit"
568
  >
569
+ {isUpdatingCourse ? (
570
+ <LoadingIndicator
571
+ label="Menyimpan"
572
+ spinnerClassName="size-3.5"
573
+ />
574
+ ) : (
575
+ 'Simpan'
576
+ )}
577
  </Button>
578
  </DialogFooter>
579
  </form>
 
627
  }}
628
  type="button"
629
  >
630
+ {isDeletingCourse ? (
631
+ <LoadingIndicator
632
+ label="Menghapus"
633
+ spinnerClassName="size-3.5"
634
+ />
635
+ ) : (
636
+ 'Hapus'
637
+ )}
638
  </Button>
639
  </DialogFooter>
640
  </div>
resources/js/pages/dosen.tsx CHANGED
@@ -2,6 +2,7 @@ import { Head, router } from '@inertiajs/react';
2
  import type { LucideIcon } from 'lucide-react';
3
  import { AlertTriangle, Database, FileText, Plus, Search } from 'lucide-react';
4
  import type { FormEvent } from 'react';
 
5
  import { useCallback, useEffect, useMemo, useState } from 'react';
6
 
7
  import AppearanceToggle from '@/components/appearance-toggle';
@@ -18,6 +19,10 @@ import {
18
  } from '@/components/ui/dialog';
19
  import { Input } from '@/components/ui/input';
20
  import { Label } from '@/components/ui/label';
 
 
 
 
21
  import { getStoredAuthToken } from '@/lib/auth';
22
  import type { CourseResponse, DocumentResponse } from '@/lib/rag';
23
  import { getRagErrorMessage, ragApi } from '@/lib/rag';
@@ -34,7 +39,7 @@ type KnowledgeBaseRow = {
34
 
35
  type Metric = {
36
  label: string;
37
- value: string;
38
  description?: string;
39
  icon: LucideIcon;
40
  tone: 'primary' | 'danger';
@@ -252,21 +257,33 @@ export default function Dosen() {
252
  icon: Database,
253
  label: 'Total Knowledge Base',
254
  tone: 'primary',
255
- value: isLoading ? '...' : String(knowledgeBases.length),
 
 
 
 
256
  },
257
  {
258
  icon: FileText,
259
  label: 'Total Dokumen',
260
  description: 'Chunked',
261
  tone: 'primary',
262
- value: isLoading ? '...' : String(documentCount),
 
 
 
 
263
  },
264
  {
265
  icon: AlertTriangle,
266
  label: 'Processing Error',
267
  description: 'dokumen',
268
  tone: 'danger',
269
- value: isLoading ? '...' : String(failedDocumentCount),
 
 
 
 
270
  },
271
  ];
272
  }, [isLoading, knowledgeBases]);
@@ -360,7 +377,7 @@ export default function Dosen() {
360
  className="lecturer-table-empty"
361
  colSpan={4}
362
  >
363
- Memuat knowledge base...
364
  </td>
365
  </tr>
366
  )}
@@ -527,9 +544,14 @@ export default function Dosen() {
527
  disabled={isCreatingCourse}
528
  type="submit"
529
  >
530
- {isCreatingCourse
531
- ? 'Menyimpan...'
532
- : 'Simpan'}
 
 
 
 
 
533
  </Button>
534
  </DialogFooter>
535
  </form>
 
2
  import type { LucideIcon } from 'lucide-react';
3
  import { AlertTriangle, Database, FileText, Plus, Search } from 'lucide-react';
4
  import type { FormEvent } from 'react';
5
+ import type { ReactNode } from 'react';
6
  import { useCallback, useEffect, useMemo, useState } from 'react';
7
 
8
  import AppearanceToggle from '@/components/appearance-toggle';
 
19
  } from '@/components/ui/dialog';
20
  import { Input } from '@/components/ui/input';
21
  import { Label } from '@/components/ui/label';
22
+ import {
23
+ LoadingDots,
24
+ LoadingIndicator,
25
+ } from '@/components/ui/loading-indicator';
26
  import { getStoredAuthToken } from '@/lib/auth';
27
  import type { CourseResponse, DocumentResponse } from '@/lib/rag';
28
  import { getRagErrorMessage, ragApi } from '@/lib/rag';
 
39
 
40
  type Metric = {
41
  label: string;
42
+ value: ReactNode;
43
  description?: string;
44
  icon: LucideIcon;
45
  tone: 'primary' | 'danger';
 
257
  icon: Database,
258
  label: 'Total Knowledge Base',
259
  tone: 'primary',
260
+ value: isLoading ? (
261
+ <LoadingDots className="lecturer-metric-loading" />
262
+ ) : (
263
+ String(knowledgeBases.length)
264
+ ),
265
  },
266
  {
267
  icon: FileText,
268
  label: 'Total Dokumen',
269
  description: 'Chunked',
270
  tone: 'primary',
271
+ value: isLoading ? (
272
+ <LoadingDots className="lecturer-metric-loading" />
273
+ ) : (
274
+ String(documentCount)
275
+ ),
276
  },
277
  {
278
  icon: AlertTriangle,
279
  label: 'Processing Error',
280
  description: 'dokumen',
281
  tone: 'danger',
282
+ value: isLoading ? (
283
+ <LoadingDots className="lecturer-metric-loading" />
284
+ ) : (
285
+ String(failedDocumentCount)
286
+ ),
287
  },
288
  ];
289
  }, [isLoading, knowledgeBases]);
 
377
  className="lecturer-table-empty"
378
  colSpan={4}
379
  >
380
+ <LoadingIndicator label="Memuat knowledge base" />
381
  </td>
382
  </tr>
383
  )}
 
544
  disabled={isCreatingCourse}
545
  type="submit"
546
  >
547
+ {isCreatingCourse ? (
548
+ <LoadingIndicator
549
+ label="Menyimpan"
550
+ spinnerClassName="size-3.5"
551
+ />
552
+ ) : (
553
+ 'Simpan'
554
+ )}
555
  </Button>
556
  </DialogFooter>
557
  </form>
resources/js/pages/mahasiswa-chat.tsx CHANGED
@@ -11,6 +11,7 @@ import {
11
  StudentShell,
12
  useStoredStudentName,
13
  } from '@/components/student/student-shell';
 
14
  import { getStoredAuthToken } from '@/lib/auth';
15
  import type {
16
  ChatHistoryResponse,
@@ -455,7 +456,7 @@ export default function MahasiswaChat({ sessionId }: MahasiswaChatProps) {
455
 
456
  {isLoading && messages.length === 0 && !isProcessing ? (
457
  <p className="student-chat-loading">
458
- Memuat percakapan...
459
  </p>
460
  ) : (
461
  <div className="student-chat-messages">
@@ -472,7 +473,10 @@ export default function MahasiswaChat({ sessionId }: MahasiswaChatProps) {
472
  <Bot className="size-4" />
473
  </span>
474
  <div className="student-message-bubble student-message-bubble--typing">
475
- Thinking...
 
 
 
476
  </div>
477
  </article>
478
  )}
 
11
  StudentShell,
12
  useStoredStudentName,
13
  } from '@/components/student/student-shell';
14
+ import { LoadingIndicator } from '@/components/ui/loading-indicator';
15
  import { getStoredAuthToken } from '@/lib/auth';
16
  import type {
17
  ChatHistoryResponse,
 
456
 
457
  {isLoading && messages.length === 0 && !isProcessing ? (
458
  <p className="student-chat-loading">
459
+ <LoadingIndicator label="Memuat percakapan" />
460
  </p>
461
  ) : (
462
  <div className="student-chat-messages">
 
473
  <Bot className="size-4" />
474
  </span>
475
  <div className="student-message-bubble student-message-bubble--typing">
476
+ <LoadingIndicator
477
+ label="Thinking"
478
+ showSpinner={false}
479
+ />
480
  </div>
481
  </article>
482
  )}