From f07e8d08c33b81de2d8befa8e4dfe89773700425 Mon Sep 17 00:00:00 2001 From: Lokowitz Date: Sun, 25 May 2025 19:01:20 +0000 Subject: [PATCH] update all --- messages/en-US.json | 8 +- next.config.mjs | 6 +- .../access/invitations/InvitationsTable.tsx | 2 +- .../invitations/RegenerateInvitationForm.tsx | 16 ++-- .../settings/access/users/UsersTable.tsx | 4 +- .../settings/access/users/create/page.tsx | 18 ++--- .../settings/api-keys/OrgApiKeysTable.tsx | 2 +- src/app/[orgId]/settings/general/page.tsx | 6 +- .../[resourceId]/ResourceInfoBox.tsx | 2 +- .../[resourceId]/authentication/page.tsx | 10 +-- .../resources/[resourceId]/general/page.tsx | 78 +++++++++--------- .../resources/[resourceId]/proxy/page.tsx | 81 +++++++++---------- .../resources/[resourceId]/rules/page.tsx | 3 +- .../settings/resources/create/page.tsx | 2 +- src/app/[orgId]/settings/resources/page.tsx | 2 +- .../settings/share-links/AccessTokenUsage.tsx | 2 +- .../share-links/CreateShareLinkForm.tsx | 21 +++-- .../share-links/ShareLinksDataTable.tsx | 2 +- .../settings/share-links/ShareLinksTable.tsx | 2 +- src/app/[orgId]/settings/share-links/page.tsx | 2 +- .../[orgId]/settings/sites/CreateSiteForm.tsx | 40 ++++----- .../settings/sites/CreateSiteModal.tsx | 2 +- .../[orgId]/settings/sites/SitesDataTable.tsx | 2 +- src/app/[orgId]/settings/sites/SitesTable.tsx | 2 +- .../settings/sites/[niceId]/SiteInfoCard.tsx | 2 +- .../settings/sites/[niceId]/general/page.tsx | 16 ++-- .../settings/sites/[niceId]/layout.tsx | 2 +- .../[orgId]/settings/sites/create/page.tsx | 54 ++++++------- src/app/[orgId]/settings/sites/page.tsx | 2 +- src/app/admin/api-keys/[apiKeyId]/layout.tsx | 2 +- src/app/admin/api-keys/create/page.tsx | 58 ++++++------- src/app/admin/api-keys/page.tsx | 2 +- src/app/admin/idp/AdminIdpTable.tsx | 2 +- src/app/admin/idp/[idpId]/general/page.tsx | 37 +++++---- src/app/admin/idp/[idpId]/layout.tsx | 4 +- src/app/admin/idp/[idpId]/policies/page.tsx | 28 +++---- src/app/admin/idp/create/page.tsx | 64 +++++++-------- src/app/admin/idp/page.tsx | 2 +- .../admin/license/LicenseKeysDataTable.tsx | 2 +- .../components/SitePriceCalculator.tsx | 2 +- src/app/admin/license/page.tsx | 26 +++--- src/app/admin/users/AdminUsersDataTable.tsx | 2 +- src/app/admin/users/AdminUsersTable.tsx | 2 +- src/app/admin/users/page.tsx | 2 +- .../auth/idp/[idpId]/oidc/callback/page.tsx | 2 +- src/app/auth/layout.tsx | 2 +- src/app/auth/login/page.tsx | 2 +- .../auth/reset-password/ResetPasswordForm.tsx | 35 ++++---- src/app/auth/reset-password/page.tsx | 2 +- .../[resourceId]/ResourceNotFound.tsx | 4 +- src/app/auth/signup/SignupForm.tsx | 2 +- src/app/auth/verify-email/VerifyEmailForm.tsx | 14 ++-- src/app/components/LicenseViolation.tsx | 2 +- src/app/components/OrganizationLanding.tsx | 2 +- src/app/invite/InviteStatusCard.tsx | 2 +- src/app/invite/page.tsx | 2 +- src/app/layout.tsx | 4 +- src/app/not-found.tsx | 2 +- src/app/setup/page.tsx | 12 +-- src/components/Enable2FaForm.tsx | 19 +++-- src/components/PermissionsSelectBox.tsx | 6 +- src/components/SupporterStatus.tsx | 19 +++-- src/components/tags/autocomplete.tsx | 2 +- 63 files changed, 380 insertions(+), 381 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index c2969842..d63ef985 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -458,6 +458,7 @@ "createdAt": "Created At", "proxyErrorInvalidHeader": "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header.", "proxyErrorTls": "Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name.", + "proxyEnableSSL": "Enable SSL (https)", "targetErrorFetch": "Failed to fetch targets", "targetErrorFetchDescription": "An error occurred while fetching targets", "siteErrorFetch": "Failed to fetch resource", @@ -850,7 +851,7 @@ "otpEmail": "One-Time Password (OTP)", "otpEmailSubmit": "Submit OTP", "backToEmail": "Back to Email", - "noSupportKey": "Server is running without a supporter key.
Consider supporting the project!", + "noSupportKey": "Server is running without a supporter key. Consider supporting the project!", "accessDenied": "Access Denied", "accessDeniedDescription": "You're not allowed to access this resource. If this is a mistake, please contact the administrator.", "accessTokenError": "Error checking access token", @@ -1059,5 +1060,8 @@ "copyText": "Copy text", "copyTextFailed": "Failed to copy text: ", "copyTextClipboard": "Copy to clipboard", - "inviteErrorInvalidConfirmation": "Invalid confirmation" + "inviteErrorInvalidConfirmation": "Invalid confirmation", + "passwordRequired": "Password is required", + "allowAll": "Allow All", + "permissionsAllowAll": "Allow All Permissions" } diff --git a/next.config.mjs b/next.config.mjs index e8856db1..c870f1c1 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,13 +1,13 @@ -import createNextIntlPlugin from 'next-intl/plugin'; +import createNextIntlPlugin from "next-intl/plugin"; const withNextIntl = createNextIntlPlugin(); -/** @type {import('next').NextConfig} */ +/** @type {import("next").NextConfig} */ const nextConfig = { eslint: { ignoreDuringBuilds: true }, - output: 'standalone' + output: "standalone" }; export default withNextIntl(nextConfig); diff --git a/src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx b/src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx index 6f947b52..95fadf42 100644 --- a/src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx +++ b/src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx @@ -148,7 +148,7 @@ export default function InvitationsTable({ dialog={

- {t('inviteQuestionRemove', {email: selectedInvitation?.email || ''})} + {t('inviteQuestionRemove', {email: selectedInvitation?.email})}

{t('inviteMessageRemove')} diff --git a/src/app/[orgId]/settings/access/invitations/RegenerateInvitationForm.tsx b/src/app/[orgId]/settings/access/invitations/RegenerateInvitationForm.tsx index 59c1b1b4..fbd1c4f5 100644 --- a/src/app/[orgId]/settings/access/invitations/RegenerateInvitationForm.tsx +++ b/src/app/[orgId]/settings/access/invitations/RegenerateInvitationForm.tsx @@ -60,13 +60,13 @@ export default function RegenerateInvitationForm({ const t = useTranslations(); const validForOptions = [ - { hours: 24, name: t('day', { count: 1 }) }, - { hours: 48, name: t('day', { count: 2 }) }, - { hours: 72, name: t('day', { count: 3 }) }, - { hours: 96, name: t('day', { count: 4 }) }, - { hours: 120, name: t('day', { count: 5 }) }, - { hours: 144, name: t('day', { count: 6 }) }, - { hours: 168, name: t('day', { count: 7 }) } + { hours: 24, name: t('day', {count: 1}) }, + { hours: 48, name: t('day', {count: 2}) }, + { hours: 72, name: t('day', {count: 3}) }, + { hours: 96, name: t('day', {count: 4}) }, + { hours: 120, name: t('day', {count: 5}) }, + { hours: 144, name: t('day', {count: 6}) }, + { hours: 168, name: t('day', {count: 7}) } ]; useEffect(() => { @@ -177,7 +177,7 @@ export default function RegenerateInvitationForm({ {!inviteLink ? (

- {t('inviteQuestionRegenerate', {email: invitation?.email || ''})} + {t('inviteQuestionRegenerate', {email: invitation?.email})}

@@ -244,7 +244,7 @@ export default function UsersTable({ users: u }: UsersTableProps) { dialog={

- {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[];