pretty decent state

This commit is contained in:
counterweight 2025-12-26 18:49:00 +01:00
parent 63a4b0f8a2
commit f6c552cefd
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
10 changed files with 75 additions and 42 deletions

View file

@ -1,5 +1,7 @@
"use client"; "use client";
export const dynamic = "force-dynamic";
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
import { Permission } from "../../auth-context"; import { Permission } from "../../auth-context";
import { adminApi } from "../../api"; import { adminApi } from "../../api";

View file

@ -1,5 +1,7 @@
"use client"; "use client";
export const dynamic = "force-dynamic";
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
import { Permission } from "../../auth-context"; import { Permission } from "../../auth-context";
import { adminApi } from "../../api"; import { adminApi } from "../../api";

View file

@ -1,5 +1,7 @@
"use client"; "use client";
export const dynamic = "force-dynamic";
import { useState } from "react"; import { useState } from "react";
import { Permission } from "../../auth-context"; import { Permission } from "../../auth-context";
import { adminApi } from "../../api"; import { adminApi } from "../../api";

View file

@ -1,5 +1,7 @@
"use client"; "use client";
export const dynamic = "force-dynamic";
import { useEffect, useState, useCallback, CSSProperties } from "react"; import { useEffect, useState, useCallback, CSSProperties } from "react";
import { Permission } from "../../auth-context"; import { Permission } from "../../auth-context";
import { adminApi } from "../../api"; import { adminApi } from "../../api";

View file

@ -56,10 +56,11 @@ export function LanguageSelector() {
top: "100%", top: "100%",
right: 0, right: 0,
marginTop: "0.5rem", marginTop: "0.5rem",
backgroundColor: "white", backgroundColor: "rgba(255, 255, 255, 0.03)",
border: "1px solid #e5e7eb", backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "0.5rem", borderRadius: "0.5rem",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)", boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
zIndex: 1000, zIndex: 1000,
minWidth: "150px", minWidth: "150px",
}} }}
@ -76,16 +77,18 @@ export function LanguageSelector() {
padding: "0.75rem 1rem", padding: "0.75rem 1rem",
textAlign: "left", textAlign: "left",
border: "none", border: "none",
backgroundColor: locale === lang.code ? "#f3f4f6" : "transparent", backgroundColor: locale === lang.code ? "rgba(255, 255, 255, 0.05)" : "transparent",
color: "rgba(255, 255, 255, 0.7)",
cursor: "pointer", cursor: "pointer",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "0.5rem", gap: "0.5rem",
fontSize: "0.875rem", fontSize: "0.875rem",
fontFamily: "'DM Sans', system-ui, sans-serif",
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
if (locale !== lang.code) { if (locale !== lang.code) {
e.currentTarget.style.backgroundColor = "#f9fafb"; e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.05)";
} }
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {

View file

@ -348,24 +348,33 @@ export function ExchangeDetailsStep({
<div style={styles.tradeSummary}> <div style={styles.tradeSummary}>
{direction === "buy" ? ( {direction === "buy" ? (
<p style={styles.summaryText}> <p style={styles.summaryText}>
{t("detailsStep.summaryBuy").split("{sats}")[0].trim()}{" "} {t("detailsStep.summaryBuy", { sats: "", eur: "" }).split("{sats}")[0].trim()}{" "}
<strong style={styles.satsValue}> <strong style={styles.satsValue}>
<SatsDisplay sats={satsAmount} /> <SatsDisplay sats={satsAmount} />
</strong> </strong>
{", "} {", "}
{t("detailsStep.summaryBuy").split("{sats}")[1]?.split("{eur}")[0]?.trim()}{" "} {t("detailsStep.summaryBuy", { sats: "", eur: "" })
.split("{sats}")[1]
?.split("{eur}")[0]
?.trim()}{" "}
<strong>{formatEur(eurAmount)}</strong> <strong>{formatEur(eurAmount)}</strong>
</p> </p>
) : ( ) : (
<p style={styles.summaryText}> <p style={styles.summaryText}>
{t("detailsStep.summarySell").split("{sats}")[0]?.split("{eur}")[0]?.trim()}{" "} {t("detailsStep.summarySell", { sats: "", eur: "" })
.split("{sats}")[0]
?.split("{eur}")[0]
?.trim()}{" "}
<strong>{formatEur(eurAmount)}</strong> <strong>{formatEur(eurAmount)}</strong>
{", "} {", "}
{t("detailsStep.summarySell").split("{sats}")[0]?.split("{eur}")[1]?.trim()}{" "} {t("detailsStep.summarySell", { sats: "", eur: "" })
.split("{sats}")[0]
?.split("{eur}")[1]
?.trim()}{" "}
<strong style={styles.satsValue}> <strong style={styles.satsValue}>
<SatsDisplay sats={satsAmount} /> <SatsDisplay sats={satsAmount} />
</strong> </strong>
{t("detailsStep.summarySell").split("{sats}")[1]?.trim()} {t("detailsStep.summarySell", { sats: "", eur: "" }).split("{sats}")[1]?.trim()}
</p> </p>
)} )}
</div> </div>

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
/** /**
* Hook for fetching async data with loading and error states. * Hook for fetching async data with loading and error states.
@ -30,6 +30,16 @@ export function useAsyncData<T>(
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Use ref to store the latest fetcher function to avoid dependency issues
const fetcherRef = useRef(fetcher);
const onErrorRef = useRef(onError);
// Update refs when values change
useEffect(() => {
fetcherRef.current = fetcher;
onErrorRef.current = onError;
}, [fetcher, onError]);
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
if (!enabled) return; if (!enabled) return;
@ -37,20 +47,20 @@ export function useAsyncData<T>(
setError(null); setError(null);
try { try {
const result = await fetcher(); const result = await fetcherRef.current();
setData(result); setData(result);
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to load data"; const errorMessage = err instanceof Error ? err.message : "Failed to load data";
setError(errorMessage); setError(errorMessage);
if (onError) { if (onErrorRef.current) {
onError(err); onErrorRef.current(err);
} else { } else {
console.error("Failed to fetch data:", err); console.error("Failed to fetch data:", err);
} }
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}, [fetcher, enabled, onError]); }, [enabled]);
useEffect(() => { useEffect(() => {
fetchData(); fetchData();

View file

@ -94,7 +94,7 @@ export default function ProfilePage() {
} finally { } finally {
setIsLoadingProfile(false); setIsLoadingProfile(false);
} }
}, []); }, [t]);
useEffect(() => { useEffect(() => {
if (user && isAuthorized) { if (user && isAuthorized) {

View file

@ -35,33 +35,36 @@ function SignupContent() {
} }
}, [user, router]); }, [user, router]);
const checkInvite = useCallback(async (code: string) => { const checkInvite = useCallback(
if (!code.trim()) { async (code: string) => {
setInviteValid(null); if (!code.trim()) {
setInviteError(""); setInviteValid(null);
return;
}
setIsCheckingInvite(true);
setInviteError("");
try {
const response = await invitesApi.checkInvite(code.trim());
if (response.valid) {
setInviteValid(true);
setInviteError(""); setInviteError("");
} else { return;
setInviteValid(false);
setInviteError(response.error || t("signup.invalidInviteCode"));
} }
} catch {
setInviteValid(false); setIsCheckingInvite(true);
setInviteError(t("signup.failedToVerify")); setInviteError("");
} finally {
setIsCheckingInvite(false); try {
} const response = await invitesApi.checkInvite(code.trim());
}, []);
if (response.valid) {
setInviteValid(true);
setInviteError("");
} else {
setInviteValid(false);
setInviteError(response.error || t("signup.invalidInviteCode"));
}
} catch {
setInviteValid(false);
setInviteError(t("signup.failedToVerify"));
} finally {
setIsCheckingInvite(false);
}
},
[t]
);
// Check invite code on mount if provided in URL // Check invite code on mount if provided in URL
useEffect(() => { useEffect(() => {

View file

@ -14,8 +14,8 @@ import {
export const authFormStyles: Record<string, CSSProperties> = { export const authFormStyles: Record<string, CSSProperties> = {
main: { main: {
...layoutStyles.main,
...layoutStyles.contentCentered, ...layoutStyles.contentCentered,
minHeight: "100vh",
padding: "1rem", padding: "1rem",
}, },
container: { container: {