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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ jobs:
fi
echo "⌈ Lock file validation passed"

check-route-file-casing:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Check App Router file casing
run: node scripts/check-route-file-casing.mjs

unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
Expand Down
56 changes: 56 additions & 0 deletions app/components/NftActivityCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import { motion } from "framer-motion";
import { Palette } from "lucide-react";
import type { NftActivitySummary } from "@/app/utils/indexer";

interface NftActivityCardProps {
summary?: NftActivitySummary;
}

export function NftActivityCard({ summary }: NftActivityCardProps) {
if (!summary || summary.mintCount === 0) return null;

const creator = summary.topCreatorAddress;

return (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 1.0, type: "spring", stiffness: 100 }}
className="mt-8 sm:mt-10 md:mt-12"
>
<h3 className="mb-3 text-xs font-black tracking-[0.25em] text-white/50 sm:mb-4 sm:text-sm">
YOUR NFT ACTIVITY
</h3>
<div
className="rounded-2xl border border-white/10 p-5 backdrop-blur-md sm:rounded-3xl sm:p-6 md:p-8"
style={{ backgroundColor: "rgba(255, 255, 255, 0.03)" }}
>
<div className="mb-4 flex items-end gap-3">
<Palette
className="h-8 w-8 shrink-0"
style={{ color: "var(--color-theme-primary)" }}
aria-hidden="true"
/>
<span className="text-4xl leading-none font-black text-white sm:text-5xl">
{summary.mintCount.toLocaleString()}
</span>
<span className="mb-1 font-medium text-white/50">
{summary.mintCount === 1 ? "NFT minted" : "NFTs minted"}
</span>
</div>

{creator && (
<p className="text-sm text-white/70 sm:text-base">
Top creator{" "}
<span className="font-mono text-white" title={creator}>
{creator.slice(0, 4)}…{creator.slice(-4)}
</span>{" "}
({summary.topCreatorMintCount.toLocaleString()})
</p>
)}
</div>
</motion.div>
);
}
4 changes: 2 additions & 2 deletions app/vibe-check/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const vibeCheckJsonLd = {
};

export default function VibeCheckLayout({
childreen,
children,
}: {
children: ReactNode;
}) {
Expand All @@ -72,7 +72,7 @@ export default function VibeCheckLayout({
/>
}
>
{childreen}
{children}
</Suspense>
</>
);
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"validate:env": "node scripts/validate-env.js",
"optimize:archetypes": "node scripts/optimize-archetypes.mjs",
"check:archetype-budget": "node scripts/check-archetype-budget.mjs",
"check:route-casing": "node scripts/check-route-file-casing.mjs",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
Expand Down
44 changes: 44 additions & 0 deletions scripts/check-route-file-casing.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env node
/**
* Fails when an App Router special file under app/ is not lowercase.
*
* macOS resolves `Page.tsx` as `page.tsx`, but Vercel builds on a
* case-sensitive filesystem where it is not a route file, so the route
* works locally and 404s in production.
*/
import { readdir } from "node:fs/promises";
import { join, relative } from "node:path";

const appDir = join(process.cwd(), "app");
const specialFiles = new Set([
"page",
"layout",
"loading",
"error",
"global-error",
"not-found",
"forbidden",
"unauthorized",
"template",
"default",
"route",
]);
const fileName = /^([^.]+)\.(jsx?|tsx?|mdx)$/;

const entries = await readdir(appDir, { recursive: true, withFileTypes: true });
const offenders = entries
.filter((entry) => entry.isFile())
.filter((entry) => {
const base = entry.name.match(fileName)?.[1];
return base && base !== base.toLowerCase() && specialFiles.has(base.toLowerCase());
})
.map((entry) => relative(process.cwd(), join(entry.parentPath, entry.name)));

if (offenders.length > 0) {
console.error("❌ App Router files must be lowercase to resolve on case-sensitive filesystems:");
for (const file of offenders) console.error(` ${file}`);
console.error("Rename them with `git mv` in two steps (e.g. Page.tsx -> tmp.tsx -> page.tsx).");
process.exit(1);
}

console.log("✅ All App Router route files are lowercase");
Loading