Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,364 @@
import { Tenant } from "@toolctrl/db/prisma-browser";
import { Form } from "react-router";
import { Dispatch, Fragment, SetStateAction, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { SubscriptionProductDto } from "~/application/dtos/subscriptions/SubscriptionProductDto";
import currencies from "~/application/pricing/currencies";
import ButtonPrimary from "~/components/ui/buttons/ButtonPrimary";
import ButtonSecondary from "~/components/ui/buttons/ButtonSecondary";
import InputDate from "~/components/ui/input/InputDate";
import InputSelector from "~/components/ui/input/InputSelector";
import InputText from "~/components/ui/input/InputText";
import Modal from "~/components/ui/modals/Modal";
import { useLocaleFormatters } from "~/locale/format";
import { i18nConfig } from "~/locale/i18n";
import { UserWithNames } from "~/utils/db/users.db.server";
export default function SessionFilterModal({
flagName,
filters,
item,
idx,
open,
onClose,
onSave,
metadata,
onRemove,
}: {
flagName?: string;
filters: string[];
item?: {
type: string;
value: string | null;
action?: string | null;
};
idx: number | undefined;
open: boolean;
onClose: () => void;
onSave: (item: { type: string; value: string | null; action?: string | null }) => void;
onRemove?: (idx: number) => void;
metadata: {
users: UserWithNames[];
tenants: Tenant[];
subscriptionProducts: SubscriptionProductDto[];
roles: { id: string; name: string }[];
analytics?: {
via: { name: string; count: number }[];
httpReferrer: { name: string; count: number }[];
browser: { name: string; count: number }[];
os: { name: string; count: number }[];
source: { name: string; count: number }[];
medium: { name: string; count: number }[];
campaign: { name: string; count: number }[];
};
};
}) {
const { t } = useTranslation();
const [filter, setFilter] = useState<{ type: string; value: string | null; action?: string | null }>();
const [actions, setActions] = useState<{
title: string;
options: { name: string; value: string }[];
}>();
useEffect(() => {
if (flagName === "currency") {
setActions({ title: t("shared.currency"), options: currencies.filter((f) => !f.disabled).map((f) => ({ name: f.name, value: f.value })) });
}
}, [flagName, t]);
useEffect(() => {
setFilter(item);
}, [item]);
function onConfirm() {
if (!filter?.type) {
return;
}
onSave(filter);
}
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
onConfirm();
}
return (
<Modal open={open} setOpen={onClose} size="md">
<Form onSubmit={onSubmit} className="inline-block w-full p-1 text-left align-bottom sm:align-middle">
<input name="action" type="hidden" value="create" readOnly hidden />
<div className="mt-3 text-center">
<h3 className="text-foreground text-lg font-medium leading-6">{idx === undefined ? t("shared.filtersModal.addTitle") : t("shared.filtersModal.editTitle")}</h3>
</div>
<div className="mt-4 space-y-2">
<InputSelector
name="type"
title={t("shared.type")}
value={filter?.type}
withSearch={true}
setValue={(e) => setFilter({ type: e?.toString() ?? "", value: filter?.value ?? null, action: filter?.action ?? null })}
options={filters.map((f) => {
return { value: f, name: f };
})}
required
/>
{filter?.type === "tenant.is" ? (
<InputSelector
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ?? undefined}
withSearch={true}
setValue={(e) => setFilter({ ...filter, value: e?.toString() ?? null })}
options={metadata.tenants.map((t) => {
return { value: t.id, name: t.name };
})}
required
/>
) : filter?.type === "user.is" ? (
<InputSelector
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ?? undefined}
withSearch={true}
setValue={(e) => setFilter({ ...filter, value: e?.toString() ?? null })}
options={metadata.users.map((t) => {
return { value: t.email, name: t.email };
})}
required
/>
) : ["user.roles.contains", "user.roles.notContains"].includes(filter?.type ?? "") ? (
<InputSelector
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ?? undefined}
withSearch={true}
setValue={(e) => setFilter({ type: filter?.type ?? "", value: e?.toString() ?? null, action: filter?.action ?? null })}
options={metadata.roles.map((t) => {
return { value: t.name, name: t.name };
})}
required
/>
) : filter?.type === "tenant.subscription.products.has" ? (
<InputSelector
name="value"
title={t("onboarding.filter.value")}
value={filter.value ?? undefined}
withSearch={true}
setValue={(e) => setFilter({ ...filter, value: e?.toString() ?? null })}
options={metadata.subscriptionProducts.map((f) => {
return { value: f.id, name: t(f.title) };
})}
required
/>
) : filter?.type === "env" ? (
<FilterSelector
filter={filter}
setFilter={setFilter}
options={[
{ value: "production", name: t("shared.environments.production") },
{ value: "development", name: t("shared.environments.development") },
{ value: "staging", name: t("shared.environments.staging") },
]}
/>
) : filter?.type === "percentage" ? (
<FilterSelector
filter={filter}
setFilter={setFilter}
options={Array.from({ length: 10 }, (_, i) => i + 1).map((f) => {
return {
value: `${f * 10}`,
name: `${f * 10}%`,
};
})}
/>
) : ["session.darkMode", "session.logged", "tenant.subscription.active", "tenant.api.used"].includes(filter?.type ?? "") ? (
<FilterSelector
filter={filter}
setFilter={setFilter}
options={[
{ value: "true", name: t("shared.boolean.true") },
{ value: "false", name: t("shared.boolean.false") },
]}
/>
) : ["user.language", "session.language"].includes(filter?.type ?? "") ? (
<FilterSelector
filter={filter}
setFilter={setFilter}
options={i18nConfig.supportedLngs.map((f) => {
return {
value: f,
name: t("shared.locales." + f),
};
})}
/>
) : metadata.analytics && filter?.type.startsWith("analytics.") ? (
<AnalyticsSelector filter={filter} setFilter={setFilter} analytics={metadata.analytics} />
) : filter?.type === "page" ? (
<FilterSelector
filter={filter}
setFilter={setFilter}
options={[
{ name: t("shared.pages.landing"), value: "/" },
{ name: t("shared.pages.login"), value: "/login" },
{ name: t("shared.pages.register"), value: "/register" },
{ name: t("shared.pages.forgotPassword"), value: "/forgot-password" },
{ name: t("shared.pages.resetPassword"), value: "/reset" },
{ name: t("shared.pages.docs"), value: "/docs" },
]}
/>
) : ["user.createdAfter", "user.createdBefore"].includes(filter?.type ?? "") ? (
<InputDate
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ? new Date(filter?.value) : undefined}
onChange={(e) => setFilter({ type: filter?.type ?? "", value: e?.toISOString() ?? null, action: filter?.action ?? null })}
required
/>
) : (
<InputText
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ?? undefined}
setValue={(e) => setFilter({ type: filter?.type ?? "", value: e.toString() ?? null, action: filter?.action ?? null })}
required
/>
)}
{actions && actions.options.length > 0 && (
<InputSelector
value={filter?.action ?? undefined}
setValue={(e) => setFilter({ type: filter?.type ?? "", value: filter?.value ?? null, action: e?.toString() ?? null })}
name="flagAction"
title={actions.title}
options={actions.options}
required
/>
)}
</div>
<div className="mt-3 flex justify-between space-x-2">
<div>
{onRemove && idx !== undefined && (
<ButtonSecondary
type="button"
destructive
onClick={() => {
onRemove(idx);
onClose();
}}
>
{t("shared.remove")}
</ButtonSecondary>
)}
</div>
<ButtonPrimary type="submit">{t("shared.save")}</ButtonPrimary>
</div>
</Form>
</Modal>
);
}
function FilterSelector({
filter,
setFilter,
options,
}: {
filter: { type: string; value: string | null; action?: string | null } | undefined;
setFilter: Dispatch<
SetStateAction<
| {
type: string;
value: string | null;
action?: string | null;
}
| undefined
>
>;
options: { name: string; value: string }[];
}) {
const { t } = useTranslation();
return (
<Fragment>
{["analytics.countryCode", "analytics.regionCode", "analytics.city", "analytics.zipCode"].includes(filter?.type ?? "") ? (
<InputText
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ?? undefined}
setValue={(e) => setFilter({ type: filter?.type ?? "", value: e.toString() ?? null, action: filter?.action ?? null })}
required
/>
) : (
<InputSelector
name="value"
title={t("shared.value")}
disabled={!filter?.type}
value={filter?.value ?? undefined}
withSearch={true}
setValue={(e) => setFilter({ type: filter?.type ?? "", value: e?.toString() ?? null, action: filter?.action ?? null })}
options={options}
required
/>
)}
</Fragment>
);
}
function AnalyticsSelector({
filter,
setFilter,
analytics,
}: {
filter: { type: string; value: string | null; action?: string | null } | undefined;
setFilter: Dispatch<SetStateAction<{ type: string; value: string | null; action?: string | null } | undefined>>;
analytics: {
via: { name: string; count: number }[];
httpReferrer: { name: string; count: number }[];
browser: { name: string; count: number }[];
os: { name: string; count: number }[];
source: { name: string; count: number }[];
medium: { name: string; count: number }[];
campaign: { name: string; count: number }[];
};
}) {
const { t } = useTranslation();
const formatters = useLocaleFormatters();
const [options, setOptions] = useState<{ name: string; count: number }[]>([]);
useEffect(() => {
let options: { name: string; count: number }[] = [];
if (filter?.type === "analytics.via") {
options = analytics.via;
} else if (filter?.type === "analytics.httpReferrer") {
options = analytics.httpReferrer;
} else if (filter?.type === "analytics.browser") {
options = analytics.browser;
} else if (filter?.type === "analytics.os") {
options = analytics.os;
} else if (filter?.type === "analytics.source") {
options = analytics.source;
} else if (filter?.type === "analytics.medium") {
options = analytics.medium;
} else if (filter?.type === "analytics.campaign") {
options = analytics.campaign;
}
setOptions(options);
}, [filter, analytics]);
return (
<FilterSelector
filter={filter}
setFilter={setFilter}
options={options.map((f) => {
const name = f.name || "null";
return {
value: name,
name: t("shared.filtersModal.optionWithCount", { name, count: f.count, formattedCount: formatters.number(f.count) }),
};
})}
/>
);
}
@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { readRepoFile } from "~/utils/testing/repoPaths";
describe("SessionFilterModal i18n", () => {
it("formats analytics option counts through locale-aware translation keys", () => {
const source = readRepoFile("apps/web/app/modules/shared/components/SessionFilterModal.tsx");
const en = JSON.parse(readRepoFile("apps/web/public/locales/en/translations.json"));
const de = JSON.parse(readRepoFile("apps/web/public/locales/de/translations.json"));
expect(source).toContain("useLocaleFormatters");
expect(source).toContain("shared.filtersModal.optionWithCount");
expect(source).toContain("formattedCount: formatters.number(f.count)");
expect(source).not.toContain('`${f.name || "null"} (${f.count})`');
expect(en.shared.filtersModal.optionWithCount).toBe("{{name}} ({{formattedCount}})");
expect(de.shared.filtersModal.optionWithCount).toBe("{{name}} ({{formattedCount}})");
});
});