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.
4
5
6
7## Install
12 const [portfolio, setPortfolio] = useState<PortfolioItem[]>([]);
13 const [loading, setLoading] = useState(true);
14 const [selectedImage, setSelectedImage] = useState<PortfolioItem | null>(null);
15
16 useEffect(() => {
152 key={item.id}
153 className="bg-white rounded-lg shadow-md overflow-hidden card-hover cursor-pointer"
154 onClick={() => setSelectedImage(item)}
155 >
156 <img
157 src={item.image_url}
158 alt={item.title}
159 className="w-full h-64 object-cover"
183 </div>
184
185 {/* Image Modal */}
186 {selectedImage && (
187 <div
188 className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4"
189 onClick={() => 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
198 onClick={() => setSelectedImage(null)}
199 className="text-gray-500 hover:text-gray-700"
200 >
205 </div>
206 <img
207 src={selectedImage.image_url}
208 alt={selectedImage.title}
209 className="w-full max-h-96 object-contain"
210 onError={(e) => {
211 e.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
168 src={item.image_url}
169 alt={item.title}
170 className="w-full h-48 object-cover"
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>
45
46 // Basic validation
47 if (!data.artist_id || !data.title || !data.description || !data.image_url || !data.category) {
48 return c.json({ error: "Missing required fields" }, 400);
49 }
59export async function createPortfolioItem(data: CreatePortfolioItemRequest): Promise<PortfolioItem> {
60 const result = await sqlite.execute(`
61 INSERT INTO portfolio_items (artist_id, title, description, image_url, category, price, is_for_sale)
62 VALUES (?, ?, ?, ?, ?, ?, ?)
63 `, [
65 data.title,
66 data.description,
67 data.image_url,
68 data.category,
69 data.price || null,
27 title TEXT NOT NULL,
28 description TEXT NOT NULL,
29 image_url TEXT NOT NULL,
30 category TEXT NOT NULL,
31 price INTEGER, -- Price in cents
21 title: string;
22 description: string;
23 image_url: string;
24 category: string;
25 price?: number;
56 title: string;
57 description: string;
58 image_url: string;
59 category: string;
60 price?: number;
3Feel free to mess around with this val and make it your own :). Just click on "Fork" in the top right.
4
5You can change the phrases that show up as you click no, you can change the firstImg and secondImg, maybe even add more images. And you can also change the colors and any of the text on the screen!
6
7Have fun with it and hopefully your crush says yes hehe.
102 return "Create an acrostic poem where the first letter of each line spells out the theme.";
103 default:
104 return "Write in free verse with natural rhythm and beautiful imagery.";
105 }
106}
109 switch (mood) {
110 case 'joyful':
111 return "Infuse the poem with happiness, celebration, and uplifting imagery.";
112 case 'melancholic':
113 return "Create a contemplative, bittersweet tone with gentle sadness.";
114 case 'peaceful':
115 return "Use calm, serene imagery that evokes tranquility and harmony.";
116 case 'energetic':
117 return "Fill the poem with dynamic movement, excitement, and vibrant energy.";