1import { DateTime } from "https://esm.sh/luxon@3.4.4";
2
3export default async function(e: Email) {
4 console.log("Received tattoo email");
5 const { sqlite } = await import("https://esm.town/v/stevekrouse/sqlite");
3import React, { useEffect, useState } from "https://esm.sh/react@18.2.0";
4
5function Slideshow({ images }) {
6 useEffect(() => {
7 let currentSlideIndex = 0;
11 const transitionDuration = 800; // 0.8 seconds (must match CSS)
12
13 function showNextSlide() {
14 if (totalSlides <= 1) return;
15
64}
65
66function Card({ title, subtitle, amount, steps, footerSlides, buttonText, modalTitle, modalContent }) {
67 const [isModalActive, setIsModalActive] = useState(false);
68
73 let footerSlideInterval = null;
74
75 function showNextFooterSlide() {
76 footerSlideContainers.forEach((container) => {
77 const slides = container.querySelectorAll(".footer-slide");
177}
178
179function Modal({ isActive, onClose, title, content }) {
180 return (
181 <div className={`modal-overlay ${isActive ? "active" : ""}`} onClick={onClose}>
194}
195
196function InviteCard() {
197 return (
198 <div className="card invite-card">
216}
217
218function BottomNavigation() {
219 return (
220 <nav className="bottom-nav">
234}
235
236function LoadingOverlay() {
237 const [message, setMessage] = useState("");
238 const [showTryLater, setShowTryLater] = useState(true);
283}
284
285function App() {
286 const [showOverlay, setShowOverlay] = useState(false);
287
372}
373
374function client() {
375 createRoot(document.getElementById("root")).render(<App />);
376}
380}
381
382export default async function server(request: Request): Promise<Response> {
383 return new Response(
384 `
285};
286
287function App() {
288 return <LoginPage />;
289}
290
291function client() {
292 createRoot(document.getElementById("root")).render(<App />);
293}
297}
298
299export default async function server(request: Request): Promise<Response> {
300 const url = new URL(request.url);
301
3import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
4
5function Slideshow({ images }) {
6 useEffect(() => {
7 let currentSlideIndex = 0;
11 const transitionDuration = 800; // 0.8 seconds (must match CSS)
12
13 function showNextSlide() {
14 if (totalSlides <= 1) return;
15
64}
65
66function Card({ title, subtitle, amount, steps, footerSlides, buttonText, modalTitle, modalContent }) {
67 const [isModalActive, setIsModalActive] = useState(false);
68
73 let footerSlideInterval = null;
74
75 function showNextFooterSlide() {
76 footerSlideContainers.forEach((container) => {
77 const slides = container.querySelectorAll(".footer-slide");
171}
172
173function Modal({ isActive, onClose, title, content }) {
174 return (
175 <div className={`modal-overlay ${isActive ? "active" : ""}`} onClick={onClose}>
186}
187
188function InviteCard() {
189 return (
190 <div className="card invite-card">
208}
209
210function BottomNavigation() {
211 return (
212 <nav className="bottom-nav">
226}
227
228function LoadingOverlay() {
229 const [message, setMessage] = useState("");
230 const [showTryLater, setShowTryLater] = useState(true);
275}
276
277function App() {
278 const [showOverlay, setShowOverlay] = useState(false);
279
364}
365
366function client() {
367 createRoot(document.getElementById("root")).render(<App />);
368}
372}
373
374export default async function server(request: Request): Promise<Response> {
375 return new Response(
376 `
4import { DateTime } from "https://esm.sh/luxon@3.4.4";
5
6export async function testDailyBrief() {
7 try {
8 const testChatId = Deno.env.get("TEST_TELEGRAM_CHAT_ID");
13} from "../memoryUtils.ts";
14
15async function generateBriefingContent(anthropic, memories, today, isSunday) {
16 try {
17 const weekdaysHelp = generateWeekDays(today);
96}
97
98export async function sendDailyBriefing(chatId?: string, today?: DateTime) {
99 // Get API keys from environment
100 const apiKey = Deno.env.get("ANTHROPIC_API_KEY");
135 const lastSunday = today.startOf("week").minus({ days: 1 });
136
137 // Fetch relevant memories using the utility function
138 const memories = await getRelevantMemories();
139
216}
217
218function generateWeekDays(today) {
219 let output = [];
220
239// console.log(weekDays);
240
241// Export a function that calls sendDailyBriefing with no parameters
242// This maintains backward compatibility with existing cron jobs
243export default async function (overrideToday?: DateTime) {
244 return await sendDailyBriefing(undefined, overrideToday);
245}
16In a normal server environment, you would likely use a middleware [like this one](https://hono.dev/docs/getting-started/nodejs#serve-static-files) to serve static files. Some frameworks or deployment platforms automatically make any content inside a `public/` folder public.
17
18However in Val Town you need to handle this yourself, and it can be suprisingly difficult to read and serve files in a Val Town Project. This template uses helper functions from [stevekrouse/utils/serve-public](https://www.val.town/x/stevekrouse/utils/branch/main/code/serve-public/README.md), which handle reading project files in a way that will work across branches and forks, automatically transpiles typescript to javascript, and assigns content-types based on the file's extension.
19
20### `index.html`
26## CRUD API Routes
27
28This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. These routes are called from the React app to refresh and update data.
29
30## Errors
4
5* `migrations.ts` - code to set up the database tables the app needs
6* `queries.ts` - functions to run queries against those tables, which are imported and used in the main Hono server in `/backend/index.ts`
7
8## Migrations
18The queries file is where running the migrations happen in this app. It'd also be reasonable for that to happen in index.ts, or as is said above, for that line to be commented out, and only run when actual changes are made to your database schema.
19
20The queries file exports functions to get and write data. It relies on shared types and data imported from the `/shared` directory.
6const tableName = "memories_demo";
7
8export async function getAllMemories(): Promise<Memory[]> {
9 const result = await sqlite.execute(
10 `SELECT id, date, text, createdBy, createdDate, tags FROM ${tableName}
23}
24
25export async function createMemory(
26 memory: Omit<Memory, "id">
27): Promise<Memory> {
51}
52
53export async function updateMemory(
54 id: string,
55 memory: Partial<Omit<Memory, "id">>
70}
71
72export async function deleteMemory(id: string): Promise<void> {
73 await sqlite.execute(`DELETE FROM ${tableName} WHERE id = ?`, [id]);
74}
4import { nanoid } from "https://esm.sh/nanoid@5.0.5";
5
6export default async function populateMemoryIds() {
7 try {
8 // Import SQLite module