Skip to content

Commit

Permalink
feat: UI for creating cloud plan > first piece
Browse files Browse the repository at this point in the history
  • Loading branch information
JanCizmar committed Feb 25, 2025
1 parent 5dc97f6 commit bb8ba56
Show file tree
Hide file tree
Showing 18 changed files with 547 additions and 1,075 deletions.
4 changes: 2 additions & 2 deletions .run/Frontend localhost.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Frontend localhost" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/webapp/package.json" />
<package-json value="$PROJECT_DIR$/public/webapp/package.json" />
<command value="run" />
<scripts>
<script value="start" />
Expand All @@ -9,4 +9,4 @@
<envs />
<method v="2" />
</configuration>
</component>
</component>
4 changes: 3 additions & 1 deletion e2e/cypress/support/dataCyType.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ declare namespace DataCy {
"administration-billing-trial-badge" |
"administration-cloud-plan-field-feature" |
"administration-cloud-plan-field-free" |
"administration-cloud-plan-field-included-keys" |
"administration-cloud-plan-field-included-mt-credits" |
"administration-cloud-plan-field-included-translations" |
"administration-cloud-plan-field-name" |
"administration-cloud-plan-field-price-monthly" |
"administration-cloud-plan-field-price-per-seat" |
"administration-cloud-plan-field-price-per-thousand-keys" |
"administration-cloud-plan-field-price-per-thousand-mt-credits" |
"administration-cloud-plan-field-price-per-thousand-translations" |
"administration-cloud-plan-field-price-yearly" |
Expand All @@ -44,7 +47,6 @@ declare namespace DataCy {
"administration-ee-license-key-input" |
"administration-ee-license-release-key-button" |
"administration-ee-plan-cancel-button" |
"administration-ee-plan-field-feature" |
"administration-ee-plan-field-free" |
"administration-ee-plan-field-included-mt-credits" |
"administration-ee-plan-field-included-seats" |
Expand Down
6 changes: 4 additions & 2 deletions webapp/src/constants/GlobalValidationSchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,10 @@ export class Validation {
prices: Yup.object().when('type', {
is: 'PAY_AS_YOU_GO',
then: Yup.object({
perThousandMtCredits: Yup.number().moreThan(0),
perThousandTranslations: Yup.number().moreThan(0),
perThousandMtCredits: Yup.number().min(0),
perThousandTranslations: Yup.number().min(0),
perSeat: Yup.number().min(0),
perThousandKeys: Yup.number().min(0),
}),
}),
free: Yup.boolean(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const AdministrationEePlanCreateView = () => {
seats: 0,
translations: 0,
mtCredits: 0,
keys: 0,
},
forOrganizationIds: [],
enabledFeatures: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ type CloudPlanModel = components['schemas']['CloudPlanRequest'];
type EnabledFeature =
components['schemas']['CloudPlanRequest']['enabledFeatures'][number];

export type MetricType = 'STRINGS' | 'SEATS_KEYS';

export type CloudPlanFormData = {
type: CloudPlanModel['type'];
metricType: MetricType;
name: string;
prices: CloudPlanModel['prices'];
includedUsage: CloudPlanModel['includedUsage'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useBillingApiQuery } from 'tg.service/http/useQueryApi';
import { Validation } from 'tg.constants/GlobalValidationSchema';
import LoadingButton from 'tg.component/common/form/LoadingButton';
import { EePlanOrganizations } from './EePlanOrganizations';
import { PlanEnabledFeaturesField } from './fields/PlanEnabledFeaturesField';

type SelfHostedEePlanRequest = components['schemas']['SelfHostedEePlanRequest'];
type EnabledFeature =
Expand All @@ -39,6 +40,7 @@ type Props = {
loading: boolean | undefined;
};

// TODO: Refactor this so it's split into smaller components
export function EePlanForm({ planId, initialData, onSubmit, loading }: Props) {
const { t } = useTranslate();

Expand All @@ -47,11 +49,6 @@ export function EePlanForm({ planId, initialData, onSubmit, loading }: Props) {
method: 'get',
});

const featuresLoadable = useBillingApiQuery({
url: '/v2/administration/billing/features',
method: 'get',
});

const products = productsLoadable.data?._embedded?.stripeProducts;

return (
Expand Down Expand Up @@ -173,44 +170,7 @@ export function EePlanForm({ planId, initialData, onSubmit, loading }: Props) {
label={t('administration_ee_plan_field_included_mt_credits')}
/>
</Box>
<Box>
<Typography sx={{ mt: 2 }}>
{t('administration_ee_plan_form_features_title')}
</Typography>
<Field name="enabledFeatures">
{(props: FieldProps<string[]>) =>
featuresLoadable.data?.map((feature) => {
const values = props.field.value;

const toggleField = () => {
let newValues = values;
if (values.includes(feature)) {
newValues = values.filter((val) => val !== feature);
} else {
newValues = [...values, feature];
}
props.form.setFieldValue(props.field.name, newValues);
};

return (
<FormControlLabel
data-cy="administration-ee-plan-field-feature"
key={feature}
control={
<Checkbox
value={feature}
checked={props.field.value.includes(feature)}
onChange={toggleField}
/>
}
label={feature}
/>
);
}) || []
}
</Field>
</Box>

<PlanEnabledFeaturesField parentName="" />
<FormControlLabel
control={
<Switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import {
Box,
Checkbox,
FormControlLabel,
MenuItem,
Switch,
Typography,
} from '@mui/material';
import { Select } from 'tg.component/common/form/fields/Select';
import { Field, FieldProps, useFormikContext } from 'formik';
import { SearchSelect } from 'tg.component/searchSelect/SearchSelect';
import { useBillingApiQuery } from 'tg.service/http/useQueryApi';
import React, { FC, useEffect } from 'react';
import React, { FC } from 'react';
import { CloudPlanPricesAndLimits } from './CloudPlanPricesAndLimits';
import { CloudPlanFormData } from '../CloudPlanFormBase';
import { PlanNonCommercialSwitch } from './PlanNonCommercialSwitch';
import { useCloudPlanFormValues } from '../useCloudPlanFormValues';
import { CloudPlanTypeSelectField } from './CloudPlanTypeSelectField';
import { StripeProductSelectField } from './StripeProductSelectField';
import { CloudPlanMetricTypeSelectField } from './CloudPlanMetricTypeSelectField';
import { PlanEnabledFeaturesField } from './PlanEnabledFeaturesField';

export const CloudPlanFields: FC<{
parentName?: string;
Expand All @@ -24,44 +25,16 @@ export const CloudPlanFields: FC<{
}> = ({ parentName, isUpdate, canEditPrices }) => {
const { t } = useTranslate();

const featuresLoadable = useBillingApiQuery({
url: '/v2/administration/billing/features',
method: 'get',
});
const { setFieldValue } = useFormikContext<any>();

const productsLoadable = useBillingApiQuery({
url: '/v2/administration/billing/stripe-products',
method: 'get',
});

const products = productsLoadable.data?._embedded?.stripeProducts;

const { setFieldValue, values: formValues } = useFormikContext<any>();

const values: CloudPlanFormData = parentName
? formValues[parentName]
: formValues;
const { values } = useCloudPlanFormValues(parentName);

parentName = parentName ? parentName + '.' : '';

const typeOptions = [
{ value: 'PAY_AS_YOU_GO', label: 'Pay as you go', enabled: !values.free },
{ value: 'FIXED', label: 'Fixed', enabled: true },
{ value: 'SLOTS_FIXED', label: 'Slots fixed', enabled: true },
];

const enabledTypeOptions = typeOptions.filter((t) => t.enabled);

function onFreeChange() {
setFieldValue(`${parentName}free`, !values.free);
}

useEffect(() => {
if (!enabledTypeOptions.find((o) => o.value === values.type)) {
setFieldValue(`${parentName}type`, enabledTypeOptions[0].value);
}
}, [values.free]);

return (
<>
<TextField
Expand All @@ -84,105 +57,20 @@ export const CloudPlanFields: FC<{
display: 'grid',
gap: 2,
mt: 2,
gridTemplateColumns: '1fr 1fr',
gridTemplateColumns: '1fr 1fr 1fr',
}}
>
<Select
label={t('administration_cloud_plan_field_type')}
name={`${parentName}type`}
size="small"
fullWidth
minHeight={false}
sx={{ flexBasis: '50%' }}
data-cy="administration-cloud-plan-field-type"
renderValue={(val) =>
enabledTypeOptions.find((o) => o.value === val)?.label
}
>
{enabledTypeOptions.map(({ value, label }) => (
<MenuItem
key={value}
value={value}
data-cy="administration-cloud-plan-field-type-item"
>
{label}
</MenuItem>
))}
</Select>
<Field name={`${parentName}stripeProductId`}>
{({ field, form, meta }: FieldProps) => {
return (
<SearchSelect
compareFunction={(prompt, label) =>
label.toLowerCase().includes(prompt.toLowerCase())
}
SelectProps={{
// @ts-ignore
'data-cy': 'administration-cloud-plan-field-stripe-product',
label: t('administration_cloud_plan_field_stripe_product'),
size: 'small',
fullWidth: true,
variant: 'outlined',
error: (meta.touched && meta.error) || '',
}}
value={field.value}
onChange={(val) => form.setFieldValue(field.name, val)}
items={[
{ value: '', name: 'None' },
...(products?.map(({ id, name }) => ({
value: id,
name: `${id} ${name}`,
})) || []),
]}
/>
);
}}
</Field>
<CloudPlanTypeSelectField parentName={parentName} />
<CloudPlanMetricTypeSelectField parentName={parentName} />
<StripeProductSelectField />
</Box>

<CloudPlanPricesAndLimits
parentName={parentName}
values={values}
canEditPrices={canEditPrices}
/>

<Box>
<Typography sx={{ mt: 2 }}>
{t('administration_cloud_plan_form_features_title')}
</Typography>
<Field name={`${parentName}enabledFeatures`}>
{(props: FieldProps<string[]>) =>
featuresLoadable.data?.map((feature) => {
const values = props.field.value;

const toggleField = () => {
let newValues: string[];
if (values.includes(feature)) {
newValues = values.filter((val) => val !== feature);
} else {
newValues = [...values, feature];
}
props.form.setFieldValue(props.field.name, newValues);
};

return (
<FormControlLabel
data-cy="administration-cloud-plan-field-feature"
key={feature}
control={
<Checkbox
value={feature}
checked={props.field.value.includes(feature)}
onChange={toggleField}
/>
}
label={feature}
/>
);
}) || []
}
</Field>
</Box>
<PlanEnabledFeaturesField parentName={parentName} />
<PlanNonCommercialSwitch />
</>
);
Expand Down
Loading

0 comments on commit bb8ba56

Please sign in to comment.