mirror of
https://github.com/vercel/commerce.git
synced 2025-05-14 21:47:51 +00:00
adds orama cloud
This commit is contained in:
parent
6a153b627c
commit
89d1571525
@ -5,3 +5,5 @@ SITE_NAME="Next.js Commerce"
|
||||
SHOPIFY_REVALIDATION_SECRET=""
|
||||
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
|
||||
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
|
||||
NEXT_PUBLIC_ORAMA_API_KEY=""
|
||||
NEXT_PUBLIC_ORAMA_ENDPOINT=""
|
||||
|
@ -1,20 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
||||
import { Results } from '@orama/orama';
|
||||
import { orama, trimDescription } from 'lib/orama';
|
||||
import { createUrl } from 'lib/utils';
|
||||
import { useOutsideClick } from './useOutsideClick';
|
||||
|
||||
export default function Search() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<Results>();
|
||||
|
||||
useEffect(() => {
|
||||
setSearchValue(searchParams?.get('q') || '');
|
||||
}, [searchParams, setSearchValue]);
|
||||
|
||||
useEffect(() => {
|
||||
orama.search({
|
||||
term: searchValue,
|
||||
limit: 5,
|
||||
boost: {
|
||||
title: 2,
|
||||
}
|
||||
})
|
||||
.then(setSearchResults)
|
||||
.catch(console.log);
|
||||
|
||||
}, [searchValue]);
|
||||
|
||||
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
|
||||
@ -31,6 +49,14 @@ export default function Search() {
|
||||
router.push(createUrl('/search', newParams));
|
||||
}
|
||||
|
||||
const searchResultsRef = useRef(null);
|
||||
|
||||
useOutsideClick(searchResultsRef.current, () => {
|
||||
setSearchValue('');
|
||||
});
|
||||
|
||||
const showSearchResults = searchValue.length > 0 && !!searchResults
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit} className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
|
||||
<input
|
||||
@ -45,6 +71,24 @@ export default function Search() {
|
||||
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
|
||||
<MagnifyingGlassIcon className="h-4" />
|
||||
</div>
|
||||
{
|
||||
showSearchResults && (
|
||||
<ul ref={searchResultsRef} className='nextra-scrollbar border border-gray-200 bg-white text-gray-100 dark:border-neutral-800 dark:bg-neutral-900 absolute top-full z-20 mt-2 overflow-auto overscroll-contain rounded-xl py-2.5 shadow-xl max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] inset-x-0 ltr:md:left-auto rtl:md:right-auto contrast-more:border contrast-more:border-gray-900 contrast-more:dark:border-gray-50 w-full min-h-[100px]'>
|
||||
{searchResults?.hits?.map((product) => (
|
||||
<li key={product.id} className='mx-2.5 break-words rounded-md contrast-more:border text-gray-800 contrast-more:border-transparent dark:text-gray-300'>
|
||||
<Link href={`/product/${product.document.handle}`} className='block scroll-m-12 px-2.5 py-2 rounded-md hover:bg-blue-600 hover:bg-opacity-10 hover:text-blue-500'>
|
||||
<div className='text-base font-semibold leading-5'>
|
||||
{product.document.title as string}
|
||||
</div>
|
||||
<div className='excerpt mt-1 text-sm leading-[1.35rem] text-gray-600 dark:text-gray-400 contrast-more:dark:text-gray-50'>
|
||||
{trimDescription((product.document.description || product.document.title) as string)}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
9
components/layout/navbar/useOutsideClick.ts
Normal file
9
components/layout/navbar/useOutsideClick.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useOutsideClick(ref: any, onClickOut: () => void, deps = []){
|
||||
useEffect(() => {
|
||||
const onClick = ({target}: any) => !ref?.contains(target) && onClickOut?.()
|
||||
document.addEventListener("click", onClick);
|
||||
return () => document.removeEventListener("click", onClick);
|
||||
}, deps);
|
||||
}
|
16
lib/orama/index.ts
Normal file
16
lib/orama/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { OramaClient } from '@oramacloud/client'
|
||||
|
||||
const ORAMA_API_KEY = process.env.NEXT_PUBLIC_ORAMA_API_KEY!
|
||||
const ORAMA_ENDPOINT = process.env.NEXT_PUBLIC_ORAMA_ENDPOINT!
|
||||
|
||||
export const orama = new OramaClient({
|
||||
endpoint: ORAMA_ENDPOINT,
|
||||
api_key: ORAMA_API_KEY
|
||||
})
|
||||
|
||||
export function trimDescription(description: string, maxSize = 80) {
|
||||
if (description.length > maxSize) {
|
||||
return `${description.substring(0, maxSize)}...`
|
||||
}
|
||||
return description
|
||||
}
|
@ -24,6 +24,8 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@orama/orama": "^1.2.1",
|
||||
"@oramacloud/client": "1.0.0-beta.19",
|
||||
"clsx": "^2.0.0",
|
||||
"next": "13.4.13-canary.15",
|
||||
"react": "18.2.0",
|
||||
|
36
pnpm-lock.yaml
generated
36
pnpm-lock.yaml
generated
@ -1,4 +1,8 @@
|
||||
lockfileVersion: '6.0'
|
||||
lockfileVersion: '6.1'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@headlessui/react':
|
||||
@ -7,6 +11,12 @@ dependencies:
|
||||
'@heroicons/react':
|
||||
specifier: ^2.0.18
|
||||
version: 2.0.18(react@18.2.0)
|
||||
'@orama/orama':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
'@oramacloud/client':
|
||||
specifier: 1.0.0-beta.19
|
||||
version: 1.0.0-beta.19
|
||||
clsx:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
@ -315,6 +325,11 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@noble/hashes@1.3.1:
|
||||
resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==}
|
||||
engines: {node: '>= 16'}
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir@2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -336,6 +351,25 @@ packages:
|
||||
fastq: 1.15.0
|
||||
dev: true
|
||||
|
||||
/@orama/orama@1.2.1:
|
||||
resolution: {integrity: sha512-vq3ar9REofq7Q87riamQB5y1aJc068Qwbnd7uAboeSfRTDj2+KdNiwjTEk01SVjr8eAvCx8iRZl4rkEYo0ETYw==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
dev: false
|
||||
|
||||
/@oramacloud/client@1.0.0-beta.19:
|
||||
resolution: {integrity: sha512-iig7JV7xzI+PYczf4XbQTv8ai6xaA5BlJNDlQJSEcFjOZQ3q2Wf8w7soTu1tQvWcXto60mbivttV5pYAoo/h9A==}
|
||||
dependencies:
|
||||
'@orama/orama': 1.2.1
|
||||
'@paralleldrive/cuid2': 2.2.2
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@paralleldrive/cuid2@2.2.2:
|
||||
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
||||
dependencies:
|
||||
'@noble/hashes': 1.3.1
|
||||
dev: false
|
||||
|
||||
/@pkgr/utils@2.4.2:
|
||||
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
Loading…
x
Reference in New Issue
Block a user