Convert Server from submodule to normal files
This commit is contained in:
656
Server/src/pages/AccountsPage.jsx
Normal file
656
Server/src/pages/AccountsPage.jsx
Normal file
@@ -0,0 +1,656 @@
|
||||
"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;
|
||||
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'AccountsPage.jsx:93',message:'handleRefreshAccount: update payload',data:{payload:updatePayload},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
// 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
|
||||
|
||||
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;
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'AccountsPage.jsx:193',message:'handleFormSubmit: parsedData before save',data:{hasStats:!!parsedData.stats,itemsSold:parsedData.stats?.itemsSold,accountSellsValue},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{});
|
||||
// #endregion
|
||||
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",
|
||||
"Status",
|
||||
"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 === "Status") {
|
||||
return row.account_status || "-";
|
||||
}
|
||||
|
||||
if (col === "Last Scan") {
|
||||
const lastScan = row.account_last_scan_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)]">
|
||||
Status (Auto)
|
||||
</span>
|
||||
: Wird automatisch auf "active" gesetzt. Du musst nichts eingeben.
|
||||
</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)]">
|
||||
Status (Auto)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={parsedData.status}
|
||||
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)]">
|
||||
Interner Status. Normalerweise "active".
|
||||
</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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user