From 2dd3f9a3b201fa87f0ab4ada20cdd82a25add289 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Thu, 30 Jul 2026 16:05:25 +0330 Subject: [PATCH 1/3] refactor: replace legacy localStorage submission flag with sessionStorage-based grace period and remove hardcoded dev authentication tokens --- Dockerfile | 2 + next.config.ts | 10 +++-- src/app/layout.tsx | 10 ++++- src/app/questions-list/page.tsx | 36 +++++++-------- src/lib/get-submit-path.ts | 35 ++++++++++----- src/lib/http.ts | 9 ++-- src/lib/match-start-grace.ts | 75 ++++++++++++++++++++++++++++++++ src/translations/locales/en.json | 5 ++- src/translations/locales/fa.json | 5 ++- 9 files changed, 145 insertions(+), 42 deletions(-) create mode 100644 src/lib/match-start-grace.ts diff --git a/Dockerfile b/Dockerfile index 64d8a57..8b17faa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,9 @@ WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ARG NEXT_PUBLIC_API_BASE_URL +ARG NEXT_PUBLIC_SECURITY_KEY ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_SECURITY_KEY=$NEXT_PUBLIC_SECURITY_KEY RUN npm run build FROM base AS runner diff --git a/next.config.ts b/next.config.ts index 95db85b..1c1ba19 100644 --- a/next.config.ts +++ b/next.config.ts @@ -58,12 +58,16 @@ const nextConfig: NextConfig = { ], }, { - // Static pages – moderate cache - source: "/:path((?!api/).*)", + // Application HTML pages – no-store to prevent HTML/shell caching in WebView + source: "/:path((?!api/|_next/|fonts/|assets/).*)", headers: [ { key: "Cache-Control", - value: "public, max-age=3600, stale-while-revalidate=86400", + value: "no-store, no-cache, must-revalidate, max-age=0", + }, + { + key: "Pragma", + value: "no-cache", }, ], }, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b176178..b0fe670 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -24,6 +24,14 @@ const amiri = Amiri({ fallback: ["Arial", "sans-serif"], }); +const isDevelopment = process.env.NODE_ENV !== "production"; + +// Never ship a fallback token to production: without it a real user whose +// Flutter token has not been injected yet would silently browse a test account. +const developmentFallbackToken = isDevelopment + ? (process.env.NEXT_PUBLIC_DEFAULT_TOKEN ?? "") + : ""; + export const metadata: Metadata = { title: "Habib Marriage", description: "Islamic Marriage Platform", @@ -117,7 +125,7 @@ export default function RootLayout({ } }, get: function() { - return this._habib_token || readCookie(HABIB_TOKEN_COOKIE) || sessionStorage.getItem(HABIB_TOKEN_COOKIE) || '${process.env.NEXT_PUBLIC_DEFAULT_TOKEN || "f3a7543b44ef0a713d1ee0d4f7866b3825cf1308"}'; + return this._habib_token || readCookie(HABIB_TOKEN_COOKIE) || sessionStorage.getItem(HABIB_TOKEN_COOKIE) || undefined; } }); } diff --git a/src/app/questions-list/page.tsx b/src/app/questions-list/page.tsx index 80c3a1b..4727aef 100644 --- a/src/app/questions-list/page.tsx +++ b/src/app/questions-list/page.tsx @@ -18,6 +18,10 @@ import { type QuestionListItem, } from "@/data/question-data"; import { hasQuestionAnswerValue } from "@/components/questions/question-answer-storage"; +import { + clearMatchStartGrace, + markMatchStarted, +} from "@/lib/match-start-grace"; import { useStartMarriageMatchMutation } from "@/hooks/marriage/use-match-start"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { useMarriageSectionsQuery } from "@/hooks/marriage/use-sections"; @@ -36,16 +40,13 @@ export default function QuestionsListPage() { }); const startMatchMutation = useStartMarriageMatchMutation({ onSuccess: () => { - if (typeof window !== "undefined") { - localStorage.setItem("match_submitted", "true"); - } + markMatchStarted(); router.push(localizePath("/finding-match", locale)); }, onError: () => { - if (typeof window !== "undefined") { - localStorage.setItem("match_submitted", "true"); - } - router.push(localizePath("/finding-match", locale)); + // Never pretend the request went through – the user stays here and can + // retry instead of being parked on the waiting screen forever. + clearMatchStartGrace(); }, }); const [isOptionalInfoSheetOpen, setIsOptionalInfoSheetOpen] = useState(false); @@ -114,20 +115,11 @@ export default function QuestionsListPage() { }, [questionListItems, sectionProgressBySlug]); const handleStartMatch = () => { - if (typeof window !== "undefined") { - localStorage.setItem("match_submitted", "true"); - } - - if (!canStartMatch) { - router.push(localizePath("/finding-match", locale)); + if (isStartMatchDisabled) { return; } - startMatchMutation.mutate(undefined, { - onSettled: () => { - router.push(localizePath("/finding-match", locale)); - }, - }); + startMatchMutation.mutate(); }; return ( @@ -244,6 +236,14 @@ export default function QuestionsListPage() { style={{ paddingBottom: "calc(16px + var(--safe-bottom))" }} className="fixed inset-x-0 bottom-0 z-20 mx-auto w-full max-w-[375px] bg-[#F5F5F5]/95 px-[17px] pt-3 backdrop-blur-md" > + {startMatchMutation.isError ? ( +

+ {t.questions.startMatchFailed} +

+ ) : null}