Skip to content

Commit

Permalink
fix: Error when no results (#7)
Browse files Browse the repository at this point in the history
* Add not found state

* Fix hydration errors
  • Loading branch information
xWiiLLz authored Sep 10, 2024
1 parent 02cfce3 commit 61750ff
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 42 deletions.
2 changes: 2 additions & 0 deletions src/app/[locale]/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Assistant } from '@/components/assistant';
import { Hits } from '@/components/hits';
import { NotFound } from '@/components/not-found';
import { SearchProvider } from '@/components/search-provider';
import { SearchBox } from '@/components/searchbox';

Expand All @@ -16,6 +17,7 @@ export default async function Search() {
<Assistant className="w-[674px]" />
{/* <QuestionsResult /> */}
<Hits />
<NotFound />
</div>
</div>
</div>
Expand Down
42 changes: 15 additions & 27 deletions src/components/assistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,21 @@ export const Assistant = ({ className }: AssistantProps) => {
const hits = useHits();
const querying = useLoading();
const [query] = useQuery();
const [seenHits, setSeenHits] = useState(false);
useEffect(() => {
if (hits.length > 0) {
setSeenHits(true);
}
}, [hits]);

if (!seenHits) {
return null;
}

return (
<div className={twMerge('rounded-lg border p-6', className)}>
<div
className={twMerge(
'rounded-lg border p-6',
className,
(hits?.length === 0 || querying) && 'hidden'
)}
>
<header className="mb-4 flex gap-4">
<Sparkles className="stroke-primary" />
<h1 className="text-base font-medium text-primary">Assistant</h1>
</header>
<div>
<AssistantListener
querying={querying}
hits={hits as any}
query={query}
/>
<AssistantListener hits={hits as any} query={query} />
</div>
<footer></footer>
</div>
Expand All @@ -50,7 +42,7 @@ export const Assistant = ({ className }: AssistantProps) => {

type AssistantListenerProps = {
query: string;
querying: boolean;
querying?: boolean;
hits: V1Hit[];
};
const AssistantListener = ({
Expand All @@ -59,16 +51,13 @@ const AssistantListener = ({
querying,
}: AssistantListenerProps) => {
const [summary, setSummary] = useState('');
// Little hack to ensure we do not display the error message between assistant requests
const [disableErrors, setDisableErrors] = useState(true);
const queryRef = useRef(query);

useEffect(() => {
// We store the query in a ref so that we only refetch the assistant when new articles are coming.
// This avoids doing a double-query in between the request-response from the query API.
queryRef.current = query;
setSummary('');
setDisableErrors(true);
}, [query]);

const { refetch, status } = useStreamRequest(
Expand All @@ -86,14 +75,13 @@ const AssistantListener = ({

useEffect(() => {
refetchHandlerFromHits(hits, queryRef.current, refetch)?.();
setDisableErrors(false);
}, [hits, refetch]);

if (
!querying &&
!disableErrors &&
(status === 'error' || (status === 'success' && summary.trim() === ''))
) {
if (querying) {
return null;
}

if (status === 'error' || (status === 'success' && summary.trim() === '')) {
return (
<ErrorDisplay
disabled={['idle', 'loading'].includes(status)}
Expand All @@ -103,7 +91,7 @@ const AssistantListener = ({
}

const classnames = [];
if (status === 'loading' || status === 'idle' || querying) {
if (status === 'loading' || status === 'idle') {
classnames.push(styles.type);
}

Expand Down
33 changes: 33 additions & 0 deletions src/components/not-found-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';

export const NotFoundIcon = ({ className }: { className?: string }) => (
<svg
className={className}
width="126"
height="127"
viewBox="0 0 126 127"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="73.241" cy="73.9562" r="52.3836" fill="#EEEAFF" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17.2087 46.2917C17.2087 30.0223 30.3976 16.8333 46.667 16.8333C62.9364 16.8333 76.1253 30.0223 76.1253 46.2917C76.1253 62.5611 62.9364 75.75 46.667 75.75C30.3976 75.75 17.2087 62.5611 17.2087 46.2917ZM46.667 8.41667C25.7492 8.41667 8.79199 25.3739 8.79199 46.2917C8.79199 67.2095 25.7492 84.1667 46.667 84.1667C55.6098 84.1667 63.8287 81.0673 70.3082 75.8842L85.7747 91.3507C87.4182 92.9942 90.0827 92.9942 91.7262 91.3507C93.3697 89.7073 93.3697 87.0427 91.7262 85.3993L76.2596 69.9327C81.4427 63.4533 84.542 55.2344 84.542 46.2917C84.542 25.3739 67.5848 8.41667 46.667 8.41667Z"
fill="url(#paint0_linear_256_2115)"
/>
<defs>
<linearGradient
id="paint0_linear_256_2115"
x1="-11.012"
y1="-27.6548"
x2="42.9044"
y2="-52.4877"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#715DE1" />
<stop offset="1" stopColor="#CF65E0" />
</linearGradient>
</defs>
</svg>
);
37 changes: 37 additions & 0 deletions src/components/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import { useHits, useLoading, useQuery } from '@clinia/search-sdk-react';
import { cn } from '@clinia-ui/react';
import { NotFoundIcon } from './not-found-icon';

type NotFoundProps = {
className?: string;
};
export const NotFound = ({ className }: NotFoundProps) => {
const [query] = useQuery();
const hits = useHits();
const loading = useLoading();

if (hits?.length > 0 || loading) {
return null;
}

return (
<div
className={cn(
'flex max-w-[790px] flex-col items-center gap-2 text-center',
className
)}
>
<NotFoundIcon />
<h2 className="text-xl font-medium text-foreground">
No results found for &apos;{query}&apos;
</h2>
<p>
There aren&apos;t any results matching your query. This can happen when
you search for something overly specific, or something outside the scope
of your dataset.
</p>
</div>
);
};
50 changes: 35 additions & 15 deletions src/components/search-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
'use client';

import { SearchRequest, SearchResponse } from '@/lib/client';
import { PropsWithChildren, useCallback, useMemo } from 'react';
import {
PropsWithChildren,
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
import { Host } from '@clinia/client-common';
import client from '@clinia/client-datapartition';
import datapartitionclient from '@clinia/client-datapartition';
import {
SearchParameters,
type SearchSDKOptions,
Expand All @@ -30,22 +36,37 @@ const getHost = (): Host => {
};
};

const datapartitionClient = client(
'clinia',
{
mode: 'BearerToken',
bearerToken: '',
},
{
hosts: [getHost()],
type DatapartitionClient = ReturnType<typeof datapartitionclient>;
let datapartitionClient: DatapartitionClient;
const getClient = (): DatapartitionClient => {
if (!datapartitionClient) {
datapartitionClient = datapartitionclient(
'clinia',
{
mode: 'BearerToken',
bearerToken: '',
},
{
hosts: [getHost()],
}
);
}
);

return datapartitionClient;
};

export const SearchProvider = ({ children, state }: SearchProviderProps) => {
const client = useRef(
// Dumb value, we're just setting this to avoid having undefined in the ref.
datapartitionclient('clinia', { mode: 'BearerToken', bearerToken: '' })
);
useEffect(() => {
// We set the client in a ref to avoid hydration errors (window not defined).
client.current = getClient();
}, []);

const search: SearchSDKOptions['search'] = async (_collection, params) => {
const resp = await datapartitionClient.searchClient.query<
Record<string, any>
>({
return client.current.searchClient.query<Record<string, any>>({
partitionKey: 'clinia',
collectionKey: 'articles',
v1SearchParameters: {
Expand Down Expand Up @@ -102,7 +123,6 @@ export const SearchProvider = ({ children, state }: SearchProviderProps) => {
],
},
});
return resp;
};

const searchForFacets: SearchSDKOptions['searchForFacets'] =
Expand Down

0 comments on commit 61750ff

Please sign in to comment.