diff --git a/packages/app/src/app/components/Create/CreateBox.tsx b/packages/app/src/app/components/Create/CreateBox.tsx index ae8e118d19e..50e9e2ddefb 100644 --- a/packages/app/src/app/components/Create/CreateBox.tsx +++ b/packages/app/src/app/components/Create/CreateBox.tsx @@ -1,4 +1,3 @@ -import styled, { keyframes } from 'styled-components'; import { Text, Stack, @@ -25,7 +24,6 @@ import { import { TemplateList } from './TemplateList'; import { useTeamTemplates } from './hooks/useTeamTemplates'; import { CreateParams, SandboxToFork } from './utils/types'; -import { SearchBox } from './SearchBox'; import { CreateBoxForm } from './CreateBox/CreateBoxForm'; import { TemplateInfo } from './CreateBox/TemplateInfo'; import { @@ -34,7 +32,6 @@ import { } from './utils/api'; import { WorkspaceSelect } from '../WorkspaceSelect'; import { FEATURED_IDS } from './utils/constants'; -import { TemplateFilter } from './TemplateFilter'; type CreateBoxProps = ModalContentProps & { collectionId?: string; @@ -58,62 +55,30 @@ export const CreateBox: React.FC = ({ const [viewState, setViewState] = useState('select'); const [selectedTemplate, setSelectedTemplate] = useState(); - const [searchQuery, setSearchQuery] = useState(''); - const [filters, setFilters] = useState([]); - const { teamTemplates, recentTemplates } = useTeamTemplates({ + const { recentTemplates } = useTeamTemplates({ teamId: activeTeam, hasLogIn, }); - const recentlyUsedTemplates = recentTemplates.slice(0, 4); + // Devboxes have been removed, so we only show templates that can run as a + // browser Sandbox and filter out any devbox-only templates. + const runsInBrowser = (template?: SandboxToFork) => + Boolean( + template && (template.type === 'sandbox' || template.browserSandboxId) + ); + + const recentlyUsedTemplates = recentTemplates + .filter(runsInBrowser) + .slice(0, 4); const hasRecentlyUsedTemplates = recentlyUsedTemplates.length > 0; const featuredTemplates = FEATURED_IDS.map(id => officialTemplates.find(t => t.id === id) ) - .filter(Boolean) + .filter(runsInBrowser) .slice(0, hasRecentlyUsedTemplates ? 8 : 12); - let filteredTemplates = officialTemplates.concat(teamTemplates); - - if (searchQuery) { - filteredTemplates = filteredTemplates.filter(template => { - return ( - template.title?.toLowerCase().includes(searchQuery.toLowerCase()) || - template.tags.some(tag => - tag.toLowerCase().includes(searchQuery.toLowerCase()) - ) - ); - }); - } - - if (filters.length > 0) { - filteredTemplates = filteredTemplates.filter(template => { - return filters - .map(item => item.toUpperCase()) - .every( - item => - template.tags - .map(tag => tag.toUpperCase().replace('-', ' ')) - .includes(item) || // by tag - template.title?.toUpperCase().split(' ').includes(item) // by keyword in title - ); - }); - } - - const gatherTags = filteredTemplates - .map(t => t.tags) - .flat() - .reduce((acc, tag) => { - acc[tag] = (acc[tag] || 0) + 1; - - return acc; - }, {}); - const additionalTags = Object.keys(gatherTags).filter( - key => gatherTags[key] > 1 - ); - useEffect(() => { if (!sandboxIdToFork) { return; @@ -224,66 +189,25 @@ export const CreateBox: React.FC = ({ flexDirection: 'column', }} > - - -
-
-
- -
-
-
- - - { - const query = e.target.value; - - setSearchQuery(query); - }} - /> -
- {filters.length === 0 && searchQuery === '' ? ( - <> - {hasRecentlyUsedTemplates && ( - - )} - - - ) : ( + {hasRecentlyUsedTemplates && ( )} +
@@ -293,59 +217,6 @@ export const CreateBox: React.FC = ({ ); }; -const ScrollView = styled.div` - flex: 1; - overflow: auto; - padding-bottom: 8px; - white-space: nowrap; - position: relative; - display: flex; - - .sticky { - position: sticky; - z-index: 9; - width: 0; - pointer-events: none; - - --scroll-buffer: 2rem; - opacity: 0; - animation: ${keyframes` - to { - opacity: 1; - } - `} both linear; - animation-timeline: scroll(x); - - > div { - position: absolute; - top: 0; - min-width: 20px; - height: 100%; - } - } - - .begin { - animation-range: 0 var(--scroll-buffer); - left: 0; - - > div { - left: 0; - background: linear-gradient(270deg, rgba(0, 0, 0, 0) 0%, #151515 100%); - } - } - - .end { - animation-range: calc(100% - var(--scroll-buffer)) 100%; - animation-direction: reverse; - right: -1px; - - > div { - right: 0; - background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, #151515 100%); - } - } -`; - const CreateBoxConfig: React.FC<{ selectedTemplate: SandboxToFork; setViewState: (viewState: ViewState) => void; diff --git a/packages/app/src/app/components/Create/CreateBox/CreateBoxForm.tsx b/packages/app/src/app/components/Create/CreateBox/CreateBoxForm.tsx index 42449fe6e89..4ae823e645b 100644 --- a/packages/app/src/app/components/Create/CreateBox/CreateBoxForm.tsx +++ b/packages/app/src/app/components/Create/CreateBox/CreateBoxForm.tsx @@ -1,4 +1,3 @@ -import styled from 'styled-components'; import React, { useEffect, useRef, useState } from 'react'; import { Stack, @@ -11,11 +10,7 @@ import { } from '@codesandbox/components'; import { useActions, useAppState, useEffects } from 'app/overmind'; -import { useWorkspaceSubscription } from 'app/hooks/useWorkspaceSubscription'; -import { useGlobalPersistedState } from 'app/hooks/usePersistedState'; import { PATHED_SANDBOXES_FOLDER_QUERY } from 'app/pages/Dashboard/queries'; -import { useWorkspaceLimits } from 'app/hooks/useWorkspaceLimits'; -import { VMTier } from 'app/overmind/effects/api/types'; import { useQuery } from '@apollo/react-hooks'; import { PathedSandboxesFoldersQuery, @@ -43,23 +38,11 @@ export const CreateBoxForm: React.FC = ({ onCancel, onSubmit, }) => { - const runsInTheBrowser = - template.type === 'sandbox' || template.browserSandboxId; - const runsOnVM = template.type === 'devbox'; - - const [runtime, setRuntime] = useState<'browser' | 'vm'>( - runsInTheBrowser ? 'browser' : 'vm' - ); - - const label = runtime === 'browser' ? 'Sandbox' : 'Devbox'; - const { activeTeamInfo, activeTeam, hasLogIn } = useAppState(); const { signInClicked } = useActions(); - const { highestAllowedVMTier, isFrozen } = useWorkspaceLimits(); const [name, setName] = useState(); const effects = useEffects(); const nameInputRef = useRef(null); - const { isFree } = useWorkspaceSubscription(); const minimumPrivacy = Math.max( initialPrivacy ?? 2, @@ -67,20 +50,6 @@ export const CreateBoxForm: React.FC = ({ ) as PrivacyLevel; const [permission, setPermission] = useState(minimumPrivacy); - const [editor, setEditor] = useGlobalPersistedState<'csb' | 'vscode'>( - 'DEFAULT_EDITOR', - 'csb' - ); - - const defaultTier = isFree ? 1 : 2; - const [selectedTier, setSelectedTier] = useState(defaultTier); - const [allVmTiers, setAllVmTiers] = useState([]); - - useEffect(() => { - effects.api.getVMSpecs().then(res => { - setAllVmTiers(res.vmTiers); - }); - }, []); useEffect(() => { effects.api.getSandboxTitle().then(({ title }) => { @@ -92,12 +61,6 @@ export const CreateBoxForm: React.FC = ({ }); }, []); - useEffect(() => { - if (selectedTier > highestAllowedVMTier) { - setSelectedTier(highestAllowedVMTier); - } - }, [selectedTier, highestAllowedVMTier]); - const { data: collectionsData } = useQuery< PathedSandboxesFoldersQuery, PathedSandboxesFoldersQueryVariables @@ -122,20 +85,13 @@ export const CreateBoxForm: React.FC = ({ onSubmit={e => { e.preventDefault(); + // Devboxes have been removed, so we always create a browser Sandbox. onSubmit({ - sandboxId: - runtime === 'browser' - ? template.browserSandboxId ?? template.id - : template.id, + sandboxId: template.browserSandboxId ?? template.id, name, - createAs: runtime === 'browser' ? 'sandbox' : 'devbox', + createAs: 'sandbox', permission, - editor: runtime === 'browser' ? 'csb' : editor, // ensure 'csb' is always passed when creating a sandbox - customVMTier: - // Only pass customVMTier if user selects something else than the default - allVmTiers.length > 0 && selectedTier !== defaultTier - ? selectedTier - : undefined, + editor: 'csb', }); }} > @@ -220,150 +176,11 @@ export const CreateBoxForm: React.FC = ({ )} - - - - Runtime - - - - setRuntime('browser')} - disabled={!runsInTheBrowser} - > - - - - - - Sandbox - - - - Ideal for prototyping and sharing code snippets. Runs on the - browser. - - - {!runsInTheBrowser && ( - - Not available for this template. - - )} - - - - setRuntime('vm')} - disabled={!runsOnVM} - > - - - - Devbox - - - - Ideal for any type of project, language or size. Runs on a - server. - - - {!runsOnVM && ( - - Not available for this template. - - )} - - {!activeTeamInfo?.featureFlags.ubbBeta && - activeTeamInfo?.subscription.status && ( - - - - Better specs are available for our new team plan, you - can upgrade{' '} - - here - - . - - - )} - - - - - - {runtime === 'vm' && ( - <> - - - VM specs - - - - - - - Open in - - - - - - )} - - {isFrozen && runtime === 'vm' && ( - - You have run our of credits. - - )} - + {hasLogIn ? ( - ) : ( )} @@ -413,38 +224,3 @@ const PRIVACY_OPTIONS = { icon: () => , }, }; - -const EDITOR_ICONS = { - csb: () => , - vscode: () => , -}; - -const CardButton = styled.button` - box-sizing: border-box; - width: 100%; - height: 100%; - padding: 16px; - background: #1d1d1d; - border: 1px solid transparent; - text-align: left; - font-family: inherit; - border-radius: 4px; - color: #e5e5e5; - outline: none; - display: flex; - cursor: pointer; - transition: all 100ms ease; - - &:hover:not(:disabled) { - background: #252525; - } - - &:disabled { - opacity: 0.4; - cursor: default; - } - - &[data-selected='true'] { - border-color: ${props => props.theme.colors.purple}; - } -`; diff --git a/packages/app/src/app/components/Create/TemplateCard.tsx b/packages/app/src/app/components/Create/TemplateCard.tsx index ef5453323a6..ae0bec3ea00 100644 --- a/packages/app/src/app/components/Create/TemplateCard.tsx +++ b/packages/app/src/app/components/Create/TemplateCard.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { formatNumber, Icon, Stack, Text } from '@codesandbox/components'; -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; import { VisuallyHidden } from 'reakit/VisuallyHidden'; import { TemplateButton } from './elements'; @@ -70,25 +69,6 @@ export const TemplateCard = ({ css={{ justifyContent: 'space-between', alignItems: 'flex-start' }} > - - {(template.type === 'sandbox' || template.browserSandboxId) && ( - - - - - - )} - {template.type === 'devbox' && ( - - - - )} - { return ( - - - {templates.length === 0 ? 'No results' : title} - - + {(title || templates.length === 0) && ( + + + {templates.length === 0 ? 'No results' : title} + + + )} {templates.length > 0 && ( diff --git a/packages/app/src/app/overmind/namespaces/dashboard/actions.ts b/packages/app/src/app/overmind/namespaces/dashboard/actions.ts index 97dcaa3ccff..5af7069f5ed 100755 --- a/packages/app/src/app/overmind/namespaces/dashboard/actions.ts +++ b/packages/app/src/app/overmind/namespaces/dashboard/actions.ts @@ -221,8 +221,9 @@ export const getDrafts = async ({ state, effects }: Context) => { sandboxes = data.me.team.drafts; } + // Devboxes have been removed from the dashboard, so we filter them out (isV2). dashboard.sandboxes[sandboxesTypes.DRAFTS] = sandboxes.filter( - s => !s.customTemplate + s => !s.customTemplate && !s.isV2 ); } catch (error) { effects.notificationToast.error( @@ -261,8 +262,9 @@ export const getSandboxesByPath = async ( dashboard.sandboxes.ALL = {}; } + // Devboxes have been removed from the dashboard, so we filter them out (isV2). dashboard.sandboxes.ALL[cleanPath] = data.me.collection.sandboxes.filter( - s => !s.customTemplate + s => !s.customTemplate && !s.isV2 ); // Update sandboxCount in allCollections from the collections query result @@ -365,7 +367,10 @@ export const getDeletedSandboxes = async ({ state, effects }: Context) => { return; } - dashboard.sandboxes[sandboxesTypes.DELETED] = data?.me?.team?.sandboxes; + // Devboxes have been removed from the dashboard, so we filter them out (isV2). + dashboard.sandboxes[sandboxesTypes.DELETED] = ( + data?.me?.team?.sandboxes || [] + ).filter(s => !s.isV2); } catch (error) { effects.notificationToast.error( 'There was a problem getting your deleted Sandboxes' @@ -411,8 +416,14 @@ export const getStartPageSandboxes = async ({ state, effects }: Context) => { teamId: state.activeTeam, }); - dashboard.sandboxes.RECENT_SANDBOXES = + const recentSandboxes = sandboxesResult?.me?.recentlyAccessedSandboxes || []; + + // Devboxes have been removed from the dashboard. Track whether the user + // recently used any so we can surface the removal banner, then filter them + // out (isV2) from the data we render. + dashboard.hasDevboxes = recentSandboxes.some(s => s.isV2); + dashboard.sandboxes.RECENT_SANDBOXES = recentSandboxes.filter(s => !s.isV2); dashboard.sandboxes.RECENT_BRANCHES = branchesResult?.me?.recentBranches || []; } catch (error) { @@ -793,9 +804,10 @@ export const getSearchSandboxes = async ({ state, effects }: Context) => { sandboxes = data.me.team.sandboxes; } + // Devboxes have been removed from the dashboard, so we filter them out (isV2). const sandboxesToShow = state.dashboard .getFilteredSandboxes(sandboxes) - .filter(x => !x.customTemplate); + .filter(x => !x.customTemplate && !x.isV2); dashboard.sandboxes[sandboxesTypes.SEARCH] = sandboxesToShow; } catch (error) { @@ -818,8 +830,10 @@ export const getWorkspaceSandboxes = async ({ state, effects }: Context) => { teamId: state.activeTeam, }); - dashboard.sandboxes.WORKSPACE_SANDBOXES = - data?.me?.team?.sandboxes || []; + // Devboxes have been removed from the dashboard, so we filter them out (isV2). + dashboard.sandboxes.WORKSPACE_SANDBOXES = ( + data?.me?.team?.sandboxes || [] + ).filter(s => !s.isV2); } catch (error) { dashboard.sandboxes.WORKSPACE_SANDBOXES = []; effects.notificationToast.error( @@ -1345,7 +1359,10 @@ export const getSharedSandboxes = async ({ state, effects }: Context) => { return; } - dashboard.sandboxes[sandboxesTypes.SHARED] = data.me?.collaboratorSandboxes; + // Devboxes have been removed from the dashboard, so we filter them out (isV2). + dashboard.sandboxes[sandboxesTypes.SHARED] = ( + data.me?.collaboratorSandboxes || [] + ).filter(s => !s.isV2); } catch (error) { effects.notificationToast.error( 'There was a problem getting Sandboxes shared with you' diff --git a/packages/app/src/app/overmind/namespaces/dashboard/state.ts b/packages/app/src/app/overmind/namespaces/dashboard/state.ts index 5a491f643fb..0145d5a8d8a 100755 --- a/packages/app/src/app/overmind/namespaces/dashboard/state.ts +++ b/packages/app/src/app/overmind/namespaces/dashboard/state.ts @@ -85,6 +85,12 @@ export type State = { removingRepository: { owner: string; name: string } | null; removingBranch: { id: string } | null; creatingBranch: boolean; + /** + * Devboxes have been removed from the dashboard. This tracks whether the + * active team still has (recently used) devboxes so we can surface a + * removal banner to those users. + */ + hasDevboxes: boolean; }; export const DEFAULT_DASHBOARD_SANDBOXES: DashboardSandboxStructure = { @@ -185,4 +191,5 @@ export const state: State = { removingRepository: null, removingBranch: null, creatingBranch: false, + hasDevboxes: false, }; diff --git a/packages/app/src/app/pages/Dashboard/Components/shared/DevboxesRemovedStripe.tsx b/packages/app/src/app/pages/Dashboard/Components/shared/DevboxesRemovedStripe.tsx new file mode 100644 index 00000000000..d04527e7109 --- /dev/null +++ b/packages/app/src/app/pages/Dashboard/Components/shared/DevboxesRemovedStripe.tsx @@ -0,0 +1,18 @@ +import { MessageStripe } from '@codesandbox/components'; +import React from 'react'; + +export const DevboxesRemovedStripe: React.FC = () => { + return ( + + Devboxes have been removed and are no longer available in the dashboard. + + Learn more + + + ); +}; diff --git a/packages/app/src/app/pages/Dashboard/Content/routes/Recent/index.tsx b/packages/app/src/app/pages/Dashboard/Content/routes/Recent/index.tsx index 4efd2381ed4..15f4c985f2c 100644 --- a/packages/app/src/app/pages/Dashboard/Content/routes/Recent/index.tsx +++ b/packages/app/src/app/pages/Dashboard/Content/routes/Recent/index.tsx @@ -7,6 +7,7 @@ import { Loading, Stack } from '@codesandbox/components'; import { RepoInfo } from 'app/overmind/namespaces/sidebar/types'; import { StyledContentWrapper } from 'app/pages/Dashboard/Components/shared/elements'; import { ContentSection } from 'app/pages/Dashboard/Components/shared/ContentSection'; +import { DevboxesRemovedStripe } from 'app/pages/Dashboard/Components/shared/DevboxesRemovedStripe'; import { useWorkspaceLimits } from 'app/hooks/useWorkspaceLimits'; import { TopBanner } from './TopBanner'; import { CreateBranchesRow } from './CreateBranchesRow'; @@ -17,7 +18,7 @@ export const Recent = () => { const { activeTeam, sidebar, - dashboard: { sandboxes }, + dashboard: { sandboxes, hasDevboxes }, } = useAppState(); const { isFrozen } = useWorkspaceLimits(); const { @@ -143,6 +144,8 @@ export const Recent = () => { + {hasDevboxes && } + {recentRepos.length > 0 && (