- Added detailed logging for various actions in the background script and content script to improve debugging capabilities. - Updated the account management flow to include the last updated timestamp and sales data. - Refined the parsing logic to ensure accurate extraction of seller statistics from eBay profiles. - Improved error handling in the parsing process to provide more informative responses in case of failures.
656 lines
25 KiB
JavaScript
656 lines
25 KiB
JavaScript
"use client";
|
|
import React, { useState, useEffect } from "react";
|
|
import { IconPlus, IconChevronDown, IconX, IconRefresh } from "@tabler/icons-react";
|
|
import { motion, AnimatePresence } from "motion/react";
|
|
import { cn } from "../lib/utils";
|
|
import { useHashRoute } from "../lib/routing";
|
|
import {
|
|
setActiveAccountId,
|
|
getAccountDisplayName,
|
|
} from "../services/accountService";
|
|
import { fetchManagedAccounts, createManagedAccount, updateManagedAccount } from "../services/accountsService";
|
|
import { getAuthUser } from "../lib/appwrite";
|
|
import { parseEbayAccount } from "../services/ebayParserService";
|
|
import { DataTable } from "../components/dashboard/ui/DataTable";
|
|
|
|
export const AccountsPage = () => {
|
|
const { navigate } = useHashRoute();
|
|
const [accounts, setAccounts] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
const [showAddForm, setShowAddForm] = useState(false);
|
|
const [formError, setFormError] = useState("");
|
|
const [formSuccess, setFormSuccess] = useState("");
|
|
const [formLoading, setFormLoading] = useState(false);
|
|
|
|
// Parse-State für Zwei-Phasen-Flow
|
|
const [parsedData, setParsedData] = useState(null);
|
|
const [parsing, setParsing] = useState(false);
|
|
const [parsingError, setParsingError] = useState("");
|
|
|
|
// Refresh-State pro Account
|
|
const [refreshingAccountId, setRefreshingAccountId] = useState(null);
|
|
const [refreshToast, setRefreshToast] = useState({ show: false, message: "", type: "success" });
|
|
|
|
// Form-Felder (nur noch URL)
|
|
const [formData, setFormData] = useState({
|
|
account_url: "",
|
|
});
|
|
|
|
// Accounts laden
|
|
useEffect(() => {
|
|
loadAccounts();
|
|
}, []);
|
|
|
|
async function loadAccounts() {
|
|
setLoading(true);
|
|
try {
|
|
const authUser = await getAuthUser();
|
|
if (!authUser) {
|
|
setAccounts([]);
|
|
return;
|
|
}
|
|
|
|
const loadedAccounts = await fetchManagedAccounts(authUser.$id);
|
|
setAccounts(loadedAccounts);
|
|
} catch (e) {
|
|
console.error("Fehler beim Laden der Accounts:", e);
|
|
setAccounts([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
const handleSelectAccount = (account) => {
|
|
const accountId = account.$id || account.id;
|
|
setActiveAccountId(accountId);
|
|
// Navigiere zurück zum Dashboard
|
|
navigate("/");
|
|
};
|
|
|
|
const handleRefreshAccount = async (account) => {
|
|
const accountId = account.$id || account.id;
|
|
const accountUrl = account.account_url;
|
|
|
|
if (!accountUrl) {
|
|
setRefreshToast({ show: true, message: "Account hat keine URL zum Aktualisieren.", type: "error" });
|
|
setTimeout(() => setRefreshToast({ show: false, message: "", type: "success" }), 3000);
|
|
return;
|
|
}
|
|
|
|
setRefreshingAccountId(accountId);
|
|
|
|
try {
|
|
// URL erneut parsen
|
|
const parsedData = await parseEbayAccount(accountUrl);
|
|
|
|
// Account in DB aktualisieren
|
|
// WICHTIG: Nur Felder setzen, die nicht leer sind und sich geändert haben
|
|
// Leere account_platform_account_id würde Unique-Index-Konflikte verursachen
|
|
const updatePayload = {};
|
|
|
|
// Nur market setzen, wenn nicht leer
|
|
if (parsedData.market && parsedData.market.trim()) {
|
|
updatePayload.account_platform_market = parsedData.market;
|
|
}
|
|
|
|
// Nur sellerId setzen, wenn nicht leer (verhindert Unique-Index-Konflikte mit leerem String)
|
|
if (parsedData.sellerId && parsedData.sellerId.trim()) {
|
|
updatePayload.account_platform_account_id = parsedData.sellerId;
|
|
}
|
|
|
|
// Shop-Name und account_sells können auch leer sein (optional)
|
|
updatePayload.account_shop_name = parsedData.shopName || null;
|
|
updatePayload.account_sells = parsedData.stats?.itemsSold ?? null;
|
|
|
|
// account_status wird weggelassen (wie beim Erstellen)
|
|
// Grund: Schema-Konflikt - Enum-Feld akzeptiert weder String noch Array im Update
|
|
// TODO: Schema in Appwrite prüfen und korrigieren, dann account_status wieder hinzufügen
|
|
|
|
// Setze account_updated_at auf aktuelle Zeit
|
|
updatePayload.account_updated_at = new Date().toISOString();
|
|
|
|
await updateManagedAccount(accountId, updatePayload);
|
|
|
|
// Accounts-Liste neu laden (in-place Update)
|
|
await loadAccounts();
|
|
|
|
// Success-Toast
|
|
setRefreshToast({ show: true, message: "Account aktualisiert", type: "success" });
|
|
setTimeout(() => setRefreshToast({ show: false, message: "", type: "success" }), 3000);
|
|
} catch (e) {
|
|
console.error("Fehler beim Aktualisieren des Accounts:", e);
|
|
|
|
let errorMessage = "Update fehlgeschlagen";
|
|
if (e.message?.includes("Parsing") || e.message?.includes("URL")) {
|
|
errorMessage = "Parsing fehlgeschlagen";
|
|
}
|
|
|
|
setRefreshToast({ show: true, message: errorMessage, type: "error" });
|
|
setTimeout(() => setRefreshToast({ show: false, message: "", type: "success" }), 3000);
|
|
} finally {
|
|
setRefreshingAccountId(null);
|
|
}
|
|
};
|
|
|
|
const handleAddAccount = () => {
|
|
setShowAddForm(true);
|
|
setFormError("");
|
|
setFormSuccess("");
|
|
};
|
|
|
|
const handleCloseForm = () => {
|
|
setShowAddForm(false);
|
|
setFormError("");
|
|
setFormSuccess("");
|
|
setParsedData(null);
|
|
setParsingError("");
|
|
// Form zurücksetzen
|
|
setFormData({
|
|
account_url: "",
|
|
});
|
|
};
|
|
|
|
const handleFormChange = (field, value) => {
|
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
// Clear errors beim Eingeben
|
|
if (formError) setFormError("");
|
|
if (parsingError) setParsingError("");
|
|
};
|
|
|
|
const handleParseUrl = async () => {
|
|
const url = formData.account_url?.trim();
|
|
|
|
if (!url) {
|
|
setParsingError("Bitte gib eine eBay-URL ein.");
|
|
return;
|
|
}
|
|
|
|
setParsing(true);
|
|
setParsingError("");
|
|
setFormError("");
|
|
|
|
try {
|
|
const result = await parseEbayAccount(url);
|
|
setParsedData(result);
|
|
setParsingError("");
|
|
} catch (e) {
|
|
setParsingError(e.message || "Bitte gib eine gültige eBay-URL ein.");
|
|
setParsedData(null);
|
|
} finally {
|
|
setParsing(false);
|
|
}
|
|
};
|
|
|
|
const handleFormSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
// Wenn noch nicht geparst, zuerst parsen
|
|
if (!parsedData) {
|
|
await handleParseUrl();
|
|
return;
|
|
}
|
|
|
|
// Save-Phase: Account in DB speichern
|
|
setFormError("");
|
|
setFormSuccess("");
|
|
setFormLoading(true);
|
|
|
|
try {
|
|
const authUser = await getAuthUser();
|
|
if (!authUser) {
|
|
setFormError("Nicht eingeloggt. Bitte neu anmelden.");
|
|
return;
|
|
}
|
|
|
|
// Payload aus parsedData zusammenstellen
|
|
const accountSellsValue = parsedData.stats?.itemsSold ?? null;
|
|
const newAccount = await createManagedAccount(authUser.$id, {
|
|
account_url: formData.account_url.trim(),
|
|
account_platform_account_id: parsedData.sellerId,
|
|
account_platform_market: parsedData.market,
|
|
account_shop_name: parsedData.shopName,
|
|
account_sells: accountSellsValue,
|
|
// account_status wird nicht mehr gesendet (optional, Schema-Problem in Appwrite)
|
|
});
|
|
|
|
// Erfolg: Liste refreshen
|
|
await loadAccounts();
|
|
|
|
// Wenn dies das erste Account ist, setze es als aktiv
|
|
if (accounts.length === 0) {
|
|
setActiveAccountId(newAccount.$id);
|
|
}
|
|
|
|
setFormSuccess("Account erfolgreich erstellt!");
|
|
setTimeout(() => {
|
|
handleCloseForm();
|
|
}, 1500);
|
|
} catch (e) {
|
|
// Fehlerbehandlung
|
|
const errorMessage =
|
|
e.message || "Fehler beim Erstellen des Accounts. Bitte versuche es erneut.";
|
|
setFormError(errorMessage);
|
|
} finally {
|
|
setFormLoading(false);
|
|
}
|
|
};
|
|
|
|
// Spalten für die Tabelle
|
|
const columns = [
|
|
"Account Name",
|
|
"Platform",
|
|
"Platform Account ID",
|
|
"Market",
|
|
"Account URL",
|
|
"Sales",
|
|
"Last Scan",
|
|
...(showAdvanced ? ["Owner User ID"] : []),
|
|
"Action",
|
|
];
|
|
|
|
const renderCell = (col, row) => {
|
|
if (col === "Action") {
|
|
const accountId = row.$id || row.id;
|
|
const isRefreshing = refreshingAccountId === accountId;
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => handleRefreshAccount(row)}
|
|
disabled={isRefreshing}
|
|
className="rounded-xl border border-[var(--line)] bg-white/3 px-3 py-2 text-xs text-[var(--text)] transition-all hover:border-[rgba(106,166,255,0.55)] hover:bg-[rgba(106,166,255,0.08)] active:translate-y-[1px] disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1.5"
|
|
title="Account aktualisieren"
|
|
>
|
|
<IconRefresh className={cn("h-3.5 w-3.5", isRefreshing && "animate-spin")} />
|
|
Refresh
|
|
</button>
|
|
<button
|
|
onClick={() => handleSelectAccount(row)}
|
|
className="rounded-xl border border-[var(--line)] bg-white/3 px-3 py-2 text-xs text-[var(--text)] transition-all hover:border-[rgba(106,166,255,0.55)] hover:bg-[rgba(106,166,255,0.08)] active:translate-y-[1px]"
|
|
>
|
|
Select
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (col === "Account Name") {
|
|
return getAccountDisplayName(row) || "-";
|
|
}
|
|
|
|
if (col === "Platform") {
|
|
return row.account_platform || "-";
|
|
}
|
|
|
|
if (col === "Platform Account ID") {
|
|
return row.account_platform_account_id || "-";
|
|
}
|
|
|
|
if (col === "Market") {
|
|
return row.account_platform_market || "-";
|
|
}
|
|
|
|
if (col === "Account URL") {
|
|
const url = row.account_url;
|
|
if (!url) return "-";
|
|
return (
|
|
<a
|
|
href={url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-blue-500 hover:underline dark:text-blue-400"
|
|
>
|
|
{url.length > 40 ? `${url.substring(0, 40)}...` : url}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
if (col === "Sales") {
|
|
const sales = row.account_sells;
|
|
if (sales === null || sales === undefined) return "-";
|
|
// Format number with thousand separators
|
|
return new Intl.NumberFormat("de-DE").format(sales);
|
|
}
|
|
|
|
if (col === "Last Scan") {
|
|
const lastScan = row.account_updated_at;
|
|
if (!lastScan) return "-";
|
|
try {
|
|
const date = new Date(lastScan);
|
|
return date.toLocaleString("de-DE", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
} catch (e) {
|
|
return "-";
|
|
}
|
|
}
|
|
|
|
if (col === "Owner User ID") {
|
|
return row.account_owner_user_id || "-";
|
|
}
|
|
|
|
return "-";
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-1">
|
|
<div className="flex h-full w-full flex-1 flex-col gap-4 rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-700 dark:bg-neutral-900">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="m-0 text-2xl font-semibold tracking-wide text-[var(--text)]">
|
|
Accounts
|
|
</h1>
|
|
<p className="mt-1.5 mb-0 text-xs text-[var(--muted)]">
|
|
Verwalte deine Plattform-Accounts
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleAddAccount}
|
|
className="flex items-center gap-2 rounded-xl border border-[var(--line)] bg-white/3 px-4 py-2.5 text-xs font-medium text-[var(--text)] transition-all hover:border-[rgba(106,166,255,0.55)] hover:bg-[rgba(106,166,255,0.08)] active:translate-y-[1px]"
|
|
>
|
|
<IconPlus className="h-4 w-4" />
|
|
Add Account
|
|
</button>
|
|
</div>
|
|
|
|
{/* Hilfe-Panel */}
|
|
<div className="relative overflow-hidden rounded-[18px] border border-[var(--line)] bg-gradient-to-b from-white/4 to-white/2 p-4 shadow-[0_10px_30px_rgba(0,0,0,0.35)]">
|
|
<div
|
|
className="pointer-events-none absolute inset-[-1px] opacity-55"
|
|
style={{
|
|
background:
|
|
"radial-gradient(600px 280px at 20% 0%, rgba(106,166,255,0.14), transparent 60%)",
|
|
}}
|
|
/>
|
|
<div className="relative">
|
|
<h2 className="mb-3 text-sm font-semibold text-[var(--text)]">
|
|
Account hinzufügen
|
|
</h2>
|
|
<div className="grid gap-3 text-xs text-[var(--muted)]">
|
|
<div>
|
|
<span className="font-medium text-[var(--text)]">
|
|
eBay Account URL <span className="text-red-500">(Pflichtfeld)</span>
|
|
</span>
|
|
: Gib einfach die eBay-URL zum Verkäuferprofil oder Shop ein. Alle weiteren Informationen (Market, Seller ID, Shop Name) werden automatisch erkannt.
|
|
</div>
|
|
<div>
|
|
<span className="font-medium text-[var(--text)]">
|
|
Market (Auto)
|
|
</span>
|
|
: Wird automatisch aus der URL extrahiert (z.B. DE, US, UK). Du musst nichts eingeben.
|
|
</div>
|
|
<div>
|
|
<span className="font-medium text-[var(--text)]">
|
|
eBay Seller ID (Auto)
|
|
</span>
|
|
: Wird automatisch erkannt. Dies ist die eindeutige Verkäufer-Kennung von eBay und verhindert Duplikate.
|
|
</div>
|
|
<div>
|
|
<span className="font-medium text-[var(--text)]">
|
|
Shop Name (Auto)
|
|
</span>
|
|
: Öffentlich sichtbarer Name des Shops. Wird automatisch aus der URL/Seite extrahiert.
|
|
</div>
|
|
<div>
|
|
<span className="font-medium text-[var(--text)]">
|
|
Sales (Auto)
|
|
</span>
|
|
: Anzahl der verkauften Artikel wird automatisch aus dem eBay-Profil gelesen.
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 rounded-lg border border-[var(--line)] bg-white/2 p-3 text-xs text-[var(--muted)]">
|
|
<span className="font-medium text-[var(--text)]">So funktioniert's:</span>{" "}
|
|
Gib einfach die eBay-URL ein und klicke auf "Account hinzufügen". Das System liest alle notwendigen Informationen automatisch aus. Du musst keine technischen Felder manuell ausfüllen.
|
|
</div>
|
|
|
|
{/* Advanced Toggle */}
|
|
<button
|
|
onClick={() => setShowAdvanced(!showAdvanced)}
|
|
className="mt-4 flex items-center gap-2 text-xs text-[var(--muted)] transition-colors hover:text-[var(--text)]"
|
|
>
|
|
<IconChevronDown
|
|
className={cn(
|
|
"h-4 w-4 transition-transform",
|
|
showAdvanced && "rotate-180"
|
|
)}
|
|
/>
|
|
{showAdvanced ? "Weniger anzeigen" : "Erweitert anzeigen"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Toast Notification */}
|
|
<AnimatePresence>
|
|
{refreshToast.show && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -10 }}
|
|
className={cn(
|
|
"fixed top-4 right-4 z-50 rounded-lg px-4 py-3 text-sm shadow-lg",
|
|
refreshToast.type === "success"
|
|
? "bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400"
|
|
: "bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400"
|
|
)}
|
|
>
|
|
{refreshToast.message}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* Tabelle */}
|
|
<div className="relative overflow-hidden rounded-[18px] border border-[var(--line)] bg-gradient-to-b from-white/4 to-white/2 p-4 shadow-[0_10px_30px_rgba(0,0,0,0.35)]">
|
|
<div
|
|
className="pointer-events-none absolute inset-[-1px] opacity-55"
|
|
style={{
|
|
background:
|
|
"radial-gradient(600px 280px at 20% 0%, rgba(106,166,255,0.14), transparent 60%)",
|
|
}}
|
|
/>
|
|
<div className="relative">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-12 text-sm text-[var(--muted)]">
|
|
Loading accounts...
|
|
</div>
|
|
) : (
|
|
<DataTable columns={columns} data={accounts} renderCell={renderCell} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Add Account Form Modal */}
|
|
<AnimatePresence>
|
|
{showAddForm && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
|
onClick={handleCloseForm}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.95, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.95, opacity: 0 }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
className="relative w-full max-w-2xl rounded-2xl border border-[var(--line)] bg-white p-6 shadow-xl dark:bg-neutral-800"
|
|
>
|
|
{/* Close Button */}
|
|
<button
|
|
onClick={handleCloseForm}
|
|
className="absolute right-4 top-4 rounded-lg p-1.5 text-[var(--muted)] transition-colors hover:bg-neutral-100 hover:text-[var(--text)] dark:hover:bg-neutral-700"
|
|
>
|
|
<IconX className="h-5 w-5" />
|
|
</button>
|
|
|
|
{/* Form Header */}
|
|
<h2 className="mb-4 text-xl font-semibold text-[var(--text)]">
|
|
Add Account
|
|
</h2>
|
|
|
|
{/* Error/Success Messages */}
|
|
{parsingError && (
|
|
<div className="mb-4 rounded-lg bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/20 dark:text-red-400">
|
|
{parsingError}
|
|
</div>
|
|
)}
|
|
{formError && (
|
|
<div className="mb-4 rounded-lg bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/20 dark:text-red-400">
|
|
{formError}
|
|
</div>
|
|
)}
|
|
{formSuccess && (
|
|
<div className="mb-4 rounded-lg bg-green-50 p-3 text-sm text-green-700 dark:bg-green-900/20 dark:text-green-400">
|
|
{formSuccess}
|
|
</div>
|
|
)}
|
|
|
|
{/* Form */}
|
|
<form onSubmit={handleFormSubmit} className="space-y-4">
|
|
{/* Phase 1: URL Input */}
|
|
<div>
|
|
<label className="mb-1.5 block text-sm font-medium text-[var(--text)]">
|
|
eBay Account URL <span className="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="url"
|
|
value={formData.account_url}
|
|
onChange={(e) => handleFormChange("account_url", e.target.value)}
|
|
placeholder="https://www.ebay.de/usr/..."
|
|
required
|
|
disabled={parsing || formLoading}
|
|
className="w-full rounded-lg border border-[var(--line)] bg-white px-3 py-2 text-sm text-[var(--text)] outline-none transition-colors focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-neutral-900"
|
|
/>
|
|
<p className="mt-1.5 text-xs text-[var(--muted)]">
|
|
Füge den Link zum eBay-Verkäuferprofil oder Shop ein. Wir lesen die Account-Daten automatisch aus.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Phase 2: Preview (wenn parsedData vorhanden) */}
|
|
{parsedData && (
|
|
<div className="space-y-4 rounded-lg border border-neutral-200 bg-neutral-50 p-4 dark:border-neutral-700 dark:bg-neutral-800/50">
|
|
<h3 className="text-sm font-semibold text-[var(--text)]">
|
|
Account-Informationen (automatisch erkannt)
|
|
</h3>
|
|
|
|
<div>
|
|
<label className="mb-1.5 block text-xs font-medium text-[var(--muted)]">
|
|
Market (Auto)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={parsedData.market}
|
|
readOnly
|
|
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-[var(--text)] opacity-75 dark:bg-neutral-900 dark:border-neutral-700"
|
|
/>
|
|
<p className="mt-1 text-xs text-[var(--muted)]">
|
|
Automatisch erkannter Marktplatz (z.B. DE oder US).
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-1.5 block text-xs font-medium text-[var(--muted)]">
|
|
eBay Seller ID (Auto)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={parsedData.sellerId}
|
|
readOnly
|
|
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-[var(--text)] opacity-75 dark:bg-neutral-900 dark:border-neutral-700"
|
|
/>
|
|
<p className="mt-1 text-xs text-[var(--muted)]">
|
|
Eindeutige Verkäufer-ID von eBay. Wird für Abgleich und Duplikat-Erkennung verwendet.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-1.5 block text-xs font-medium text-[var(--muted)]">
|
|
Shop Name (Auto)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={parsedData.shopName}
|
|
readOnly
|
|
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-[var(--text)] opacity-75 dark:bg-neutral-900 dark:border-neutral-700"
|
|
/>
|
|
<p className="mt-1 text-xs text-[var(--muted)]">
|
|
Öffentlich sichtbarer Name des Shops auf eBay.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-1.5 block text-xs font-medium text-[var(--muted)]">
|
|
Artikel verkauft (Auto)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={parsedData.stats?.itemsSold ?? "-"}
|
|
readOnly
|
|
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-[var(--text)] opacity-75 dark:bg-neutral-900 dark:border-neutral-700"
|
|
/>
|
|
<p className="mt-1 text-xs text-[var(--muted)]">
|
|
Automatisch aus dem eBay-Profil gelesen.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-1.5 block text-xs font-medium text-[var(--muted)]">
|
|
Sales (Auto)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={parsedData.stats?.itemsSold ? new Intl.NumberFormat("de-DE").format(parsedData.stats.itemsSold) : "-"}
|
|
readOnly
|
|
className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm text-[var(--text)] opacity-75 dark:bg-neutral-900 dark:border-neutral-700"
|
|
/>
|
|
<p className="mt-1 text-xs text-[var(--muted)]">
|
|
Gesamtzahl der verkauften Artikel.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Form Actions */}
|
|
<div className="flex justify-end gap-3 pt-4">
|
|
<button
|
|
type="button"
|
|
onClick={handleCloseForm}
|
|
disabled={parsing || formLoading}
|
|
className="rounded-lg border border-[var(--line)] bg-white px-4 py-2 text-sm font-medium text-[var(--text)] transition-colors hover:bg-neutral-50 disabled:opacity-50 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
|
>
|
|
Cancel
|
|
</button>
|
|
{parsedData ? (
|
|
<button
|
|
type="submit"
|
|
disabled={formLoading}
|
|
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
|
>
|
|
{formLoading ? "Speichern..." : "Account speichern"}
|
|
</button>
|
|
) : (
|
|
<button
|
|
type="submit"
|
|
disabled={parsing || formLoading || !formData.account_url?.trim()}
|
|
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
|
>
|
|
{parsing ? "Lese Accountdaten aus..." : "Account hinzufügen"}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</form>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|