Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions src/app/dashboard/[teamSlug]/templates/(tabs)/list/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
import { Suspense } from 'react'
import LoadingLayout from '@/features/dashboard/loading-layout'
import {
TEMPLATES_DEFAULT_SORT,
TEMPLATES_PAGE_SIZE,
} from '@/features/dashboard/templates/list/constants'
import TemplatesTable from '@/features/dashboard/templates/list/table'
import { HydrateClient, prefetch, trpc } from '@/trpc/server'

export default async function TemplatesListPage({
params,
}: PageProps<'/dashboard/[teamSlug]/templates/list'>) {
const { teamSlug } = await params

prefetch(
trpc.templates.getTemplates.infiniteQueryOptions({
teamSlug,
limit: TEMPLATES_PAGE_SIZE,
sort: TEMPLATES_DEFAULT_SORT,
})
)

export default function TemplatesListPage() {
return (
<HydrateClient>
<Suspense fallback={<LoadingLayout />}>
<TemplatesTable />
</Suspense>
</HydrateClient>
<Suspense fallback={<LoadingLayout />}>
<TemplatesTable />
</Suspense>
)
}
15 changes: 0 additions & 15 deletions src/core/modules/templates/models.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import type {
components as DashboardComponents,
paths as DashboardPaths,
} from '@/contracts/dashboard-api'
import type { components as InfraComponents } from '@/contracts/infra-api'

export type Template = Pick<
Expand All @@ -27,14 +23,3 @@ export type DefaultTemplate = Template & {
isDefault: true
defaultDescription?: string
}

export type TemplatesSort = DashboardComponents['parameters']['templates_sort']

export type ListTeamTemplatesOptions = NonNullable<
DashboardPaths['/templates']['get']['parameters']['query']
>

export interface ListTeamTemplatesResult {
data: Array<Template | DefaultTemplate>
nextCursor: string | null
}
66 changes: 1 addition & 65 deletions src/core/modules/templates/repository.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ import {
MOCK_DEFAULT_TEMPLATES_DATA,
MOCK_TEMPLATES_DATA,
} from '@/configs/mock-data'
import type {
DefaultTemplate,
ListTeamTemplatesOptions,
ListTeamTemplatesResult,
Template,
} from '@/core/modules/templates/models'
import type { DefaultTemplate, Template } from '@/core/modules/templates/models'
import {
type AuthUserEmailResolver,
getAuthUserEmailsById,
Expand All @@ -35,9 +30,6 @@ type TemplatesRepositoryDeps = {

export interface TeamTemplatesRepository {
getTeamTemplates(): Promise<RepoResult<{ templates: Template[] }>>
listTeamTemplates(
options: ListTeamTemplatesOptions
): Promise<RepoResult<ListTeamTemplatesResult>>
deleteTemplate(templateId: string): Promise<RepoResult<{ success: true }>>
updateTemplateVisibility(
templateId: string,
Expand Down Expand Up @@ -95,62 +87,6 @@ export function createTemplatesRepository(
),
})
},
async listTeamTemplates(options) {
if (USE_MOCK_DATA) {
return ok({ data: MOCK_TEMPLATES_DATA, nextCursor: null })
}

const res = await deps.apiClient.GET('/templates', {
params: {
query: options,
},
headers: {
...deps.authHeaders(scope.accessToken, scope.teamId),
},
})

if (!res.response.ok || res.error) {
return err(
repoErrorFromHttp(
res.response.status,
res.error?.message ?? 'Failed to fetch templates',
res.error
)
)
}

if (!res.data?.data?.length) {
return ok({ data: [], nextCursor: res.data?.nextCursor ?? null })
}

const data = res.data.data.map((t): Template | DefaultTemplate => ({
templateID: t.templateID,
buildID: t.buildID,
cpuCount: t.cpuCount,
memoryMB: t.memoryMB,
diskSizeMB: t.diskSizeMB ?? 0,
public: t.public,
aliases: t.aliases,
names: t.names,
createdAt: t.createdAt,
updatedAt: t.updatedAt,
// Email resolution is deferred while the Supabase auth migration is
// in progress; the endpoint returns only the creator id for now.
createdBy: t.createdBy
? { id: t.createdBy.id, email: t.createdBy.email ?? '' }
: null,
lastSpawnedAt: t.lastSpawnedAt ?? null,
spawnCount: t.spawnCount,
buildCount: t.buildCount,
envdVersion: t.envdVersion ?? '',
...(t.isDefault && {
isDefault: true as const,
defaultDescription: t.defaultDescription ?? undefined,
}),
}))

return ok({ data, nextCursor: res.data.nextCursor ?? null })
},
async deleteTemplate(templateId) {
const res = await deps.infraClient.DELETE('/templates/{templateID}', {
params: {
Expand Down
43 changes: 5 additions & 38 deletions src/core/server/api/routers/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,44 +36,11 @@ const teamTemplatesRepositoryProcedure = protectedTeamProcedure.use(
export const templatesRouter = createTRPCRouter({
// QUERIES

getTemplates: teamTemplatesRepositoryProcedure
.input(
z.object({
cursor: z.string().optional(),
limit: z.number().int().min(1).max(100).default(50),
cpuCount: z.number().int().positive().optional(),
memoryMB: z.number().int().positive().optional(),
public: z.boolean().optional(),
search: z.string().optional(),
sort: z
.enum([
'name_asc',
'name_desc',
'cpu_count_asc',
'cpu_count_desc',
'memory_mb_asc',
'memory_mb_desc',
'created_at_asc',
'created_at_desc',
'updated_at_asc',
'updated_at_desc',
])
.default('updated_at_desc'),
})
)
.query(async ({ ctx, input }) => {
const result = await ctx.templatesRepository.listTeamTemplates({
cursor: input.cursor,
limit: input.limit,
cpuCount: input.cpuCount,
memoryMB: input.memoryMB,
public: input.public,
search: input.search,
sort: input.sort,
})
if (!result.ok) throwTRPCErrorFromRepoError(result.error)
return result.data
}),
getTemplates: teamTemplatesRepositoryProcedure.query(async ({ ctx }) => {
const result = await ctx.templatesRepository.getTeamTemplates()
if (!result.ok) throwTRPCErrorFromRepoError(result.error)
return result.data
}),

getDefaultTemplatesCached: templatesRepositoryProcedure.query(
async ({ ctx }) => {
Expand Down
37 changes: 37 additions & 0 deletions src/features/dashboard/templates/builds/table-cells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import CopyButtonInline from '@/ui/copy-button-inline'
import { Badge } from '@/ui/primitives/badge'
import { Button } from '@/ui/primitives/button'
import { CheckIcon, CloseIcon } from '@/ui/primitives/icons'
import { Loader } from '@/ui/primitives/loader'

export function BuildId({ id }: { id: string }) {
return (
Expand Down Expand Up @@ -60,6 +61,42 @@ export function Template({
)
}

export function LoadMoreButton({
isLoading,
onLoadMore,
}: {
isLoading: boolean
onLoadMore: () => void
}) {
if (isLoading) {
return (
<span className="inline-flex items-center gap-1">
Loading
<Loader variant="dots" />
</span>
)
}
return (
<button
onClick={onLoadMore}
className="underline text-fg-secondary hover:text-accent-main-highlight transition-colors"
>
Load more
</button>
)
}

export function BackToTopButton({ onBackToTop }: { onBackToTop: () => void }) {
return (
<button
onClick={onBackToTop}
className="underline text-fg-secondary hover:text-accent-main-highlight transition-colors"
>
Back to top
</button>
)
}

export function Duration({
createdAt,
finishedAt,
Expand Down
3 changes: 2 additions & 1 deletion src/features/dashboard/templates/builds/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import type {
import { useRouteParams } from '@/lib/hooks/use-route-params'
import { cn } from '@/lib/utils/ui'
import { useTRPC } from '@/trpc/client'
import { BackToTopButton, LoadMoreButton } from '@/ui/pagination-buttons'
import { ArrowDownIcon } from '@/ui/primitives/icons'
import { Loader } from '@/ui/primitives/loader'
import {
Expand All @@ -29,8 +28,10 @@ import {
} from '@/ui/primitives/table'
import BuildsEmpty from './empty'
import {
BackToTopButton,
BuildId,
Duration,
LoadMoreButton,
Reason,
StartedAt,
Status,
Expand Down
16 changes: 0 additions & 16 deletions src/features/dashboard/templates/list/constants.ts

This file was deleted.

33 changes: 17 additions & 16 deletions src/features/dashboard/templates/list/header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Table } from '@tanstack/react-table'
import { Suspense } from 'react'
import type { Template } from '@/core/modules/templates/models'
import { useTemplateTableStore } from './stores/table-store'
import TemplatesTableFilters from './table-filters'
import { SearchInput } from './table-search'

Expand All @@ -12,16 +11,13 @@ interface TemplatesHeaderProps {
export default function TemplatesHeader({ table }: TemplatesHeaderProps) {
'use no memo'

const { globalFilter, cpuCount, memoryMB, isPublic } = useTemplateTableStore()
const isFiltered =
Boolean(globalFilter) ||
cpuCount !== undefined ||
memoryMB !== undefined ||
isPublic !== undefined
const { columnFilters, globalFilter } = table.getState()
const showFilteredRowCount = columnFilters.length > 0 || Boolean(globalFilter)

// With server-side pagination we only know how many rows are currently
// loaded, not the grand total.
const loadedCount = table.options.data.length
const totalCount = table.options.data.length
const filteredCount = showFilteredRowCount
? table.getFilteredRowModel().rows.length
: totalCount

return (
<div className="flex min-w-0 flex-wrap items-start gap-1 sm:items-center">
Expand All @@ -37,12 +33,17 @@ export default function TemplatesHeader({ table }: TemplatesHeaderProps) {
<div className="hidden w-2 shrink-0 sm:block" aria-hidden="true" />

<span className="prose-label-highlight h-9 flex w-full min-w-0 items-center gap-1 uppercase sm:w-auto">
<span className="text-fg">
{loadedCount} {loadedCount === 1 ? 'template' : 'templates'}
</span>
{isFiltered ? (
<span className="text-fg-tertiary"> · filtered</span>
) : null}
{showFilteredRowCount ? (
<>
<span className="text-fg">
{filteredCount} {filteredCount === 1 ? 'result' : 'results'}
</span>
<span className="text-fg-tertiary"> · </span>
<span className="text-fg-tertiary">{totalCount} total</span>
</>
) : (
<span className="text-fg-tertiary">{totalCount} total</span>
)}
</span>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { OnChangeFn, SortingState } from '@tanstack/react-table'
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
import { createHashStorage } from '@/lib/utils/store'
import { TEMPLATES_DEFAULT_SORTING } from '../constants'
import { trackTemplateTableInteraction } from '../table-config'

interface TemplateTableState {
Expand Down Expand Up @@ -34,7 +33,7 @@ type Store = TemplateTableState & TemplateTableActions

const initialState: TemplateTableState = {
// Table state
sorting: TEMPLATES_DEFAULT_SORTING,
sorting: [{ id: 'updatedAt', desc: true }],
globalFilter: '',
// Filter state
cpuCount: undefined,
Expand Down
Loading
Loading