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";
export const dynamic = "force-dynamic";
import { useEffect, useState, useCallback } from "react";
import { Permission } from "../../auth-context";
import { adminApi } from "../../api";

View file

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

View file

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

View file

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

View file

@ -56,10 +56,11 @@ export function LanguageSelector() {
top: "100%",
right: 0,
marginTop: "0.5rem",
backgroundColor: "white",
border: "1px solid #e5e7eb",
backgroundColor: "rgba(255, 255, 255, 0.03)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.08)",
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,
minWidth: "150px",
}}
@ -76,16 +77,18 @@ export function LanguageSelector() {
padding: "0.75rem 1rem",
textAlign: "left",
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",
display: "flex",
alignItems: "center",
gap: "0.5rem",
fontSize: "0.875rem",
fontFamily: "'DM Sans', system-ui, sans-serif",
}}
onMouseEnter={(e) => {
if (locale !== lang.code) {
e.currentTarget.style.backgroundColor = "#f9fafb";
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.05)";
}
}}
onMouseLeave={(e) => {

View file

@ -348,24 +348,33 @@ export function ExchangeDetailsStep({
<div style={styles.tradeSummary}>
{direction === "buy" ? (
<p style={styles.summaryText}>
{t("detailsStep.summaryBuy").split("{sats}")[0].trim()}{" "}
{t("detailsStep.summaryBuy", { sats: "", eur: "" }).split("{sats}")[0].trim()}{" "}
<strong style={styles.satsValue}>
<SatsDisplay sats={satsAmount} />
</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>
</p>
) : (
<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>
{", "}
{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}>
<SatsDisplay sats={satsAmount} />
</strong>
{t("detailsStep.summarySell").split("{sats}")[1]?.trim()}
{t("detailsStep.summarySell", { sats: "", eur: "" }).split("{sats}")[1]?.trim()}
</p>
)}
</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.
@ -30,6 +30,16 @@ export function useAsyncData<T>(
const [isLoading, setIsLoading] = useState(true);
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 () => {
if (!enabled) return;
@ -37,20 +47,20 @@ export function useAsyncData<T>(
setError(null);
try {
const result = await fetcher();
const result = await fetcherRef.current();
setData(result);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to load data";
setError(errorMessage);
if (onError) {
onError(err);
if (onErrorRef.current) {
onErrorRef.current(err);
} else {
console.error("Failed to fetch data:", err);
}
} finally {
setIsLoading(false);
}
}, [fetcher, enabled, onError]);
}, [enabled]);
useEffect(() => {
fetchData();

View file

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

View file

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

View file

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