Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ next-env.d.ts
CLAUDE.md
.agent

# Cursor IDE
.cursor/

# tooling
/template
63 changes: 18 additions & 45 deletions src/app/dashboard/[teamSlug]/keys/page.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,28 @@
import { Plus } from 'lucide-react'
import CreateApiKeyDialog from '@/features/dashboard/settings/keys/create-api-key-dialog'
import ApiKeysTable from '@/features/dashboard/settings/keys/table'
import Frame from '@/ui/frame'
import { Button } from '@/ui/primitives/button'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/ui/primitives/card'
import { Page } from '@/features/dashboard/layouts/page'
import { ApiKeysPageContent } from '@/features/dashboard/settings/keys'
import { HydrateClient, prefetch, trpc } from '@/trpc/server'
import { Card, CardContent } from '@/ui/primitives/card'

interface KeysPageClientProps {
interface KeysPageProps {
params: Promise<{
teamSlug: string
}>
}

export default async function KeysPage({ params }: KeysPageClientProps) {
return (
<Frame
classNames={{
wrapper: 'w-full max-md:p-0',
frame: 'max-md:border-none',
}}
>
<Card className="w-full">
<CardHeader>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
<div className="flex flex-col gap-2">
<CardTitle>Manage API Keys</CardTitle>
<CardDescription className="max-w-[400px]">
API Keys are used to authenticate API requests from your teams
applications.
</CardDescription>
</div>
export default async function KeysPage({ params }: KeysPageProps) {
const { teamSlug } = await params

<CreateApiKeyDialog>
<Button className="w-full sm:w-auto sm:self-start">
<Plus className="size-4" /> CREATE KEY
</Button>
</CreateApiKeyDialog>
</div>
</CardHeader>
prefetch(trpc.teams.listApiKeys.queryOptions({ teamSlug }))

<CardContent>
<div className="w-full overflow-x-auto">
<ApiKeysTable params={params} className="min-w-[800px]" />
</div>
</CardContent>
</Card>
</Frame>
return (
<HydrateClient>
<Page>
<Card className="border-stroke/80">
<CardContent className="pb-8 pt-6">
<ApiKeysPageContent teamSlug={teamSlug} />
</CardContent>
</Card>
</Page>
</HydrateClient>
)
}
1 change: 0 additions & 1 deletion src/configs/cache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export const CACHE_TAGS = {
TEAM_ID_FROM_SLUG: (segment: string) => `team-id-from-slug-${segment}`,
TEAM_USAGE: (teamId: string) => `team-usage-${teamId}`,
TEAM_API_KEYS: (teamId: string) => `team-api-keys-${teamId}`,

DEFAULT_TEMPLATES: 'default-templates',
} as const
100 changes: 0 additions & 100 deletions src/core/server/actions/key-actions.ts

This file was deleted.

85 changes: 83 additions & 2 deletions src/core/server/api/routers/teams.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { z } from 'zod'
import { createKeysRepository } from '@/core/modules/keys/repository.server'
import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server'
import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors'
import { withAuthedRequestRepository } from '@/core/server/api/middlewares/repository'
import {
withAuthedRequestRepository,
withTeamAuthedRequestRepository,
} from '@/core/server/api/middlewares/repository'
import { createTRPCRouter } from '@/core/server/trpc/init'
import { protectedProcedure } from '@/core/server/trpc/procedures'
import {
protectedProcedure,
protectedTeamProcedure,
} from '@/core/server/trpc/procedures'
import { l } from '@/core/shared/clients/logger/logger'

const teamsRepositoryProcedure = protectedProcedure.use(
withAuthedRequestRepository(createUserTeamsRepository, (teamsRepository) => ({
teamsRepository,
}))
)

const keysRepositoryProcedure = protectedTeamProcedure.use(
withTeamAuthedRequestRepository(createKeysRepository, (keysRepository) => ({
keysRepository,
}))
)

export const teamsRouter = createTRPCRouter({
list: teamsRepositoryProcedure.query(async ({ ctx }) => {
const teamsResult = await ctx.teamsRepository.listUserTeams()
Expand All @@ -20,4 +35,70 @@ export const teamsRouter = createTRPCRouter({

return teamsResult.data
}),

listApiKeys: keysRepositoryProcedure.query(async ({ ctx }) => {
const result = await ctx.keysRepository.listTeamApiKeys()

if (!result.ok) {
throwTRPCErrorFromRepoError(result.error)
}

return { apiKeys: result.data }
}),

createApiKey: keysRepositoryProcedure
.input(
z.object({
name: z
.string({ error: 'Name is required' })
.min(1, 'Name cannot be empty')
.max(50, 'Name cannot be longer than 50 characters')
.trim(),
})
)
.mutation(async ({ ctx, input }) => {
const { name } = input

const result = await ctx.keysRepository.createApiKey(name)

if (!result.ok) {
l.error({
key: 'create_api_key_trpc:error',
message: result.error.message,
error: result.error,
team_id: ctx.teamId,
user_id: ctx.session.user.id,
context: { name },
})

throwTRPCErrorFromRepoError(result.error)
}

return { createdApiKey: result.data }
}),

deleteApiKey: keysRepositoryProcedure
.input(
z.object({
apiKeyId: z.uuid(),
})
)
.mutation(async ({ ctx, input }) => {
const { apiKeyId } = input

const result = await ctx.keysRepository.deleteApiKey(apiKeyId)

if (!result.ok) {
l.error({
key: 'delete_api_key_trpc:error',
message: result.error.message,
error: result.error,
team_id: ctx.teamId,
user_id: ctx.session.user.id,
context: { apiKeyId },
})

throwTRPCErrorFromRepoError(result.error)
}
}),
})
49 changes: 0 additions & 49 deletions src/core/server/functions/keys/get-api-keys.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/core/server/functions/keys/types.ts

This file was deleted.

10 changes: 4 additions & 6 deletions src/features/dashboard/billing/invoices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,14 @@ function InvoicesEmpty({ error }: InvoicesEmptyProps) {
return (
<TableEmptyState colSpan={4}>
<InvoiceIcon
className={cn('size-4', error && 'text-accent-error-highlight')}
/>
<p
className={cn(
'prose-body-highlight',
'size-4 shrink-0',
error && 'text-accent-error-highlight'
)}
>
/>
<span className={cn(error && 'text-accent-error-highlight')}>
{error ? error : 'No invoices yet'}
</p>
</span>
</TableEmptyState>
)
}
Expand Down
Loading
Loading