578 lines
27 KiB
JavaScript
578 lines
27 KiB
JavaScript
"use client";
|
|
import React, { useEffect, useState } from "react";
|
|
import { Sidebar, SidebarBody, SidebarLink } from "./components/sidebar";
|
|
import {
|
|
IconArrowLeft,
|
|
IconBrandTabler,
|
|
IconSettings,
|
|
IconUserBolt,
|
|
IconShoppingBag,
|
|
} from "@tabler/icons-react";
|
|
import { motion } from "motion/react";
|
|
import { cn } from "./lib/utils";
|
|
import { BackgroundRippleEffect } from "./components/layout/BackgroundRippleEffect";
|
|
import { Dashboard } from "./components/dashboard/Dashboard";
|
|
import { AccountsPage } from "./pages/AccountsPage";
|
|
import LogoutButton from "./components/ui/LogoutButton";
|
|
import { OnboardingGate } from "./components/onboarding/OnboardingGate";
|
|
import { SidebarHeader } from "./components/sidebar/SidebarHeader";
|
|
import { useHashRoute } from "./lib/routing";
|
|
import { account, databases, databaseId, usersCollectionId } from "./lib/appwrite";
|
|
import { fetchManagedAccounts } from "./services/accountsService";
|
|
import { useScan } from "./context/ScanContext";
|
|
import ScanningLoader from "./components/ui/ScanningLoader";
|
|
|
|
export default function App() {
|
|
const { route, navigate } = useHashRoute();
|
|
const { scanning, scanProgress } = useScan();
|
|
const [email, setEmail] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [status, setStatus] = useState({ loading: true, authed: false, error: "" });
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
|
|
// Gate-Management State
|
|
const [authUser, setAuthUser] = useState(null);
|
|
const [hasUserDoc, setHasUserDoc] = useState(false);
|
|
const [checkingUserDoc, setCheckingUserDoc] = useState(false);
|
|
const [onboardingLoading, setOnboardingLoading] = useState(false);
|
|
const [onboardingError, setOnboardingError] = useState("");
|
|
const [userExtensionLoad, setUserExtensionLoad] = useState(null); // null = nicht geprüft, true/false = geprüft
|
|
const [hasAccounts, setHasAccounts] = useState(false); // true wenn User Accounts hat
|
|
|
|
// Gate soll angezeigt werden, wenn:
|
|
// 1. User-Dokument nicht existiert ODER
|
|
// 2. User-Dokument existiert, aber user_extension_load = false UND keine Accounts vorhanden
|
|
// Gate wird versteckt, wenn:
|
|
// - User-Dokument existiert UND (user_extension_load = true ODER Accounts vorhanden)
|
|
const showGate = (!hasUserDoc || (hasUserDoc && userExtensionLoad === false && !hasAccounts)) && !checkingUserDoc && authUser !== null;
|
|
|
|
async function checkUserDocument(userId) {
|
|
if (!databases || !databaseId || !usersCollectionId) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
await databases.getDocument(databaseId, usersCollectionId, userId);
|
|
return true; // Dokument existiert
|
|
} catch (e) {
|
|
// 404 bedeutet, dass das Dokument nicht existiert - das ist OK
|
|
if (e.code === 404 || e.type === 'document_not_found') {
|
|
return false; // Dokument existiert nicht
|
|
}
|
|
// Andere Fehler: Loggen, aber nicht als kritischen Fehler behandeln
|
|
console.warn('Fehler beim Prüfen des User-Dokuments:', e.message || e);
|
|
return false; // Im Zweifel als "nicht vorhanden" behandeln
|
|
}
|
|
}
|
|
|
|
async function refreshAuth() {
|
|
setStatus((s) => ({ ...s, loading: true, error: "" }));
|
|
setCheckingUserDoc(true);
|
|
setOnboardingError("");
|
|
|
|
try {
|
|
const user = await account.get(); // wirft Fehler, wenn keine Session
|
|
setAuthUser(user);
|
|
setStatus({ loading: false, authed: true, error: "" });
|
|
|
|
// Prüfe, ob User-Dokument existiert
|
|
const userDocExists = await checkUserDocument(user.$id);
|
|
// #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:'App.jsx:68',message:'refreshAuth: userDocExists check',data:{userId:user.$id,userDocExists},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H2'})}).catch(()=>{});
|
|
// #endregion
|
|
setHasUserDoc(userDocExists);
|
|
// #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:'App.jsx:71',message:'refreshAuth: hasUserDoc set',data:{hasUserDoc:userDocExists,showGateWillBe:!userDocExists && !checkingUserDoc && user !== null},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H2'})}).catch(()=>{});
|
|
// #endregion
|
|
|
|
// Prüfe user_extension_load und Accounts, wenn User-Dokument existiert
|
|
if (userDocExists && databases && databaseId && usersCollectionId) {
|
|
try {
|
|
const userDoc = await databases.getDocument(databaseId, usersCollectionId, user.$id);
|
|
const extensionLoad = userDoc?.user_extension_load === true;
|
|
// #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:'App.jsx:78',message:'refreshAuth: userDoc retrieved for extension check',data:{userExtensionLoad:userDoc?.user_extension_load,userExtensionLoadType:typeof userDoc?.user_extension_load,userExtensionLoadStrictFalse:userDoc?.user_extension_load === false,extensionLoad},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H2'})}).catch(()=>{});
|
|
// #endregion
|
|
setUserExtensionLoad(extensionLoad);
|
|
|
|
// Prüfe auch Accounts
|
|
try {
|
|
const existingAccounts = await fetchManagedAccounts(user.$id);
|
|
const hasAccountsValue = Array.isArray(existingAccounts) && existingAccounts.length > 0;
|
|
setHasAccounts(hasAccountsValue);
|
|
} catch (accountsErr) {
|
|
setHasAccounts(false);
|
|
}
|
|
} catch (docErr) {
|
|
// #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:'App.jsx:82',message:'refreshAuth: error getting userDoc for extension check',data:{error:docErr.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H2'})}).catch(()=>{});
|
|
// #endregion
|
|
// Bei Fehler: Setze auf null (nicht geprüft)
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
}
|
|
} else {
|
|
// Kein User-Dokument → user_extension_load ist nicht relevant
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
}
|
|
|
|
await handoffJwtToExtension();
|
|
} catch (e) {
|
|
setStatus({ loading: false, authed: false, error: "" });
|
|
setAuthUser(null);
|
|
setHasUserDoc(false);
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
} finally {
|
|
setCheckingUserDoc(false);
|
|
}
|
|
}
|
|
|
|
async function login(e) {
|
|
e.preventDefault();
|
|
setStatus((s) => ({ ...s, loading: true, error: "" }));
|
|
setCheckingUserDoc(true);
|
|
setOnboardingError("");
|
|
|
|
try {
|
|
await account.createEmailPasswordSession(email, password);
|
|
const user = await account.get();
|
|
setAuthUser(user);
|
|
setStatus({ loading: false, authed: true, error: "" });
|
|
|
|
// Prüfe, ob User-Dokument existiert
|
|
const userDocExists = await checkUserDocument(user.$id);
|
|
setHasUserDoc(userDocExists);
|
|
|
|
// Prüfe user_extension_load und Accounts, wenn User-Dokument existiert
|
|
if (userDocExists && databases && databaseId && usersCollectionId) {
|
|
try {
|
|
const userDoc = await databases.getDocument(databaseId, usersCollectionId, user.$id);
|
|
const extensionLoad = userDoc?.user_extension_load === true;
|
|
setUserExtensionLoad(extensionLoad);
|
|
|
|
// Prüfe auch Accounts
|
|
try {
|
|
const existingAccounts = await fetchManagedAccounts(user.$id);
|
|
const hasAccountsValue = Array.isArray(existingAccounts) && existingAccounts.length > 0;
|
|
setHasAccounts(hasAccountsValue);
|
|
} catch (accountsErr) {
|
|
setHasAccounts(false);
|
|
}
|
|
} catch (docErr) {
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
}
|
|
} else {
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
}
|
|
|
|
await handoffJwtToExtension();
|
|
} catch (e) {
|
|
setStatus({ loading: false, authed: false, error: "Login fehlgeschlagen" });
|
|
setAuthUser(null);
|
|
setHasUserDoc(false);
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
} finally {
|
|
setCheckingUserDoc(false);
|
|
}
|
|
}
|
|
|
|
async function handleOnboardingStart() {
|
|
// #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:'App.jsx:162',message:'handleOnboardingStart: entry',data:{hasAuthUser:!!authUser,hasDatabases:!!databases,hasDatabaseId:!!databaseId,hasUsersCollectionId:!!usersCollectionId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
if (!authUser || !databases || !databaseId || !usersCollectionId) {
|
|
// #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:'App.jsx:165',message:'handleOnboardingStart: configuration incomplete',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
setOnboardingError("Fehler: Konfiguration unvollständig");
|
|
return;
|
|
}
|
|
|
|
setOnboardingLoading(true);
|
|
setOnboardingError("");
|
|
|
|
try {
|
|
// Prüfe zuerst, ob User-Dokument bereits existiert
|
|
let userDocExists = false;
|
|
try {
|
|
await databases.getDocument(databaseId, usersCollectionId, authUser.$id);
|
|
userDocExists = true;
|
|
// #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:'App.jsx:178',message:'handleOnboardingStart: userDoc exists',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
} catch (e) {
|
|
// Dokument existiert nicht - wird erstellt
|
|
// #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:'App.jsx:182',message:'handleOnboardingStart: userDoc does not exist, will create',data:{error:e.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
}
|
|
|
|
if (!userDocExists) {
|
|
// #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:'App.jsx:187',message:'handleOnboardingStart: creating userDoc',data:{userId:authUser.$id},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
await databases.createDocument(
|
|
databaseId,
|
|
usersCollectionId,
|
|
authUser.$id, // Document-ID = Auth-User-ID
|
|
{
|
|
user_name: authUser.name || "User",
|
|
user_extension_load: false
|
|
}
|
|
);
|
|
// Erfolg: User-Dokument erstellt
|
|
setHasUserDoc(true);
|
|
// user_extension_load ist false beim Erstellen (siehe payload)
|
|
setUserExtensionLoad(false);
|
|
// #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:'App.jsx:202',message:'handleOnboardingStart: userDoc created, setting hasUserDoc=true',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
|
|
// Prüfe, ob User bereits Accounts hat (nach dem Erstellen des User-Dokuments)
|
|
// #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:'App.jsx:207',message:'handleOnboardingStart: checking for accounts after userDoc creation',data:{userId:authUser.$id},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
try {
|
|
const existingAccounts = await fetchManagedAccounts(authUser.$id);
|
|
const hasAccountsValue = Array.isArray(existingAccounts) && existingAccounts.length > 0;
|
|
setHasAccounts(hasAccountsValue);
|
|
// #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:'App.jsx:212',message:'handleOnboardingStart: accounts check result after creation',data:{hasAccounts:hasAccountsValue,accountsCount:existingAccounts?.length || 0},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
} catch (accountsErr) {
|
|
// #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:'App.jsx:216',message:'handleOnboardingStart: error checking accounts after creation',data:{error:accountsErr.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
setHasAccounts(false);
|
|
}
|
|
} else {
|
|
// Dokument existiert bereits - prüfe user_extension_load
|
|
// #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:'App.jsx:206',message:'handleOnboardingStart: userDoc exists, checking extension load',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
const userDoc = await databases.getDocument(databaseId, usersCollectionId, authUser.$id);
|
|
const extensionLoad = userDoc?.user_extension_load === true;
|
|
setHasUserDoc(true);
|
|
setUserExtensionLoad(extensionLoad);
|
|
// #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:'App.jsx:212',message:'handleOnboardingStart: userDoc retrieved, setting hasUserDoc=true',data:{extensionLoad},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
}
|
|
|
|
// Prüfe, ob User Accounts hat (nach dem Erstellen/Prüfen des User-Dokuments)
|
|
// #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:'App.jsx:217',message:'handleOnboardingStart: checking for accounts',data:{userId:authUser.$id},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
try {
|
|
const existingAccounts = await fetchManagedAccounts(authUser.$id);
|
|
const hasAccountsValue = Array.isArray(existingAccounts) && existingAccounts.length > 0;
|
|
setHasAccounts(hasAccountsValue);
|
|
// #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:'App.jsx:222',message:'handleOnboardingStart: accounts check result',data:{hasAccounts:hasAccountsValue,accountsCount:existingAccounts?.length || 0},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
} catch (accountsErr) {
|
|
// #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:'App.jsx:226',message:'handleOnboardingStart: error checking accounts',data:{error:accountsErr.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
setHasAccounts(false);
|
|
}
|
|
|
|
setOnboardingError("");
|
|
// #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:'App.jsx:232',message:'handleOnboardingStart: success, showGate will be',data:{hasUserDoc:true,userExtensionLoad,hasAccounts,showGateWillBe:(hasUserDoc && userExtensionLoad === false && !hasAccounts)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
} catch (e) {
|
|
// #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:'App.jsx:220',message:'handleOnboardingStart: error caught',data:{errorCode:e.code,errorType:e.type,errorMessage:e.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
// 409 Conflict bedeutet, dass das Dokument bereits existiert
|
|
// Das ist ok, da wir idempotent sein wollen
|
|
if (e.code === 409 || e.type === 'document_already_exists') {
|
|
// #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:'App.jsx:225',message:'handleOnboardingStart: document already exists (409)',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
setHasUserDoc(true);
|
|
// Prüfe user_extension_load für existierendes Dokument
|
|
if (authUser && databases && databaseId && usersCollectionId) {
|
|
try {
|
|
const userDoc = await databases.getDocument(databaseId, usersCollectionId, authUser.$id);
|
|
const extensionLoad = userDoc?.user_extension_load === true;
|
|
setUserExtensionLoad(extensionLoad);
|
|
|
|
// Prüfe auch Accounts
|
|
try {
|
|
const existingAccounts = await fetchManagedAccounts(authUser.$id);
|
|
const hasAccountsValue = Array.isArray(existingAccounts) && existingAccounts.length > 0;
|
|
setHasAccounts(hasAccountsValue);
|
|
} catch (accountsErr) {
|
|
setHasAccounts(false);
|
|
}
|
|
} catch (docErr) {
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
}
|
|
} else {
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
}
|
|
setOnboardingError("");
|
|
} else if (e.code === 401 || e.type === 'general_unauthorized_scope') {
|
|
// 401 Unauthorized: Permissions nicht richtig gesetzt
|
|
setOnboardingError(
|
|
"Berechtigung verweigert. Bitte prüfe in Appwrite, ob die users Collection die richtigen Permissions hat. " +
|
|
"Siehe setup/USERS_COLLECTION_SETUP.md für Details."
|
|
);
|
|
} else {
|
|
// Andere Fehler anzeigen
|
|
setOnboardingError(e.message || "Fehler beim Erstellen des Profils. Bitte versuche es erneut.");
|
|
}
|
|
} finally {
|
|
// #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:'App.jsx:245',message:'handleOnboardingStart: finally, setting loading=false',data:{hasUserDoc,userExtensionLoad,showGateWillBe:(!hasUserDoc || (hasUserDoc && userExtensionLoad === false)) && !checkingUserDoc && authUser !== null},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'H1'})}).catch(()=>{});
|
|
// #endregion
|
|
setOnboardingLoading(false);
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
setStatus((s) => ({ ...s, loading: true, error: "" }));
|
|
try {
|
|
await account.deleteSession("current");
|
|
} catch {}
|
|
setStatus({ loading: false, authed: false, error: "" });
|
|
setAuthUser(null);
|
|
setHasUserDoc(false);
|
|
setUserExtensionLoad(null);
|
|
setHasAccounts(false);
|
|
setOnboardingError("");
|
|
|
|
// Extension informieren: Token weg
|
|
sendToExtension({ type: "AUTH_CLEARED" });
|
|
}
|
|
|
|
async function handoffJwtToExtension() {
|
|
// JWT ist userbezogen und kann serverseitig validiert werden
|
|
const jwt = await account.createJWT();
|
|
sendToExtension({ type: "AUTH_JWT", jwt: jwt.jwt });
|
|
}
|
|
|
|
function sendToExtension(payload) {
|
|
// Sende Nachricht über window.postMessage (keine Extension ID nötig)
|
|
// Das Content Script der Extension lauscht darauf
|
|
window.postMessage(
|
|
{
|
|
source: "eship-webapp",
|
|
...payload,
|
|
},
|
|
"*"
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
refreshAuth();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const links = [
|
|
{
|
|
label: "Dashboard",
|
|
href: "#/",
|
|
icon: (
|
|
<IconBrandTabler className="h-5 w-5 shrink-0 text-neutral-700 dark:text-neutral-200" />
|
|
),
|
|
onClick: (e) => {
|
|
e.preventDefault();
|
|
navigate("/");
|
|
},
|
|
},
|
|
{
|
|
label: "Accounts",
|
|
href: "#/accounts",
|
|
icon: (
|
|
<IconShoppingBag className="h-5 w-5 shrink-0 text-neutral-700 dark:text-neutral-200" />
|
|
),
|
|
disabled: scanning,
|
|
onClick: (e) => {
|
|
e.preventDefault();
|
|
navigate("/accounts");
|
|
},
|
|
},
|
|
{
|
|
label: "Profile",
|
|
href: "#",
|
|
icon: (
|
|
<IconUserBolt className="h-5 w-5 shrink-0 text-neutral-700 dark:text-neutral-200" />
|
|
),
|
|
},
|
|
{
|
|
label: "Settings",
|
|
href: "#",
|
|
icon: (
|
|
<IconSettings className="h-5 w-5 shrink-0 text-neutral-700 dark:text-neutral-200" />
|
|
),
|
|
},
|
|
{
|
|
label: "Logout",
|
|
href: "#",
|
|
icon: (
|
|
<IconArrowLeft className="h-5 w-5 shrink-0 text-neutral-700 dark:text-neutral-200" />
|
|
),
|
|
onClick: (e) => {
|
|
e.preventDefault();
|
|
logout();
|
|
},
|
|
},
|
|
];
|
|
|
|
// Rendere Content basierend auf Route
|
|
const renderContent = () => {
|
|
if (route === "/accounts") {
|
|
return <AccountsPage />;
|
|
}
|
|
// Default: Dashboard
|
|
return <Dashboard />;
|
|
};
|
|
|
|
return (
|
|
<div style={styles.page}>
|
|
{!status.authed && (
|
|
<div style={styles.lockOverlay}>
|
|
<div style={styles.card}>
|
|
<div style={styles.title}>Login</div>
|
|
<div style={styles.sub}>Appwrite Session erforderlich</div>
|
|
|
|
<form onSubmit={login} style={styles.form}>
|
|
<input
|
|
style={styles.input}
|
|
placeholder="E-Mail"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
autoComplete="email"
|
|
/>
|
|
<input
|
|
style={styles.input}
|
|
placeholder="Passwort"
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
autoComplete="current-password"
|
|
/>
|
|
<button style={styles.button} disabled={status.loading}>
|
|
{status.loading ? "..." : "Anmelden"}
|
|
</button>
|
|
{status.error && <div style={styles.error}>{status.error}</div>}
|
|
</form>
|
|
|
|
<div style={styles.hint}>
|
|
Nach Login wird der Sperrbildschirm entfernt und die Extension erhaelt ein JWT.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{status.authed && (
|
|
<>
|
|
{showGate && (
|
|
<OnboardingGate
|
|
userName={authUser?.name || "User"}
|
|
onStart={handleOnboardingStart}
|
|
loading={onboardingLoading}
|
|
error={onboardingError}
|
|
initialPhase={userExtensionLoad === false ? "extension" : "welcome"}
|
|
/>
|
|
)}
|
|
<div style={{ display: showGate ? "none" : "block" }}>
|
|
<BackgroundRippleEffect />
|
|
<div
|
|
className={cn(
|
|
"flex w-full flex-1 flex-col overflow-hidden rounded-md border border-neutral-200 bg-gray-100 md:flex-row dark:border-neutral-700 dark:bg-neutral-800",
|
|
"h-screen relative z-10"
|
|
)}>
|
|
{!scanning && (
|
|
<Sidebar open={sidebarOpen} setOpen={setSidebarOpen} animate={true}>
|
|
<SidebarBody className="justify-between gap-10">
|
|
<div className="flex flex-1 flex-col overflow-x-hidden overflow-y-auto">
|
|
<SidebarHeader />
|
|
<div className="mt-8 flex flex-col gap-2">
|
|
{links.map((link, idx) => (
|
|
<SidebarLink key={idx} link={link} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<LogoutButton onClick={(e) => {
|
|
e.preventDefault();
|
|
logout();
|
|
}} />
|
|
</div>
|
|
</SidebarBody>
|
|
</Sidebar>
|
|
)}
|
|
{renderContent()}
|
|
</div>
|
|
{scanning && (
|
|
<ScanningLoader percent={scanProgress?.percent ?? 0} />
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const styles = {
|
|
page: {
|
|
minHeight: "100vh",
|
|
background: "#0b0f19",
|
|
color: "#e7aaf0",
|
|
fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif",
|
|
},
|
|
lockOverlay: {
|
|
position: "fixed",
|
|
inset: 0,
|
|
background: "rgba(0,0,0,0.70)",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
padding: 18,
|
|
zIndex: 9999,
|
|
},
|
|
card: {
|
|
width: "100%",
|
|
maxWidth: 420,
|
|
background: "rgba(255,255,255,0.07)",
|
|
border: "1px solid rgba(255,255,255,0.12)",
|
|
borderRadius: 18,
|
|
padding: 20,
|
|
backdropFilter: "blur(10px)",
|
|
},
|
|
title: { fontSize: 20, fontWeight: 800 },
|
|
sub: { marginTop: 6, opacity: 0.8, fontSize: 13 },
|
|
form: { marginTop: 14, display: "grid", gap: 10 },
|
|
input: {
|
|
height: 42,
|
|
borderRadius: 12,
|
|
border: "1px solid rgba(255,255,255,0.16)",
|
|
outline: "none",
|
|
padding: "0 12px",
|
|
background: "rgba(0,0,0,0.20)",
|
|
color: "#e7aaf0",
|
|
},
|
|
button: {
|
|
height: 42,
|
|
borderRadius: 12,
|
|
border: "0",
|
|
cursor: "pointer",
|
|
fontWeight: 800,
|
|
background: "#e7aaf0",
|
|
color: "#0b0f19",
|
|
},
|
|
error: { marginTop: 6, color: "#ffb3b3", fontSize: 13 },
|
|
hint: { marginTop: 12, opacity: 0.75, fontSize: 12, lineHeight: 1.4 },
|
|
};
|