|
| 1 | +/** @jsx h */ |
| 2 | +import { |
| 3 | + autocomplete, |
| 4 | + getAlgoliaHits, |
| 5 | + snippetHit, |
| 6 | +} from '@algolia/autocomplete-js'; |
| 7 | +import { |
| 8 | + AutocompleteInsightsApi, |
| 9 | + createAlgoliaInsightsPlugin, |
| 10 | +} from '@algolia/autocomplete-plugin-algolia-insights'; |
| 11 | +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; |
| 12 | +import { Hit } from '@algolia/client-search'; |
| 13 | +import algoliasearch from 'algoliasearch'; |
| 14 | +import { h, Fragment } from 'preact'; |
| 15 | +import insightsClient from 'search-insights'; |
| 16 | + |
| 17 | +import '@algolia/autocomplete-theme-classic'; |
| 18 | + |
| 19 | +type Product = { |
| 20 | + brand: string; |
| 21 | + categories: string[]; |
| 22 | + description: string; |
| 23 | + image: string; |
| 24 | + name: string; |
| 25 | + price: number; |
| 26 | + rating: number; |
| 27 | + __autocomplete_indexName: string; |
| 28 | + __autocomplete_queryID: string; |
| 29 | +}; |
| 30 | +type ProductHit = Hit<Product>; |
| 31 | + |
| 32 | +const appId = 'latency'; |
| 33 | +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; |
| 34 | +const searchClient = algoliasearch(appId, apiKey); |
| 35 | +insightsClient('init', { appId, apiKey }); |
| 36 | + |
| 37 | +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); |
| 38 | + |
| 39 | +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ |
| 40 | + searchClient, |
| 41 | + indexName: 'instant_search_demo_query_suggestions', |
| 42 | + getSearchParams() { |
| 43 | + return { |
| 44 | + hitsPerPage: 5, |
| 45 | + }; |
| 46 | + }, |
| 47 | +}); |
| 48 | + |
| 49 | +autocomplete({ |
| 50 | + container: '#autocomplete', |
| 51 | + placeholder: 'Search', |
| 52 | + debug: true, |
| 53 | + openOnFocus: true, |
| 54 | + plugins: [algoliaInsightsPlugin, querySuggestionsPlugin], |
| 55 | + getSources({ query, state }) { |
| 56 | + if (!query) { |
| 57 | + return []; |
| 58 | + } |
| 59 | + |
| 60 | + return [ |
| 61 | + { |
| 62 | + sourceId: 'products', |
| 63 | + getItems() { |
| 64 | + return getAlgoliaHits<Product>({ |
| 65 | + searchClient, |
| 66 | + queries: [ |
| 67 | + { |
| 68 | + indexName: 'instant_search', |
| 69 | + query, |
| 70 | + params: { |
| 71 | + clickAnalytics: true, |
| 72 | + attributesToSnippet: ['name:10'], |
| 73 | + snippetEllipsisText: '…', |
| 74 | + }, |
| 75 | + }, |
| 76 | + ], |
| 77 | + }); |
| 78 | + }, |
| 79 | + templates: { |
| 80 | + header() { |
| 81 | + return ( |
| 82 | + <Fragment> |
| 83 | + <span className="aa-SourceHeaderTitle">Products</span> |
| 84 | + <div className="aa-SourceHeaderLine" /> |
| 85 | + </Fragment> |
| 86 | + ); |
| 87 | + }, |
| 88 | + item({ item }) { |
| 89 | + return ( |
| 90 | + <ProductItem |
| 91 | + hit={item} |
| 92 | + insights={state.context.algoliaInsightsPlugin.insights} |
| 93 | + /> |
| 94 | + ); |
| 95 | + }, |
| 96 | + noResults() { |
| 97 | + return ( |
| 98 | + <div className="aa-ItemContent">No products for this query.</div> |
| 99 | + ); |
| 100 | + }, |
| 101 | + }, |
| 102 | + }, |
| 103 | + ]; |
| 104 | + }, |
| 105 | +}); |
| 106 | + |
| 107 | +type ProductItemProps = { |
| 108 | + hit: ProductHit; |
| 109 | + insights: AutocompleteInsightsApi; |
| 110 | +}; |
| 111 | + |
| 112 | +function ProductItem({ hit, insights }: ProductItemProps) { |
| 113 | + return ( |
| 114 | + <Fragment> |
| 115 | + <div className="aa-ItemIcon aa-ItemIcon--align-top"> |
| 116 | + <img src={hit.image} alt={hit.name} width="40" height="40" /> |
| 117 | + </div> |
| 118 | + <div className="aa-ItemContent"> |
| 119 | + <div className="aa-ItemContentTitle"> |
| 120 | + {snippetHit<ProductHit>({ hit, attribute: 'name' })} |
| 121 | + </div> |
| 122 | + <div className="aa-ItemContentDescription"> |
| 123 | + From <strong>{hit.brand}</strong> in{' '} |
| 124 | + <strong>{hit.categories[0]}</strong> |
| 125 | + </div> |
| 126 | + {hit.rating > 0 && ( |
| 127 | + <div className="aa-ItemContentDescription"> |
| 128 | + <div style={{ display: 'flex', gap: 1, color: '#ffc107' }}> |
| 129 | + {Array.from({ length: 5 }, (_value, index) => { |
| 130 | + const isFilled = hit.rating >= index + 1; |
| 131 | + |
| 132 | + return ( |
| 133 | + <svg |
| 134 | + key={index} |
| 135 | + width="16" |
| 136 | + height="16" |
| 137 | + viewBox="0 0 24 24" |
| 138 | + fill={isFilled ? 'currentColor' : 'none'} |
| 139 | + stroke="currentColor" |
| 140 | + strokeWidth="3" |
| 141 | + strokeLinecap="round" |
| 142 | + strokeLinejoin="round" |
| 143 | + > |
| 144 | + <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /> |
| 145 | + </svg> |
| 146 | + ); |
| 147 | + })} |
| 148 | + </div> |
| 149 | + </div> |
| 150 | + )} |
| 151 | + <div className="aa-ItemContentDescription" style={{ color: '#000' }}> |
| 152 | + <strong>${hit.price.toLocaleString()}</strong> |
| 153 | + </div> |
| 154 | + </div> |
| 155 | + <div className="aa-ItemActions"> |
| 156 | + <button |
| 157 | + className="aa-ItemActionButton aa-TouchOnly aa-ActiveOnly" |
| 158 | + type="button" |
| 159 | + title="Select" |
| 160 | + > |
| 161 | + <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> |
| 162 | + <path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" /> |
| 163 | + </svg> |
| 164 | + </button> |
| 165 | + <button |
| 166 | + className="aa-ItemActionButton" |
| 167 | + type="button" |
| 168 | + title="Add to cart" |
| 169 | + onClick={(event) => { |
| 170 | + event.preventDefault(); |
| 171 | + event.stopPropagation(); |
| 172 | + |
| 173 | + insights.convertedObjectIDsAfterSearch({ |
| 174 | + eventName: 'Added to cart', |
| 175 | + index: hit.__autocomplete_indexName, |
| 176 | + objectIDs: [hit.objectID], |
| 177 | + queryID: hit.__autocomplete_queryID, |
| 178 | + }); |
| 179 | + }} |
| 180 | + > |
| 181 | + <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"> |
| 182 | + <path d="M19 5h-14l1.5-2h11zM21.794 5.392l-2.994-3.992c-0.196-0.261-0.494-0.399-0.8-0.4h-12c-0.326 0-0.616 0.156-0.8 0.4l-2.994 3.992c-0.043 0.056-0.081 0.117-0.111 0.182-0.065 0.137-0.096 0.283-0.095 0.426v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-14c0-0.219-0.071-0.422-0.189-0.585-0.004-0.005-0.007-0.010-0.011-0.015zM4 7h16v13c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707zM15 10c0 0.829-0.335 1.577-0.879 2.121s-1.292 0.879-2.121 0.879-1.577-0.335-2.121-0.879-0.879-1.292-0.879-2.121c0-0.552-0.448-1-1-1s-1 0.448-1 1c0 1.38 0.561 2.632 1.464 3.536s2.156 1.464 3.536 1.464 2.632-0.561 3.536-1.464 1.464-2.156 1.464-3.536c0-0.552-0.448-1-1-1s-1 0.448-1 1z" /> |
| 183 | + </svg> |
| 184 | + </button> |
| 185 | + </div> |
| 186 | + </Fragment> |
| 187 | + ); |
| 188 | +} |
0 commit comments