feat: dashboard v2, esm, upgrades (#211)

* esm

* wip

* wip

* wip

* wip

* wip

* wip

* subscription notice

* wip

* wip

* wip

* fix envs

* fix: update docker build

* fix

* esm/types

* delete dashboard :D

* add patches to dockerfiles

* update packages + catalogs + ts

* wip

* remove native libs

* ts

* improvements

* fix redirects and fetching session

* try fix favicon

* fixes

* fix

* order and resize reportds within a dashboard

* improvements

* wip

* added userjot to dashboard

* fix

* add op

* wip

* different cache key

* improve date picker

* fix table

* event details loading

* redo onboarding completely

* fix login

* fix

* fix

* extend session, billing and improve bars

* fix

* reduce price on 10M
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-10-16 12:27:44 +02:00
committed by GitHub
parent 436e81ecc9
commit 81a7e5d62e
741 changed files with 32695 additions and 16996 deletions

View File

@@ -0,0 +1,141 @@
import { TooltipComplete } from '@/components/tooltip-complete';
import { Badge } from '@/components/ui/badge';
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
import { useTRPC } from '@/integrations/trpc/react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { ColumnDef, Row } from '@tanstack/react-table';
import { toast } from 'sonner';
import { createActionColumn } from '@/components/ui/data-table/data-table-helpers';
import type { RouterOutputs } from '@/trpc/client';
import { clipboard } from '@/utils/clipboard';
export function useColumns(): ColumnDef<
RouterOutputs['organization']['invitations'][number]
>[] {
return [
{
accessorKey: 'id',
},
{
accessorKey: 'email',
header: 'Mail',
cell: ({ row }) => (
<div className="font-medium">{row.original.email}</div>
),
meta: {
label: 'Email',
placeholder: 'Search email',
variant: 'text',
},
},
{
accessorKey: 'role',
header: 'Role',
meta: {
label: 'Role',
},
},
{
accessorKey: 'createdAt',
header: 'Created',
cell: ({ row }) => (
<TooltipComplete
content={new Date(row.original.createdAt).toLocaleString()}
>
{new Date(row.original.createdAt).toLocaleDateString()}
</TooltipComplete>
),
meta: {
label: 'Created',
},
},
{
accessorKey: 'projectAccess',
header: 'Access',
cell: ({ row }) => {
return <AccessCell row={row} />;
},
meta: {
label: 'Access',
},
},
createActionColumn(({ row }) => {
const trpc = useTRPC();
const queryClient = useQueryClient();
const revoke = useMutation(
trpc.organization.revokeInvite.mutationOptions({
onSuccess() {
toast.success(`Invite for ${row.original.email} revoked`);
queryClient.invalidateQueries(
trpc.organization.invitations.queryFilter({
organizationId: row.original.organizationId,
}),
);
},
onError() {
toast.error(`Failed to revoke invite for ${row.original.email}`);
},
}),
);
return (
<>
<DropdownMenuItem
onClick={() => {
clipboard(
`${window.location.origin}/onboarding?inviteId=${row.original.id}`,
);
}}
>
Copy invite link
</DropdownMenuItem>
<DropdownMenuItem
className="text-destructive"
onClick={() => {
revoke.mutate({ inviteId: row.original.id });
}}
>
Revoke invite
</DropdownMenuItem>
</>
);
}),
];
}
function AccessCell({
row,
}: {
row: Row<RouterOutputs['organization']['invitations'][number]>;
}) {
const trpc = useTRPC();
const projectsQuery = useQuery(
trpc.project.list.queryOptions({
organizationId: row.original.organizationId,
}),
);
const projects = projectsQuery.data ?? [];
const access = row.original.projectAccess ?? [];
return (
<>
{access.map((id) => {
const project = projects.find((p) => p.id === id);
if (!project) {
return (
<Badge key={id} className="mr-1">
Unknown
</Badge>
);
}
return (
<Badge key={id} color="blue" className="mr-1">
{project.name}
</Badge>
);
})}
{access.length === 0 && <Badge variant={'secondary'}>All projects</Badge>}
</>
);
}

View File

@@ -0,0 +1,41 @@
import type { RouterOutputs } from '@/trpc/client';
import { Button } from '@/components/ui/button';
import { DataTable } from '@/components/ui/data-table/data-table';
import { DataTableToolbar } from '@/components/ui/data-table/data-table-toolbar';
import { useTable } from '@/components/ui/data-table/use-table';
import { pushModal } from '@/modals';
import type { UseQueryResult } from '@tanstack/react-query';
import { PlusIcon } from 'lucide-react';
import { useColumns } from './columns';
type CommonProps = {
query: UseQueryResult<RouterOutputs['organization']['invitations'], unknown>;
};
type Props = CommonProps;
export const InvitesTable = ({ query }: Props) => {
const columns = useColumns();
const { data, isLoading } = query;
const { table } = useTable({
columns,
data: data ?? [],
loading: isLoading,
pageSize: 50,
});
return (
<>
<DataTableToolbar table={table}>
<Button
icon={PlusIcon}
onClick={() => {
pushModal('CreateInvite');
}}
>
Invite user
</Button>
</DataTableToolbar>
<DataTable table={table} loading={isLoading} />
</>
);
};