Skip to main content

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.

Version License

📦 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

PropertyTypeRequiredDescription
environmentstringYesEnvironment mode: 'development', 'qa', 'staging' or 'production'
jwtstringYesJWT authentication token for API calls
configurationGlSearchResultsConfigPropsNoComponent configuration object

GlSearchResultsConfigProps

PropertyTypeDefaultDescription
layoutstring'list'Layout style: 'list' or 'titled'
showCYOBannerbooleanfalseShow "Create Your Own" banner
showPurchaseButtonbooleantrueDisplay purchase button on results
purchaseButtonEnabledbooleantrueEnable/disable purchase button
purchaseButtonTextstring'Purchase'Text displayed on purchase button
purchaseButtonColorstring'#ED1164'Background color of purchase button
numberOfResultsnumber10Number 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 NameDescriptionDetail 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

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>
);
<!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

  1. Ensure you've imported the CSS file
  2. Check that JWT token is valid
  3. Verify environment setting matches your API endpoint
  4. Check browser console for errors

Events not firing

  1. Make sure event listeners are registered before dispatching events
  2. Use correct event names from SearchResultsEvents
  3. Check that custom event format matches expected structure

Web Component not loading (Vanilla JS)

  1. Ensure script is loaded after DOM is ready
  2. Check that the script path is correct
  3. 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:


Made with ❤️ by Gift Card Market Team