Profile page UI and functionality implemented

This commit is contained in:
adilallo
2026-04-25 17:57:58 -06:00
parent 7dd2562bae
commit 68517796a9
103 changed files with 4439 additions and 1476 deletions
+24 -3
View File
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useSearchParams } from "next/navigation";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import type { CreateFlowState } from "./types";
import { createFlowStateHasKeys } from "../../../lib/create/draftHydrationUtils";
import {
@@ -11,6 +11,10 @@ import {
import { useCreateFlow } from "./context/CreateFlowContext";
import { fetchDraftFromServer } from "../../../lib/create/api";
import messages from "../../../messages/en/index";
import {
isValidStep,
parseCreateFlowScreenFromPathname,
} from "./utils/flowSteps";
const SYNC_ENABLED = process.env.NEXT_PUBLIC_ENABLE_BACKEND_SYNC === "true";
@@ -36,6 +40,8 @@ export function SignedInDraftHydration({
sessionResolved: boolean;
}) {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const syncDraftParam = searchParams.get("syncDraft");
const { replaceState, interactionTouched } = useCreateFlow();
const touchedRef = useRef(interactionTouched);
@@ -82,7 +88,15 @@ export function SignedInDraftHydration({
}
if (serverDraft != null && createFlowStateHasKeys(serverDraft)) {
replaceState(serverDraft as CreateFlowState);
const next = serverDraft as CreateFlowState;
replaceState(next);
const saved = next.currentStep;
if (saved && isValidStep(saved)) {
const urlStep = parseCreateFlowScreenFromPathname(pathname ?? null);
if (urlStep !== saved) {
router.replace(`/create/${saved}`);
}
}
}
finishedUserIdRef.current = userId;
} finally {
@@ -93,7 +107,14 @@ export function SignedInDraftHydration({
return () => {
cancelled = true;
};
}, [sessionResolved, sessionUser, syncDraftParam, replaceState]);
}, [
sessionResolved,
sessionUser,
syncDraftParam,
replaceState,
pathname,
router,
]);
if (!loadingHydration) return null;
@@ -175,7 +175,7 @@ export function FinalReviewChipEditModal({
<Create
isOpen={isOpen}
onClose={onClose}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
headerContent={
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
<ContentLockup
@@ -47,7 +47,7 @@ export function CreateFlowScreenView({
<CreateFlowTextFieldScreen
messageNamespace="create.community.communityContext"
stateField="communityContext"
maxLength={48}
maxLength={200}
mainAlign="center"
/>
);
@@ -213,7 +213,7 @@ export function CommunicationMethodsScreen() {
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
>
{pendingCardId && pendingDraft ? (
<CommunicationMethodEditFields
@@ -215,7 +215,7 @@ export function ConflictManagementScreen() {
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
>
{pendingCardId && pendingDraft ? (
<ConflictManagementEditFields
@@ -212,7 +212,7 @@ export function MembershipMethodsScreen() {
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
>
{pendingCardId && pendingDraft ? (
<MembershipMethodEditFields
@@ -1,12 +1,17 @@
"use client";
import { useState, useEffect, useMemo } from "react";
import { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import CommunityRuleDocument from "../../../../components/sections/CommunityRuleDocument";
import type { CommunityRuleDocumentSection } from "../../../../components/sections/CommunityRuleDocument/CommunityRuleDocument.types";
import Alert from "../../../../components/modals/Alert";
import { useMessages } from "../../../../contexts/MessagesContext";
import { fetchPublishedRuleDetail } from "../../../../../lib/create/api";
import { parseDocumentSectionsForDisplay } from "../../../../../lib/create/buildPublishPayload";
import { readLastPublishedRule } from "../../../../../lib/create/lastPublishedRule";
import {
readLastPublishedRule,
writeLastPublishedRule,
} from "../../../../../lib/create/lastPublishedRule";
import { useCreateFlowMdUp } from "../../hooks/useCreateFlowMdUp";
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
import {
@@ -14,40 +19,112 @@ import {
CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS,
} from "../../components/createFlowLayoutTokens";
function initialCompletedUi(
ruleIdFromUrl: string | null,
): {
headerTitle: string;
headerDescription: string | undefined;
documentSections: CommunityRuleDocumentSection[];
} {
if (ruleIdFromUrl) {
return {
headerTitle: "",
headerDescription: undefined,
documentSections: [],
};
}
if (typeof sessionStorage === "undefined") {
return {
headerTitle: "",
headerDescription: undefined,
documentSections: [],
};
}
const stored = readLastPublishedRule();
if (!stored) {
return {
headerTitle: "",
headerDescription: undefined,
documentSections: [],
};
}
const parsed = parseDocumentSectionsForDisplay(stored.document);
if (parsed.length === 0) {
return {
headerTitle: "",
headerDescription: undefined,
documentSections: [],
};
}
const sum =
typeof stored.summary === "string" ? stored.summary.trim() : "";
return {
headerTitle: stored.title,
headerDescription: sum.length > 0 ? sum : undefined,
documentSections: parsed,
};
}
export function CompletedScreen() {
const router = useRouter();
const searchParams = useSearchParams();
const ruleIdParam = searchParams.get("ruleId");
const mdUp = useCreateFlowMdUp();
const m = useMessages();
const completed = m.create.reviewAndComplete.completed;
const fallbackSections = useMemo(
() =>
[...completed.fallbackDocumentSections] as CommunityRuleDocumentSection[],
[completed.fallbackDocumentSections],
);
const initial = initialCompletedUi(ruleIdParam);
const [toastDismissed, setToastDismissed] = useState(false);
const [headerTitle, setHeaderTitle] = useState(
() => completed.fallbackTitle,
);
const [headerTitle, setHeaderTitle] = useState(initial.headerTitle);
const [headerDescription, setHeaderDescription] = useState<
string | undefined
>(() => completed.fallbackDescription);
>(initial.headerDescription);
const [documentSections, setDocumentSections] =
useState<CommunityRuleDocumentSection[]>(fallbackSections);
useState<CommunityRuleDocumentSection[]>(initial.documentSections);
useEffect(() => {
const stored = readLastPublishedRule();
if (!stored) return;
const parsed = parseDocumentSectionsForDisplay(stored.document);
if (parsed.length === 0) return;
queueMicrotask(() => {
setDocumentSections(parsed);
setHeaderTitle(stored.title);
const sum =
typeof stored.summary === "string" ? stored.summary.trim() : "";
setHeaderDescription(sum.length > 0 ? sum : undefined);
});
}, []);
if (!ruleIdParam) return;
let cancelled = false;
void (async () => {
const detail = await fetchPublishedRuleDetail(ruleIdParam);
if (cancelled) return;
if (
!detail ||
!detail.viewerIsOwner ||
detail.rule.document === null ||
typeof detail.rule.document !== "object" ||
Array.isArray(detail.rule.document)
) {
router.replace(`/rules/${encodeURIComponent(ruleIdParam)}`);
return;
}
const doc = detail.rule.document as Record<string, unknown>;
writeLastPublishedRule({
id: detail.rule.id,
title: detail.rule.title,
summary: detail.rule.summary,
document: doc,
});
const parsed = parseDocumentSectionsForDisplay(doc);
if (parsed.length === 0) {
router.replace(`/rules/${encodeURIComponent(ruleIdParam)}`);
return;
}
queueMicrotask(() => {
setDocumentSections(parsed);
setHeaderTitle(detail.rule.title);
const sum =
typeof detail.rule.summary === "string"
? detail.rule.summary.trim()
: "";
setHeaderDescription(sum.length > 0 ? sum : undefined);
});
router.replace("/create/completed");
})();
return () => {
cancelled = true;
};
}, [ruleIdParam, router]);
const toast = !toastDismissed ? (
<div
@@ -244,7 +244,7 @@ export function DecisionApproachesScreen() {
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
>
{pendingCardId && pendingDraft ? (
<DecisionApproachEditFields
@@ -383,7 +383,7 @@ export function CoreValuesSelectScreen() {
<Create
isOpen={activeModalChipId !== null}
onClose={handleModalDismiss}
backdropVariant="loginYellow"
backdropVariant="blurredYellow"
headerContent={
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
<ContentLockup