pretty decent state
This commit is contained in:
parent
63a4b0f8a2
commit
f6c552cefd
10 changed files with 75 additions and 42 deletions
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ export default function ProfilePage() {
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingProfile(false);
|
setIsLoadingProfile(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && isAuthorized) {
|
if (user && isAuthorized) {
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue