Merge branch 'dev' into refactor/save-button-positions

This commit is contained in:
Fred KISSIE
2025-12-18 01:46:13 +01:00
100 changed files with 5856 additions and 1149 deletions

View File

@@ -0,0 +1,56 @@
import AuthPageBrandingForm from "@app/components/AuthPageBrandingForm";
import AuthPageSettings from "@app/components/private/AuthPageSettings";
import { SettingsContainer } from "@app/components/Settings";
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import { getCachedSubscription } from "@app/lib/api/getCachedSubscription";
import { build } from "@server/build";
import type { GetOrgTierResponse } from "@server/routers/billing/types";
import {
GetLoginPageBrandingResponse,
GetLoginPageResponse
} from "@server/routers/loginPage/types";
import { AxiosResponse } from "axios";
export interface AuthPageProps {
params: Promise<{ orgId: string }>;
}
export default async function AuthPage(props: AuthPageProps) {
const orgId = (await props.params).orgId;
let subscriptionStatus: GetOrgTierResponse | null = null;
try {
const subRes = await getCachedSubscription(orgId);
subscriptionStatus = subRes.data.data;
} catch {}
let loginPage: GetLoginPageResponse | null = null;
try {
if (build === "saas") {
const res = await internal.get<AxiosResponse<GetLoginPageResponse>>(
`/org/${orgId}/login-page`,
await authCookieHeader()
);
if (res.status === 200) {
loginPage = res.data.data;
}
}
} catch (error) {}
let loginPageBranding: GetLoginPageBrandingResponse | null = null;
try {
const res = await internal.get<
AxiosResponse<GetLoginPageBrandingResponse>
>(`/org/${orgId}/login-page-branding`, await authCookieHeader());
if (res.status === 200) {
loginPageBranding = res.data.data;
}
} catch (error) {}
return (
<SettingsContainer>
{build === "saas" && <AuthPageSettings loginPage={loginPage} />}
<AuthPageBrandingForm orgId={orgId} branding={loginPageBranding} />
</SettingsContainer>
);
}

View File

@@ -1,16 +1,14 @@
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { HorizontalTabs, type TabItem } from "@app/components/HorizontalTabs";
import { verifySession } from "@app/lib/auth/verifySession";
import OrgProvider from "@app/providers/OrgProvider";
import OrgUserProvider from "@app/providers/OrgUserProvider";
import { GetOrgResponse } from "@server/routers/org";
import { GetOrgUserResponse } from "@server/routers/user";
import { AxiosResponse } from "axios";
import { redirect } from "next/navigation";
import { cache } from "react";
import { getTranslations } from "next-intl/server";
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
import { build } from "@server/build";
type GeneralSettingsProps = {
children: React.ReactNode;
@@ -23,8 +21,7 @@ export default async function GeneralSettingsPage({
}: GeneralSettingsProps) {
const { orgId } = await params;
const getUser = cache(verifySession);
const user = await getUser();
const user = await verifySession();
if (!user) {
redirect(`/`);
@@ -32,13 +29,7 @@ export default async function GeneralSettingsPage({
let orgUser = null;
try {
const getOrgUser = cache(async () =>
internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${orgId}/user/${user.userId}`,
await authCookieHeader()
)
);
const res = await getOrgUser();
const res = await getCachedOrgUser(orgId, user.userId);
orgUser = res.data.data;
} catch {
redirect(`/${orgId}`);
@@ -46,13 +37,7 @@ export default async function GeneralSettingsPage({
let org = null;
try {
const getOrg = cache(async () =>
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${orgId}`,
await authCookieHeader()
)
);
const res = await getOrg();
const res = await getCachedOrg(orgId);
org = res.data.data;
} catch {
redirect(`/${orgId}`);
@@ -60,12 +45,19 @@ export default async function GeneralSettingsPage({
const t = await getTranslations();
const navItems = [
const navItems: TabItem[] = [
{
title: t("general"),
href: `/{orgId}/settings/general`
href: `/{orgId}/settings/general`,
exact: true
}
];
if (build !== "oss") {
navItems.push({
title: t("authPage"),
href: `/{orgId}/settings/general/auth-page`
});
}
return (
<>

View File

@@ -43,14 +43,13 @@ import {
SettingsSectionTitle,
SettingsSectionDescription,
SettingsSectionBody,
SettingsSectionForm,
SettingsSectionFooter
SettingsSectionForm
} from "@app/components/Settings";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from "next-intl";
import { build } from "@server/build";
import { SwitchInput } from "@app/components/SwitchInput";
import { SecurityFeaturesAlert } from "@app/components/SecurityFeaturesAlert";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
@@ -113,29 +112,18 @@ const LOG_RETENTION_OPTIONS = [
export default function GeneralPage() {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { orgUser } = userOrgUserContext();
const router = useRouter();
const { org } = useOrgContext();
const api = createApiClient(useEnvContext());
const { user } = useUserContext();
const t = useTranslations();
const { env } = useEnvContext();
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
// Check if security features are disabled due to licensing/subscription
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
const { isPaidUser, hasSaasSubscription } = usePaidStatus();
const [loadingDelete, setLoadingDelete] = useState(false);
const [loadingSave, setLoadingSave] = useState(false);
const [isSecurityPolicyConfirmOpen, setIsSecurityPolicyConfirmOpen] =
useState(false);
const authPageSettingsRef = useRef<AuthPageSettingsRef>(null);
const form = useForm({
resolver: zodResolver(GeneralFormSchema),
@@ -258,14 +246,6 @@ export default function GeneralPage() {
// Update organization
await api.post(`/org/${org?.org.orgId}`, reqData);
// Also save auth page settings if they have unsaved changes
if (
build === "saas" &&
authPageSettingsRef.current?.hasUnsavedChanges()
) {
await authPageSettingsRef.current.saveAuthSettings();
}
toast({
title: t("orgUpdated"),
description: t("orgUpdatedDescription")
@@ -410,9 +390,7 @@ export default function GeneralPage() {
{LOG_RETENTION_OPTIONS.filter(
(option) => {
if (
build ==
"saas" &&
!subscription?.subscribed &&
hasSaasSubscription &&
option.value >
30
) {
@@ -440,19 +418,15 @@ export default function GeneralPage() {
)}
/>
{build != "oss" && (
{build !== "oss" && (
<>
<SecurityFeaturesAlert />
<PaidFeaturesAlert />
<FormField
control={form.control}
name="settingsLogRetentionDaysAccess"
render={({ field }) => {
const isDisabled =
(build == "saas" &&
!subscription?.subscribed) ||
(build == "enterprise" &&
!isUnlocked());
const isDisabled = !isPaidUser;
return (
<FormItem>
@@ -518,11 +492,7 @@ export default function GeneralPage() {
control={form.control}
name="settingsLogRetentionDaysAction"
render={({ field }) => {
const isDisabled =
(build == "saas" &&
!subscription?.subscribed) ||
(build == "enterprise" &&
!isUnlocked());
const isDisabled = !isPaidUser;
return (
<FormItem>
@@ -590,8 +560,7 @@ export default function GeneralPage() {
</SettingsSectionBody>
{build !== "oss" && (
<>
<hr className="my-10 max-w-xl" />
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t("securitySettings")}
@@ -601,14 +570,13 @@ export default function GeneralPage() {
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm className="mb-4">
<SecurityFeaturesAlert />
<SettingsSectionForm>
<PaidFeaturesAlert />
<FormField
control={form.control}
name="requireTwoFactor"
render={({ field }) => {
const isDisabled =
isSecurityFeatureDisabled();
const isDisabled = !isPaidUser;
return (
<FormItem className="col-span-2">
@@ -655,8 +623,7 @@ export default function GeneralPage() {
control={form.control}
name="maxSessionLengthHours"
render={({ field }) => {
const isDisabled =
isSecurityFeatureDisabled();
const isDisabled = !isPaidUser;
return (
<FormItem className="col-span-2">
@@ -744,8 +711,7 @@ export default function GeneralPage() {
control={form.control}
name="passwordExpiryDays"
render={({ field }) => {
const isDisabled =
isSecurityFeatureDisabled();
const isDisabled = !isPaidUser;
return (
<FormItem className="col-span-2">
@@ -831,7 +797,7 @@ export default function GeneralPage() {
/>
</SettingsSectionForm>
</SettingsSectionBody>
</>
</SettingsSection>
)}
<div className="flex justify-end gap-2 mt-4">
@@ -848,8 +814,6 @@ export default function GeneralPage() {
</form>
</Form>
{build === "saas" && <AuthPageSettings ref={authPageSettingsRef} />}
{build !== "saas" && (
<SettingsSection>
<SettingsSectionHeader>