7price: number;
8category: string;
9image_url?: string;
10stock: number;
11created_at: string;
RobbieCryptoList.tsx1 match
35<div className="flex items-center space-x-3">
36<img
37src={crypto.image}
38alt={crypto.name}
39className="w-8 h-8 rounded-full"
24price_change_percentage_24h: coin.price_change_percentage_24h || 0,
25market_cap: coin.market_cap,
26image: coin.image
27}));
28
8price_change_percentage_24h: number;
9market_cap: number;
10image: string;
11}
12
sqliteExplorerAppREADME.md1 match
3View and interact with your Val Town SQLite data. It's based off Steve's excellent [SQLite Admin](https://www.val.town/v/stevekrouse/sqlite_admin?v=46) val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by [LibSQL Studio](https://github.com/invisal/libsql-studio) by [invisal](https://github.com/invisal). This is now more an SPA, with tables, queries and results showing up on the same page.
45
67## Install
ArtreonPortfolio.tsx15 matches
12const [portfolio, setPortfolio] = useState<PortfolioItem[]>([]);
13const [loading, setLoading] = useState(true);
14const [selectedImage, setSelectedImage] = useState<PortfolioItem | null>(null);
1516useEffect(() => {
152key={item.id}
153className="bg-white rounded-lg shadow-md overflow-hidden card-hover cursor-pointer"
154onClick={() => setSelectedImage(item)}
155>
156<img
157src={item.image_url}
158alt={item.title}
159className="w-full h-64 object-cover"
183</div>
184185{/* Image Modal */}
186{selectedImage && (
187<div
188className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4"
189onClick={() => setSelectedImage(null)}
190>
191<div
194>
195<div className="p-4 border-b flex justify-between items-center">
196<h3 className="text-xl font-semibold">{selectedImage.title}</h3>
197<button
198onClick={() => setSelectedImage(null)}
199className="text-gray-500 hover:text-gray-700"
200>
205</div>
206<img
207src={selectedImage.image_url}
208alt={selectedImage.title}
209className="w-full max-h-96 object-contain"
210onError={(e) => {
211e.currentTarget.src = `https://maxm-imggenurl.web.val.run/${encodeURIComponent(selectedImage.title + ' ' + selectedImage.category)}`;
212}}
213/>
214<div className="p-4">
215<p className="text-gray-700 mb-4">{selectedImage.description}</p>
216<div className="flex justify-between items-center">
217<span className="bg-purple-100 text-purple-800 px-3 py-1 rounded">
218{selectedImage.category}
219</span>
220{selectedImage.is_for_sale && selectedImage.price && (
221<div className="text-right">
222<div className="text-green-600 font-semibold text-lg">
223${(selectedImage.price / 100).toFixed(2)}
224</div>
225<button
166<div key={item.id} className="bg-white rounded-lg shadow-md overflow-hidden card-hover">
167<img
168src={item.image_url}
169alt={item.title}
170className="w-full h-48 object-cover"
Artreonindex.html1 match
14
15<!-- Favicon -->
16<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎨</text></svg>">
17
18<style>
Artreonportfolio.ts1 match
45
46// Basic validation
47if (!data.artist_id || !data.title || !data.description || !data.image_url || !data.category) {
48return c.json({ error: "Missing required fields" }, 400);
49}
Artreonqueries.ts2 matches
59export async function createPortfolioItem(data: CreatePortfolioItemRequest): Promise<PortfolioItem> {
60const result = await sqlite.execute(`
61INSERT INTO portfolio_items (artist_id, title, description, image_url, category, price, is_for_sale)
62VALUES (?, ?, ?, ?, ?, ?, ?)
63`, [
65data.title,
66data.description,
67data.image_url,
68data.category,
69data.price || null,