mirror of
https://github.com/vercel/commerce.git
synced 2025-05-14 13:47:49 +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_REVALIDATION_SECRET=""
|
||||||
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
|
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
|
||||||
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
|
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
|
||||||
|
NEXT_PUBLIC_ORAMA_API_KEY=""
|
||||||
|
NEXT_PUBLIC_ORAMA_ENDPOINT=""
|
||||||
|
@ -1,20 +1,38 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
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 { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { Results } from '@orama/orama';
|
||||||
|
import { orama, trimDescription } from 'lib/orama';
|
||||||
import { createUrl } from 'lib/utils';
|
import { createUrl } from 'lib/utils';
|
||||||
|
import { useOutsideClick } from './useOutsideClick';
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [searchResults, setSearchResults] = useState<Results>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSearchValue(searchParams?.get('q') || '');
|
setSearchValue(searchParams?.get('q') || '');
|
||||||
}, [searchParams, setSearchValue]);
|
}, [searchParams, setSearchValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
orama.search({
|
||||||
|
term: searchValue,
|
||||||
|
limit: 5,
|
||||||
|
boost: {
|
||||||
|
title: 2,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(setSearchResults)
|
||||||
|
.catch(console.log);
|
||||||
|
|
||||||
|
}, [searchValue]);
|
||||||
|
|
||||||
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -31,6 +49,14 @@ export default function Search() {
|
|||||||
router.push(createUrl('/search', newParams));
|
router.push(createUrl('/search', newParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchResultsRef = useRef(null);
|
||||||
|
|
||||||
|
useOutsideClick(searchResultsRef.current, () => {
|
||||||
|
setSearchValue('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const showSearchResults = searchValue.length > 0 && !!searchResults
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
|
<form onSubmit={onSubmit} className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
|
||||||
<input
|
<input
|
||||||
@ -45,6 +71,24 @@ export default function Search() {
|
|||||||
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
|
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
|
||||||
<MagnifyingGlassIcon className="h-4" />
|
<MagnifyingGlassIcon className="h-4" />
|
||||||
</div>
|
</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>
|
</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": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
|
"@orama/orama": "^1.2.1",
|
||||||
|
"@oramacloud/client": "1.0.0-beta.19",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"next": "13.4.13-canary.15",
|
"next": "13.4.13-canary.15",
|
||||||
"react": "18.2.0",
|
"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:
|
dependencies:
|
||||||
'@headlessui/react':
|
'@headlessui/react':
|
||||||
@ -7,6 +11,12 @@ dependencies:
|
|||||||
'@heroicons/react':
|
'@heroicons/react':
|
||||||
specifier: ^2.0.18
|
specifier: ^2.0.18
|
||||||
version: 2.0.18(react@18.2.0)
|
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:
|
clsx:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
@ -315,6 +325,11 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
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:
|
/@nodelib/fs.scandir@2.1.5:
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -336,6 +351,25 @@ packages:
|
|||||||
fastq: 1.15.0
|
fastq: 1.15.0
|
||||||
dev: true
|
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:
|
/@pkgr/utils@2.4.2:
|
||||||
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
|
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user