Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/onboarding/blocks/OnboardingBlock.tsx
T

200 lines
5.7 KiB
TypeScript

import { useFetcher, useNavigate, useParams } from "react-router";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import ErrorModal, { RefErrorModal } from "~/components/ui/modals/ErrorModal";
import { OnboardingSessionWithDetails } from "~/modules/onboarding/db/onboardingSessions.db.server";
import { OnboardingSessionActionDto } from "~/modules/onboarding/dtos/OnboardingSessionActionDto";
import { useAppOrAdminData } from "~/utils/data/useAppOrAdminData";
import { useRootData } from "~/utils/data/useRootData";
import { OnboardingBlockDto } from "./OnboardingBlockUtils";
import { resolveOnboardingHref } from "./OnboardingLinkUtils";
import OnboardingVariantModal from "./OnboardingVariantModal";
export default function OnboardingBlock({
session,
item,
open,
onClose,
}: {
session?: OnboardingSessionWithDetails | null;
item: OnboardingBlockDto;
open: boolean;
onClose: () => void;
}) {
const { t } = useTranslation();
const fetcher = useFetcher();
const navigate = useNavigate();
const params = useParams();
const appOrAdminData = useAppOrAdminData();
const rootData = useRootData();
const errorModal = useRef<RefErrorModal>(null);
const [block, setBlock] = useState<OnboardingBlockDto>(item);
const [currentStepIdx, setCurrentStepIdx] = useState(0);
useEffect(() => {
setBlock(item);
}, [item]);
useEffect(() => {
if (session) {
const stepIdx = session.sessionSteps.findIndex((f) => !f.completedAt);
if (stepIdx !== undefined && stepIdx !== -1 && stepIdx < item.steps.length) {
setCurrentStepIdx(stepIdx);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [stepStates, setStepStates] = useState<{ idx: number; state: { [key: string]: string } }[]>([]);
useEffect(() => {
if (session && !session.startedAt) {
const form = new FormData();
form.set("action", "started");
submitSessionForm(form);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function onUpdateCurrentStepState(state: { [key: string]: string }) {
setStepStates((prev) => {
const idx = prev.findIndex((x) => x.idx === currentStepIdx);
if (idx === -1) {
return [...prev, { idx: currentStepIdx, state }];
}
return [...prev.slice(0, idx), { idx: currentStepIdx, state }, ...prev.slice(idx + 1)];
});
}
function submitSessionForm(form: FormData, actions?: OnboardingSessionActionDto[]) {
if (session) {
if (rootData.csrf && !form.has("csrf")) {
form.set("csrf", rootData.csrf);
}
if (actions) {
actions
.filter((f) => f.value)
.forEach((action) => {
form.append("actions[]", JSON.stringify(action));
});
}
fetcher.submit(form, {
action: "/onboarding/" + session.id,
method: "post",
});
}
}
function onDismiss() {
if (validInput()) {
if (session && !session.dismissedAt) {
const form = new FormData();
form.set("action", "dismissed");
submitSessionForm(form);
}
onClose();
}
}
function setStep(idx: number) {
const form = new FormData();
form.set("action", "set-step");
form.set("fromIdx", currentStepIdx.toString());
form.set("toIdx", idx.toString());
const currentState = stepStates.find((x) => x.idx === currentStepIdx);
const keyValuePairs = Object.entries(currentState?.state || {});
submitSessionForm(
form,
keyValuePairs.map(([key, value]) => {
return { type: "input", name: key, value };
})
);
const currentStep = block.steps[currentStepIdx];
currentStep.completedAt = new Date();
setBlock({ ...block, steps: [...block.steps.slice(0, currentStepIdx), currentStep, ...block.steps.slice(currentStepIdx + 1)] });
if (validInput()) {
setCurrentStepIdx(idx);
}
}
function validInput() {
const missingFields = getMissingFields();
if (missingFields.length === 0) {
return true;
} else {
errorModal.current?.show(t("shared.error"), t("onboarding.errors.missingInput", { 0: missingFields.join(", ") }));
}
}
function onComplete() {
if (session && !session.completedAt) {
const form = new FormData();
form.set("action", "complete");
form.set("fromIdx", currentStepIdx.toString());
submitSessionForm(form);
}
if (validInput()) {
onClose();
}
}
function onLinkClick(item: { text: string; href: string }) {
const form = new FormData();
form.set("action", "add-actions");
submitSessionForm(form, [
{
type: "click",
name: item.text,
value: item.href,
},
]);
const linkWithVariables = resolveOnboardingHref(item.href, params, appOrAdminData?.user?.id);
navigate(linkWithVariables);
onClose();
}
function getMissingFields() {
const step = block.steps[currentStepIdx];
const fields: string[] = [];
step.input.forEach((input) => {
if (input.isRequired) {
const currentState = stepStates.find((x) => x.idx === currentStepIdx);
if (!currentState?.state[input.name]) {
fields.push(t(input.title));
}
}
});
return fields;
}
function onClosing() {
if (validInput()) {
onClose();
}
}
return (
<>
{item.style === "modal" && (
<OnboardingVariantModal
block={block}
currentStepIdx={currentStepIdx}
setStep={setStep}
open={open}
onClose={onClosing}
onDismiss={onDismiss}
onComplete={onComplete}
onLinkClick={onLinkClick}
onUpdateCurrentStepState={onUpdateCurrentStepState}
/>
)}
<ErrorModal ref={errorModal} />
</>
);
}