5const LOCAL_STORAGE_KEY = "aiCardGradingResults_v2";
67function generateTimestamp() {
8try {
9return new Date().toLocaleString("en-US", { timeZone: "America/Phoenix" });
13}
1415function getGradeColorClass(grade) {
16const gradeLetter = (grade || "").charAt(0).toUpperCase();
17switch (gradeLetter) {
37}
3839function formatNumber(num) {
40return Math.round(num).toString();
41}
4243function ScoreRevealAnimation({ startSignal, gradeData, onComplete }) {
44const [phase, setPhase] = useState("idle");
45const [score, setScore] = useState(0);
157}
158159function ImageUploader({ side, imageFile, onFileChange, disabled }) {
160const [preview, setPreview] = useState(null);
161const fileInputRef = useRef(null);
196}
197198function CardGraderApp() {
199const [frontImageFile, setFrontImageFile] = useState(null);
200const [backImageFile, setBackImageFile] = useState(null);
482}
483484function client() {
485const root = document.getElementById("root");
486if (root) createRoot(root).render(<CardGraderApp />);
490}
491492export default async function server(request) {
493const { OpenAI } = await import("https://esm.town/v/std/openai");
494const corsHeaders = {
2import type { ReactNode } from "npm:react@18.2.0";
34export function Layout({ children }: { children: ReactNode }) {
5return (
6<html lang="en">
markdownBlogStarterindex.tsx3 matches
5import { Layout } from "./Layout.tsx";
67function PostComponent({ markdown, link }: { markdown: string; link?: string }) {
8return (
9<div style={{ border: "1px solid gray", padding: "10px", marginBottom: "20px", borderRadius: "5px" }}>
14}
1516export default async function(req: Request): Promise<Response> {
17const url = new URL(req.url);
18if (url.pathname === "/") {
44}
4546function html(children: React.ReactNode) {
47return new Response(
48renderToString(
reactHonoStarterApp.tsx1 match
2import { useState } from "https://esm.sh/react@18.2.0";
34export function App() {
5const [clicked, setClicked] = useState(0);
6return (
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.
1718However 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.
1920### `index.html`
26## CRUD API Routes
2728This 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.
2930## Errors
45* `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`
78## 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.
1920The queries file exports functions to get and write data. It relies on shared types and data imported from the `/shared` directory.
MsgBoardqueries.ts2 matches
10await createTables();
1112export async function getMessages(limit = MESSAGE_LIMIT): Promise<Message[]> {
13const messages = await sqlite.execute(
14`SELECT * FROM ${tableName}
20}
2122export async function insertMessage(content: string) {
23await sqlite.execute(
24`INSERT INTO ${tableName} (content)
MsgBoardmigrations.ts1 match
3export const tableName = "reactHonoStarter_messages";
45export async function createTables() {
6await sqlite.batch([
7`CREATE TABLE IF NOT EXISTS ${tableName} (
MsgBoardMessageInput.tsx1 match
3import type { Message } from "../shared/types.ts";
45export function MessageInput({ onSubmit }: { onSubmit: () => void }) {
6const [message, setMessage] = React.useState("");
7
4import { MessageInput } from "./MessageInput.tsx";
56export function App(
7{ initialMessages = [], thisProjectURL }: { initialMessages?: Message[]; thisProjectURL?: string },
8) {
41}
4243function MessageList({ messages }: { messages: Message[] }) {
44const displayedMessages = messages.slice(0, MESSAGE_LIMIT);
45return (
50}
5152function MessageItem({ message }) {
53const formattedDate = new Date(message.timestamp).toLocaleString();
54