GL Search Results
Version: 1.0.16
Package:@gift-card-market/gl-search-results
Last Updated: January 16, 2026
A React component library for displaying search results with pagination and ratings. Supports both React integration and vanilla JavaScript usage via Web Components.
📦 Installation
From GitHub Packages
npm install @gift-card-market/gl-search-results
Authentication Setup
Create or edit .npmrc in your project root:
@gift-card-market:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
🚀 Quick Start
React Usage
import React, { useRef } from 'react';
import { GlSearchResults, GlSearchResultsHandle } from '@gift-card-market/gl-search-results';
import '@gift-card-market/gl-search-results/style.css';
function App() {
const searchRef = useRef<GlSearchResultsHandle>(null);
return (
<GlSearchResults
ref={searchRef}
environment="production"
jwt="your-jwt-token"
configuration={{
layout: 'list',
showCYOBanner: false,
showPurchaseButton: true,
purchaseButtonEnabled: true,
purchaseButtonText: 'Purchase',
purchaseButtonColor: '#ED1164',
numberOfResults: 10,
}}
/>
);
}
Vanilla JavaScript Usage
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="node_modules/@gift-card-market/gl-search-results/dist/style.css">
</head>
<body>
<gl-search-results
id="searchResults"
environment="production"
jwt="your-jwt-token">
</gl-search-results>
<script src="node_modules/@gift-card-market/gl-search-results/dist/browser/gl-search-results.js"></script>
<script>
// Configure the component
const element = document.getElementById('searchResults');
element.setAttribute('configuration', JSON.stringify({
layout: 'list',
showCYOBanner: false,
showPurchaseButton: true,
purchaseButtonEnabled: true,
purchaseButtonText: 'Purchase',
purchaseButtonColor: '#ED1164',
numberOfResults: 10,
}));
</script>
</body>
</html>
📖 API Reference
Component Props
GlSearchResultsProps
| Property | Type | Required | Description |
|---|---|---|---|
environment | string | Yes | Environment mode: 'development', 'qa', 'staging' or 'production' |
jwt | string | Yes | JWT authentication token for API calls |
configuration | GlSearchResultsConfigProps | No | Component configuration object |
GlSearchResultsConfigProps
| Property | Type | Default | Description |
|---|---|---|---|
layout | string | 'list' | Layout style: 'list' or 'titled' |
showCYOBanner | boolean | false | Show "Create Your Own" banner |
showPurchaseButton | boolean | true | Display purchase button on results |
purchaseButtonEnabled | boolean | true | Enable/disable purchase button |
purchaseButtonText | string | 'Purchase' | Text displayed on purchase button |
purchaseButtonColor | string | '#ED1164' | Background color of purchase button |
numberOfResults | number | 10 | Number of results per page |
Component Methods
refreshResults()
Manually refresh the search results with current parameters.
React Example:
const searchRef = useRef<GlSearchResultsHandle>(null);
// Call refresh
searchRef.current?.refreshResults();
Vanilla JS Example:
const element = document.getElementById('searchResults');
element.refreshResults();
TypeScript Interfaces
GlSearchRequest
Search request parameters structure:
interface GlSearchRequest {
Location: string; // Search location
SearchTerm: string; // Search query term
Category?: string; // Optional category filter
SortBy: string; // Sort criteria
ProviderId: string; // Provider identifier (e.g., 'yelp')
Limit?: number; // Results per page
Offset?: number; // Pagination offset
PageIndex?: number; // Current page number
}
BusinessType
Business result data structure:
interface BusinessType {
id: string; // Unique business identifier
name: string; // Business name
image_url: string; // Business image URL
rating: number; // Rating (0-5)
review_count: number; // Number of reviews
address: string; // Business address
location?: any; // Additional location data
}
🎯 Custom Events
The component emits custom events that you can listen to:
Event Types
| Event Name | Description | Detail Data |
|---|---|---|
search_bar.search | (Input) Trigger a search | { data: { location, term, sort_by, provider_id } } |
gl:resultClick | (Output) User clicks a result | { business_id: string, is_cyo: boolean } |
gl:pageChange | (Output) Page navigation | { pageIndex: string } |
Event Details
search_bar.search (Input Event)
Dispatch this event to trigger a search in the component.
Event Detail Structure:
{
data: {
location: string; // e.g., "New York"
term: string; // e.g., "restaurant"
sort_by: string; // e.g., "best_match"
provider_id: string; // e.g., "yelp"
}
}
gl:resultClick (Output Event)
Fired when a user clicks on a search result.
Event Detail Structure:
{
business_id: string; // ID of clicked business
is_cyo: boolean; // Whether it's a "Create Your Own" banner click
}
gl:pageChange (Output Event)
Fired when the user navigates to a different page.
Event Detail Structure:
{
pageIndex: string; // The new page number
}
🎪 Event Handling
React Event Handling
import React, { useEffect, useRef } from 'react';
import { GlSearchResults, GlSearchResultsHandle, SearchResultsEvents } from '@gift-card-market/gl-search-results';
import '@gift-card-market/gl-search-results/style.css';
function App() {
const searchRef = useRef<GlSearchResultsHandle>(null);
// Trigger a search
const triggerSearch = () => {
const searchEvent = new CustomEvent('search_bar.search', {
detail: {
data: {
location: 'New York',
term: 'restaurant',
sort_by: 'best_match',
provider_id: 'yelp',
},
},
});
document.dispatchEvent(searchEvent);
};
// Listen to component events
useEffect(() => {
const handleResultClick = (e: Event) => {
const customEvent = e as CustomEvent;
console.log('Business clicked:', customEvent.detail.business_id);
// Handle the click - e.g., navigate to business page
window.location.href = `/business/${customEvent.detail.business_id}`;
};
const handlePageChange = (e: Event) => {
const customEvent = e as CustomEvent;
console.log('Page changed to:', customEvent.detail.pageIndex);
// Handle page change - e.g., analytics tracking
};
// Register event listeners
document.addEventListener(SearchResultsEvents.RESULT_CLICK, handleResultClick);
document.addEventListener(SearchResultsEvents.PAGE_CHANGE, handlePageChange);
// Cleanup
return () => {
document.removeEventListener(SearchResultsEvents.RESULT_CLICK, handleResultClick);
document.removeEventListener(SearchResultsEvents.PAGE_CHANGE, handlePageChange);
};
}, []);
return (
<div>
<button onClick={triggerSearch}>Search Restaurants in New York</button>
<GlSearchResults
ref={searchRef}
environment="production"
jwt="your-jwt-token"
configuration={{
layout: 'list',
showCYOBanner: false,
showPurchaseButton: true,
purchaseButtonEnabled: true,
purchaseButtonText: 'Purchase',
purchaseButtonColor: '#ED1164',
numberOfResults: 10,
}}
/>
</div>
);
}
export default App;
Vanilla JavaScript Event Handling
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GL Search Results - Vanilla JS</title>
<link rel="stylesheet" href="node_modules/@gift-card-market/gl-search-results/dist/style.css">
</head>
<body>
<button onclick="triggerSearch()">Search Restaurants in New York</button>
<button onclick="refreshResults()">Refresh Results</button>
<gl-search-results
id="searchResults"
environment="production"
jwt="your-jwt-token">
</gl-search-results>
<script src="node_modules/@gift-card-market/gl-search-results/dist/browser/gl-search-results.js"></script>
<script>
// Configure the component
function configureComponent() {
const element = document.getElementById('searchResults');
const configuration = {
layout: 'list',
showCYOBanner: false,
showPurchaseButton: true,
purchaseButtonEnabled: true,
purchaseButtonText: 'Purchase',
purchaseButtonColor: '#ED1164',
numberOfResults: 10,
};
element.setAttribute('configuration', JSON.stringify(configuration));
}
// Trigger a search
function triggerSearch() {
const searchEvent = new CustomEvent('search_bar.search', {
detail: {
data: {
location: 'New York',
term: 'restaurant',
sort_by: 'best_match',
provider_id: 'yelp',
},
},
});
document.dispatchEvent(searchEvent);
}
// Refresh results
function refreshResults() {
const element = document.getElementById('searchResults');
if (element && typeof element.refreshResults === 'function') {
element.refreshResults();
}
}
// Listen to result clicks
document.addEventListener('gl:resultClick', function(e) {
console.log('Business clicked:', e.detail.business_id);
// Handle the click
if (e.detail.is_cyo) {
console.log('Create Your Own banner clicked');
} else {
window.location.href = '/business/' + e.detail.business_id;
}
});
// Listen to page changes
document.addEventListener('gl:pageChange', function(e) {
console.log('Page changed to:', e.detail.pageIndex);
// Handle page change - e.g., scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// Initialize on page load
window.addEventListener('load', function() {
configureComponent();
console.log('GL Search Results component initialized');
});
</script>
</body>
</html>
📋 Complete Examples
React - Full Featured Example
import React, { useRef, useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
import { GlSearchResults, GlSearchResultsHandle, SearchResultsEvents } from '@gift-card-market/gl-search-results';
import '@gift-card-market/gl-search-results/style.css';
const App = () => {
const searchResultsRef = useRef<GlSearchResultsHandle>(null);
const [environment, setEnvironment] = useState<'development' | 'production'>('production');
const [jwt, setJwt] = useState<string>('your-jwt-token-here');
const [layout, setLayout] = useState<'list' | 'titled'>('list');
const [showCYOBanner, setShowCYOBanner] = useState(false);
const [showPurchaseButton, setShowPurchaseButton] = useState(true);
const [purchaseButtonEnabled, setPurchaseButtonEnabled] = useState(true);
const [numberOfResults, setNumberOfResults] = useState(10);
// Trigger a manual search event
const triggerSearch = () => {
const searchEvent = new CustomEvent('search_bar.search', {
detail: {
data: {
location: 'New York',
term: 'restaurant',
sort_by: 'best_match',
provider_id: 'yelp',
},
},
});
document.dispatchEvent(searchEvent);
};
// Refresh results manually
const handleRefresh = () => {
if (searchResultsRef.current?.refreshResults) {
searchResultsRef.current.refreshResults();
}
};
// Listen to custom events from the component
useEffect(() => {
const handleResultClick = (e: Event) => {
const customEvent = e as CustomEvent;
console.log('Result clicked:', customEvent.detail);
// Navigate to business detail page
if (!customEvent.detail.is_cyo) {
window.location.href = `/business/${customEvent.detail.business_id}`;
}
};
const handlePageChange = (e: Event) => {
const customEvent = e as CustomEvent;
console.log('Page changed to:', customEvent.detail.pageIndex);
// Scroll to top on page change
window.scrollTo({ top: 0, behavior: 'smooth' });
};
document.addEventListener(SearchResultsEvents.RESULT_CLICK, handleResultClick);
document.addEventListener(SearchResultsEvents.PAGE_CHANGE, handlePageChange);
return () => {
document.removeEventListener(SearchResultsEvents.RESULT_CLICK, handleResultClick);
document.removeEventListener(SearchResultsEvents.PAGE_CHANGE, handlePageChange);
};
}, []);
return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1 style={{ color: '#ED1164', marginBottom: '20px' }}>
GL Search Results - Demo
</h1>
{/* Configuration Panel */}
<div style={{ backgroundColor: '#f5f5f5', padding: '20px', borderRadius: '8px', marginBottom: '20px' }}>
<h2 style={{ marginTop: 0 }}>Configuration Panel</h2>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
{/* Environment */}
<div>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
Environment:
</label>
<select
value={environment}
onChange={(e) => setEnvironment(e.target.value as any)}
style={{ padding: '8px', width: '100%', borderRadius: '4px' }}
>
<option value="development">Development</option>
<option value="production">Production</option>
</select>
</div>
{/* JWT Token */}
<div>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
JWT Token:
</label>
<input
type="text"
value={jwt}
onChange={(e) => setJwt(e.target.value)}
style={{ padding: '8px', width: '100%', borderRadius: '4px' }}
placeholder="Enter JWT token"
/>
</div>
{/* Layout */}
<div>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
Layout:
</label>
<select
value={layout}
onChange={(e) => setLayout(e.target.value as any)}
style={{ padding: '8px', width: '100%', borderRadius: '4px' }}
>
<option value="list">List</option>
<option value="titled">Titled</option>
</select>
</div>
{/* Number of Results */}
<div>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
Results Per Page:
</label>
<input
type="number"
value={numberOfResults}
onChange={(e) => setNumberOfResults(parseInt(e.target.value) || 10)}
style={{ padding: '8px', width: '100%', borderRadius: '4px' }}
min="5"
max="50"
/>
</div>
{/* Show CYO Banner */}
<div>
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<input
type="checkbox"
checked={showCYOBanner}
onChange={(e) => setShowCYOBanner(e.target.checked)}
/>
<span style={{ fontWeight: 'bold' }}>Show CYO Banner</span>
</label>
</div>
{/* Show Purchase Button */}
<div>
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<input
type="checkbox"
checked={showPurchaseButton}
onChange={(e) => setShowPurchaseButton(e.target.checked)}
/>
<span style={{ fontWeight: 'bold' }}>Show Purchase Button</span>
</label>
</div>
{/* Purchase Button Enabled */}
<div>
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<input
type="checkbox"
checked={purchaseButtonEnabled}
onChange={(e) => setPurchaseButtonEnabled(e.target.checked)}
/>
<span style={{ fontWeight: 'bold' }}>Purchase Button Enabled</span>
</label>
</div>
</div>
{/* Action Buttons */}
<div style={{ marginTop: '20px', display: 'flex', gap: '10px' }}>
<button
onClick={triggerSearch}
style={{
padding: '10px 20px',
backgroundColor: '#ED1164',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold',
}}
>
Trigger Search Event
</button>
<button
onClick={handleRefresh}
style={{
padding: '10px 20px',
backgroundColor: '#F38E18',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold',
}}
>
Refresh Results
</button>
</div>
</div>
{/* Search Results Component */}
<div style={{ marginTop: '30px' }}>
<h2>Search Results:</h2>
<GlSearchResults
ref={searchResultsRef}
environment={environment}
jwt={jwt}
configuration={{
layout: layout,
showCYOBanner: showCYOBanner,
showPurchaseButton: showPurchaseButton,
purchaseButtonEnabled: purchaseButtonEnabled,
purchaseButtonText: 'Purchase',
purchaseButtonColor: '#ED1164',
numberOfResults: numberOfResults,
}}
/>
</div>
{/* Instructions */}
<div style={{ marginTop: '30px', padding: '15px', backgroundColor: '#e3f2fd', borderRadius: '8px', borderLeft: '4px solid #2196f3' }}>
<h3 style={{ marginTop: 0 }}>Instructions:</h3>
<ol style={{ lineHeight: '1.8' }}>
<li>Configure the component using the panel above</li>
<li>Click "Trigger Search Event" to simulate a search</li>
<li>The component listens to custom "search_bar.search" events</li>
<li>Click on results to see event handling in action</li>
<li>Use "Refresh Results" to manually reload data</li>
<li>Open browser console to see event logs</li>
</ol>
</div>
</div>
);
};
// Render the app
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Vanilla JavaScript - Full Featured Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GL Search Results - Vanilla JS Demo</title>
<!-- Load CSS -->
<link rel="stylesheet" href="node_modules/@gift-card-market/gl-search-results/dist/style.css">
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f8f9fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #ED1164 0%, #F38E18 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
}
.control-panel {
background: white;
padding: 25px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
margin-right: 10px;
color: white;
}
.btn-primary { background-color: #ED1164; }
.btn-secondary { background-color: #F38E18; }
.btn-info { background-color: #65CEB5; }
.results-container {
background: white;
padding: 25px;
border-radius: 12px;
margin-bottom: 30px;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>GL Search Results - Vanilla JS</h1>
<p>Web Component Demo</p>
</div>
<!-- Control Panel -->
<div class="control-panel">
<h2>Configuration Panel</h2>
<div>
<label>Environment:</label>
<select id="environment">
<option value="development">Development</option>
<option value="production" selected>Production</option>
</select>
</div>
<div style="margin-top: 15px;">
<label>JWT Token:</label>
<input type="text" id="jwt" placeholder="your-jwt-token-here" style="width: 100%; padding: 8px;">
</div>
<div style="margin-top: 20px;">
<button class="btn btn-primary" onclick="triggerSearchEvent()">
Trigger Search Event
</button>
<button class="btn btn-secondary" onclick="refreshResults()">
Refresh Results
</button>
<button class="btn btn-info" onclick="applyConfiguration()">
Apply Configuration
</button>
</div>
</div>
<!-- Search Results -->
<div class="results-container">
<h2>Search Results:</h2>
<gl-search-results
id="searchResults"
environment="production"
jwt="your-jwt-token-here">
</gl-search-results>
</div>
</div>
<!-- Load Web Component -->
<script src="node_modules/@gift-card-market/gl-search-results/dist/browser/gl-search-results.js"></script>
<!-- Application Logic -->
<script>
// Trigger search event
function triggerSearchEvent() {
const searchEvent = new CustomEvent('search_bar.search', {
detail: {
data: {
location: 'New York',
term: 'restaurant',
sort_by: 'best_match',
provider_id: 'yelp',
},
},
});
document.dispatchEvent(searchEvent);
console.log('Search event triggered');
}
// Refresh results
function refreshResults() {
const element = document.getElementById('searchResults');
if (element && typeof element.refreshResults === 'function') {
element.refreshResults();
console.log('Results refreshed');
}
}
// Apply configuration
function applyConfiguration() {
const element = document.getElementById('searchResults');
const environment = document.getElementById('environment').value;
const jwt = document.getElementById('jwt').value;
const configuration = {
layout: 'list',
showCYOBanner: false,
showPurchaseButton: true,
purchaseButtonEnabled: true,
purchaseButtonText: 'Purchase',
purchaseButtonColor: '#ED1164',
numberOfResults: 10,
};
element.setAttribute('environment', environment);
element.setAttribute('jwt', jwt);
element.setAttribute('configuration', JSON.stringify(configuration));
console.log('Configuration applied');
}
// Listen to component events
document.addEventListener('gl:resultClick', function(e) {
console.log('Result clicked:', e.detail);
if (!e.detail.is_cyo) {
// Navigate to business page
window.location.href = '/business/' + e.detail.business_id;
} else {
console.log('CYO banner clicked');
}
});
document.addEventListener('gl:pageChange', function(e) {
console.log('Page changed to:', e.detail.pageIndex);
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// Initialize
window.addEventListener('load', function() {
console.log('GL Search Results Demo loaded');
applyConfiguration();
});
</script>
</body>
</html>
🔧 Development
Build from Source
# Install dependencies
npm install
# Development mode with hot reload
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
🐛 Troubleshooting
Component not rendering
- Ensure you've imported the CSS file
- Check that JWT token is valid
- Verify environment setting matches your API endpoint
- Check browser console for errors
Events not firing
- Make sure event listeners are registered before dispatching events
- Use correct event names from
SearchResultsEvents - Check that custom event format matches expected structure
Web Component not loading (Vanilla JS)
- Ensure script is loaded after DOM is ready
- Check that the script path is correct
- Verify browser supports Custom Elements (modern browsers)
📄 License
MIT © Gift Card Market
🤝 Contributing
This package is part of the Gift Card Market monorepo. For contribution guidelines, please refer to the main repository.
📞 Support
For issues, questions, or contributions, please visit:
- GitHub Issues: https://github.com/Gift-Card-Market/GiftcardMarketplace/issues
- Repository: https://github.com/Gift-Card-Market/GiftcardMarketplace
Made with ❤️ by Gift Card Market Team