Skip to content

Commit 6a99669

Browse files
committed
feat(create-project): support the default product privacy of units
1 parent 36b2bfb commit 6a99669

File tree

9 files changed

+112
-31
lines changed

9 files changed

+112
-31
lines changed

components/projects/CreateProjectButton.tsx renamed to components/projects/CreateProject/CreateProjectButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const CreateProjectButton = ({
2929
</Button>
3030

3131
<CreateProjectForm
32+
defaultPrivacy={unit.default_product_privacy}
3233
modal={{
3334
id: "create-project",
3435
title: "Create Project",

components/projects/CreateProjectForm.tsx renamed to components/projects/CreateProject/CreateProjectForm.tsx

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import {
22
type AsError,
33
type ProductDetail,
4+
ProductDetailFlavour,
5+
ProductDetailType,
46
type UnitDetail,
7+
UnitDetailDefaultProductPrivacy,
58
type UnitProductPostBodyBodyFlavour,
69
} from "@squonk/account-server-client";
710
import {
@@ -15,32 +18,36 @@ import { getGetUserInventoryQueryKey } from "@squonk/data-manager-client/invento
1518
import { getGetProjectsQueryKey, useCreateProject } from "@squonk/data-manager-client/project";
1619

1720
import {
18-
Box,
1921
Button,
20-
FormControlLabel,
22+
FormControl,
23+
FormLabel,
2124
MenuItem,
25+
Tooltip,
2226
Typography,
2327
useMediaQuery,
2428
useTheme,
2529
} from "@mui/material";
2630
import { useQueryClient } from "@tanstack/react-query";
2731
import { Field, Form, Formik, type FormikConfig } from "formik";
28-
import { Checkbox, TextField } from "formik-mui";
32+
import { TextField } from "formik-mui";
2933
import * as yup from "yup";
3034

31-
import { DEFAULT_PRODUCT_FLAVOUR, PROJECT_SUB } from "../../constants/products";
32-
import { useCurrentProjectId } from "../../hooks/projectHooks";
33-
import { useEnqueueError } from "../../hooks/useEnqueueStackError";
34-
import { type Resolve } from "../../types";
35-
import { formatTierString } from "../../utils/app/products";
36-
import { getErrorMessage } from "../../utils/next/orvalError";
37-
import { FormikModalWrapper, type FormikModalWrapperProps } from "../modals/FormikModalWrapper";
35+
import { useCurrentProjectId } from "../../../hooks/projectHooks";
36+
import { useEnqueueError } from "../../../hooks/useEnqueueStackError";
37+
import { type Resolve } from "../../../types";
38+
import { formatTierString } from "../../../utils/app/products";
39+
import { getErrorMessage } from "../../../utils/next/orvalError";
40+
import { FormikModalWrapper, type FormikModalWrapperProps } from "../../modals/FormikModalWrapper";
41+
import { PrivacySwitch } from "./PrivacySwitch";
42+
43+
const PROJECT_SUB = ProductDetailType.DATA_MANAGER_PROJECT_TIER_SUBSCRIPTION;
3844

3945
export interface CreateProjectFormProps {
4046
modal?: Resolve<
4147
Pick<FormikModalWrapperProps, "id" | "onClose" | "open" | "submitText" | "title">
4248
>;
4349
unitId: UnitDetail["id"] | (() => Promise<UnitDetail["id"]>);
50+
defaultPrivacy: UnitDetailDefaultProductPrivacy;
4451
product?: ProductDetail;
4552
autoFocus?: boolean;
4653
}
@@ -53,16 +60,32 @@ export interface Values {
5360

5461
type ProjectFormikProps = FormikConfig<Values>;
5562

63+
const isPrivateDefaultValues: Record<UnitDetailDefaultProductPrivacy, boolean> = {
64+
ALWAYS_PRIVATE: true,
65+
ALWAYS_PUBLIC: false,
66+
DEFAULT_PUBLIC: false,
67+
DEFAULT_PRIVATE: true,
68+
};
69+
5670
export const CreateProjectForm = ({
5771
modal,
5872
unitId,
73+
defaultPrivacy,
5974
product,
6075
autoFocus = true,
6176
}: CreateProjectFormProps) => {
77+
const evaluationAllowed = defaultPrivacy !== UnitDetailDefaultProductPrivacy.ALWAYS_PRIVATE;
78+
const defaultFlavour = (
79+
defaultPrivacy === UnitDetailDefaultProductPrivacy.ALWAYS_PRIVATE ||
80+
defaultPrivacy === UnitDetailDefaultProductPrivacy.DEFAULT_PRIVATE
81+
? ProductDetailFlavour.BRONZE
82+
: ProductDetailFlavour.EVALUATION
83+
) satisfies ProductDetailFlavour;
84+
6285
const initialValues: Values = {
6386
projectName: "",
64-
flavour: product?.flavour ?? DEFAULT_PRODUCT_FLAVOUR,
65-
isPrivate: false,
87+
flavour: product?.flavour ?? defaultFlavour,
88+
isPrivate: isPrivateDefaultValues[defaultPrivacy],
6689
};
6790
const theme = useTheme();
6891
const biggerThanSm = useMediaQuery(theme.breakpoints.up("sm"));
@@ -142,13 +165,19 @@ export const CreateProjectForm = ({
142165
values,
143166
}) => (
144167
<Form style={{ marginTop: theme.spacing() }}>
145-
<Box
168+
<FormControl
169+
component="fieldset"
146170
sx={{
147171
display: "grid",
148-
gridTemplateColumns: biggerThanSm ? "1fr 1fr auto auto" : "1fr",
172+
gridTemplateColumns: biggerThanSm ? "1fr 1fr auto" + (modal ? "" : " auto") : "1fr",
149173
gap: 1,
174+
alignItems: "baseline",
150175
}}
151176
>
177+
<FormLabel component="legend" sx={{ mb: 1 }}>
178+
Unit default privacy: {defaultPrivacy.split("_").join(" ").toLowerCase()}
179+
</FormLabel>
180+
152181
<Field
153182
fullWidth
154183
autoFocus={autoFocus}
@@ -169,7 +198,7 @@ export const CreateProjectForm = ({
169198
name="flavour"
170199
onChange={(event: any) => {
171200
handleChange(event);
172-
if (event.target.value === DEFAULT_PRODUCT_FLAVOUR) {
201+
if (event.target.value === ProductDetailFlavour.EVALUATION) {
173202
void setFieldValue("isPrivate", false);
174203
}
175204
}}
@@ -179,7 +208,13 @@ export const CreateProjectForm = ({
179208
) : (
180209
productTypes?.map((product) => {
181210
return (
182-
<MenuItem key={product.flavour} value={product.flavour}>
211+
<MenuItem
212+
disabled={
213+
product.flavour === ProductDetailFlavour.EVALUATION && !evaluationAllowed
214+
}
215+
key={product.flavour}
216+
value={product.flavour}
217+
>
183218
{formatTierString(product.flavour ?? "Unknown Flavour")}
184219
</MenuItem>
185220
);
@@ -188,19 +223,16 @@ export const CreateProjectForm = ({
188223
</Field>
189224
)}
190225

191-
<FormControlLabel
192-
control={<Field color="primary" component={Checkbox} name="isPrivate" type="checkbox" />}
193-
disabled={values.flavour === DEFAULT_PRODUCT_FLAVOUR}
194-
label="Private"
195-
labelPlacement="start"
196-
/>
226+
<Tooltip title="Toggle whether this project can be viewed by other platform users">
227+
<PrivacySwitch defaultPrivacy={defaultPrivacy} flavour={values.flavour} />
228+
</Tooltip>
197229

198230
{!modal && (
199231
<Button disabled={isSubmitting || !isValid} onClick={() => void submitForm()}>
200232
Create
201233
</Button>
202234
)}
203-
</Box>
235+
</FormControl>
204236
</Form>
205237
);
206238

components/projects/CreateProjectListItem.tsx renamed to components/projects/CreateProject/CreateProjectListItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const CreateProjectListItem = ({ unit }: CreateProjectListItemProps) => {
3030
</ListItemButton>
3131

3232
<CreateProjectForm
33+
defaultPrivacy={unit.default_product_privacy}
3334
modal={{
3435
id: "create-project",
3536
title: "Create Project",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
ProductDetailFlavour,
3+
UnitDetailDefaultProductPrivacy,
4+
} from "@squonk/account-server-client";
5+
6+
import { FormControlLabel } from "@mui/material";
7+
import { Field } from "formik";
8+
import { Switch } from "formik-mui";
9+
10+
export interface PrivacySwitchProps {
11+
/**
12+
* Selected flavour of the product
13+
*/
14+
flavour: string;
15+
/**
16+
* Default privacy of the product given by the unit
17+
*/
18+
defaultPrivacy: UnitDetailDefaultProductPrivacy;
19+
}
20+
21+
export const PrivacySwitch = ({ flavour, defaultPrivacy }: PrivacySwitchProps) => {
22+
// Disable the switch if the product is an evaluation product or if the default privacy is set to
23+
// always private or always public
24+
const isDisabled =
25+
flavour === ProductDetailFlavour.EVALUATION ||
26+
defaultPrivacy === UnitDetailDefaultProductPrivacy.ALWAYS_PRIVATE ||
27+
defaultPrivacy === UnitDetailDefaultProductPrivacy.ALWAYS_PUBLIC;
28+
29+
return (
30+
<FormControlLabel
31+
control={<Field color="primary" component={Switch} name="isPrivate" type="checkbox" />}
32+
disabled={isDisabled}
33+
label="Private"
34+
labelPlacement="start"
35+
/>
36+
);
37+
};

components/usage/UnitUserUsage.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { useQueryClient } from "@tanstack/react-query";
2525
import dynamic from "next/dynamic";
2626

2727
import { CenterLoader } from "../CenterLoader";
28-
import { CreateProjectForm } from "../projects/CreateProjectForm";
28+
import { CreateProjectForm } from "../projects/CreateProject/CreateProjectForm";
2929
import { type UserUsageByProjectTableProps } from "./UserUsageByProjectTable";
3030
import { UserUsageTable } from "./UserUsageTable";
3131

@@ -112,7 +112,11 @@ export const UnitUserUsage = ({ unitId }: UnitUserUsageProps) => {
112112
</Box>
113113

114114
<Typography variant="h4">Create Project</Typography>
115-
<CreateProjectForm autoFocus={false} unitId={unit.id} />
115+
<CreateProjectForm
116+
autoFocus={false}
117+
defaultPrivacy={unit.default_product_privacy}
118+
unitId={unit.id}
119+
/>
116120
</Container>
117121
);
118122
};

constants/products.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

features/ProductTables/ProjectProductTable/ProjectProductTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { DataTable } from "../../../components/DataTable";
88
import { NextLink } from "../../../components/NextLink";
99
import { ChargesLinkIconButton } from "../../../components/products/ChargesLinkIconButton";
1010
import { DeleteProductButton } from "../../../components/products/DeleteProductButton";
11-
import { CreateProjectButton } from "../../../components/projects/CreateProjectButton";
11+
import { CreateProjectButton } from "../../../components/projects/CreateProject/CreateProjectButton";
1212
import { formatTierString } from "../../../utils/app/products";
1313
import { getSharedColumns } from "../columns";
1414

features/UserBootstrapper/BootstrapAlert/BootstrapForm.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import { useCreateDefaultUnit } from "@squonk/account-server-client/unit";
22

3-
import { CreateProjectForm } from "../../../components/projects/CreateProjectForm";
3+
import { CreateProjectForm } from "../../../components/projects/CreateProject/CreateProjectForm";
4+
import { useSelectedOrganisation } from "../../../state/organisationSelection";
45
import { getBillingDay } from "../../../utils/app/products";
56

67
/**
78
* Form for creating a default unit with a project.
89
*/
910
export const BootstrapForm = () => {
11+
const [organisation] = useSelectedOrganisation();
1012
const { mutateAsync: createUnit } = useCreateDefaultUnit();
13+
14+
if (!organisation) {
15+
return null;
16+
}
17+
18+
// The personal unit will be created with the default privacy of the default organisation
1119
return (
1220
<CreateProjectForm
21+
defaultPrivacy={organisation.default_product_privacy}
1322
unitId={async () => {
1423
const { id: unitId } = await createUnit({
1524
data: { billing_day: getBillingDay() },

features/userSettings/UserSettingsContent/ContextSection/contextActions/UnitActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { List } from "@mui/material";
22

3-
import { CreateProjectListItem } from "../../../../../components/projects/CreateProjectListItem";
3+
import { CreateProjectListItem } from "../../../../../components/projects/CreateProject/CreateProjectListItem";
44
import { useGetPersonalUnit } from "../../../../../hooks/useGetPersonalUnit";
55
import { useKeycloakUser } from "../../../../../hooks/useKeycloakUser";
66
import { useSelectedOrganisation } from "../../../../../state/organisationSelection";

0 commit comments

Comments
 (0)