diff --git a/apps/backoffice-tokenization/messages/en.json b/apps/backoffice-tokenization/messages/en.json index f181ce4..872fcd4 100644 --- a/apps/backoffice-tokenization/messages/en.json +++ b/apps/backoffice-tokenization/messages/en.json @@ -22,7 +22,9 @@ }, "nav": { "campaigns": "Campaigns", - "roi": "Return on Investment" + "roi": "Return on Investment", + "documentation": "Documentation", + "documentationTooltip": "View documentation" }, "home": { "heroTitle": "Entrepreneur Support", diff --git a/apps/backoffice-tokenization/messages/es.json b/apps/backoffice-tokenization/messages/es.json index f190cff..d1c3375 100644 --- a/apps/backoffice-tokenization/messages/es.json +++ b/apps/backoffice-tokenization/messages/es.json @@ -22,7 +22,9 @@ }, "nav": { "campaigns": "Campañas", - "roi": "Retorno de Inversión" + "roi": "Retorno de Inversión", + "documentation": "Documentación", + "documentationTooltip": "Ver documentación" }, "home": { "heroTitle": "Apoyo al emprendedor", diff --git a/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx b/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx index 2bd22ac..3a589ca 100644 --- a/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx +++ b/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx @@ -25,18 +25,18 @@ const logo: AppSidebarLogoConfig = { href: "/", }; -const footerItems: AppSidebarFooterItem[] = [ - { - label: "Documentation", - icon: BookOpen, - href: "https://interactuar.gitbook.io/interactuar-x-trustless/", - tooltip: "View documentation", - }, -]; - export function AppSidebar() { const t = useTranslations("nav"); + const footerItems: AppSidebarFooterItem[] = [ + { + label: t("documentation"), + icon: BookOpen, + href: "https://interactuar.gitbook.io/interactuar-x-trustless/", + tooltip: t("documentationTooltip"), + }, + ]; + const navItems: AppSidebarNavItem[] = [ { label: t("campaigns"), diff --git a/apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts b/apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts index 5212adf..7da6ed5 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts +++ b/apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -67,6 +67,7 @@ function createCampaignSchema(t: (key: string) => string) { // --- LocalStorage persistence --- interface FlowState { + step: number; campaign: CreateCampaignFormValues | null; escrowContractId: string | null; escrowEngagementId: string | null; @@ -78,6 +79,7 @@ interface FlowState { function emptyFlowState(): FlowState { return { + step: 1, campaign: null, escrowContractId: null, escrowEngagementId: null, @@ -118,7 +120,9 @@ export function useCreateCampaign() { const queryClient = useQueryClient(); const { walletAddress } = useWalletContext(); const { deployEscrow } = useEscrowsMutations(); - const [step, setStep] = useState(1); + + const savedFlow = useMemo(() => loadFlowState(), []); + const [step, setStep] = useState(() => savedFlow.step ?? 1); const deployPhaseLabels = useMemo(() => [ t("deployPhase1"), @@ -127,9 +131,11 @@ export function useCreateCampaign() { const campaignSchema = useMemo(() => createCampaignSchema(t), [t]); - // --- Escrow state (Step 2) --- - const [escrowStatus, setEscrowStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); - const [escrowContractId, setEscrowContractId] = useState(null); + // --- Escrow state (Step 2) — restored from localStorage --- + const [escrowStatus, setEscrowStatus] = useState<"idle" | "loading" | "success" | "error">( + savedFlow.escrowContractId ? "success" : "idle", + ); + const [escrowContractId, setEscrowContractId] = useState(savedFlow.escrowContractId); const [escrowError, setEscrowError] = useState(null); // --- Deploy state (Step 3) --- @@ -138,10 +144,10 @@ export function useCreateCampaign() { ); const [deployFailedAt, setDeployFailedAt] = useState(null); - // --- Form --- + // --- Form — default values restored from localStorage --- const form = useForm({ resolver: zodResolver(campaignSchema), - defaultValues: { + defaultValues: savedFlow.campaign ?? { name: "", description: "", poolSize: "" as unknown as number, @@ -153,6 +159,18 @@ export function useCreateCampaign() { mode: "onChange", }); + // Persist form values and step to localStorage continuously + useEffect(() => { + const subscription = form.watch((values) => { + saveFlowState({ campaign: values as CreateCampaignFormValues }); + }); + return () => subscription.unsubscribe(); + }, [form]); + + useEffect(() => { + saveFlowState({ step }); + }, [step]); + // --- Step navigation --- const nextStep = async () => { if (step === 1) { diff --git a/apps/backoffice-tokenization/src/lib/utils.ts b/apps/backoffice-tokenization/src/lib/utils.ts index bbb5474..5c16b04 100644 --- a/apps/backoffice-tokenization/src/lib/utils.ts +++ b/apps/backoffice-tokenization/src/lib/utils.ts @@ -5,3 +5,11 @@ const SOROBAN_DECIMAL_SCALE = 1e7; export function fromStroops(stroops: number | string): number { return Number(stroops) / SOROBAN_DECIMAL_SCALE; } + +export function formatCurrency(amount: number, currency?: string): string { + const formatted = new Intl.NumberFormat("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(amount); + return currency ? `${currency} ${formatted}` : formatted; +}