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
52 changes: 29 additions & 23 deletions apps/web/app/(app)/[organization]/connections/api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { isSuccess } from '@/lib/result';
import type { ResponseObject } from '@dory/shared';
import { ConnectionListItem, CreateConnectionPayload } from '@dory/shared/types/connections';
import type { LocalFilesCreateRequest, LocalFilesInspectRequest, LocalFilesInspectResponse } from '@dory/shared/types/local-files';
import { authFetch } from '@/lib/client/auth-fetch';
import { translate } from '@dory/i18n/translate';
import { getClientLocale } from '@dory/i18n/client';

async function fetchJsonResponse<T>(
input: RequestInfo,
init: RequestInit,
errorMessage: string,
): Promise<ResponseObject<T>> {
async function fetchJsonResponse<T>(input: RequestInfo, init: RequestInit, errorMessage: string): Promise<ResponseObject<T>> {
const response = await authFetch(input, init);
const result = await response.json().catch(e => {
console.error('Failed to parse JSON response', e);
Expand All @@ -30,7 +27,6 @@ function translateConnectionsApi(key: string) {
return translate(getClientLocale(), key);
}


export async function addConnection(params: CreateConnectionPayload): Promise<ResponseObject<ConnectionListItem>> {
const res = await fetchJsonResponse<ConnectionListItem>(
'/api/connection',
Expand All @@ -45,10 +41,7 @@ export async function addConnection(params: CreateConnectionPayload): Promise<Re
return res;
}


export async function updateConnection(
params: CreateConnectionPayload & { id?: string },
): Promise<ResponseObject<ConnectionListItem>> {
export async function updateConnection(params: CreateConnectionPayload & { id?: string }): Promise<ResponseObject<ConnectionListItem>> {
const id = params.id ?? params.connection?.id;

if (!id) {
Expand All @@ -68,13 +61,8 @@ export async function updateConnection(
return res;
}


export async function getConnections(): Promise<{ data: ConnectionListItem[] }> {
const res = await fetchJsonResponse<ConnectionListItem[]>(
'/api/connection',
{ method: 'GET' },
translateConnectionsApi('Connections.Api.ListFailed'),
);
const res = await fetchJsonResponse<ConnectionListItem[]>('/api/connection', { method: 'GET' }, translateConnectionsApi('Connections.Api.ListFailed'));

if (!isSuccess(res)) {
throw new Error(res.message || translateConnectionsApi('Connections.Api.ListFailed'));
Expand All @@ -83,7 +71,6 @@ export async function getConnections(): Promise<{ data: ConnectionListItem[] }>
return { data: res.data ?? [] };
}


export async function deleteConnection(id: string): Promise<ResponseObject<null>> {
const res = await fetchJsonResponse<null>(
`/api/connection?id=${encodeURIComponent(id)}`,
Expand All @@ -100,7 +87,6 @@ export async function deleteConnection(id: string): Promise<ResponseObject<null>
return res;
}


export async function getConnectionDetail(id: string): Promise<{ data: ConnectionListItem }> {
const res = await fetchJsonResponse<ConnectionListItem>(
`/api/connection?id=${encodeURIComponent(id)}`,
Expand All @@ -120,10 +106,7 @@ export async function getConnectionDetail(id: string): Promise<{ data: Connectio
return { data: detail };
}


export async function testConnection(
params: CreateConnectionPayload & { timeout?: number },
): Promise<ResponseObject<unknown>> {
export async function testConnection(params: CreateConnectionPayload & { timeout?: number }): Promise<ResponseObject<unknown>> {
const res = await fetchJsonResponse<unknown>(
'/api/connection/test',
{
Expand All @@ -137,7 +120,6 @@ export async function testConnection(
return res;
}


export async function connectConnection(params: ConnectionListItem): Promise<ResponseObject<unknown>> {
const res = await fetchJsonResponse<unknown>(
'/api/connection/connect',
Expand All @@ -155,3 +137,27 @@ export async function connectConnection(params: ConnectionListItem): Promise<Res

return res;
}

export async function inspectLocalFiles(params: LocalFilesInspectRequest): Promise<ResponseObject<LocalFilesInspectResponse>> {
return fetchJsonResponse<LocalFilesInspectResponse>(
'/api/local-files/inspect',
{
method: 'POST',
body: JSON.stringify(params),
headers: { 'Content-Type': 'application/json' },
},
'Failed to inspect local file',
);
}

export async function createLocalFiles(params: LocalFilesCreateRequest): Promise<ResponseObject<unknown>> {
return fetchJsonResponse<unknown>(
'/api/local-files/create',
{
method: 'POST',
body: JSON.stringify(params),
headers: { 'Content-Type': 'application/json' },
},
'Failed to create Local Files dataset',
);
}
Original file line number Diff line number Diff line change
@@ -1,88 +1,66 @@
'use client';

import { Button } from '@/registry/new-york-v4/ui/button';
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from '@/registry/new-york-v4/ui/empty';
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/registry/new-york-v4/ui/empty';
import { Database } from 'lucide-react';
import { useTranslations } from 'next-intl';

type ConnectionsEmptyStateProps = {
searchQuery: string;
showSearchEmpty: boolean;
onAddConnection: () => void;
onLoadDemoData?: () => void;
searchQuery: string;
showSearchEmpty: boolean;
onAddConnection: () => void;
onAddLocalFiles?: () => void;
onLoadDemoData?: () => void;
};

export function ConnectionsEmptyState({
searchQuery,
showSearchEmpty,
onAddConnection,
onLoadDemoData,
}: ConnectionsEmptyStateProps) {
const t = useTranslations('Connections');
const trimmedQuery = searchQuery.trim();
export function ConnectionsEmptyState({ searchQuery, showSearchEmpty, onAddConnection, onAddLocalFiles, onLoadDemoData }: ConnectionsEmptyStateProps) {
const t = useTranslations('Connections');
const trimmedQuery = searchQuery.trim();

const title = showSearchEmpty ? t('Search.emptyTitle') : t('Empty.title');
const desc = showSearchEmpty
? t('Search.emptyDescription', { query: trimmedQuery })
: t('Empty.description');
const title = showSearchEmpty ? t('Search.emptyTitle') : t('Empty.title');
const desc = showSearchEmpty ? t('Search.emptyDescription', { query: trimmedQuery }) : t('Empty.description');

return (
<div className="mt-12">
<Empty className="mx-auto max-w-md">
<EmptyMedia
variant="icon"
className="mx-auto grid h-12 w-12 place-items-center rounded-2xl bg-muted text-primary"
>
<Database className="h-8 w-8" />
</EmptyMedia>
return (
<div className="mt-12">
<Empty className="mx-auto max-w-md">
<EmptyMedia variant="icon" className="mx-auto grid h-12 w-12 place-items-center rounded-2xl bg-muted text-primary">
<Database className="h-8 w-8" />
</EmptyMedia>

<EmptyHeader className="gap-2 text-center">
<EmptyTitle className="text-2xl font-semibold tracking-tight">
{title}
</EmptyTitle>
<EmptyHeader className="gap-2 text-center">
<EmptyTitle className="text-2xl font-semibold tracking-tight">{title}</EmptyTitle>

<EmptyDescription className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">
{desc}
</EmptyDescription>
</EmptyHeader>
<EmptyDescription className="mx-auto max-w-sm text-sm leading-relaxed text-muted-foreground">{desc}</EmptyDescription>
</EmptyHeader>

<EmptyContent className="gap-3">
<div className="mx-auto flex w-full max-w-sm flex-col gap-2 sm:flex-row sm:justify-center">
<Button className="w-full sm:w-auto" onClick={onAddConnection} data-testid="add-connection">
{t('Add Connection')}
</Button>
<EmptyContent className="gap-3">
<div className="mx-auto flex w-full max-w-sm flex-col gap-2 sm:flex-row sm:justify-center">
<Button className="w-full sm:w-auto" onClick={onAddConnection} data-testid="add-connection">
{t('Add Connection')}
</Button>

{!showSearchEmpty && onLoadDemoData && (
<Button
variant="secondary"
className="w-full sm:w-auto"
onClick={onLoadDemoData}
>
{t('Empty.loadDemo')}
</Button>
)}
</div>
{!showSearchEmpty && onAddLocalFiles && (
<Button variant="secondary" className="w-full sm:w-auto" onClick={onAddLocalFiles}>
Local Files
</Button>
)}

{/* {!showSearchEmpty && (
{!showSearchEmpty && onLoadDemoData && (
<Button variant="secondary" className="w-full sm:w-auto" onClick={onLoadDemoData}>
{t('Empty.loadDemo')}
</Button>
)}
</div>

{/* {!showSearchEmpty && (
<div className="mx-auto max-w-sm text-center text-xs text-muted-foreground">
{t('Empty.supportHint')}
</div>
)} */}

{showSearchEmpty && (
<div className="mx-auto max-w-sm text-center text-xs text-muted-foreground">
{t('Search.emptyHint')}
</div>
)}
</EmptyContent>
</Empty>
</div>
);
{showSearchEmpty && <div className="mx-auto max-w-sm text-center text-xs text-muted-foreground">{t('Search.emptyHint')}</div>}
</EmptyContent>
</Empty>
</div>
);
}
Loading
Loading