Skip to content

Commit 2bf576f

Browse files
authored
feat: use official templates from sandbox-templates (#8413)
* fix: cleanup old template fetching and * small refactor * ensure templates are preloaded on all screens * proper use of editorUrl * feat: sort by fork count * feat: categories
1 parent b23fedf commit 2bf576f

File tree

15 files changed

+263
-364
lines changed

15 files changed

+263
-364
lines changed

packages/app/src/app/components/Create/CreateBox.tsx

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,20 @@ import {
3131
SandboxAlternative,
3232
} from './elements';
3333
import { TemplateList } from './TemplateList';
34-
import { useTemplateCollections } from './hooks/useTemplateCollections';
35-
import { useOfficialTemplates } from './hooks/useOfficialTemplates';
3634
import { useTeamTemplates } from './hooks/useTeamTemplates';
3735
import { CreateParams, SandboxToFork } from './utils/types';
3836
import { SearchBox } from './SearchBox';
3937
import { ImportTemplate } from './ImportTemplate';
40-
import { CreateBoxForm, PrivacyLevel } from './CreateBox/CreateBoxForm';
38+
import { CreateBoxForm } from './CreateBox/CreateBoxForm';
4139
import { TemplateInfo } from './CreateBox/TemplateInfo';
42-
import { useFeaturedTemplates } from './hooks/useFeaturedTemplates';
43-
import { useAllTemplates } from './hooks/useAllTemplates';
44-
import { mapSandboxGQLResponseToSandboxToFork } from './utils/api';
40+
import {
41+
getTemplatesInCollections,
42+
getAllMatchingTemplates,
43+
mapSandboxGQLResponseToSandboxToFork,
44+
parsePrivacy,
45+
} from './utils/api';
4546
import { WorkspaceSelect } from '../WorkspaceSelect';
47+
import { FEATURED_IDS } from './utils/constants';
4648

4749
type CreateBoxProps = ModalContentProps & {
4850
collectionId?: string;
@@ -51,30 +53,19 @@ type CreateBoxProps = ModalContentProps & {
5153
isStandalone?: boolean;
5254
};
5355

54-
function parsePrivacy(privacy: string | undefined): PrivacyLevel | undefined {
55-
if (privacy === 'public') {
56-
return 0;
57-
}
58-
59-
if (privacy === 'unlisted') {
60-
return 1;
61-
}
62-
63-
if (privacy === 'private') {
64-
return 2;
65-
}
66-
67-
return undefined;
68-
}
69-
7056
export const CreateBox: React.FC<CreateBoxProps> = ({
7157
collectionId: initialCollectionId,
7258
sandboxIdToFork,
7359
type = 'devbox',
7460
closeModal,
7561
isStandalone,
7662
}) => {
77-
const { hasLogIn, activeTeam } = useAppState();
63+
const {
64+
hasLogIn,
65+
activeTeam,
66+
officialDevboxTemplates,
67+
officialSandboxTemplates,
68+
} = useAppState();
7869
const effects = useEffects();
7970
const actions = useActions();
8071
const { isFrozen } = useWorkspaceLimits();
@@ -106,32 +97,44 @@ export const CreateBox: React.FC<CreateBoxProps> = ({
10697
false
10798
);
10899

109-
const { collections } = useTemplateCollections({ type });
110-
const { templates: officialTemplates } = useOfficialTemplates({ type });
100+
const officialTemplates =
101+
type === 'devbox' ? officialDevboxTemplates : officialSandboxTemplates;
102+
103+
const collections = getTemplatesInCollections(officialTemplates, [
104+
{ tag: 'frontend', title: 'Frontend frameworks' },
105+
{ tag: 'backend', title: 'Backend frameworks' },
106+
{ tag: 'playground', title: 'Code playgrounds' },
107+
{ tag: 'starter', title: 'Project starters' },
108+
]);
109+
111110
const { teamTemplates, recentTemplates } = useTeamTemplates({
112111
teamId: activeTeam,
113112
hasLogIn,
114113
type,
115114
});
116115

117116
const recentlyUsedTemplates = recentTemplates.slice(0, 3);
118-
const featuredTemplates = useFeaturedTemplates({
119-
officialTemplates,
120-
recentTemplates,
121-
});
117+
const hasRecentlyUsedTemplates = recentlyUsedTemplates.length > 0;
118+
const recentlyUsedTemplatesIds = recentlyUsedTemplates.map(t => t.id);
122119

123-
const allTemplates = useAllTemplates({
124-
featuredTemplates,
120+
const featuredTemplates = FEATURED_IDS.map(id =>
121+
officialTemplates.find(
122+
t => t.id === id && !recentlyUsedTemplatesIds.includes(t.id)
123+
)
124+
)
125+
.filter(Boolean)
126+
.slice(0, hasRecentlyUsedTemplates ? 6 : 9);
127+
128+
const allTemplates = getAllMatchingTemplates({
125129
officialTemplates,
126130
teamTemplates,
127-
collections,
128131
searchQuery,
129132
});
130133

131134
/**
132135
* Only show the team templates if the list is populated.
133136
*/
134-
const hasRecentlyUsedTemplates = recentlyUsedTemplates.length > 0;
137+
135138
const showTeamTemplates = teamTemplates.length > 0;
136139
const showImportTemplates = hasLogIn && activeTeam && type === 'devbox';
137140

@@ -224,7 +227,8 @@ export const CreateBox: React.FC<CreateBoxProps> = ({
224227
const selectTemplate = (sandbox: SandboxToFork, trackingSource: string) => {
225228
if (!hasLogIn) {
226229
// Open template in editor for anonymous users
227-
window.location.href = sandboxUrl(sandbox, hasBetaEditorExperiment);
230+
window.location.href =
231+
sandbox.editorUrl || sandboxUrl(sandbox, hasBetaEditorExperiment);
228232
return;
229233
}
230234

@@ -239,7 +243,8 @@ export const CreateBox: React.FC<CreateBoxProps> = ({
239243
};
240244

241245
const openTemplate = (sandbox: SandboxToFork, trackingSource: string) => {
242-
const url = sandboxUrl(sandbox, hasBetaEditorExperiment);
246+
const url =
247+
sandbox.editorUrl || sandboxUrl(sandbox, hasBetaEditorExperiment);
243248
window.open(url, '_blank');
244249

245250
track(`Create ${type} - Open template`, {

packages/app/src/app/components/Create/CreateBox/CreateBoxForm.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
PathedSandboxesFoldersQuery,
2424
PathedSandboxesFoldersQueryVariables,
2525
} from 'app/graphql/types';
26-
import { CreateParams } from '../utils/types';
26+
import { CreateParams, PrivacyLevel } from '../utils/types';
2727

2828
interface CreateBoxFormProps {
2929
type: 'sandbox' | 'devbox';
@@ -35,8 +35,6 @@ interface CreateBoxFormProps {
3535
onClose: () => void;
3636
}
3737

38-
export type PrivacyLevel = 0 | 1 | 2;
39-
4038
export const CreateBoxForm: React.FC<CreateBoxFormProps> = ({
4139
type,
4240
initialPrivacy,

packages/app/src/app/components/Create/CreateBox/TemplateInfo.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
import { getTemplateIcon } from '@codesandbox/common/lib/utils/getTemplateIcon';
21
import { Stack, Text, Icon } from '@codesandbox/components';
32
import { formatNumber } from '@codesandbox/components/lib/components/Stats';
43
import React from 'react';
54
import { SandboxToFork } from '../utils/types';
5+
import { TemplateIcon } from '../TemplateIcon';
66

77
interface TemplateInfoProps {
88
template: SandboxToFork;
99
}
1010

1111
export const TemplateInfo = ({ template }: TemplateInfoProps) => {
12-
const { UserIcon } = getTemplateIcon(
13-
template.title,
14-
template.iconUrl,
15-
template.sourceTemplate
16-
);
17-
1812
const title = template.title || template.alias;
19-
const owner = template.owner;
13+
const owner = template.author;
2014

2115
return (
2216
<Stack direction="vertical" gap={4}>
23-
<UserIcon />
17+
<TemplateIcon template={template} />
2418
<Stack direction="vertical" gap={1}>
2519
<Text size={3} weight="500">
2620
{title}

packages/app/src/app/components/Create/TemplateCard.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React from 'react';
22
import { formatNumber, Icon, Stack, Text } from '@codesandbox/components';
3-
import { getTemplateIcon } from '@codesandbox/common/lib/utils/getTemplateIcon';
43
import { VisuallyHidden } from 'reakit/VisuallyHidden';
54
import { TemplateButton } from './elements';
65
import { SandboxToFork } from './utils/types';
6+
import { TemplateIcon } from './TemplateIcon';
77

88
interface TemplateCardProps {
99
disabled?: boolean;
@@ -22,14 +22,8 @@ export const TemplateCard = ({
2222
padding,
2323
forks,
2424
}: TemplateCardProps) => {
25-
const { UserIcon } = getTemplateIcon(
26-
template.title,
27-
template.iconUrl,
28-
template.sourceTemplate
29-
);
30-
3125
const sandboxTitle = template.title || template.alias;
32-
const teamName = template.owner;
26+
const teamName = template.author;
3327

3428
return (
3529
<TemplateButton
@@ -72,7 +66,7 @@ export const TemplateCard = ({
7266
<Stack
7367
css={{ justifyContent: 'space-between', alignItems: 'flex-start' }}
7468
>
75-
<UserIcon height="20" width="20" />
69+
<TemplateIcon template={template} />
7670
</Stack>
7771
<Stack direction="vertical" gap={1}>
7872
<Text
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { getTemplateIcon } from '@codesandbox/common/lib/utils/getTemplateIcon';
3+
import { SandboxToFork } from './utils/types';
4+
5+
export const TemplateIcon: React.FC<{ template: SandboxToFork }> = ({
6+
template,
7+
}) => {
8+
if (template.iconUrl?.startsWith('https://')) {
9+
return (
10+
<img src={template.iconUrl} width={20} height={20} alt={template.title} />
11+
);
12+
}
13+
14+
// Legacy way of handling Icon which I didn't want to tackle with this PR
15+
const { UserIcon } = getTemplateIcon(
16+
template.title,
17+
template.iconUrl,
18+
template.sourceTemplate
19+
);
20+
21+
return <UserIcon height="20" width="20" />;
22+
};

packages/app/src/app/components/Create/hooks/useAllTemplates.ts

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

packages/app/src/app/components/Create/hooks/useFeaturedTemplates.ts

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

packages/app/src/app/components/Create/hooks/useOfficialTemplates.ts

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

0 commit comments

Comments
 (0)