- {t('userQuestionOrgRemove', {email: selectedUser?.email || selectedUser?.name || selectedUser?.username})} // FIXME
+ {t('userQuestionOrgRemove', {email: selectedUser?.email || selectedUser?.name || selectedUser?.username})}
diff --git a/src/app/[orgId]/settings/access/users/create/page.tsx b/src/app/[orgId]/settings/access/users/create/page.tsx
index efaf64fd..e4ea99fe 100644
--- a/src/app/[orgId]/settings/access/users/create/page.tsx
+++ b/src/app/[orgId]/settings/access/users/create/page.tsx
@@ -60,15 +60,6 @@ interface IdpOption {
type: string;
}
-const formatIdpType = (type: string) => {
- switch (type.toLowerCase()) {
- case "oidc":
- return "Generic OAuth2/OIDC provider.";
- default:
- return type;
- }
-};
-
export default function Page() {
const { orgId } = useParams();
const router = useRouter();
@@ -104,6 +95,15 @@ export default function Page() {
idpId: z.string().min(1, { message: t('idpSelectPlease') })
});
+ const formatIdpType = (type: string) => {
+ switch (type.toLowerCase()) {
+ case "oidc":
+ return t('idpGenericOidc');
+ default:
+ return type;
+ }
+ };
+
const validFor = [
{ hours: 24, name: t('day', {count: 1}) },
{ hours: 48, name: t('day', {count: 2}) },
diff --git a/src/app/[orgId]/settings/api-keys/OrgApiKeysTable.tsx b/src/app/[orgId]/settings/api-keys/OrgApiKeysTable.tsx
index 8ca7c2fc..b0e55c4b 100644
--- a/src/app/[orgId]/settings/api-keys/OrgApiKeysTable.tsx
+++ b/src/app/[orgId]/settings/api-keys/OrgApiKeysTable.tsx
@@ -78,7 +78,7 @@ export default function OrgApiKeysTable({
diff --git a/src/app/[orgId]/settings/general/page.tsx b/src/app/[orgId]/settings/general/page.tsx
index 463c9463..c692fbc9 100644
--- a/src/app/[orgId]/settings/general/page.tsx
+++ b/src/app/[orgId]/settings/general/page.tsx
@@ -93,7 +93,7 @@ export default function GeneralPage() {
toast({
variant: "destructive",
title: t('orgErrorDelete'),
- description: formatAxiosError(err,t('orgErrorDeleteMessage'))
+ description: formatAxiosError(err, t('orgErrorDeleteMessage'))
});
} finally {
setLoadingDelete(false);
@@ -121,7 +121,7 @@ export default function GeneralPage() {
toast({
variant: "destructive",
title: t('orgErrorFetch'),
- description: formatAxiosError(err,t('orgErrorFetchMessage'))
+ description: formatAxiosError(err, t('orgErrorFetchMessage'))
});
}
}
@@ -144,7 +144,7 @@ export default function GeneralPage() {
toast({
variant: "destructive",
title: t('orgErrorUpdate'),
- description: formatAxiosError(e,t('orgErrorUpdateMessage'))
+ description: formatAxiosError(e, t('orgErrorUpdateMessage'))
});
})
.finally(() => {
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx
index 788652f4..03970bd5 100644
--- a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx
+++ b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx
@@ -13,7 +13,7 @@ import {
} from "@app/components/InfoSection";
import Link from "next/link";
import { Switch } from "@app/components/ui/switch";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type ResourceInfoBoxType = {};
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx
index 704f8fac..6182c04a 100644
--- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx
+++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx
@@ -162,10 +162,9 @@ export default function ResourceAuthenticationPage() {
rolesResponse.data.data.roles
.map((role) => ({
id: role.roleId.toString(),
- text: role.name,
- isAdmin: role.isAdmin
+ text: role.name
}))
- .filter((role) => !role.isAdmin)
+ .filter((role) => role.text !== "Admin")
);
usersRolesForm.setValue(
@@ -173,10 +172,9 @@ export default function ResourceAuthenticationPage() {
resourceRolesResponse.data.data.roles
.map((i) => ({
id: i.roleId.toString(),
- text: i.name,
- isAdmin: i.isAdmin
+ text: i.name
}))
- .filter((role) => !role.isAdmin)
+ .filter((role) => role.text !== "Admin")
);
setAllUsers(
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx
index 9facce58..d571f7b8 100644
--- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx
+++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx
@@ -67,45 +67,6 @@ import {
import { SwitchInput } from "@app/components/SwitchInput";
import { useTranslations } from "next-intl";
-const GeneralFormSchema = z
- .object({
- subdomain: z.string().optional(),
- name: z.string().min(1).max(255),
- proxyPort: z.number().optional(),
- http: z.boolean(),
- isBaseDomain: z.boolean().optional(),
- domainId: z.string().optional()
- })
- .refine(
- (data) => {
- if (!data.http) {
- return z
- .number()
- .int()
- .min(1)
- .max(65535)
- .safeParse(data.proxyPort).success;
- }
- return true;
- },
- {
- message: "Invalid port number",
- path: ["proxyPort"]
- }
- )
- .refine(
- (data) => {
- if (data.http && !data.isBaseDomain) {
- return subdomainSchema.safeParse(data.subdomain).success;
- }
- return true;
- },
- {
- message: "Invalid subdomain",
- path: ["subdomain"]
- }
- );
-
const TransferFormSchema = z.object({
siteId: z.number()
});
@@ -140,6 +101,45 @@ export default function GeneralForm() {
resource.isBaseDomain ? "basedomain" : "subdomain"
);
+ const GeneralFormSchema = z
+ .object({
+ subdomain: z.string().optional(),
+ name: z.string().min(1).max(255),
+ proxyPort: z.number().optional(),
+ http: z.boolean(),
+ isBaseDomain: z.boolean().optional(),
+ domainId: z.string().optional()
+ })
+ .refine(
+ (data) => {
+ if (!data.http) {
+ return z
+ .number()
+ .int()
+ .min(1)
+ .max(65535)
+ .safeParse(data.proxyPort).success;
+ }
+ return true;
+ },
+ {
+ message: t('proxyErrorInvalidPort'),
+ path: ["proxyPort"]
+ }
+ )
+ .refine(
+ (data) => {
+ if (data.http && !data.isBaseDomain) {
+ return subdomainSchema.safeParse(data.subdomain).success;
+ }
+ return true;
+ },
+ {
+ message: t('subdomainErrorInvalid'),
+ path: ["subdomain"]
+ }
+ );
+
const form = useForm({
resolver: zodResolver(GeneralFormSchema),
defaultValues: {
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx
index 634e82e4..68743286 100644
--- a/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx
+++ b/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx
@@ -93,45 +93,6 @@ type LocalTarget = Omit<
"protocol"
>;
-const proxySettingsSchema = z.object({
- setHostHeader: z
- .string()
- .optional()
- .refine(
- (data) => {
- if (data) {
- return tlsNameSchema.safeParse(data).success;
- }
- return true;
- },
- {
- message: "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
- }
- )
-});
-
-const tlsSettingsSchema = z.object({
- ssl: z.boolean(),
- tlsServerName: z
- .string()
- .optional()
- .refine(
- (data) => {
- if (data) {
- return tlsNameSchema.safeParse(data).success;
- }
- return true;
- },
- {
- message: "Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name."
- }
- )
-});
-
-type ProxySettingsValues = z.infer;
-type TlsSettingsValues = z.infer;
-type TargetsSettingsValues = z.infer;
-
export default function ReverseProxyTargets(props: {
params: Promise<{ resourceId: number }>;
}) {
@@ -154,6 +115,45 @@ export default function ReverseProxyTargets(props: {
const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
const router = useRouter();
+ const proxySettingsSchema = z.object({
+ setHostHeader: z
+ .string()
+ .optional()
+ .refine(
+ (data) => {
+ if (data) {
+ return tlsNameSchema.safeParse(data).success;
+ }
+ return true;
+ },
+ {
+ message: t('proxyErrorInvalidHeader')
+ }
+ )
+ });
+
+ const tlsSettingsSchema = z.object({
+ ssl: z.boolean(),
+ tlsServerName: z
+ .string()
+ .optional()
+ .refine(
+ (data) => {
+ if (data) {
+ return tlsNameSchema.safeParse(data).success;
+ }
+ return true;
+ },
+ {
+ message: t('proxyErrorTls')
+ }
+ )
+ });
+
+ type ProxySettingsValues = z.infer;
+ type TlsSettingsValues = z.infer;
+ type TargetsSettingsValues = z.infer;
+
const addTargetForm = useForm({
resolver: zodResolver(addTargetSchema),
defaultValues: {
@@ -583,7 +583,7 @@ export default function ReverseProxyTargets(props: {
32) {
throw new Error(t('subnetMaskErrorInvalid'));
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx
index 02833359..d10e71e6 100644
--- a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx
+++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx
@@ -102,7 +102,8 @@ export default function ResourceRules(props: {
const router = useRouter();
const t = useTranslations();
- const RuleAction = {
+
+ RuleAction = {
ACCEPT: t('alwaysAllow'),
DROP: t('alwaysDeny')
} as const;
diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx
index ee3d9b30..aeaa258e 100644
--- a/src/app/[orgId]/settings/resources/create/page.tsx
+++ b/src/app/[orgId]/settings/resources/create/page.tsx
@@ -62,7 +62,7 @@ import { cn } from "@app/lib/cn";
import { SquareArrowOutUpRight } from "lucide-react";
import CopyTextBox from "@app/components/CopyTextBox";
import Link from "next/link";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
const baseResourceFormSchema = z.object({
name: z.string().min(1).max(255),
diff --git a/src/app/[orgId]/settings/resources/page.tsx b/src/app/[orgId]/settings/resources/page.tsx
index 908a8691..bbd2a582 100644
--- a/src/app/[orgId]/settings/resources/page.tsx
+++ b/src/app/[orgId]/settings/resources/page.tsx
@@ -9,7 +9,7 @@ import { cache } from "react";
import { GetOrgResponse } from "@server/routers/org";
import OrgProvider from "@app/providers/OrgProvider";
import ResourcesSplashCard from "./ResourcesSplashCard";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
type ResourcesPageProps = {
params: Promise<{ orgId: string }>;
diff --git a/src/app/[orgId]/settings/share-links/AccessTokenUsage.tsx b/src/app/[orgId]/settings/share-links/AccessTokenUsage.tsx
index 62c223e0..c44f43b7 100644
--- a/src/app/[orgId]/settings/share-links/AccessTokenUsage.tsx
+++ b/src/app/[orgId]/settings/share-links/AccessTokenUsage.tsx
@@ -15,7 +15,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { useEnvContext } from "@app/hooks/useEnvContext";
import CopyToClipboard from "@app/components/CopyToClipboard";
import CopyTextBox from "@app/components/CopyTextBox";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
interface AccessTokenSectionProps {
token: string;
diff --git a/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx b/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx
index 66bf8fcf..cce81da7 100644
--- a/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx
+++ b/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx
@@ -66,7 +66,7 @@ import {
CollapsibleTrigger
} from "@app/components/ui/collapsible";
import AccessTokenSection from "./AccessTokenUsage";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type FormProps = {
open: boolean;
@@ -74,15 +74,6 @@ type FormProps = {
onCreated?: (result: ShareLinkRow) => void;
};
-const formSchema = z.object({
- resourceId: z.number({ message: "Please select a resource" }),
- resourceName: z.string(),
- resourceUrl: z.string(),
- timeUnit: z.string(),
- timeValue: z.coerce.number().int().positive().min(1),
- title: z.string().optional()
-});
-
export default function CreateShareLinkForm({
open,
setOpen,
@@ -100,6 +91,7 @@ export default function CreateShareLinkForm({
const [neverExpire, setNeverExpire] = useState(false);
const [isOpen, setIsOpen] = useState(false);
+ const t = useTranslations();
const [resources, setResources] = useState<
{
@@ -110,7 +102,14 @@ export default function CreateShareLinkForm({
}[]
>([]);
- const t = useTranslations();
+ const formSchema = z.object({
+ resourceId: z.number({ message: t('shareErrorSelectResource') }),
+ resourceName: z.string(),
+ resourceUrl: z.string(),
+ timeUnit: z.string(),
+ timeValue: z.coerce.number().int().positive().min(1),
+ title: z.string().optional()
+ });
const timeUnits = [
{ unit: "minutes", name: t('minutes') },
diff --git a/src/app/[orgId]/settings/share-links/ShareLinksDataTable.tsx b/src/app/[orgId]/settings/share-links/ShareLinksDataTable.tsx
index 9e9d914a..e9fc4c6a 100644
--- a/src/app/[orgId]/settings/share-links/ShareLinksDataTable.tsx
+++ b/src/app/[orgId]/settings/share-links/ShareLinksDataTable.tsx
@@ -4,7 +4,7 @@ import {
ColumnDef,
} from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
interface DataTableProps {
columns: ColumnDef[];
diff --git a/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx b/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx
index 3000575a..de419319 100644
--- a/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx
+++ b/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx
@@ -33,7 +33,7 @@ import { ListAccessTokensResponse } from "@server/routers/accessToken";
import moment from "moment";
import CreateShareLinkForm from "./CreateShareLinkForm";
import { constructShareLink } from "@app/lib/shareLinks";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
export type ShareLinkRow = {
accessTokenId: string;
diff --git a/src/app/[orgId]/settings/share-links/page.tsx b/src/app/[orgId]/settings/share-links/page.tsx
index 14a0d9b6..e4efabd9 100644
--- a/src/app/[orgId]/settings/share-links/page.tsx
+++ b/src/app/[orgId]/settings/share-links/page.tsx
@@ -9,7 +9,7 @@ import OrgProvider from "@app/providers/OrgProvider";
import { ListAccessTokensResponse } from "@server/routers/accessToken";
import ShareLinksTable, { ShareLinkRow } from "./ShareLinksTable";
import ShareableLinksSplash from "./ShareLinksSplash";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
type ShareLinksPageProps = {
params: Promise<{ orgId: string }>;
diff --git a/src/app/[orgId]/settings/sites/CreateSiteForm.tsx b/src/app/[orgId]/settings/sites/CreateSiteForm.tsx
index 9973dcf0..8d7e711e 100644
--- a/src/app/[orgId]/settings/sites/CreateSiteForm.tsx
+++ b/src/app/[orgId]/settings/sites/CreateSiteForm.tsx
@@ -50,26 +50,7 @@ import {
CollapsibleTrigger
} from "@app/components/ui/collapsible";
import LoaderPlaceholder from "@app/components/PlaceHolderLoader";
-import { useTranslations } from 'next-intl';
-
-const createSiteFormSchema = z.object({
- name: z
- .string()
- .min(2, {
- message: "Name must be at least 2 characters."
- })
- .max(30, {
- message: "Name must not be longer than 30 characters."
- }),
- method: z.enum(["wireguard", "newt", "local"])
-});
-
-type CreateSiteFormValues = z.infer;
-
-const defaultValues: Partial = {
- name: "",
- method: "newt"
-};
+import { useTranslations } from "next-intl";
type CreateSiteFormProps = {
onCreate?: (site: SiteRow) => void;
@@ -97,6 +78,25 @@ export default function CreateSiteForm({
privateKey: string;
} | null>(null);
+ const createSiteFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, {
+ message: t('nameMin', {len: 2})
+ })
+ .max(30, {
+ message: t('nameMax', {len: 30})
+ }),
+ method: z.enum(["wireguard", "newt", "local"])
+ });
+
+ type CreateSiteFormValues = z.infer;
+
+ const defaultValues: Partial = {
+ name: "",
+ method: "newt"
+ };
+
const [siteDefaults, setSiteDefaults] =
useState(null);
diff --git a/src/app/[orgId]/settings/sites/CreateSiteModal.tsx b/src/app/[orgId]/settings/sites/CreateSiteModal.tsx
index 4cecdab4..8ecee55c 100644
--- a/src/app/[orgId]/settings/sites/CreateSiteModal.tsx
+++ b/src/app/[orgId]/settings/sites/CreateSiteModal.tsx
@@ -14,7 +14,7 @@ import {
} from "@app/components/Credenza";
import { SiteRow } from "./SitesTable";
import CreateSiteForm from "./CreateSiteForm";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type CreateSiteFormProps = {
open: boolean;
diff --git a/src/app/[orgId]/settings/sites/SitesDataTable.tsx b/src/app/[orgId]/settings/sites/SitesDataTable.tsx
index fdf7805c..99445dea 100644
--- a/src/app/[orgId]/settings/sites/SitesDataTable.tsx
+++ b/src/app/[orgId]/settings/sites/SitesDataTable.tsx
@@ -2,7 +2,7 @@
import { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
interface DataTableProps {
columns: ColumnDef[];
diff --git a/src/app/[orgId]/settings/sites/SitesTable.tsx b/src/app/[orgId]/settings/sites/SitesTable.tsx
index 5df661e0..6975e24d 100644
--- a/src/app/[orgId]/settings/sites/SitesTable.tsx
+++ b/src/app/[orgId]/settings/sites/SitesTable.tsx
@@ -27,7 +27,7 @@ import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import CreateSiteFormModal from "./CreateSiteModal";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
export type SiteRow = {
id: number;
diff --git a/src/app/[orgId]/settings/sites/[niceId]/SiteInfoCard.tsx b/src/app/[orgId]/settings/sites/[niceId]/SiteInfoCard.tsx
index c2e830a6..2803e987 100644
--- a/src/app/[orgId]/settings/sites/[niceId]/SiteInfoCard.tsx
+++ b/src/app/[orgId]/settings/sites/[niceId]/SiteInfoCard.tsx
@@ -9,7 +9,7 @@ import {
InfoSections,
InfoSectionTitle
} from "@app/components/InfoSection";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type SiteInfoCardProps = {};
diff --git a/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx
index c490f1e9..f406de6c 100644
--- a/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx
+++ b/src/app/[orgId]/settings/sites/[niceId]/general/page.tsx
@@ -31,13 +31,7 @@ import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useState } from "react";
-import { useTranslations } from 'next-intl';
-
-const GeneralFormSchema = z.object({
- name: z.string().nonempty("Name is required")
-});
-
-type GeneralFormValues = z.infer;
+import { useTranslations } from "next-intl";
export default function GeneralPage() {
const { site, updateSite } = useSiteContext();
@@ -47,6 +41,13 @@ export default function GeneralPage() {
const [loading, setLoading] = useState(false);
const router = useRouter();
+ const t = useTranslations();
+
+ const GeneralFormSchema = z.object({
+ name: z.string().nonempty(t('nameRequired'))
+ });
+
+ type GeneralFormValues = z.infer;
const form = useForm({
resolver: zodResolver(GeneralFormSchema),
@@ -55,7 +56,6 @@ export default function GeneralPage() {
},
mode: "onChange"
});
- const t = useTranslations();
async function onSubmit(data: GeneralFormValues) {
setLoading(true);
diff --git a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx
index f5c98c31..2eb3bd13 100644
--- a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx
+++ b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx
@@ -16,7 +16,7 @@ import {
BreadcrumbSeparator
} from "@app/components/ui/breadcrumb";
import SiteInfoCard from "./SiteInfoCard";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
interface SettingsLayoutProps {
children: React.ReactNode;
diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx
index d5b9a9fa..ab60e2e8 100644
--- a/src/app/[orgId]/settings/sites/create/page.tsx
+++ b/src/app/[orgId]/settings/sites/create/page.tsx
@@ -64,33 +64,7 @@ import {
} from "@app/components/ui/breadcrumb";
import Link from "next/link";
import { QRCodeCanvas } from "qrcode.react";
-import { useTranslations } from 'next-intl';
-
-const createSiteFormSchema = z
- .object({
- name: z
- .string()
- .min(2, "Name must be at least 2 characters.")
- .max(30, {
- message: "Name must not be longer than 30 characters."
- }),
- method: z.enum(["newt", "wireguard", "local"]),
- copied: z.boolean()
- })
- .refine(
- (data) => {
- if (data.method !== "local") {
- return data.copied;
- }
- return true;
- },
- {
- message: "Please confirm that you have copied the config.",
- path: ["copied"]
- }
- );
-
-type CreateSiteFormValues = z.infer;
+import { useTranslations } from "next-intl";
type SiteType = "newt" | "wireguard" | "local";
@@ -127,6 +101,32 @@ export default function Page() {
const router = useRouter();
const t = useTranslations();
+ const createSiteFormSchema = z
+ .object({
+ name: z
+ .string()
+ .min(2, { message: t('nameMin', {len: 2}) })
+ .max(30, {
+ message: t('nameMax', {len: 30})
+ }),
+ method: z.enum(["newt", "wireguard", "local"]),
+ copied: z.boolean()
+ })
+ .refine(
+ (data) => {
+ if (data.method !== "local") {
+ return data.copied;
+ }
+ return true;
+ },
+ {
+ message: t('sitesConfirmCopy'),
+ path: ["copied"]
+ }
+ );
+
+ type CreateSiteFormValues = z.infer;
+
const [tunnelTypes, setTunnelTypes] = useState<
ReadonlyArray
>([
diff --git a/src/app/[orgId]/settings/sites/page.tsx b/src/app/[orgId]/settings/sites/page.tsx
index d76e5fe1..401fb2e5 100644
--- a/src/app/[orgId]/settings/sites/page.tsx
+++ b/src/app/[orgId]/settings/sites/page.tsx
@@ -5,7 +5,7 @@ import { AxiosResponse } from "axios";
import SitesTable, { SiteRow } from "./SitesTable";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import SitesSplashCard from "./SitesSplashCard";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
type SitesPageProps = {
params: Promise<{ orgId: string }>;
diff --git a/src/app/admin/api-keys/[apiKeyId]/layout.tsx b/src/app/admin/api-keys/[apiKeyId]/layout.tsx
index 818fcccc..0d6f7bdb 100644
--- a/src/app/admin/api-keys/[apiKeyId]/layout.tsx
+++ b/src/app/admin/api-keys/[apiKeyId]/layout.tsx
@@ -15,7 +15,7 @@ import {
import { GetApiKeyResponse } from "@server/routers/apiKeys";
import ApiKeyProvider from "@app/providers/ApiKeyProvider";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
interface SettingsLayoutProps {
children: React.ReactNode;
diff --git a/src/app/admin/api-keys/create/page.tsx b/src/app/admin/api-keys/create/page.tsx
index 4a58385c..5ca647c5 100644
--- a/src/app/admin/api-keys/create/page.tsx
+++ b/src/app/admin/api-keys/create/page.tsx
@@ -56,35 +56,6 @@ import CopyTextBox from "@app/components/CopyTextBox";
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
import { useTranslations } from "next-intl";
-const createFormSchema = z.object({
- name: z
- .string()
- .min(2, {
- message: "Name must be at least 2 characters."
- })
- .max(255, {
- message: "Name must not be longer than 255 characters."
- })
-});
-
-type CreateFormValues = z.infer;
-
-const copiedFormSchema = z
- .object({
- copied: z.boolean()
- })
- .refine(
- (data) => {
- return data.copied;
- },
- {
- message: "You must confirm that you have copied the API key.",
- path: ["copied"]
- }
- );
-
-type CopiedFormValues = z.infer;
-
export default function Page() {
const { env } = useEnvContext();
const api = createApiClient({ env });
@@ -98,6 +69,35 @@ export default function Page() {
Record
>({});
+ const createFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, {
+ message: t('nameMin', {len: 2})
+ })
+ .max(255, {
+ message: t('nameMax', {len: 255})
+ })
+ });
+
+ type CreateFormValues = z.infer;
+
+ const copiedFormSchema = z
+ .object({
+ copied: z.boolean()
+ })
+ .refine(
+ (data) => {
+ return data.copied;
+ },
+ {
+ message: t('apiKeysConfirmCopy2'),
+ path: ["copied"]
+ }
+ );
+
+ type CopiedFormValues = z.infer;
+
const form = useForm({
resolver: zodResolver(createFormSchema),
defaultValues: {
diff --git a/src/app/admin/api-keys/page.tsx b/src/app/admin/api-keys/page.tsx
index bba54e1d..22607f2f 100644
--- a/src/app/admin/api-keys/page.tsx
+++ b/src/app/admin/api-keys/page.tsx
@@ -4,7 +4,7 @@ import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { ListRootApiKeysResponse } from "@server/routers/apiKeys";
import ApiKeysTable, { ApiKeyRow } from "./ApiKeysTable";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
type ApiKeyPageProps = {};
diff --git a/src/app/admin/idp/AdminIdpTable.tsx b/src/app/admin/idp/AdminIdpTable.tsx
index efb9fc35..c55a2b35 100644
--- a/src/app/admin/idp/AdminIdpTable.tsx
+++ b/src/app/admin/idp/AdminIdpTable.tsx
@@ -60,7 +60,7 @@ export default function IdpTable({ idps }: Props) {
const getTypeDisplay = (type: string) => {
switch (type) {
case "oidc":
- return t('idpOidc');
+ return "OAuth2/OIDC";
default:
return type;
}
diff --git a/src/app/admin/idp/[idpId]/general/page.tsx b/src/app/admin/idp/[idpId]/general/page.tsx
index 8621cf26..308bca34 100644
--- a/src/app/admin/idp/[idpId]/general/page.tsx
+++ b/src/app/admin/idp/[idpId]/general/page.tsx
@@ -45,23 +45,6 @@ import { Badge } from "@app/components/ui/badge";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useTranslations } from "next-intl";
-const GeneralFormSchema = z.object({
- name: z.string().min(2, "Name must be at least 2 characters."),
- clientId: z.string().min(1, { message: "Client ID is required." }),
- clientSecret: z.string().min(1, { message: "Client Secret is required." }),
- authUrl: z.string().url({ message: "Auth URL must be a valid URL." }),
- tokenUrl: z.string().url({ message: "Token URL must be a valid URL." }),
- identifierPath: z
- .string()
- .min(1, { message: "Identifier Path is required." }),
- emailPath: z.string().optional(),
- namePath: z.string().optional(),
- scopes: z.string().min(1, { message: "Scopes are required." }),
- autoProvision: z.boolean().default(false)
-});
-
-type GeneralFormValues = z.infer;
-
export default function GeneralPage() {
const { env } = useEnvContext();
const api = createApiClient({ env });
@@ -72,6 +55,24 @@ export default function GeneralPage() {
const { isUnlocked } = useLicenseStatusContext();
const redirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
+ const t = useTranslations();
+
+ const GeneralFormSchema = z.object({
+ name: z.string().min(2, { message: t('nameMin', {len: 2}) }),
+ clientId: z.string().min(1, { message: t('idpClientIdRequired') }),
+ clientSecret: z.string().min(1, { message: t('idpClientSecretRequired') }),
+ authUrl: z.string().url({ message: t('idpErrorAuthUrlInvalid') }),
+ tokenUrl: z.string().url({ message: t('idpErrorTokenUrlInvalid') }),
+ identifierPath: z
+ .string()
+ .min(1, { message: t('idpPathRequired') }),
+ emailPath: z.string().optional(),
+ namePath: z.string().optional(),
+ scopes: z.string().min(1, { message: t('idpScopeRequired') }),
+ autoProvision: z.boolean().default(false)
+ });
+
+ type GeneralFormValues = z.infer;
const form = useForm({
resolver: zodResolver(GeneralFormSchema),
@@ -89,8 +90,6 @@ export default function GeneralPage() {
}
});
- const t = useTranslations();
-
useEffect(() => {
const loadIdp = async () => {
try {
diff --git a/src/app/admin/idp/[idpId]/layout.tsx b/src/app/admin/idp/[idpId]/layout.tsx
index 4b8e5be1..79b1e196 100644
--- a/src/app/admin/idp/[idpId]/layout.tsx
+++ b/src/app/admin/idp/[idpId]/layout.tsx
@@ -15,7 +15,7 @@ import {
BreadcrumbPage,
BreadcrumbSeparator
} from "@app/components/ui/breadcrumb";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
interface SettingsLayoutProps {
children: React.ReactNode;
@@ -52,7 +52,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
return (
<>
diff --git a/src/app/admin/idp/[idpId]/policies/page.tsx b/src/app/admin/idp/[idpId]/policies/page.tsx
index fa473cd9..8f36434e 100644
--- a/src/app/admin/idp/[idpId]/policies/page.tsx
+++ b/src/app/admin/idp/[idpId]/policies/page.tsx
@@ -70,20 +70,6 @@ type Organization = {
name: string;
};
-const policyFormSchema = z.object({
- orgId: z.string().min(1, { message: "Organization is required" }),
- roleMapping: z.string().optional(),
- orgMapping: z.string().optional()
-});
-
-const defaultMappingsSchema = z.object({
- defaultRoleMapping: z.string().optional(),
- defaultOrgMapping: z.string().optional()
-});
-
-type PolicyFormValues = z.infer;
-type DefaultMappingsValues = z.infer;
-
export default function PoliciesPage() {
const { env } = useEnvContext();
const api = createApiClient({ env });
@@ -102,6 +88,20 @@ export default function PoliciesPage() {
const [showAddDialog, setShowAddDialog] = useState(false);
const [editingPolicy, setEditingPolicy] = useState(null);
+ const policyFormSchema = z.object({
+ orgId: z.string().min(1, { message: t('orgRequired') }),
+ roleMapping: z.string().optional(),
+ orgMapping: z.string().optional()
+ });
+
+ const defaultMappingsSchema = z.object({
+ defaultRoleMapping: z.string().optional(),
+ defaultOrgMapping: z.string().optional()
+ });
+
+ type PolicyFormValues = z.infer;
+ type DefaultMappingsValues = z.infer;
+
const form = useForm({
resolver: zodResolver(policyFormSchema),
defaultValues: {
diff --git a/src/app/admin/idp/create/page.tsx b/src/app/admin/idp/create/page.tsx
index d89f14b6..8cd62e65 100644
--- a/src/app/admin/idp/create/page.tsx
+++ b/src/app/admin/idp/create/page.tsx
@@ -39,38 +39,6 @@ import { Badge } from "@app/components/ui/badge";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useTranslations } from "next-intl";
-const createIdpFormSchema = z.object({
- name: z.string().min(2, "Name must be at least 2 characters."),
- type: z.enum(["oidc"]),
- clientId: z.string().min(1, { message: "Client ID is required." }),
- clientSecret: z.string().min(1, { message: "Client Secret is required." }),
- authUrl: z.string().url({ message: "Auth URL must be a valid URL." }),
- tokenUrl: z.string().url({ message: "Token URL must be a valid URL." }),
- identifierPath: z
- .string()
- .min(1, { message: "Identifier Path is required." }),
- emailPath: z.string().optional(),
- namePath: z.string().optional(),
- scopes: z.string().min(1, { message: "Scopes are required." }),
- autoProvision: z.boolean().default(false)
-});
-
-type CreateIdpFormValues = z.infer;
-
-interface ProviderTypeOption {
- id: "oidc";
- title: string;
- description: string;
-}
-
-const providerTypes: ReadonlyArray = [
- {
- id: "oidc",
- title: "OAuth2/OIDC",
- description: "Configure an OpenID Connect identity provider"
- }
-];
-
export default function Page() {
const { env } = useEnvContext();
const api = createApiClient({ env });
@@ -79,6 +47,38 @@ export default function Page() {
const { isUnlocked } = useLicenseStatusContext();
const t = useTranslations();
+ const createIdpFormSchema = z.object({
+ name: z.string().min(2, { message: t('nameMin', {len: 2}) }),
+ type: z.enum(["oidc"]),
+ clientId: z.string().min(1, { message: t('idpClientIdRequired') }),
+ clientSecret: z.string().min(1, { message: t('idpClientSecretRequired') }),
+ authUrl: z.string().url({ message: t('idpErrorAuthUrlInvalid') }),
+ tokenUrl: z.string().url({ message: t('idpErrorTokenUrlInvalid') }),
+ identifierPath: z
+ .string()
+ .min(1, { message: t('idpPathRequired') }),
+ emailPath: z.string().optional(),
+ namePath: z.string().optional(),
+ scopes: z.string().min(1, { message: t('idpScopeRequired') }),
+ autoProvision: z.boolean().default(false)
+ });
+
+ type CreateIdpFormValues = z.infer;
+
+ interface ProviderTypeOption {
+ id: "oidc";
+ title: string;
+ description: string;
+ }
+
+ const providerTypes: ReadonlyArray = [
+ {
+ id: "oidc",
+ title: "OAuth2/OIDC",
+ description: t('idpOidcDescription')
+ }
+ ];
+
const form = useForm({
resolver: zodResolver(createIdpFormSchema),
defaultValues: {
diff --git a/src/app/admin/idp/page.tsx b/src/app/admin/idp/page.tsx
index 3f40e960..4db77785 100644
--- a/src/app/admin/idp/page.tsx
+++ b/src/app/admin/idp/page.tsx
@@ -3,7 +3,7 @@ import { authCookieHeader } from "@app/lib/api/cookies";
import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import IdpTable, { IdpRow } from "./AdminIdpTable";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export default async function IdpPage() {
let idps: IdpRow[] = [];
diff --git a/src/app/admin/license/LicenseKeysDataTable.tsx b/src/app/admin/license/LicenseKeysDataTable.tsx
index e98fef1a..d9ace464 100644
--- a/src/app/admin/license/LicenseKeysDataTable.tsx
+++ b/src/app/admin/license/LicenseKeysDataTable.tsx
@@ -8,7 +8,7 @@ import { LicenseKeyCache } from "@server/license/license";
import { ArrowUpDown } from "lucide-react";
import moment from "moment";
import CopyToClipboard from "@app/components/CopyToClipboard";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type LicenseKeysDataTableProps = {
licenseKeys: LicenseKeyCache[];
diff --git a/src/app/admin/license/components/SitePriceCalculator.tsx b/src/app/admin/license/components/SitePriceCalculator.tsx
index d42facc5..5d09fa54 100644
--- a/src/app/admin/license/components/SitePriceCalculator.tsx
+++ b/src/app/admin/license/components/SitePriceCalculator.tsx
@@ -11,7 +11,7 @@ import {
CredenzaHeader,
CredenzaTitle
} from "@app/components/Credenza";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type SitePriceCalculatorProps = {
isOpen: boolean;
diff --git a/src/app/admin/license/page.tsx b/src/app/admin/license/page.tsx
index ac2c6d67..83549e92 100644
--- a/src/app/admin/license/page.tsx
+++ b/src/app/admin/license/page.tsx
@@ -54,17 +54,7 @@ import Link from "next/link";
import { Checkbox } from "@app/components/ui/checkbox";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
-import { useTranslations } from 'next-intl';
-
-const formSchema = z.object({
- licenseKey: z
- .string()
- .nonempty({ message: "License key is required" })
- .max(255),
- agreeToTerms: z.boolean().refine((val) => val === true, {
- message: "You must agree to the license terms"
- })
-});
+import { useTranslations } from "next-intl";
function obfuscateLicenseKey(key: string): string {
if (key.length <= 8) return key;
@@ -95,6 +85,18 @@ export default function LicensePage() {
const [isRecheckingLicense, setIsRecheckingLicense] = useState(false);
const { supporterStatus } = useSupporterStatusContext();
+ const t = useTranslations();
+
+ const formSchema = z.object({
+ licenseKey: z
+ .string()
+ .nonempty({ message: t('licenseKeyRequired') })
+ .max(255),
+ agreeToTerms: z.boolean().refine((val) => val === true, {
+ message: t('licenseTermsAgree')
+ })
+ });
+
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -103,8 +105,6 @@ export default function LicensePage() {
}
});
- const t = useTranslations();
-
useEffect(() => {
async function load() {
setIsInitialLoading(true);
diff --git a/src/app/admin/users/AdminUsersDataTable.tsx b/src/app/admin/users/AdminUsersDataTable.tsx
index 3a1e85cf..5e1e3ce8 100644
--- a/src/app/admin/users/AdminUsersDataTable.tsx
+++ b/src/app/admin/users/AdminUsersDataTable.tsx
@@ -4,7 +4,7 @@ import {
ColumnDef,
} from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
interface DataTableProps {
columns: ColumnDef[];
diff --git a/src/app/admin/users/AdminUsersTable.tsx b/src/app/admin/users/AdminUsersTable.tsx
index 75d7a731..fdb09f8a 100644
--- a/src/app/admin/users/AdminUsersTable.tsx
+++ b/src/app/admin/users/AdminUsersTable.tsx
@@ -11,7 +11,7 @@ import { toast } from "@app/hooks/useToast";
import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
export type GlobalUserRow = {
id: string;
diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx
index 793c5f31..1e29a311 100644
--- a/src/app/admin/users/page.tsx
+++ b/src/app/admin/users/page.tsx
@@ -6,7 +6,7 @@ import { AdminListUsersResponse } from "@server/routers/user/adminListUsers";
import UsersTable, { GlobalUserRow } from "./AdminUsersTable";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon } from "lucide-react";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
type PageProps = {
params: Promise<{ orgId: string }>;
diff --git a/src/app/auth/idp/[idpId]/oidc/callback/page.tsx b/src/app/auth/idp/[idpId]/oidc/callback/page.tsx
index 2bbea496..f5e9eb07 100644
--- a/src/app/auth/idp/[idpId]/oidc/callback/page.tsx
+++ b/src/app/auth/idp/[idpId]/oidc/callback/page.tsx
@@ -3,7 +3,7 @@ import ValidateOidcToken from "./ValidateOidcToken";
import { idp } from "@server/db/schemas";
import db from "@server/db";
import { eq } from "drizzle-orm";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export default async function Page(props: {
params: Promise<{ orgId: string; idpId: string }>;
diff --git a/src/app/auth/layout.tsx b/src/app/auth/layout.tsx
index 3017030b..05a65fce 100644
--- a/src/app/auth/layout.tsx
+++ b/src/app/auth/layout.tsx
@@ -8,7 +8,7 @@ import { AxiosResponse } from "axios";
import { ExternalLink } from "lucide-react";
import { Metadata } from "next";
import { cache } from "react";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export const metadata: Metadata = {
title: `Auth - Pangolin`,
diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx
index 13204796..9ac45c66 100644
--- a/src/app/auth/login/page.tsx
+++ b/src/app/auth/login/page.tsx
@@ -9,7 +9,7 @@ import { cleanRedirect } from "@app/lib/cleanRedirect";
import db from "@server/db";
import { idp } from "@server/db/schemas";
import { LoginFormIDP } from "@app/components/LoginForm";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export const dynamic = "force-dynamic";
diff --git a/src/app/auth/reset-password/ResetPasswordForm.tsx b/src/app/auth/reset-password/ResetPasswordForm.tsx
index 92cfa7db..8262c738 100644
--- a/src/app/auth/reset-password/ResetPasswordForm.tsx
+++ b/src/app/auth/reset-password/ResetPasswordForm.tsx
@@ -50,22 +50,6 @@ const requestSchema = z.object({
email: z.string().email()
});
-const formSchema = z
- .object({
- email: z.string().email({ message: "Invalid email address" }),
- token: z.string().min(8, { message: "Invalid token" }),
- password: passwordSchema,
- confirmPassword: passwordSchema
- })
- .refine((data) => data.password === data.confirmPassword, {
- path: ["confirmPassword"],
- message: "Passwords do not match"
- });
-
-const mfaSchema = z.object({
- code: z.string().length(6, { message: "Invalid code" })
-});
-
export type ResetPasswordFormProps = {
emailParam?: string;
tokenParam?: string;
@@ -82,6 +66,7 @@ export default function ResetPasswordForm({
const [error, setError] = useState(null);
const [successMessage, setSuccessMessage] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const t = useTranslations();
function getState() {
if (emailParam && !tokenParam) {
@@ -99,6 +84,22 @@ export default function ResetPasswordForm({
const api = createApiClient(useEnvContext());
+ const formSchema = z
+ .object({
+ email: z.string().email({ message: t('emailInvalid') }),
+ token: z.string().min(8, { message: t('tokenInvalid') }),
+ password: passwordSchema,
+ confirmPassword: passwordSchema
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ path: ["confirmPassword"],
+ message: t('passwordNotMatch')
+ });
+
+ const mfaSchema = z.object({
+ code: z.string().length(6, { message: t('pincodeInvalid') })
+ });
+
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -123,8 +124,6 @@ export default function ResetPasswordForm({
}
});
- const t = useTranslations();
-
async function onRequest(data: z.infer) {
const { email } = data;
diff --git a/src/app/auth/reset-password/page.tsx b/src/app/auth/reset-password/page.tsx
index f948c34b..a0466208 100644
--- a/src/app/auth/reset-password/page.tsx
+++ b/src/app/auth/reset-password/page.tsx
@@ -4,7 +4,7 @@ import { cache } from "react";
import ResetPasswordForm from "./ResetPasswordForm";
import Link from "next/link";
import { cleanRedirect } from "@app/lib/cleanRedirect";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export const dynamic = "force-dynamic";
diff --git a/src/app/auth/resource/[resourceId]/ResourceNotFound.tsx b/src/app/auth/resource/[resourceId]/ResourceNotFound.tsx
index 11c7b817..518fe488 100644
--- a/src/app/auth/resource/[resourceId]/ResourceNotFound.tsx
+++ b/src/app/auth/resource/[resourceId]/ResourceNotFound.tsx
@@ -7,11 +7,11 @@ import {
CardTitle,
} from "@app/components/ui/card";
import Link from "next/link";
-import { useTranslations } from "next-intl";
+import { getTranslations } from "next-intl/server";
export default async function ResourceNotFound() {
- const t = useTranslations();
+ const t = await getTranslations();
return (
diff --git a/src/app/auth/signup/SignupForm.tsx b/src/app/auth/signup/SignupForm.tsx
index d3639a0e..bd693180 100644
--- a/src/app/auth/signup/SignupForm.tsx
+++ b/src/app/auth/signup/SignupForm.tsx
@@ -31,7 +31,7 @@ import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import Image from "next/image";
import { cleanRedirect } from "@app/lib/cleanRedirect";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type SignupFormProps = {
redirect?: string;
diff --git a/src/app/auth/verify-email/VerifyEmailForm.tsx b/src/app/auth/verify-email/VerifyEmailForm.tsx
index eb7c4db0..cbe1e5fb 100644
--- a/src/app/auth/verify-email/VerifyEmailForm.tsx
+++ b/src/app/auth/verify-email/VerifyEmailForm.tsx
@@ -39,13 +39,6 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { cleanRedirect } from "@app/lib/cleanRedirect";
import { useTranslations } from "next-intl";
-const FormSchema = z.object({
- email: z.string().email({ message: "Invalid email address" }),
- pin: z.string().min(8, {
- message: "Your verification code must be 8 characters.",
- }),
-});
-
export type VerifyEmailFormProps = {
email: string;
redirect?: string;
@@ -65,6 +58,13 @@ export default function VerifyEmailForm({
const api = createApiClient(useEnvContext());
+ const FormSchema = z.object({
+ email: z.string().email({ message: t('emailInvalid') }),
+ pin: z.string().min(8, {
+ message: t('verificationCodeLengthRequirements'),
+ }),
+ });
+
const form = useForm>({
resolver: zodResolver(FormSchema),
defaultValues: {
diff --git a/src/app/components/LicenseViolation.tsx b/src/app/components/LicenseViolation.tsx
index 980fa7d1..ea025e4c 100644
--- a/src/app/components/LicenseViolation.tsx
+++ b/src/app/components/LicenseViolation.tsx
@@ -3,7 +3,7 @@
import { Button } from "@app/components/ui/button";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useState } from "react";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
export default function LicenseViolation() {
const { licenseStatus } = useLicenseStatusContext();
diff --git a/src/app/components/OrganizationLanding.tsx b/src/app/components/OrganizationLanding.tsx
index 1a3a4086..a443fcf3 100644
--- a/src/app/components/OrganizationLanding.tsx
+++ b/src/app/components/OrganizationLanding.tsx
@@ -11,7 +11,7 @@ import {
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { ArrowRight, Plus } from "lucide-react";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
interface Organization {
id: string;
diff --git a/src/app/invite/InviteStatusCard.tsx b/src/app/invite/InviteStatusCard.tsx
index c2c757c6..3ecf16f5 100644
--- a/src/app/invite/InviteStatusCard.tsx
+++ b/src/app/invite/InviteStatusCard.tsx
@@ -12,7 +12,7 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext";
import { XCircle } from "lucide-react";
import { useRouter } from "next/navigation";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type InviteStatusCardProps = {
type: "rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in";
diff --git a/src/app/invite/page.tsx b/src/app/invite/page.tsx
index bf423a75..014fb45b 100644
--- a/src/app/invite/page.tsx
+++ b/src/app/invite/page.tsx
@@ -6,7 +6,7 @@ import { AxiosResponse } from "axios";
import { redirect } from "next/navigation";
import InviteStatusCard from "./InviteStatusCard";
import { formatAxiosError } from "@app/lib/api";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export default async function InvitePage(props: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7e11ea7..dd02c489 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -13,8 +13,8 @@ import LicenseStatusProvider from "@app/providers/LicenseStatusProvider";
import { GetLicenseStatusResponse } from "@server/routers/license";
import LicenseViolation from "./components/LicenseViolation";
import { cache } from "react";
-import { NextIntlClientProvider } from 'next-intl';
-import { getLocale } from 'next-intl/server';
+import { NextIntlClientProvider } from "next-intl";
+import { getLocale } from "next-intl/server";
export const metadata: Metadata = {
title: `Dashboard - Pangolin`,
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
index 058ccc2b..60c02bee 100644
--- a/src/app/not-found.tsx
+++ b/src/app/not-found.tsx
@@ -1,4 +1,4 @@
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
export default async function NotFound() {
diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx
index 293dc24a..7926f359 100644
--- a/src/app/setup/page.tsx
+++ b/src/app/setup/page.tsx
@@ -33,15 +33,10 @@ import {
} from "@app/components/ui/form";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import CreateSiteForm from "../[orgId]/settings/sites/CreateSiteForm";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type Step = "org" | "site" | "resources";
-const orgSchema = z.object({
- orgName: z.string().min(1, { message: "Organization name is required" }),
- orgId: z.string().min(1, { message: "Organization ID is required" })
-});
-
export default function StepperForm() {
const [currentStep, setCurrentStep] = useState("org");
const [orgIdTaken, setOrgIdTaken] = useState(false);
@@ -51,6 +46,11 @@ export default function StepperForm() {
const [isChecked, setIsChecked] = useState(false);
const [error, setError] = useState(null);
+ const orgSchema = z.object({
+ orgName: z.string().min(1, { message: t('orgNameRequired') }),
+ orgId: z.string().min(1, { message: t('orgIdRequired') })
+ });
+
const orgForm = useForm>({
resolver: zodResolver(orgSchema),
defaultValues: {
diff --git a/src/components/Enable2FaForm.tsx b/src/components/Enable2FaForm.tsx
index 80224173..47cfb8e8 100644
--- a/src/components/Enable2FaForm.tsx
+++ b/src/components/Enable2FaForm.tsx
@@ -42,14 +42,6 @@ import { QRCodeCanvas, QRCodeSVG } from "qrcode.react";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from "next-intl";
-const enableSchema = z.object({
- password: z.string().min(1, { message: "Password is required" })
-});
-
-const confirmSchema = z.object({
- code: z.string().length(6, { message: "Invalid code" })
-});
-
type Enable2FaProps = {
open: boolean;
setOpen: (val: boolean) => void;
@@ -68,6 +60,15 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
const { user, updateUser } = useUserContext();
const api = createApiClient(useEnvContext());
+ const t = useTranslations();
+
+ const enableSchema = z.object({
+ password: z.string().min(1, { message: t('passwordRequired') })
+ });
+
+ const confirmSchema = z.object({
+ code: z.string().length(6, { message: t('pincodeInvalid') })
+ });
const enableForm = useForm>({
resolver: zodResolver(enableSchema),
@@ -83,8 +84,6 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
}
});
- const t = useTranslations();
-
const request2fa = async (values: z.infer) => {
setLoading(true);
diff --git a/src/components/PermissionsSelectBox.tsx b/src/components/PermissionsSelectBox.tsx
index 3bd90ae4..3f225dee 100644
--- a/src/components/PermissionsSelectBox.tsx
+++ b/src/components/PermissionsSelectBox.tsx
@@ -163,13 +163,15 @@ export default function PermissionsSelectBox({
onChange(updated);
};
+ const t = useTranslations();
+
return (
<>
toggleAllPermissions(checked as boolean)
@@ -188,7 +190,7 @@ export default function PermissionsSelectBox({
toggleAllInCategory(
diff --git a/src/components/SupporterStatus.tsx b/src/components/SupporterStatus.tsx
index 5febb624..a17b9b9f 100644
--- a/src/components/SupporterStatus.tsx
+++ b/src/components/SupporterStatus.tsx
@@ -50,13 +50,6 @@ import { Check, ExternalLink } from "lucide-react";
import confetti from "canvas-confetti";
import { useTranslations } from "next-intl";
-const formSchema = z.object({
- githubUsername: z
- .string()
- .nonempty({ message: "GitHub username is required" }),
- key: z.string().nonempty({ message: "Supporter key is required" })
-});
-
export default function SupporterStatus() {
const { supporterStatus, updateSupporterStatus } =
useSupporterStatusContext();
@@ -65,6 +58,14 @@ export default function SupporterStatus() {
const [purchaseOptionsOpen, setPurchaseOptionsOpen] = useState(false);
const api = createApiClient(useEnvContext());
+ const t = useTranslations();
+
+ const formSchema = z.object({
+ githubUsername: z
+ .string()
+ .nonempty({ message: "GitHub username is required" }),
+ key: z.string().nonempty({ message: "Supporter key is required" })
+ });
const form = useForm>({
resolver: zodResolver(formSchema),
@@ -74,8 +75,6 @@ export default function SupporterStatus() {
}
});
- const t = useTranslations();
-
async function hide() {
await api.post("/supporter-key/hide");
@@ -167,7 +166,7 @@ export default function SupporterStatus() {
title: t('error'),
description: formatAxiosError(
error,
- "Failed to validate supporter key."
+ t('supportKeyErrorValidationDescription')
)
});
return;
diff --git a/src/components/tags/autocomplete.tsx b/src/components/tags/autocomplete.tsx
index 32e6f42d..04806643 100644
--- a/src/components/tags/autocomplete.tsx
+++ b/src/components/tags/autocomplete.tsx
@@ -4,7 +4,7 @@ import { TagInputStyleClassesProps, type Tag as TagType } from "./tag-input";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { Button } from "../ui/button";
import { cn } from "@app/lib/cn";
-import { useTranslations } from 'next-intl';
+import { useTranslations } from "next-intl";
type AutocompleteProps = {
tags: TagType[];