AkurAI Build
Menu

Public repository / c3ef99b092caa55fb6a3eed9d3e776734cc99e97

AkurAI-Mail / src/lib/webmail/SyncBanner.svelte

Latest change 22202d1bde69be4ace8a299dffd91f8ff24378df - Recover deployed webmail frontend (frontend parity with EC2 a6afffc) by Ólafur Búi Ólafsson

<script lang="ts">
  import { Check, RefreshCw, TriangleAlert, WifiOff, X } from "lucide-svelte";
  import { SYNC_ERROR_MESSAGES, syncedLabel } from "$lib/webmail/sync";
  import type { SyncState } from "$lib/webmail/sync.svelte";

  let { sync, onRetry, onReauth }: { sync: SyncState; onRetry: () => void; onReauth: () => void } = $props();
</script>

<div class="sync-root">
  <div class="sync-status" role="status">
    {#if sync.status === "syncing"}
      <RefreshCw size={14} class="spin" /> <span>Syncing…</span>
    {:else}
      <Check size={14} /> <span>{syncedLabel(sync.lastSyncAt)}</span>
    {/if}
    <button class="sync-now" disabled={sync.status === "syncing"} onclick={onRetry}>Sync now</button>
  </div>
  {#if sync.errorKind && !sync.dismissed}
    <div class="sync-banner" class:auth={sync.errorKind === "auth-expired"} role="alert">
      {#if sync.errorKind === "offline"}<WifiOff size={16} />{:else}<TriangleAlert size={16} />{/if}
      <span class="sync-banner-text">{SYNC_ERROR_MESSAGES[sync.errorKind]}</span>
      {#if sync.errorKind === "auth-expired"}
        <button class="sync-action" onclick={onReauth}>Sign in again</button>
      {:else}
        <button class="sync-action" onclick={onRetry}>Retry now</button>
      {/if}
      <button class="icon-button" aria-label="Dismiss sync error" onclick={sync.dismiss}><X size={15} /></button>
    </div>
  {/if}
</div>

<style>
  .sync-status {
    color: var(--muted);
    align-items: center;
    gap: 6px;
    font-size: 12px;
    display: flex;
  }
  .sync-status :global(.spin) {
    animation: 0.8s linear infinite spin;
  }
  .sync-now {
    color: var(--accent);
    background: 0 0;
    border: 0;
    padding: 0 4px;
    font-weight: 700;
  }
  .sync-now:hover {
    text-decoration: underline;
  }
  .sync-banner {
    border-bottom: 1px solid var(--line);
    background: color-mix(in srgb, var(--danger) 8%, var(--surface));
    color: var(--danger);
    align-items: center;
    gap: 10px;
    padding: 9px 14px;
    font-size: 13px;
    display: flex;
  }
  .sync-banner.auth {
    background: color-mix(in srgb, #d6a62c 14%, var(--surface));
    color: #7a5a00;
  }
  .sync-banner-text {
    flex: 1;
    min-width: 0;
  }
  .sync-action {
    background: var(--surface);
    color: inherit;
    border: 0;
    border-radius: 6px;
    padding: 5px 10px;
    font-weight: 700;
  }
  .sync-action:hover {
    background: var(--surface-selected);
  }
  .icon-button {
    color: inherit;
    background: 0 0;
    border: 0;
    padding: 4px;
    display: inline-flex;
  }
  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }
</style>