Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit27dfd55

Browse files
committed
feat: initial changes for multi org template creation
1 parentde2585b commit27dfd55

File tree

10 files changed

+440
-149
lines changed

10 files changed

+440
-149
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import{css}from"@emotion/css";
2+
importAutocompletefrom"@mui/material/Autocomplete";
3+
importCircularProgressfrom"@mui/material/CircularProgress";
4+
importTextFieldfrom"@mui/material/TextField";
5+
import{
6+
typeChangeEvent,
7+
typeComponentProps,
8+
typeFC,
9+
useState,
10+
}from"react";
11+
import{useQuery}from"react-query";
12+
import{myOrganizations}from"api/queries/users";
13+
importtype{Organization}from"api/typesGenerated";
14+
import{Avatar}from"components/Avatar/Avatar";
15+
import{AvatarData}from"components/AvatarData/AvatarData";
16+
import{useDebouncedFunction}from"hooks/debounce";
17+
// import { prepareQuery } from "utils/filters";
18+
19+
exporttypeOrganizationAutocompleteProps={
20+
value:Organization|null;
21+
onChange:(organization:Organization|null)=>void;
22+
label?:string;
23+
className?:string;
24+
size?:ComponentProps<typeofTextField>["size"];
25+
};
26+
27+
exportconstOrganizationAutocomplete:FC<OrganizationAutocompleteProps>=({
28+
value,
29+
onChange,
30+
label,
31+
className,
32+
size="small",
33+
})=>{
34+
const[autoComplete,setAutoComplete]=useState<{
35+
value:string;
36+
open:boolean;
37+
}>({
38+
value:value?.name??"",
39+
open:false,
40+
});
41+
// const usersQuery = useQuery({
42+
// ...users({
43+
// q: prepareQuery(encodeURI(autoComplete.value)),
44+
// limit: 25,
45+
// }),
46+
// enabled: autoComplete.open,
47+
// keepPreviousData: true,
48+
// });
49+
constorganizationsQuery=useQuery(myOrganizations());
50+
51+
const{debounced:debouncedInputOnChange}=useDebouncedFunction(
52+
(event:ChangeEvent<HTMLInputElement>)=>{
53+
setAutoComplete((state)=>({
54+
...state,
55+
value:event.target.value,
56+
}));
57+
},
58+
750,
59+
);
60+
61+
return(
62+
<Autocomplete
63+
// Since the values are filtered by the API we don't need to filter them
64+
// in the FE because it can causes some mismatches.
65+
filterOptions={(organization)=>organization}
66+
noOptionsText="No users found"
67+
className={className}
68+
options={organizationsQuery.data??[]}
69+
loading={organizationsQuery.isLoading}
70+
value={value}
71+
id="organization-autocomplete"
72+
open={autoComplete.open}
73+
onOpen={()=>{
74+
setAutoComplete((state)=>({
75+
...state,
76+
open:true,
77+
}));
78+
}}
79+
onClose={()=>{
80+
setAutoComplete({
81+
value:value?.name??"",
82+
open:false,
83+
});
84+
}}
85+
onChange={(_,newValue)=>{
86+
onChange(newValue);
87+
}}
88+
isOptionEqualToValue={(option:Organization,value:Organization)=>
89+
option.name===value.name
90+
}
91+
getOptionLabel={(option)=>option.name}
92+
renderOption={(props,option)=>(
93+
<li{...props}>
94+
<AvatarData
95+
title={option.name}
96+
subtitle={option.display_name}
97+
src={option.icon}
98+
/>
99+
</li>
100+
)}
101+
renderInput={(params)=>(
102+
<TextField
103+
{...params}
104+
fullWidth
105+
size={size}
106+
label={label}
107+
placeholder="Organization name"
108+
css={{
109+
"&:not(:has(label))":{
110+
margin:0,
111+
},
112+
}}
113+
InputProps={{
114+
...params.InputProps,
115+
onChange:debouncedInputOnChange,
116+
startAdornment:value&&(
117+
<Avatarsize="sm"src={value.icon}>
118+
{value.name}
119+
</Avatar>
120+
),
121+
endAdornment:(
122+
<>
123+
{organizationsQuery.isFetching&&autoComplete.open ?(
124+
<CircularProgresssize={16}/>
125+
) :null}
126+
{params.InputProps.endAdornment}
127+
</>
128+
),
129+
classes:{ root},
130+
}}
131+
InputLabelProps={{
132+
shrink:true,
133+
}}
134+
/>
135+
)}
136+
/>
137+
);
138+
};
139+
140+
constroot=css`
141+
padding-left:14px!important; // Same padding left as input
142+
gap:4px;
143+
`;

‎site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsxrenamed to‎site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,34 @@ import type { TemplateExample } from "api/typesGenerated";
66
import{useDashboard}from"modules/dashboard/useDashboard";
77
import{pageTitle}from"utils/page";
88
import{getTemplatesByTag}from"utils/starterTemplates";
9+
import{CreateTemplatesPageView}from"./CreateTemplatesPageView";
910
import{StarterTemplatesPageView}from"./StarterTemplatesPageView";
1011

11-
constStarterTemplatesPage:FC=()=>{
12-
const{ organizationId}=useDashboard();
12+
constCreateTemplatesGalleryPage:FC=()=>{
13+
const{ organizationId, experiments}=useDashboard();
1314
consttemplateExamplesQuery=useQuery(templateExamples(organizationId));
1415
conststarterTemplatesByTag=templateExamplesQuery.data
1516
?// Currently, the scratch template should not be displayed on the starter templates page.
1617
getTemplatesByTag(removeScratchExample(templateExamplesQuery.data))
1718
:undefined;
19+
constmultiOrgExperimentEnabled=experiments.includes("multi-organization");
1820

1921
return(
2022
<>
2123
<Helmet>
22-
<title>{pageTitle("Starter Templates")}</title>
24+
<title>{pageTitle("Create a Template")}</title>
2325
</Helmet>
24-
25-
<StarterTemplatesPageView
26-
error={templateExamplesQuery.error}
27-
starterTemplatesByTag={starterTemplatesByTag}
28-
/>
26+
{multiOrgExperimentEnabled ?(
27+
<CreateTemplatesPageView
28+
error={templateExamplesQuery.error}
29+
starterTemplatesByTag={starterTemplatesByTag}
30+
/>
31+
) :(
32+
<StarterTemplatesPageView
33+
error={templateExamplesQuery.error}
34+
starterTemplatesByTag={starterTemplatesByTag}
35+
/>
36+
)}
2937
</>
3038
);
3139
};
@@ -34,4 +42,4 @@ const removeScratchExample = (data: TemplateExample[]) => {
3442
returndata.filter((example)=>example.id!=="scratch");
3543
};
3644

37-
exportdefaultStarterTemplatesPage;
45+
exportdefaultCreateTemplatesGalleryPage;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
importtype{Interpolation,Theme}from"@emotion/react";
2+
import{useState,typeFC}from"react";
3+
import{useQuery}from"react-query";
4+
import{Link,useSearchParams}from"react-router-dom";
5+
import{templateExamples}from"api/queries/templates";
6+
importtype{Organization,TemplateExample}from"api/typesGenerated";
7+
import{ErrorAlert}from"components/Alert/ErrorAlert";
8+
import{Loader}from"components/Loader/Loader";
9+
import{Margins}from"components/Margins/Margins";
10+
import{OrganizationAutocomplete}from"components/OrganizationAutocomplete/OrganizationAutocomplete";
11+
import{PageHeader,PageHeaderTitle}from"components/PageHeader/PageHeader";
12+
import{Stack}from"components/Stack/Stack";
13+
import{useDashboard}from"modules/dashboard/useDashboard";
14+
import{TemplateExampleCard}from"modules/templates/TemplateExampleCard/TemplateExampleCard";
15+
import{
16+
getTemplatesByTag,
17+
typeStarterTemplatesByTag,
18+
}from"utils/starterTemplates";
19+
import{StarterTemplates}from"./StarterTemplates";
20+
21+
// const getTagLabel = (tag: string) => {
22+
// const labelByTag: Record<string, string> = {
23+
// all: "All templates",
24+
// digitalocean: "DigitalOcean",
25+
// aws: "AWS",
26+
// google: "Google Cloud",
27+
// };
28+
// // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined
29+
// return labelByTag[tag] ?? tag;
30+
// };
31+
32+
// const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => {
33+
// return starterTemplatesByTag
34+
// ? Object.keys(starterTemplatesByTag).sort((a, b) => a.localeCompare(b))
35+
// : undefined;
36+
// };
37+
38+
exportinterfaceCreateTemplatePageViewProps{
39+
starterTemplatesByTag?:StarterTemplatesByTag;
40+
error?:unknown;
41+
}
42+
43+
// const removeScratchExample = (data: TemplateExample[]) => {
44+
// return data.filter((example) => example.id !== "scratch");
45+
// };
46+
47+
exportconstCreateTemplatesPageView:FC<CreateTemplatePageViewProps>=({
48+
starterTemplatesByTag,
49+
error,
50+
})=>{
51+
const[selectedOrg,setSelectedOrg]=useState<Organization|null>(null);
52+
// const { organizationId } = useDashboard();
53+
// const templateExamplesQuery = useQuery(templateExamples(organizationId));
54+
// const starterTemplatesByTag = templateExamplesQuery.data
55+
// ? // Currently, the scratch template should not be displayed on the starter templates page.
56+
// getTemplatesByTag(removeScratchExample(templateExamplesQuery.data))
57+
// : undefined;
58+
59+
return(
60+
<Margins>
61+
<PageHeader>
62+
<PageHeaderTitle>Create a Template</PageHeaderTitle>
63+
</PageHeader>
64+
65+
<OrganizationAutocomplete
66+
css={styles.autoComplete}
67+
value={selectedOrg}
68+
onChange={(newValue)=>{
69+
setSelectedOrg(newValue);
70+
}}
71+
/>
72+
73+
{Boolean(error)&&<ErrorAlerterror={error}/>}
74+
75+
{Boolean(!starterTemplatesByTag)&&<Loader/>}
76+
77+
<StarterTemplatesstarterTemplatesByTag={starterTemplatesByTag}/>
78+
</Margins>
79+
);
80+
};
81+
82+
conststyles={
83+
autoComplete:{
84+
width:300,
85+
},
86+
87+
filterCaption:(theme)=>({
88+
textTransform:"uppercase",
89+
fontWeight:600,
90+
fontSize:12,
91+
color:theme.palette.text.secondary,
92+
letterSpacing:"0.1em",
93+
}),
94+
95+
tagLink:(theme)=>({
96+
color:theme.palette.text.secondary,
97+
textDecoration:"none",
98+
fontSize:14,
99+
textTransform:"capitalize",
100+
101+
"&:hover":{
102+
color:theme.palette.text.primary,
103+
},
104+
}),
105+
106+
tagLinkActive:(theme)=>({
107+
color:theme.palette.text.primary,
108+
fontWeight:600,
109+
}),
110+
}satisfiesRecord<string,Interpolation<Theme>>;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp