Merge remote changes and update project files

This commit is contained in:
2026-01-26 06:48:58 +01:00
24 changed files with 2943 additions and 850 deletions

View File

@@ -18,9 +18,13 @@ 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: "" });
@@ -32,8 +36,15 @@ export default function App() {
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
const showGate = !hasUserDoc && !checkingUserDoc && authUser !== null;
// 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) {
@@ -66,13 +77,53 @@ export default function App() {
// 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);
}
@@ -94,18 +145,50 @@ export default function App() {
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;
}
@@ -114,38 +197,147 @@ export default function App() {
setOnboardingError("");
try {
await databases.createDocument(
databaseId,
usersCollectionId,
authUser.$id, // Document-ID = Auth-User-ID
{
user_name: authUser.name || "User"
}
);
// Erfolg: User-Dokument erstellt
setHasUserDoc(true);
setOnboardingError("");
} catch (e) {
// 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') {
setHasUserDoc(true);
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.");
// 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);
}
} finally {
setOnboardingLoading(false);
}
}
async function logout() {
setStatus((s) => ({ ...s, loading: true, error: "" }));
@@ -153,9 +345,11 @@ export default function App() {
await account.deleteSession("current");
} catch {}
setStatus({ loading: false, authed: false, error: "" });
setAuthUser(null);
setHasUserDoc(false);
setOnboardingError("");
setAuthUser(null);
setHasUserDoc(false);
setUserExtensionLoad(null);
setHasAccounts(false);
setOnboardingError("");
// Extension informieren: Token weg
sendToExtension({ type: "AUTH_CLEARED" });
@@ -202,6 +396,7 @@ export default function App() {
icon: (
<IconShoppingBag className="h-5 w-5 shrink-0 text-neutral-700 dark:text-neutral-200" />
),
disabled: scanning,
onClick: (e) => {
e.preventDefault();
navigate("/accounts");
@@ -288,6 +483,7 @@ export default function App() {
onStart={handleOnboardingStart}
loading={onboardingLoading}
error={onboardingError}
initialPhase={userExtensionLoad === false ? "extension" : "welcome"}
/>
)}
<div style={{ display: showGate ? "none" : "block" }}>
@@ -297,26 +493,31 @@ export default function App() {
"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"
)}>
<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} />
))}
{!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>
<div>
<LogoutButton onClick={(e) => {
e.preventDefault();
logout();
}} />
</div>
</SidebarBody>
</Sidebar>
<div>
<LogoutButton onClick={(e) => {
e.preventDefault();
logout();
}} />
</div>
</SidebarBody>
</Sidebar>
)}
{renderContent()}
</div>
{scanning && (
<ScanningLoader percent={scanProgress?.percent ?? 0} />
)}
</div>
</>
)}