Compare commits

..

No commits in common. "1f64c881b94706a67e18bfa77bf378e22cd5a550" and "bd84089d7bd4da31b1ac58f9535317a1b0b8cb91" have entirely different histories.

7 changed files with 24 additions and 145 deletions

View file

@ -1,2 +0,0 @@
# sparkle base url
NEXT_PUBLIC_SPARKLE_BASE_URL=

7
package-lock.json generated
View file

@ -8,7 +8,6 @@
"name": "twinkle",
"version": "0.1.0",
"dependencies": {
"lodash.debounce": "^4.0.8",
"next": "14.2.8",
"react": "^18",
"react-dom": "^18",
@ -3660,12 +3659,6 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View file

@ -9,7 +9,6 @@
"lint": "next lint"
},
"dependencies": {
"lodash.debounce": "^4.0.8",
"next": "14.2.8",
"react": "^18",
"react-dom": "^18",

View file

@ -1,87 +0,0 @@
import { useState, useEffect, useCallback } from 'react'
import debounce from 'lodash.debounce'
const SearchBar = ({ onSelectSymbol }: { onSelectSymbol: (symbol: string) => void }) => {
const [query, setQuery] = useState('')
const [suggestions, setSuggestions] = useState<{ symbol: string, description: string }[]>([])
const [loading, setLoading] = useState(false)
const [notFound, setNotFound] = useState(false)
const fetchSuggestions = async (query: string) => {
setLoading(true)
setNotFound(false)
try {
const res = await fetch(`/api/search?query=${query}`)
const data = await res.json()
if (data.result && data.result.length > 0) {
setSuggestions(data.result)
} else {
setNotFound(true)
setSuggestions([])
}
} catch (error) {
console.error('Error fetching suggestions:', error)
setNotFound(true)
setSuggestions([])
} finally {
setLoading(false)
}
}
const debouncedFetchSuggestions = useCallback(debounce((query: string) => {
fetchSuggestions(query)
}, 300), [])
useEffect(() => {
if (query.length > 1) {
debouncedFetchSuggestions(query)
} else {
setSuggestions([])
setNotFound(false)
}
}, [query, debouncedFetchSuggestions])
const handleSelect = (symbol: string) => {
setQuery('')
setSuggestions([])
onSelectSymbol(symbol)
}
return (
<div className="relative">
<input
type="text"
placeholder="Search stocks..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="border p-2 w-full"
/>
{loading && (
<div className="absolute border bg-white p-2 w-full mt-1 text-center">
Loading...
</div>
)}
{!loading && notFound && (
<div className="absolute border bg-white p-2 w-full mt-1 text-center text-red-500">
No results found
</div>
)}
{suggestions.length > 0 && (
<ul className="absolute border bg-white w-full mt-1">
{suggestions.map((item) => (
<li
key={item.symbol}
onClick={() => handleSelect(item.symbol)}
className="p-2 hover:bg-gray-200 cursor-pointer"
>
{item.description} ({item.symbol})
</li>
))}
</ul>
)}
</div>
)
}
export default SearchBar

View file

@ -1,21 +1,19 @@
import { useState } from 'react'
import SearchBar from './SearchBar'
const StockPrice = () => {
const [symbol, setSymbol] = useState('')
const [price, setPrice] = useState<number | null>(null)
const [error, setError] = useState('')
const fetchStockPrice = async (selectedSymbol: string) => {
const fetchStockPrice = async () => {
setError('')
try {
const res = await fetch(`/api/quote?symbol=${selectedSymbol}`)
const res = await fetch(`/api/quote?symbol=${symbol}`)
const data = await res.json()
if (data.error) {
setError(data.error)
} else {
setPrice(data.c)
setSymbol(selectedSymbol)
}
} catch (err) {
setError('Failed to fetch stock price')
@ -24,22 +22,29 @@ const StockPrice = () => {
return (
<div className="my-4">
<SearchBar onSelectSymbol={fetchStockPrice} />
{symbol && (
<div>
<h2 className="text-2xl font-bold mt-4">Symbol: {symbol}</h2>
{price !== null && (
<div className="mt-2">
<p>Current Price: ${price}</p>
</div>
)}
{error && (
<div className="mt-2 text-red-500">
<p>{error}</p>
</div>
)}
<input
type="text"
placeholder="Enter stock symbol"
value={symbol}
onChange={(e) => setSymbol(e.target.value)}
className="border p-2 mr-2"
/>
<button
onClick={fetchStockPrice}
className="bg-blue-500 text-white p-2"
>
Get Price
</button>
{price !== null && (
<div className="mt-4">
<p>Current Price: ${price}</p>
</div>
)}
{error && (
<div className="mt-4 text-red-500">
<p>{error}</p>
</div>
)}
</div>
)
}

View file

@ -1,20 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { fetchSymbols } from "../../utils/sparkle";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { query } = req.query;
if (!query || typeof query !== "string") {
res.status(400).json({ error: "Invalid symbol" });
return;
}
try {
const quote = await fetchSymbols(query);
res.status(200).json(quote);
} catch (error) {
res.status(500).json({ error: "Error fetching quote" });
}
}

View file

@ -7,13 +7,4 @@ export const fetchQuote = async (symbol: string) => {
throw new Error('Error fetching quote')
}
return res.json()
}
export const fetchSymbols = async (symbol: string) => {
const url = `${SPARKLE_BASE_URL}/search?query=${symbol}`
const res = await fetch(url)
if (!res.ok) {
throw new Error('Error fetching quote')
}
return res.json()
}
}