Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import '@ui5/webcomponents-cypress-commands';
import "../../i18n";
import "../../src/utils/i18n/i18n";
22 changes: 0 additions & 22 deletions i18n.ts

This file was deleted.

8 changes: 6 additions & 2 deletions src/components/Dialogs/CreateProjectDialog.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import React, { useState, useRef } from 'react';
import React, { useState, useRef, useMemo } from 'react';
import { CreateProjectWorkspaceDialog, OnCreatePayload } from './CreateProjectWorkspaceDialog';
import { MemberRoles } from '../../lib/api/types/shared/members';
import { ErrorDialogHandle } from '../Shared/ErrorMessageBox';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts';
import { createProjectWorkspaceSchema } from '../../lib/api/validations/schemas.ts';
import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx';
import { useTranslation } from 'react-i18next';

export const CreateProjectWorkspaceDialogWrapper: React.FC<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
spyFormBody?: (data: any) => object;
}> = ({ spyFormBody }) => {
const [isOpen, setIsOpen] = useState(true);
const { t } = useTranslation();

const errorDialogRef = useRef<ErrorDialogHandle>(null);

const validationSchemaProjectWorkspace = useMemo(() => createProjectWorkspaceSchema(t), [t]);

const {
register,
handleSubmit,
Expand Down
7 changes: 4 additions & 3 deletions src/components/Dialogs/CreateProjectDialogContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useApiResourceMutation } from '../../lib/api/useApiResource';
import { ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
import { APIError } from '../../lib/api/error';
Expand All @@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { CreateProject, CreateProjectResource, CreateProjectType } from '../../lib/api/types/crate/createProject.ts';
import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts';
import { createProjectWorkspaceSchema } from '../../lib/api/validations/schemas.ts';
import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx';

export function CreateProjectDialogContainer({
Expand All @@ -22,6 +22,8 @@ export function CreateProjectDialogContainer({
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
}) {
const { t } = useTranslation();
const validationSchemaProjectWorkspace = useMemo(() => createProjectWorkspaceSchema(t), [t]);
const {
watch,
register,
Expand All @@ -39,7 +41,6 @@ export function CreateProjectDialogContainer({
members: [],
},
});
const { t } = useTranslation();
const { user } = useAuthOnboarding();

const username = user?.email;
Expand Down
7 changes: 4 additions & 3 deletions src/components/Dialogs/CreateWorkspaceDialogContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useApiResourceMutation, useRevalidateApiResource } from '../../lib/api/useApiResource';
import { ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
import { APIError } from '../../lib/api/error';
Expand All @@ -16,7 +16,7 @@ import { Member, MemberRoles } from '../../lib/api/types/shared/members.ts';
import { useTranslation } from 'react-i18next';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { validationSchemaProjectWorkspace } from '../../lib/api/validations/schemas.ts';
import { createProjectWorkspaceSchema } from '../../lib/api/validations/schemas.ts';
import { ComponentsListItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';

export type CreateDialogProps = {
Expand All @@ -37,6 +37,8 @@ export function CreateWorkspaceDialogContainer({
setIsOpen: (isOpen: boolean) => void;
project?: string;
}) {
const { t } = useTranslation();
const validationSchemaProjectWorkspace = useMemo(() => createProjectWorkspaceSchema(t), [t]);
const {
register,
handleSubmit,
Expand All @@ -54,7 +56,6 @@ export function CreateWorkspaceDialogContainer({
chargingTargetType: '',
},
});
const { t } = useTranslation();
const { user } = useAuthOnboarding();

const username = user?.email;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useTranslation } from 'react-i18next';
import { useAuthOnboarding } from '../../../spaces/onboarding/auth/AuthContextOnboarding.tsx';
import { ErrorDialog, ErrorDialogHandle } from '../../Shared/ErrorMessageBox.tsx';
import { CreateDialogProps } from '../../Dialogs/CreateWorkspaceDialogContainer.tsx';
import { validationSchemaCreateManagedControlPlane } from '../../../lib/api/validations/schemas.ts';
import { createManagedControlPlaneSchema } from '../../../lib/api/validations/schemas.ts';
import { Member, MemberRoles } from '../../../lib/api/types/shared/members.ts';
import { useApiResourceMutation } from '../../../lib/api/useApiResource.ts';
import {
Expand Down Expand Up @@ -62,6 +62,7 @@ export const CreateManagedControlPlaneWizardContainer: FC<CreateManagedControlPl
const errorDialogRef = useRef<ErrorDialogHandle>(null);

const [selectedStep, setSelectedStep] = useState<WizardStepType>('metadata');
const validationSchemaCreateManagedControlPlane = useMemo(() => createManagedControlPlaneSchema(t), [t]);

const {
register,
Expand Down
93 changes: 48 additions & 45 deletions src/lib/api/validations/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,59 @@
import { z } from 'zod';
import { Member } from '../types/shared/members.ts';
import i18n from '../../../../i18n.ts';
import { TFunction } from 'i18next';
import { btpChargingTargetRegex, managedControlPlaneNameRegex, projectWorkspaceNameRegex } from './regex.ts';

const { t } = i18n;

const member = z.custom<Member>();

// Shared superRefine helper for charging target validation
function validateChargingTarget<T extends { chargingTargetType?: string; chargingTarget?: string }>(
data: T,
ctx: z.RefinementCtx,
function createValidateChargingTarget<T extends { chargingTargetType?: string; chargingTarget?: string }>(
t: TFunction,
) {
if (data.chargingTargetType && data.chargingTarget && !btpChargingTargetRegex.test(data.chargingTarget ?? '')) {
ctx.addIssue({
path: ['chargingTarget'],
code: z.ZodIssueCode.custom,
message: t('validationErrors.notValidChargingTargetFormat'),
});
} else if (data.chargingTargetType && !data.chargingTarget) {
ctx.addIssue({
path: ['chargingTarget'],
code: z.ZodIssueCode.custom,
message: t('validationErrors.required'),
});
}
return (data: T, ctx: z.RefinementCtx) => {
if (data.chargingTargetType && data.chargingTarget && !btpChargingTargetRegex.test(data.chargingTarget ?? '')) {
ctx.addIssue({
path: ['chargingTarget'],
code: z.ZodIssueCode.custom,
message: t('validationErrors.notValidChargingTargetFormat'),
});
} else if (data.chargingTargetType && !data.chargingTarget) {
ctx.addIssue({
path: ['chargingTarget'],
code: z.ZodIssueCode.custom,
message: t('validationErrors.required'),
});
}
};
}

export const validationSchemaProjectWorkspace = z
.object({
name: z
.string()
.min(1, t('validationErrors.required'))
.regex(projectWorkspaceNameRegex, t('validationErrors.properFormatting'))
.max(25, t('validationErrors.maxChars', { maxLength: 25 })),
displayName: z.string().optional(),
chargingTarget: z.string().optional(),
chargingTargetType: z.string().optional(),
members: z.array(member).refine((members) => members?.length > 0),
})
.superRefine(validateChargingTarget);
export function createProjectWorkspaceSchema(t: TFunction) {
return z
.object({
name: z
.string()
.min(1, t('validationErrors.required'))
.regex(projectWorkspaceNameRegex, t('validationErrors.properFormatting'))
.max(25, t('validationErrors.maxChars', { maxLength: 25 })),
displayName: z.string().optional(),
chargingTarget: z.string().optional(),
chargingTargetType: z.string().optional(),
members: z.array(member).refine((members) => members?.length > 0),
})
.superRefine(createValidateChargingTarget(t));
}

export const validationSchemaCreateManagedControlPlane = z
.object({
name: z
.string()
.min(1, t('validationErrors.required'))
.regex(managedControlPlaneNameRegex, t('validationErrors.properFormattingLowercase'))
.max(36, t('validationErrors.maxChars', { maxLength: 36 })),
displayName: z.string().optional(),
chargingTarget: z.string().optional(),
chargingTargetType: z.string().optional(),
members: z.array(member),
})
.superRefine(validateChargingTarget);
export function createManagedControlPlaneSchema(t: TFunction) {
return z
.object({
name: z
.string()
.min(1, t('validationErrors.required'))
.regex(managedControlPlaneNameRegex, t('validationErrors.properFormattingLowercase'))
.max(36, t('validationErrors.maxChars', { maxLength: 36 })),
displayName: z.string().optional(),
chargingTarget: z.string().optional(),
chargingTargetType: z.string().optional(),
members: z.array(member),
})
.superRefine(createValidateChargingTarget(t));
}
2 changes: 1 addition & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CopyButtonProvider } from './context/CopyButtonContext.tsx';
import { FrontendConfigProvider } from './context/FrontendConfigContext.tsx';
import '@ui5/webcomponents-react/dist/Assets'; //used for loading themes
import { ThemeManager } from './components/ThemeManager.tsx';
import '.././i18n.ts';
import './utils/i18n/i18n.ts';
import './utils/i18n/timeAgo';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { ApolloClientProvider } from './spaces/onboarding/services/ApolloClientProvider/ApolloClientProvider.tsx';
Expand Down
19 changes: 19 additions & 0 deletions src/utils/i18n/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import translationEN from '../../../public/locales/en.json';

const resources = {
en: {
translation: translationEN,
},
};

i18n.use(initReactI18next).init({
resources,
lng: 'en',
fallbackLng: 'en',
debug: process.env.NODE_ENV === 'development',
});

export default i18n;
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"noFallthroughCasesInSwitch": true,
"types": ["node", "cypress"]
},
"include": ["src", "cypress.d.ts", "server.js", "i18n.ts", "server/**/*"],
"include": ["src", "cypress.d.ts", "server.js", "server/**/*"],
"references": [
{
"path": "./tsconfig.node.json"
Expand Down
Loading