mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-13 11:03:44 +00:00
181 lines
5.7 KiB
Svelte
181 lines
5.7 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { browser } from '$app/environment';
|
|
|
|
type ExportIntegrationSummary = {
|
|
id: string;
|
|
provider: 'google_drive' | 'dropbox';
|
|
enabled: boolean;
|
|
fileName: string | null;
|
|
folderId: string | null;
|
|
path: string | null;
|
|
metadata?: Record<string, unknown> | null;
|
|
lastExportedAt: string | null;
|
|
lastError: string | null;
|
|
};
|
|
|
|
let exportIntegrations: ExportIntegrationSummary[] = [];
|
|
let exportLoading = false;
|
|
let exportError: string | null = null;
|
|
let googleIntegration: ExportIntegrationSummary | undefined;
|
|
let dropboxIntegration: ExportIntegrationSummary | undefined;
|
|
|
|
function formatTimestamp(value: string | null) {
|
|
if (!value) return '';
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return value;
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
async function loadExportIntegrations() {
|
|
exportLoading = true;
|
|
exportError = null;
|
|
try {
|
|
const response = await fetch('/api/export-integrations', {
|
|
credentials: 'include'
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(await response.text());
|
|
}
|
|
exportIntegrations = (await response.json()) as ExportIntegrationSummary[];
|
|
googleIntegration = exportIntegrations.find((i) => i.provider === 'google_drive');
|
|
dropboxIntegration = exportIntegrations.find((i) => i.provider === 'dropbox');
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
exportError = message || 'Failed to load export settings';
|
|
} finally {
|
|
exportLoading = false;
|
|
}
|
|
}
|
|
|
|
function getGoogleFolderUrl(folderId: string): string {
|
|
return `https://drive.google.com/drive/folders/${folderId}`;
|
|
}
|
|
|
|
function getDropboxFolderUrl(path?: string | null): string {
|
|
const fallback = '/Apps/Living Dex Tracker';
|
|
const trimmed = (path ?? '').trim() || fallback;
|
|
const normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
return `https://www.dropbox.com/home${normalized}`;
|
|
}
|
|
|
|
function connectGoogleDrive() {
|
|
const params = new URLSearchParams({
|
|
returnTo: '/backup-settings'
|
|
});
|
|
window.location.href = `/api/integrations/google-drive/connect?${params.toString()}`;
|
|
}
|
|
|
|
function connectDropbox() {
|
|
const params = new URLSearchParams({
|
|
returnTo: '/backup-settings'
|
|
});
|
|
window.location.href = `/api/integrations/dropbox/connect?${params.toString()}`;
|
|
}
|
|
|
|
onMount(() => {
|
|
if (!browser) return;
|
|
void loadExportIntegrations();
|
|
});
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Backup Settings - Living Dex Tracker</title>
|
|
</svelte:head>
|
|
|
|
<div class="container mx-auto p-4 max-w-screen-lg">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h1 class="text-3xl font-bold">Backup Settings</h1>
|
|
<a href="/my-pokedexes" class="btn btn-outline btn-sm">Back to My Pokédexes</a>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<p class="text-sm text-base-content/70">
|
|
Backups automatically export every time you update a pokédex. Each export is named after the
|
|
pokédex, so you only need to connect once.
|
|
</p>
|
|
|
|
{#if exportLoading}
|
|
<p class="text-sm text-base-content/70 mt-4">Loading export settings…</p>
|
|
{:else if exportError}
|
|
<p class="text-sm text-error mt-4">{exportError}</p>
|
|
{/if}
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-6">
|
|
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="font-semibold">Google Drive</h2>
|
|
<span class={`badge ${googleIntegration ? 'badge-success' : 'badge-ghost'}`}>
|
|
{googleIntegration ? 'Connected' : 'Not Connected'}
|
|
</span>
|
|
</div>
|
|
<div class="mt-3 space-y-2">
|
|
<div class="flex items-center justify-end gap-2">
|
|
<button class="btn btn-sm btn-outline" on:click={connectGoogleDrive}>
|
|
{googleIntegration ? 'Reconnect' : 'Connect'}
|
|
</button>
|
|
</div>
|
|
{#if googleIntegration?.folderId}
|
|
<div class="text-xs">
|
|
<a
|
|
href={getGoogleFolderUrl(googleIntegration.folderId)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="link link-primary"
|
|
>
|
|
Open Drive folder
|
|
</a>
|
|
</div>
|
|
{/if}
|
|
{#if googleIntegration?.lastExportedAt}
|
|
<p class="text-xs text-base-content/60">
|
|
Last export: {formatTimestamp(googleIntegration.lastExportedAt)}
|
|
</p>
|
|
{/if}
|
|
{#if googleIntegration?.lastError}
|
|
<p class="text-xs text-error">{googleIntegration.lastError}</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="font-semibold">Dropbox</h2>
|
|
<span class={`badge ${dropboxIntegration ? 'badge-success' : 'badge-ghost'}`}>
|
|
{dropboxIntegration ? 'Connected' : 'Not Connected'}
|
|
</span>
|
|
</div>
|
|
<div class="mt-3 space-y-2">
|
|
<div class="flex items-center justify-end gap-2">
|
|
<button class="btn btn-sm btn-outline" on:click={connectDropbox}>
|
|
{dropboxIntegration ? 'Reconnect' : 'Connect'}
|
|
</button>
|
|
</div>
|
|
{#if dropboxIntegration}
|
|
<div class="text-xs">
|
|
<a
|
|
href={getDropboxFolderUrl(dropboxIntegration.path)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="link link-primary"
|
|
>
|
|
Open Dropbox folder
|
|
</a>
|
|
</div>
|
|
{/if}
|
|
{#if dropboxIntegration?.lastExportedAt}
|
|
<p class="text-xs text-base-content/60">
|
|
Last export: {formatTimestamp(dropboxIntegration.lastExportedAt)}
|
|
</p>
|
|
{/if}
|
|
{#if dropboxIntegration?.lastError}
|
|
<p class="text-xs text-error">{dropboxIntegration.lastError}</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|