Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Bulk reformats. `git config blame.ignoreRevsFile .git-blame-ignore-revs`
8ddf7b4f70a3118cd60c6677f227fc6876a18f30 # prettier, 10 files
4 changes: 2 additions & 2 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ name: Auto-merge

on:
workflow_run:
workflows: ['CI']
workflows: ["CI"]
types: [completed]
# HOURLY, not */10 as in the rest of the fleet — this repo is PRIVATE, so
# Actions minutes are billed. Every other repo running this sweep is public,
Expand All @@ -31,7 +31,7 @@ on:
# common path is workflow_run above, which fires within seconds of CI going
# green. Make this repo public and */10 becomes free again.
schedule:
- cron: '0 * * * *'
- cron: "0 * * * *"
workflow_dispatch: {}

permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
env:
POSTGRES_PASSWORD: ci
POSTGRES_DB: hamstercheek
ports: ['5432:5432']
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready --health-interval 5s
--health-timeout 5s --health-retries 10
Expand Down
31 changes: 31 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build output and vendored trees — formatting these is noise.
node_modules
.next
dist
build
out
coverage
.turbo
.vercel
*.min.js
*.min.css

# Generated during a build, so it is absent locally and present in CI — which
# makes a clean local --check no evidence at all. Contentlayer's output also
# uses import assertions, which prettier's parser rejects outright.
.contentlayer
.astro
.svelte-kit
storybook-static
test-results
playwright-report

# Lockfiles are generated; prettier would rewrite them wholesale.
package-lock.json
pnpm-lock.yaml
yarn.lock

# Markdown is deliberately out of scope for now. Prettier rewraps prose, which
# is where it is most opinionated and least useful, and it would bury the real
# diff. Remove this line when you want docs formatted too.
*.md
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"trailingComma": "all",
"arrowParens": "always",
"endOfLine": "lf"
}
2 changes: 1 addition & 1 deletion drizzle/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@
"schemas": {},
"tables": {}
}
}
}
2 changes: 1 addition & 1 deletion drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
"breakpoints": true
}
]
}
}
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
"lint": "next lint",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"verify": "npm run typecheck && npm run lint && npm run test",
"verify": "npm run format:check && npm run typecheck && npm run lint && npm run test",
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx src/db/migrate.ts",
"db:studio": "drizzle-kit studio"
"db:studio": "drizzle-kit studio",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"dotenv": "^17.4.2",
Expand All @@ -29,14 +31,15 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4.0.0",
"@types/leaflet": "^1.9.14",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4.0.0",
"drizzle-kit": "^0.31.10",
"eslint": "^9",
"eslint-config-next": "^15.0.0",
"prettier": "3.9.6",
"tailwindcss": "^4.0.0",
"tsx": "^4.23.12",
"typescript": "^5",
Expand Down
13 changes: 10 additions & 3 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ const ALLOWED_PHOTO_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);

export type CreateStashState = { error?: string };

export async function createStash(_prevState: CreateStashState, formData: FormData): Promise<CreateStashState> {
export async function createStash(
_prevState: CreateStashState,
formData: FormData,
): Promise<CreateStashState> {
const name = String(formData.get("name") ?? "").trim();
const description = String(formData.get("description") ?? "").trim();
const lat = Number(formData.get("lat"));
Expand All @@ -34,7 +37,8 @@ export async function createStash(_prevState: CreateStashState, formData: FormDa
if (!ALLOWED_PHOTO_TYPES.has(photo.type)) {
return { error: "Photo must be a JPEG, PNG, or WebP image." };
}
const extension = photo.type === "image/png" ? "png" : photo.type === "image/webp" ? "webp" : "jpg";
const extension =
photo.type === "image/png" ? "png" : photo.type === "image/webp" ? "webp" : "jpg";
const filename = `${randomUUID()}.${extension}`;
await mkdir(UPLOADS_DIR, { recursive: true });
const bytes = Buffer.from(await photo.arrayBuffer());
Expand All @@ -51,7 +55,10 @@ export async function createStash(_prevState: CreateStashState, formData: FormDa
}

export async function deleteStash(id: number): Promise<void> {
const [deleted] = await db.delete(stashes).where(eq(stashes.id, id)).returning({ photoUrl: stashes.photoUrl });
const [deleted] = await db
.delete(stashes)
.where(eq(stashes.id, id))
.returning({ photoUrl: stashes.photoUrl });

if (deleted?.photoUrl) {
await unlink(path.join(process.cwd(), "public", deleted.photoUrl)).catch(() => {});
Expand Down
6 changes: 1 addition & 5 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ export const metadata: Metadata = {
description: "Store and find your valuables, off the grid.",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className="antialiased">{children}</body>
Expand Down
4 changes: 3 additions & 1 deletion src/app/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ export default function NewStashPage() {

<div className="flex flex-col gap-2">
<span className="text-sm font-medium">Location</span>
<p className="text-xs text-muted-foreground">Click the map to drop a pin where the box is buried or hidden.</p>
<p className="text-xs text-muted-foreground">
Click the map to drop a pin where the box is buried or hidden.
</p>
<MapPicker value={coords} onChange={setCoords} />
<p className="text-xs text-muted-foreground">
{coords ? formatCoordinates(coords) : "No location selected yet"}
Expand Down
4 changes: 3 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export default async function Home() {
>
<div>
<p className="font-medium">{item.name}</p>
<p className="text-xs text-muted-foreground">{formatCoordinates({ lat: item.lat, lng: item.lng })}</p>
<p className="text-xs text-muted-foreground">
{formatCoordinates({ lat: item.lat, lng: item.lng })}
</p>
</div>
{item.photoUrl && (
<span className="h-12 w-12 shrink-0 overflow-hidden rounded-[var(--radius-token)] border border-border">
Expand Down
4 changes: 3 additions & 1 deletion src/app/stashes/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export default async function StashDetailPage({ params }: { params: Promise<{ id
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-2xl font-bold tracking-tight">{stash.name}</h1>
<p className="text-sm text-muted-foreground">{formatCoordinates({ lat: stash.lat, lng: stash.lng })}</p>
<p className="text-sm text-muted-foreground">
{formatCoordinates({ lat: stash.lat, lng: stash.lng })}
</p>
</div>
<DeleteStashButton id={stash.id} name={stash.name} />
</div>
Expand Down
7 changes: 6 additions & 1 deletion src/lib/coordinates.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest";
import { formatCoordinates, isValidCoordinates, isValidLatitude, isValidLongitude } from "./coordinates";
import {
formatCoordinates,
isValidCoordinates,
isValidLatitude,
isValidLongitude,
} from "./coordinates";

describe("isValidLatitude", () => {
it("accepts values within -90..90", () => {
Expand Down