diff --git a/packages/app/src/app/graphql/types.ts b/packages/app/src/app/graphql/types.ts index 2c36f683425..983f021e210 100644 --- a/packages/app/src/app/graphql/types.ts +++ b/packages/app/src/app/graphql/types.ts @@ -4636,6 +4636,20 @@ export type JoinEligibleWorkspaceMutation = { joinEligibleWorkspace: { __typename?: 'Team'; id: any }; }; +export type RecentlyDeletedTeamSandboxesFragment = { + __typename?: 'Sandbox'; + id: string; + alias: string | null; + isV2: boolean; + removedAt: string | null; + title: string | null; + collection: { + __typename?: 'Collection'; + id: any | null; + path: string; + } | null; +}; + export type RecentlyDeletedTeamSandboxesQueryVariables = Exact<{ teamId: Scalars['UUID4']; }>; @@ -4651,44 +4665,13 @@ export type RecentlyDeletedTeamSandboxesQuery = { __typename?: 'Sandbox'; id: string; alias: string | null; - title: string | null; - description: string | null; - lastAccessedAt: any; - insertedAt: string; - updatedAt: string; - removedAt: string | null; - privacy: number; - isFrozen: boolean; - screenshotUrl: string | null; - viewCount: number; - likeCount: number; isV2: boolean; - draft: boolean; - restricted: boolean; - authorId: any | null; - teamId: any | null; - source: { __typename?: 'Source'; template: string | null }; - customTemplate: { - __typename?: 'Template'; - id: any | null; - iconUrl: string | null; - } | null; - forkedTemplate: { - __typename?: 'Template'; - id: any | null; - color: string | null; - iconUrl: string | null; - } | null; + removedAt: string | null; + title: string | null; collection: { __typename?: 'Collection'; - path: string; id: any | null; - } | null; - author: { __typename?: 'User'; username: string } | null; - permissions: { - __typename?: 'SandboxProtectionSettings'; - preventSandboxLeaving: boolean; - preventSandboxExport: boolean; + path: string; } | null; }>; } | null; diff --git a/packages/app/src/app/overmind/effects/gql/dashboard/queries.ts b/packages/app/src/app/overmind/effects/gql/dashboard/queries.ts index d834c15c529..bb428410c0e 100644 --- a/packages/app/src/app/overmind/effects/gql/dashboard/queries.ts +++ b/packages/app/src/app/overmind/effects/gql/dashboard/queries.ts @@ -60,6 +60,23 @@ import { githubRepoFragment, } from './fragments'; +const RECENTLY_DELETED_TEAM_SANDBOXES_FRAGMENT = gql` +fragment recentlyDeletedTeamSandboxes on Sandbox { + id + + alias + + collection { + id + path + } + + isV2 + removedAt + title +} +`; + export const deletedTeamSandboxes: Query< RecentlyDeletedTeamSandboxesQuery, RecentlyDeletedTeamSandboxesQueryVariables @@ -73,12 +90,12 @@ export const deletedTeamSandboxes: Query< showDeleted: true orderBy: { field: "updated_at", direction: DESC } ) { - ...sandboxFragmentDashboard + ...recentlyDeletedTeamSandboxes } } } } - ${sandboxFragmentDashboard} + ${RECENTLY_DELETED_TEAM_SANDBOXES_FRAGMENT} `; export const sandboxesByPath: Query< diff --git a/packages/app/src/app/overmind/namespaces/dashboard/internalActions.ts b/packages/app/src/app/overmind/namespaces/dashboard/internalActions.ts index 8e6e9c52b15..b81167d8124 100644 --- a/packages/app/src/app/overmind/namespaces/dashboard/internalActions.ts +++ b/packages/app/src/app/overmind/namespaces/dashboard/internalActions.ts @@ -151,6 +151,13 @@ export const deleteSandboxesFromState = ( repoSandbox.sandboxes = newSandboxes; } }); + } else if (type === 'DELETED') { + const newSandboxes = sandboxStructure[type].filter( + sandbox => !ids.includes(sandbox.id) + ); + if (newSandboxes.length !== sandboxStructure[type].length) { + dashboard.sandboxes[type] = newSandboxes; + } } else if (type !== 'RECENT_BRANCHES') { const newSandboxes = sandboxStructure[type].filter(sandboxFilter); if (newSandboxes.length !== sandboxStructure[type].length) { diff --git a/packages/app/src/app/overmind/namespaces/dashboard/state.ts b/packages/app/src/app/overmind/namespaces/dashboard/state.ts index 30a8d40a8b5..134a9a472e7 100755 --- a/packages/app/src/app/overmind/namespaces/dashboard/state.ts +++ b/packages/app/src/app/overmind/namespaces/dashboard/state.ts @@ -6,6 +6,7 @@ import { BranchFragment as Branch, ProjectFragment as Repository, ProjectWithBranchesFragment as RepositoryWithBranches, + RecentlyDeletedTeamSandboxesFragment, } from 'app/graphql/types'; import isSameWeek from 'date-fns/isSameWeek'; import { sortBy } from 'lodash-es'; @@ -17,7 +18,7 @@ import { DELETE_ME_COLLECTION, OrderBy } from './types'; export type DashboardSandboxStructure = { DRAFTS: Sandbox[] | null; TEMPLATES: Template[] | null; - DELETED: Sandbox[] | null; + DELETED: RecentlyDeletedTeamSandboxesFragment[] | null; RECENT_SANDBOXES: Sandbox[] | null; RECENT_BRANCHES: Branch[] | null; SEARCH: Sandbox[] | null; @@ -47,11 +48,11 @@ export type State = { viewMode: 'grid' | 'list'; orderBy: OrderBy; getFilteredSandboxes: ( - sandboxes: Array + sandboxes: Array ) => Sandbox[]; deletedSandboxesByTime: { - week: Sandbox[]; - older: Sandbox[]; + week: RecentlyDeletedTeamSandboxesFragment[]; + older: RecentlyDeletedTeamSandboxesFragment[]; }; contributions: Branch[] | null; /** @@ -104,8 +105,7 @@ export const state: State = { week: [], older: [], }; - const noTemplateSandboxes = deletedSandboxes.filter(s => !s.customTemplate); - const timeSandboxes = noTemplateSandboxes.reduce( + const timeSandboxes = deletedSandboxes.reduce( (accumulator, currentValue) => { if (!currentValue.removedAt) return accumulator; if (isSameWeek(new Date(currentValue.removedAt), new Date())) { @@ -137,7 +137,7 @@ export const state: State = { }, getFilteredSandboxes: derived( ({ orderBy }: State) => ( - sandboxes: Array + sandboxes: Array ) => { const orderField = orderBy.field; const orderOrder = orderBy.order; @@ -156,7 +156,7 @@ export const state: State = { return field.toLowerCase(); } - if (orderField === 'views') { + if ('viewCount' in sandbox && orderField === 'views') { return sandbox.viewCount; } diff --git a/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxBadge.tsx b/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxBadge.tsx index ae2eb6f40a0..42ef33029b8 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxBadge.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxBadge.tsx @@ -1,19 +1,20 @@ import { Icon, Stack, Text } from '@codesandbox/components'; -import { SandboxFragmentDashboardFragment as Sandbox } from 'app/graphql/types'; import React from 'react'; export interface SandboxBadgeProps { - sandbox: Sandbox; - restricted: boolean; + isSandboxV2: boolean; + isSandboxTemplate: boolean; + isSandboxRestricted: boolean; } export const SandboxBadge: React.FC = ({ - sandbox, - restricted, + isSandboxV2, + isSandboxTemplate, + isSandboxRestricted, }) => { - const isDevbox = sandbox.isV2; - const isRestricted = restricted; - const isTemplate = !!sandbox.customTemplate; + const isDevbox = isSandboxV2; + const isRestricted = isSandboxRestricted; + const isTemplate = isSandboxTemplate; const boxIcon = isDevbox ? 'server' : 'boxDevbox'; let boxTypeLabel = isDevbox ? 'Devbox' : 'Sandbox'; diff --git a/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxCard.tsx b/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxCard.tsx index 01867288813..a95368ec7c1 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxCard.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Sandbox/SandboxCard.tsx @@ -73,9 +73,11 @@ const SandboxTitle: React.FC = React.memo( ) : ( - - - + {TemplateIcon && ( + + + + )} {interaction === 'button' ? ( = React.memo( className="sandbox-stats" > - + {PrivacyIcon && } {isFrozen && ( )} {noDrag ? null : timeAgoText} - + ); } @@ -297,7 +303,7 @@ export const SandboxCard = ({ - {screenshotUrl ? null : } + {!screenshotUrl && TemplateIcon && ( + + )} {editing ? ( @@ -131,7 +132,7 @@ export const SandboxListItem = ({ ) : ( - + {PrivacyIcon ? : null} - + diff --git a/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx b/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx index 7bea89a9fde..31987cce629 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx @@ -8,7 +8,6 @@ import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator'; import { ESC } from '@codesandbox/common/lib/utils/keycodes'; import track from '@codesandbox/common/lib/utils/analytics'; import { Icon } from '@codesandbox/components'; -import { formatNumber } from '@codesandbox/components/lib/components/Stats'; import { SandboxCard } from './SandboxCard'; import { SandboxListItem } from './SandboxListItem'; @@ -19,7 +18,7 @@ import { SandboxItemComponentProps } from './types'; import { useDrag } from '../../utils/dnd'; const PrivacyIcons = { - 0: () => null, + 0: null, 1: () => , 2: () => , }; @@ -47,7 +46,7 @@ function getFolderName(item: GenericSandboxProps['item']): string | undefined { const { sandbox } = item; if (sandbox.collection) { - if (sandbox.collection.path === '/' && !sandbox.teamId) { + if (sandbox.collection.path === '/' && !('teamId' in sandbox)) { return undefined; } @@ -66,26 +65,72 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { const sandboxTitle = sandbox.title || sandbox.alias || sandbox.id; const sandboxLocation = getFolderName(item); - const timeStampToUse = - page === 'recent' ? sandbox.lastAccessedAt : sandbox.updatedAt; - const timeAgo = formatDistanceStrict( - zonedTimeToUtc(timeStampToUse, 'Etc/UTC'), - new Date(), - { + let timeStampToUse: string | undefined; + + // 'lastAccessedAt' and 'updatedAt' are irrelevant for: + // - deleted sandboxes + if (page === 'recent' && 'lastAccessedAt' in sandbox) { + timeStampToUse = sandbox.lastAccessedAt; + } else if ('updatedAt' in sandbox) { + timeStampToUse = sandbox.updatedAt; + } + + let timeAgo: string | undefined; + + // timeStampToUse might be undefined due to the checks above, but + // typescript is not smart enought to know. + if (timeStampToUse) { + const timeStampToUseDate = zonedTimeToUtc(timeStampToUse, 'Etc/UTC'); + const now = new Date(); + + timeAgo = formatDistanceStrict(timeStampToUseDate, now, { addSuffix: true, + }); + } + + const url = sandboxUrl(sandbox); + + let TemplateIcon: + | React.ComponentType<{ width: string; height: string }> + | undefined; + + // 'source' is not present in: + // - deleted sandboxes + if ('source' in sandbox) { + TemplateIcon = getTemplateIcon(sandbox); + } + + let PrivacyIcon: React.ComponentType | undefined; + + // 'privacy' is not present in: + // - deleted sandboxes + if ('privacy' in sandbox) { + PrivacyIcon = PrivacyIcons[sandbox.privacy]; + } + + let restricted = false; + + // 'restricted' and 'draft' are not present in: + // - deleted sandboxes + if ('restricted' in sandbox) { + restricted = sandbox.restricted; + + if ('draft' in sandbox) { + restricted = sandbox.restricted && !sandbox.draft; } - ); + } - const viewCount = formatNumber(sandbox.viewCount); + let screenshotUrl: string | undefined; - const url = sandboxUrl(sandbox); + // 'screenshotUrl' is not present in: + // - deleted sandboxes + if ('screenshotUrl' in sandbox) { + screenshotUrl = sandbox.screenshotUrl; + } - const TemplateIcon = getTemplateIcon(sandbox); - const PrivacyIcon = PrivacyIcons[sandbox.privacy]; - const restricted = sandbox.restricted && !sandbox.draft; + // TODO: Check if screnshotUrl fallback below is still relevant - let screenshotUrl = sandbox.screenshotUrl; // We set a fallback thumbnail in the API which is used for // both old and new dashboard, we can move this logic to the // backend when we deprecate the old dashboard @@ -244,7 +289,6 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { sandboxTitle, sandboxLocation, timeAgo, - viewCount, sandbox, TemplateIcon, PrivacyIcon, @@ -275,18 +319,24 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { }); }, [preview]); + let username: string | undefined; + + // 'author' is not present in: + // - deleted sandboxes + if ('author' in sandbox && sandbox.author) { + username = + sandbox.author.username === user?.username + ? 'you' + : sandbox.author.username; + } + return (
); diff --git a/packages/app/src/app/pages/Dashboard/Components/Sandbox/types.ts b/packages/app/src/app/pages/Dashboard/Components/Sandbox/types.ts index c27fb1e408b..a44317811e6 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Sandbox/types.ts +++ b/packages/app/src/app/pages/Dashboard/Components/Sandbox/types.ts @@ -5,17 +5,16 @@ export interface SandboxItemComponentProps { sandbox: DashboardSandbox['sandbox'] | DashboardTemplate['sandbox']; sandboxTitle: string; sandboxLocation?: string; - timeAgo: string; - viewCount: number | string; - TemplateIcon: React.FC<{ + timeAgo?: string; + TemplateIcon?: React.ComponentType<{ width: string; height: string; style?: React.CSSProperties; }>; - PrivacyIcon: React.FC; - screenshotUrl: string | null; + PrivacyIcon?: React.ComponentType; + screenshotUrl?: string; restricted: boolean; - username: string | null; + username?: string; interaction: 'button' | 'link'; isScrolling: boolean; selected: boolean; diff --git a/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/MultiItemMenu.tsx b/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/MultiItemMenu.tsx index f77fb0da398..b0b50a364aa 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/MultiItemMenu.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/MultiItemMenu.tsx @@ -58,7 +58,11 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { const exportItems = () => { const ids = [ ...sandboxes - .filter(sandbox => !sandbox.sandbox.permissions.preventSandboxExport) + .filter( + s => + 'permissions' in s.sandbox && + !s.sandbox.permissions.preventSandboxExport + ) .map(sandbox => sandbox.sandbox.id), ...templates.map(template => template.sandbox.id), ]; @@ -66,7 +70,8 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { actions.dashboard.downloadSandboxes(ids); const skippedSandboxes = sandboxes.filter( - sandbox => sandbox.sandbox.permissions.preventSandboxExport + s => + 'permissions' in s.sandbox && s.sandbox.permissions.preventSandboxExport ); if (skippedSandboxes.length) { @@ -97,7 +102,7 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { sandboxIds: [...sandboxes, ...templates].map(s => s.sandbox.id), preventSandboxLeaving: Boolean( [...sandboxes, ...templates].find( - s => s.sandbox.permissions.preventSandboxLeaving + s => 'permissions' in s.sandbox && s.sandbox.permissions.preventSandboxLeaving ) ), }); @@ -149,7 +154,7 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { const FROZEN_ITEMS = isInDrafts ? [] : [ - sandboxes.some(s => !s.sandbox.isFrozen) && { + sandboxes.some(s => 'isFrozen' in s.sandbox && !s.sandbox.isFrozen) && { label: 'Protect', fn: () => { actions.dashboard.changeSandboxesFrozen({ @@ -158,7 +163,7 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { }); }, }, - sandboxes.some(s => s.sandbox.isFrozen) && { + sandboxes.some(s => 'isFrozen' in s.sandbox && s.sandbox.isFrozen) && { label: 'Remove protection', fn: () => { actions.dashboard.changeSandboxesFrozen({ @@ -171,7 +176,11 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { const PROTECTED_SANDBOXES_ITEMS = isAdmin ? [ - sandboxes.some(s => !s.sandbox.permissions.preventSandboxLeaving) && { + sandboxes.some( + s => + 'permissions' in s.sandbox && + s.sandbox.permissions.preventSandboxLeaving + ) && { label: 'Prevent leaving workspace', fn: () => { actions.dashboard.setPreventSandboxesLeavingWorkspace({ @@ -180,7 +189,11 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { }); }, }, - sandboxes.some(s => s.sandbox.permissions.preventSandboxLeaving) && { + sandboxes.some( + s => + 'permissions' in s.sandbox && + s.sandbox.permissions.preventSandboxLeaving + ) && { label: 'Allow leaving workspace', fn: () => { actions.dashboard.setPreventSandboxesLeavingWorkspace({ @@ -189,7 +202,11 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { }); }, }, - sandboxes.some(s => !s.sandbox.permissions.preventSandboxExport) && + sandboxes.some( + s => + 'permissions' in s.sandbox && + s.sandbox.permissions.preventSandboxExport + ) && sandboxes.every(s => !s.sandbox.isV2) && { label: 'Prevent export as .zip', fn: () => { @@ -199,7 +216,11 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { }); }, }, - sandboxes.some(s => s.sandbox.permissions.preventSandboxExport) && + sandboxes.some( + s => + 'permissions' in s.sandbox && + s.sandbox.permissions.preventSandboxExport + ) && sandboxes.every(s => !s.sandbox.isV2) && { label: 'Allow export as .zip', fn: () => { @@ -213,8 +234,13 @@ export const MultiMenu = ({ selectedItems, page }: IMultiMenuProps) => { : []; const EXPORT = - sandboxes.some(s => !s.sandbox.permissions.preventSandboxExport) && - sandboxes.every(s => !s.sandbox.isV2) + sandboxes.some( + s => + !( + 'permissions' in s.sandbox && + s.sandbox.permissions.preventSandboxExport + ) + ) && sandboxes.every(s => !s.sandbox.isV2) ? [{ label: 'Download', fn: exportItems }] : []; diff --git a/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/SandboxMenu.tsx b/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/SandboxMenu.tsx index 11fd30c2d7b..5d133d1ec28 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/SandboxMenu.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Selection/ContextMenus/SandboxMenu.tsx @@ -30,7 +30,7 @@ export const SandboxMenu: React.FC = ({ browser: { copyToClipboard }, } = useEffects(); const { sandbox } = item; - const isTemplate = !!sandbox.customTemplate; + const isTemplate = 'customTemplate' in sandbox && !!sandbox.customTemplate; const { visible, setVisibility, position } = React.useContext(Context); const history = useHistory(); @@ -45,7 +45,7 @@ export const SandboxMenu: React.FC = ({ const restrictedFork = isFrozen; const isInActiveTeam = React.useMemo(() => { - if (item.sandbox.teamId === activeTeam) { + if ('teamId' in item.sandbox && item.sandbox.teamId === activeTeam) { return true; } @@ -82,7 +82,7 @@ export const SandboxMenu: React.FC = ({ } const preventSandboxExport = - !hasWriteAccess || sandbox.permissions.preventSandboxExport; + !hasWriteAccess || ('permissions' in sandbox && sandbox.permissions.preventSandboxExport); // TODO(@CompuIves): refactor this to an array @@ -147,8 +147,8 @@ export const SandboxMenu: React.FC = ({ openInNewWindow: true, redirectAfterFork: true, body: { - privacy: sandbox.privacy as 2 | 1 | 0, - collectionId: sandbox.draft ? undefined : sandbox.collection.id, + privacy: 'privacy' in sandbox ? sandbox.privacy as 2 | 1 | 0 : undefined, + collectionId: 'draft' in sandbox && sandbox.draft ? undefined : 'collection' in sandbox && sandbox.collection.id, }, }); }} @@ -164,7 +164,7 @@ export const SandboxMenu: React.FC = ({ actions.modals.moveSandboxModal.open({ sandboxIds: [item.sandbox.id], preventSandboxLeaving: - item.sandbox.permissions.preventSandboxLeaving, + 'permissions' in item.sandbox && item.sandbox.permissions.preventSandboxLeaving, }); }} > @@ -194,7 +194,7 @@ export const SandboxMenu: React.FC = ({
)} - {hasWriteAccess ? ( + {hasWriteAccess && 'privacy' in sandbox ? ( <> {sandbox.privacy !== 0 && ( @@ -244,7 +244,7 @@ export const SandboxMenu: React.FC = ({ )} {hasWriteAccess && !isTemplate && - (sandbox.isFrozen ? ( + ('isFrozen' in sandbox && sandbox.isFrozen ? ( { actions.dashboard.changeSandboxesFrozen({ @@ -304,7 +304,7 @@ export const SandboxMenu: React.FC = ({ ))} {isPro && hasAdminAccess && - (sandbox.permissions.preventSandboxLeaving ? ( + ('permissions' in sandbox && sandbox.permissions.preventSandboxLeaving ? ( { actions.dashboard.setPreventSandboxesLeavingWorkspace({ @@ -330,7 +330,7 @@ export const SandboxMenu: React.FC = ({ {!sandbox.isV2 && isPro && hasAdminAccess && - (sandbox.permissions.preventSandboxExport ? ( + ('permissions' in sandbox && sandbox.permissions.preventSandboxExport ? ( { actions.dashboard.setPreventSandboxesExport({ @@ -394,7 +394,7 @@ const getFolderUrl = ( if (item.type === 'template') return dashboard.templates(activeTeamId); const path = item.sandbox.collection?.path; - if (path == null || (!item.sandbox.teamId && path === '/')) { + if (path == null || ('teamId' in item.sandbox && !item.sandbox.teamId && path === '/')) { return dashboard.drafts(activeTeamId); } diff --git a/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx b/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx index b9be2dc9796..cc1598e5e63 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx @@ -76,17 +76,30 @@ export const DragPreview: React.FC = React.memo( const sandbox = dashboardEntry.sandbox; - let screenshotUrl = sandbox.screenshotUrl; - // We set a fallback thumbnail in the API which is used for - // both old and new dashboard, we can move this logic to the - // backend when we deprecate the old dashboard - if ( - screenshotUrl === 'https://codesandbox.io/static/img/banner.png' - ) { - screenshotUrl = '/static/img/default-sandbox-thumbnail.png'; + let screenshotUrl; + + // 'screenshotUrl' is not present in: + // - deleted sandboxes + // TODO: Decide if we really want deleted sandboxes to be draggable. + if ('screenshotUrl' in sandbox) { + screenshotUrl = sandbox.screenshotUrl; + // We set a fallback thumbnail in the API which is used for + // both old and new dashboard, we can move this logic to the + // backend when we deprecate the old dashboard + if ( + screenshotUrl === 'https://codesandbox.io/static/img/banner.png' + ) { + screenshotUrl = '/static/img/default-sandbox-thumbnail.png'; + } } - const TemplateIcon = getTemplateIcon(sandbox); + let TemplateIcon: React.ComponentType<{ width: string; height: string }> | undefined; + + // 'source' is not present in: + // - deleted sandboxes + if ('source' in sandbox) { + TemplateIcon = getTemplateIcon(sandbox); + } return { type: 'sandbox', diff --git a/packages/app/src/app/pages/Dashboard/Content/routes/Search/searchItems.ts b/packages/app/src/app/pages/Dashboard/Content/routes/Search/searchItems.ts index 9eca0a149f3..b183390c624 100644 --- a/packages/app/src/app/pages/Dashboard/Content/routes/Search/searchItems.ts +++ b/packages/app/src/app/pages/Dashboard/Content/routes/Search/searchItems.ts @@ -13,6 +13,13 @@ type DashboardItem = | SandboxFragmentDashboardFragment | SidebarCollectionDashboardFragment; +// Type guard to check if an item is a sandbox +function isSandbox( + item: DashboardItem | { repository?: any; path?: string } +): item is SandboxFragmentDashboardFragment { + return !('path' in item) && !('repository' in item); +} + // define which fields to search, with per-key thresholds & weights const SEARCH_KEYS = [ { name: 'title', threshold: 0.2, weight: 0.4 }, @@ -99,7 +106,7 @@ export const useGetItems = ({ query: string; username: string; getFilteredSandboxes: ( - list: DashboardItem[] + list: SandboxFragmentDashboardFragment[] ) => SandboxFragmentDashboardFragment[]; }) => { const state = useAppState(); @@ -137,7 +144,7 @@ export const useGetItems = ({ }, [query, searchIndex]); // then the rest is just your existing filtering / mapping logic: - const sandboxesInSearch = foundResults.filter(s => !(s as any).path); + const sandboxesInSearch = foundResults.filter(isSandbox); const foldersInSearch = foundResults.filter(s => (s as any).path); const filteredSandboxes = getFilteredSandboxes(sandboxesInSearch); const isLoadingQuery = query && !searchIndex; diff --git a/packages/app/src/app/pages/Dashboard/types.ts b/packages/app/src/app/pages/Dashboard/types.ts index d0a94dff15f..d352e4fd880 100644 --- a/packages/app/src/app/pages/Dashboard/types.ts +++ b/packages/app/src/app/pages/Dashboard/types.ts @@ -4,6 +4,7 @@ import { RepoFragmentDashboardFragment, BranchFragment as Branch, ProjectFragment as Repository, + RecentlyDeletedTeamSandboxesFragment, } from 'app/graphql/types'; import { Context } from 'app/overmind'; import { @@ -21,10 +22,12 @@ export type DashboardBaseFolder = { export type DashboardSandbox = { type: 'sandbox'; - sandbox: SandboxFragmentDashboardFragment & { - prNumber?: number; - originalGit?: RepoFragmentDashboardFragment['originalGit']; - }; + sandbox: + | (SandboxFragmentDashboardFragment & { + prNumber?: number; + originalGit?: RepoFragmentDashboardFragment['originalGit']; + }) + | RecentlyDeletedTeamSandboxesFragment; noDrag?: boolean; }; diff --git a/packages/components/src/components/Stats/index.tsx b/packages/components/src/components/Stats/index.tsx index 4163ac77249..cca916fad67 100644 --- a/packages/components/src/components/Stats/index.tsx +++ b/packages/components/src/components/Stats/index.tsx @@ -74,4 +74,4 @@ export const Stats = ({ sandbox, ...props }) => ( )} -); +); \ No newline at end of file diff --git a/packages/components/src/components/Stats/stats.stories.tsx b/packages/components/src/components/Stats/stats.stories.tsx deleted file mode 100644 index 00832a73d18..00000000000 --- a/packages/components/src/components/Stats/stats.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { Stats } from '.'; - -export default { - title: 'components/Stats', - component: Stats, -}; - -// replace the text inside with Text variants when available -export const Basic = () => ( - -); - -export const BigNumber = () => ( - -);