Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3e961ec
Enable Cache Components and stream the root provider tree
lucasdoell Jun 26, 2026
4330e0b
Remove route segment configs and migrate unstable_cache to use cache
lucasdoell Jun 26, 2026
34954df
Defer sidebar skeleton random width to after mount
lucasdoell Jun 26, 2026
5a9c03d
Mount the Vercel Toolbar on the client only
lucasdoell Jun 26, 2026
b7bff57
Add a cookie-free metadata translator for Cache Components
lucasdoell Jun 26, 2026
0e6f73f
Resolve route metadata in the default locale (Cache Components)
lucasdoell Jun 26, 2026
88e0040
Skip OpenTelemetry setup during the build phase
lucasdoell Jun 26, 2026
fff2bce
Cache the map hero trends page data
lucasdoell Jun 26, 2026
2e8932d
Render the map hero trends page at request time
lucasdoell Jun 26, 2026
911363b
Clean up metadata migration types and lint
lucasdoell Jun 26, 2026
94fcaa3
Tighten Cache Components migration comments
lucasdoell Jun 26, 2026
cf33844
Upgrade apps/web to Next.js 16.3 canary
lucasdoell Jun 26, 2026
fe432fb
Enable partial prefetching and stream the map trends page
lucasdoell Jun 26, 2026
5ddb127
Cache immutable per-map reads aggressively
lucasdoell Jun 26, 2026
b095d86
Make the cached map readers async
lucasdoell Jun 26, 2026
23fd2b6
Cache scrim aggregates and team stats with tag invalidation
lucasdoell Jun 26, 2026
d0a6964
Drop redundant comments on cache invalidation calls
lucasdoell Jun 26, 2026
b9a3683
Use BatchSpanProcessor so telemetry never blocks prerendering
lucasdoell Jun 26, 2026
4db1e85
Hold partialPrefetching until authed routes are instant-ready
lucasdoell Jun 26, 2026
8889845
Stream auth-gated content on the scrim and map layouts
lucasdoell Jun 26, 2026
f970a34
Stream auth-gated chrome in shared and section layouts
lucasdoell Jun 26, 2026
acfcfd5
Add segment loading shells for instant navigation
lucasdoell Jun 26, 2026
236cf09
Add loading shells for the remaining auth/i18n marketing pages
lucasdoell Jun 26, 2026
1a26dbd
Hoist next-themes into the static shell to stop the light-mode flash
lucasdoell Jun 26, 2026
329913c
Show an app-chrome skeleton while the root providers stream
lucasdoell Jun 26, 2026
529e440
Match loading skeletons to their routes and fix the dashboard tour in…
lucasdoell Jun 26, 2026
d9ab3b7
Give every route a bespoke loading skeleton matching its content
lucasdoell Jun 26, 2026
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
10 changes: 8 additions & 2 deletions apps/web/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<!-- NEXT-AGENTS-MD-START -->[Next.js Docs Index]|root: ./.next-docs|STOP. What you remember about Next.js is WRONG for this project. Always search docs and read before any task.|If docs missing, run this command first: npx @next/codemod agents-md --output CLAUDE.md|01-app:{04-glossary.mdx}|01-app/01-getting-started:{01-installation.mdx,02-project-structure.mdx,03-layouts-and-pages.mdx,04-linking-and-navigating.mdx,05-server-and-client-components.mdx,06-fetching-data.mdx,07-mutating-data.mdx,08-caching.mdx,09-revalidating.mdx,10-error-handling.mdx,11-css.mdx,12-images.mdx,13-fonts.mdx,14-metadata-and-og-images.mdx,15-route-handlers.mdx,16-proxy.mdx,17-deploying.mdx,18-upgrading.mdx}|01-app/02-guides:{ai-agents.mdx,analytics.mdx,authentication.mdx,backend-for-frontend.mdx,caching-without-cache-components.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,data-security.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instant-navigation.mdx,instrumentation.mdx,internationalization.mdx,json-ld.mdx,lazy-loading.mdx,local-development.mdx,mcp.mdx,mdx.mdx,memory-usage.mdx,migrating-to-cache-components.mdx,multi-tenant.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,prefetching.mdx,preserving-ui-state.mdx,production-checklist.mdx,progressive-web-apps.mdx,public-static-pages.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,single-page-applications.mdx,static-exports.mdx,streaming.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx,videos.mdx}|01-app/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|01-app/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|01-app/02-guides/upgrading:{codemods.mdx,version-14.mdx,version-15.mdx,version-16.mdx}|01-app/03-api-reference:{07-edge.mdx,08-turbopack.mdx}|01-app/03-api-reference/01-directives:{use-cache-private.mdx,use-cache-remote.mdx,use-cache.mdx,use-client.mdx,use-server.mdx}|01-app/03-api-reference/02-components:{font.mdx,form.mdx,image.mdx,link.mdx,script.mdx}|01-app/03-api-reference/03-file-conventions/01-metadata:{app-icons.mdx,manifest.mdx,opengraph-image.mdx,robots.mdx,sitemap.mdx}|01-app/03-api-reference/03-file-conventions/02-route-segment-config:{dynamicParams.mdx,instant.mdx,maxDuration.mdx,preferredRegion.mdx,runtime.mdx}|01-app/03-api-reference/03-file-conventions:{default.mdx,dynamic-routes.mdx,error.mdx,forbidden.mdx,instrumentation-client.mdx,instrumentation.mdx,intercepting-routes.mdx,layout.mdx,loading.mdx,mdx-components.mdx,not-found.mdx,page.mdx,parallel-routes.mdx,proxy.mdx,public-folder.mdx,route-groups.mdx,route.mdx,src-folder.mdx,template.mdx,unauthorized.mdx}|01-app/03-api-reference/04-functions:{after.mdx,cacheLife.mdx,cacheTag.mdx,catchError.mdx,connection.mdx,cookies.mdx,draft-mode.mdx,fetch.mdx,forbidden.mdx,generate-image-metadata.mdx,generate-metadata.mdx,generate-sitemaps.mdx,generate-static-params.mdx,generate-viewport.mdx,headers.mdx,image-response.mdx,next-request.mdx,next-response.mdx,not-found.mdx,permanentRedirect.mdx,redirect.mdx,refresh.mdx,revalidatePath.mdx,revalidateTag.mdx,unauthorized.mdx,unstable_cache.mdx,unstable_noStore.mdx,unstable_rethrow.mdx,updateTag.mdx,use-link-status.mdx,use-params.mdx,use-pathname.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,use-selected-layout-segment.mdx,use-selected-layout-segments.mdx,userAgent.mdx}|01-app/03-api-reference/05-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,appDir.mdx,assetPrefix.mdx,authInterrupts.mdx,basePath.mdx,cacheComponents.mdx,cacheHandlers.mdx,cacheLife.mdx,compress.mdx,crossOrigin.mdx,cssChunking.mdx,deploymentId.mdx,devIndicators.mdx,distDir.mdx,env.mdx,expireTime.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,htmlLimitedBots.mdx,httpAgentOptions.mdx,images.mdx,incrementalCacheHandlerPath.mdx,inlineCss.mdx,logging.mdx,mdxRs.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactCompiler.mdx,reactMaxHeadersLength.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,sassOptions.mdx,serverActions.mdx,serverComponentsHmrCache.mdx,serverExternalPackages.mdx,staleTimes.mdx,staticGeneration.mdx,taint.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,turbopackFileSystemCache.mdx,turbopackIgnoreIssue.mdx,typedRoutes.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,viewTransition.mdx,webVitalsAttribution.mdx,webpack.mdx}|01-app/03-api-reference/05-config:{02-typescript.mdx,03-eslint.mdx}|01-app/03-api-reference/06-cli:{create-next-app.mdx,next.mdx}|02-pages/01-getting-started:{01-installation.mdx,02-project-structure.mdx,04-images.mdx,05-fonts.mdx,06-css.mdx,11-deploying.mdx}|02-pages/02-guides:{analytics.mdx,authentication.mdx,babel.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instrumentation.mdx,internationalization.mdx,lazy-loading.mdx,mdx.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,post-css.mdx,preview-mode.mdx,production-checklist.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,static-exports.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx}|02-pages/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|02-pages/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|02-pages/02-guides/upgrading:{codemods.mdx,version-10.mdx,version-11.mdx,version-12.mdx,version-13.mdx,version-14.mdx,version-9.mdx}|02-pages/03-building-your-application/01-routing:{01-pages-and-layouts.mdx,02-dynamic-routes.mdx,03-linking-and-navigating.mdx,05-custom-app.mdx,06-custom-document.mdx,07-api-routes.mdx,08-custom-error.mdx}|02-pages/03-building-your-application/02-rendering:{01-server-side-rendering.mdx,02-static-site-generation.mdx,04-automatic-static-optimization.mdx,05-client-side-rendering.mdx}|02-pages/03-building-your-application/03-data-fetching:{01-get-static-props.mdx,02-get-static-paths.mdx,03-forms-and-mutations.mdx,03-get-server-side-props.mdx,05-client-side.mdx}|02-pages/03-building-your-application/06-configuring:{12-error-handling.mdx}|02-pages/04-api-reference:{06-edge.mdx,08-turbopack.mdx}|02-pages/04-api-reference/01-components:{font.mdx,form.mdx,head.mdx,image-legacy.mdx,image.mdx,link.mdx,script.mdx}|02-pages/04-api-reference/02-file-conventions:{instrumentation.mdx,proxy.mdx,public-folder.mdx,src-folder.mdx}|02-pages/04-api-reference/03-functions:{get-initial-props.mdx,get-server-side-props.mdx,get-static-paths.mdx,get-static-props.mdx,next-request.mdx,next-response.mdx,use-params.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,userAgent.mdx}|02-pages/04-api-reference/04-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,assetPrefix.mdx,basePath.mdx,bundlePagesRouterDependencies.mdx,compress.mdx,crossOrigin.mdx,deploymentId.mdx,devIndicators.mdx,distDir.mdx,env.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,httpAgentOptions.mdx,images.mdx,logging.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,serverExternalPackages.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,webVitalsAttribution.mdx,webpack.mdx}|02-pages/04-api-reference/04-config:{01-typescript.mdx,02-eslint.mdx}|02-pages/04-api-reference/05-cli:{create-next-app.mdx,next.mdx}|03-architecture:{accessibility.mdx,fast-refresh.mdx,nextjs-compiler.mdx,supported-browsers.mdx}|04-community:{01-contribution-guide.mdx,02-rspack.mdx}<!-- NEXT-AGENTS-MD-END -->

## Internationalization

The Next.js app uses `next-intl` for internationalization, with `en.json`, `ko.json`, and `zh.json` files living at `apps/web/messages`. When building user-facing components, make sure to include proper translation support.
Expand All @@ -21,3 +19,11 @@ Canonical defaults (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-
### Domain docs

Single-context: one `CONTEXT.md` + `docs/adr/` at the repo root (created lazily by `/grill-with-docs`). See `docs/agents/domain.md`.

<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.

**Keep this block, including in commits.** It is part of the project's agent setup, maintained by `next dev` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated.
<!-- END:nextjs-agent-rules -->
2 changes: 2 additions & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const cspHeader = `
`;

const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
reactCompiler: true,
typedRoutes: true,
transpilePackages: ["@parsertime/transactional"],
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
"media-chrome": "^4.18.2",
"motion": "^12.23.26",
"nanoid": "^5.1.5",
"next": "16.2.9",
"next": "16.3.0-preview.5",
"next-axiom": "^1.9.2",
"next-intl": "^4.9.2",
"next-seo": "^7.2.0",
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/(auth)/auth-error/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Footer } from "@/components/footer";
import { getMetadataTranslations } from "@/lib/metadata-i18n";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";

export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("authError.metadata");
export function generateMetadata(): Metadata {
const t = getMetadataTranslations("authError.metadata");
return { title: t("title"), description: t("description") };
}

Expand Down
15 changes: 15 additions & 0 deletions apps/web/src/app/(auth)/auth-error/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Skeleton } from "@/components/ui/skeleton";

export default function Loading() {
return (
<div className="flex h-[90vh] flex-col items-center justify-center space-y-6 p-6">
<Skeleton className="h-9 w-64" />
<Skeleton className="h-4 w-[500px] max-w-full" />
<Skeleton className="h-4 w-[440px] max-w-full" />
<div className="flex space-x-4">
<Skeleton className="h-10 w-24" />
<Skeleton className="h-10 w-24" />
</div>
</div>
);
}
41 changes: 41 additions & 0 deletions apps/web/src/app/(auth)/sign-in/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Skeleton } from "@/components/ui/skeleton";

export default function Loading() {
return (
<div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
<div className="flex w-full max-w-sm flex-col gap-6">
<div className="flex items-center gap-2 self-center">
<Skeleton className="size-6 rounded-md" />
<Skeleton className="h-4 w-24" />
</div>
<div className="bg-card rounded-xl border shadow">
<div className="flex flex-col items-center gap-2 p-6 pb-0 text-center">
<Skeleton className="h-6 w-36" />
<Skeleton className="h-4 w-56" />
</div>
<div className="p-6">
<div className="grid gap-6">
<div className="flex flex-col gap-4">
{["discord", "google", "github"].map((k) => (
<Skeleton key={k} className="h-9 w-full rounded-md" />
))}
</div>
<div className="relative">
<Skeleton className="h-px w-full" />
</div>
<div className="grid gap-6">
<div className="grid gap-2">
<Skeleton className="h-4 w-10" />
<Skeleton className="h-9 w-full rounded-md" />
</div>
<Skeleton className="h-9 w-full rounded-md" />
<Skeleton className="mx-auto h-4 w-48" />
</div>
</div>
</div>
</div>
<Skeleton className="mx-auto h-3 w-64" />
</div>
</div>
);
}
18 changes: 5 additions & 13 deletions apps/web/src/app/(auth)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import { UserAuthForm } from "@/components/auth/user-auth-form";
import { Link } from "@/components/ui/link";
import { auth } from "@/lib/auth";
import type { PagePropsWithLocale } from "@/types/next";
import { defaultLocale } from "@/i18n/config";
import { getMetadataTranslations } from "@/lib/metadata-i18n";
import type { Metadata, Route } from "next";
import { getTranslations } from "next-intl/server";
import Image from "next/image";
import { redirect } from "next/navigation";

export async function generateMetadata(
props: PagePropsWithLocale<"/sign-in">
): Promise<Metadata> {
const params = await props.params;

const { locale } = params;

const t = await getTranslations({
locale,
namespace: "signInPage.metadataSignIn",
});
export function generateMetadata(): Metadata {
const t = getMetadataTranslations("signInPage.metadataSignIn");

return {
title: t("title"),
Expand All @@ -35,7 +27,7 @@ export async function generateMetadata(
height: 630,
},
],
locale,
locale: defaultLocale,
},
};
}
Expand Down
37 changes: 37 additions & 0 deletions apps/web/src/app/(auth)/sign-up/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Skeleton } from "@/components/ui/skeleton";

export default function Loading() {
return (
<div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
<div className="flex w-full max-w-sm flex-col gap-6">
<div className="flex items-center gap-2 self-center">
<Skeleton className="size-6 rounded-md" />
<Skeleton className="h-4 w-20" />
</div>
<div className="bg-card rounded-xl border shadow">
<div className="flex flex-col items-center gap-2 p-6 pb-4">
<Skeleton className="h-6 w-36" />
<Skeleton className="h-4 w-52" />
</div>
<div className="flex flex-col gap-6 p-6 pt-2">
<div className="flex flex-col gap-4">
{["a", "b", "c"].map((k) => (
<Skeleton key={k} className="h-9 w-full rounded-md" />
))}
</div>
<Skeleton className="h-px w-full" />
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2">
<Skeleton className="h-4 w-10" />
<Skeleton className="h-9 w-full rounded-md" />
</div>
<Skeleton className="h-9 w-full rounded-md" />
</div>
<Skeleton className="mx-auto h-4 w-48" />
</div>
</div>
<Skeleton className="mx-auto h-3 w-64" />
</div>
</div>
);
}
18 changes: 5 additions & 13 deletions apps/web/src/app/(auth)/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
import { UserAuthForm } from "@/components/auth/user-auth-form";
import { Link } from "@/components/ui/link";
import type { PagePropsWithLocale } from "@/types/next";
import { defaultLocale } from "@/i18n/config";
import { getMetadataTranslations } from "@/lib/metadata-i18n";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import Image from "next/image";

export async function generateMetadata(
props: PagePropsWithLocale<"/sign-up">
): Promise<Metadata> {
const params = await props.params;

const { locale } = params;

const t = await getTranslations({
locale,
namespace: "signInPage.metadataSignUp",
});
export function generateMetadata(): Metadata {
const t = getMetadataTranslations("signInPage.metadataSignUp");

return {
title: t("title"),
Expand All @@ -33,7 +25,7 @@ export async function generateMetadata(
height: 630,
},
],
locale,
locale: defaultLocale,
},
};
}
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/(auth)/verify-request/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Footer } from "@/components/footer";
import { getMetadataTranslations } from "@/lib/metadata-i18n";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";

export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("verifyRequest.metadata");
export function generateMetadata(): Metadata {
const t = getMetadataTranslations("verifyRequest.metadata");
return { title: t("title"), description: t("description") };
}

Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/app/(auth)/verify-request/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Skeleton } from "@/components/ui/skeleton";

export default function Loading() {
return (
<div className="flex h-[90vh] flex-col items-center justify-center space-y-6 p-6">
<Skeleton className="h-9 w-64" />
<Skeleton className="h-4 w-96 max-w-[600px]" />
<Skeleton className="h-10 w-32 rounded-md" />
</div>
);
}
84 changes: 84 additions & 0 deletions apps/web/src/app/[team]/compare/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Skeleton } from "@/components/ui/skeleton";

const FOUR = ["a", "b", "c", "d"];
const SIX = ["a", "b", "c", "d", "e", "f"];

export default function Loading() {
return (
<div className="flex-1 space-y-6 p-8 pt-6">
<div className="space-y-2">
<Skeleton className="h-9 w-56" />
<Skeleton className="h-4 w-80 max-w-full" />
</div>

<div className="rounded-xl border p-4">
<div className="flex items-center justify-between">
<div className="space-y-1.5">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-3.5 w-64 max-w-full" />
</div>
<div className="flex items-center gap-3">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-5 w-9 rounded-full" />
<Skeleton className="h-4 w-14" />
</div>
</div>
</div>

<div className="space-y-4">
<div className="rounded-xl border p-4">
<div className="flex items-center justify-between">
<div className="space-y-1.5">
<Skeleton className="h-4 w-36" />
<Skeleton className="h-3.5 w-60 max-w-full" />
</div>
<div className="flex items-center gap-3">
<Skeleton className="h-4 w-14" />
<Skeleton className="h-5 w-9 rounded-full" />
<Skeleton className="h-4 w-12" />
</div>
</div>
</div>

<div className="rounded-xl border">
<div className="flex items-center justify-between border-b px-6 py-4">
<Skeleton className="h-5 w-20" />
<Skeleton className="h-8 w-24 rounded-md" />
</div>
<div className="grid gap-4 p-6 lg:grid-cols-2">
<div className="space-y-2">
<Skeleton className="h-3.5 w-20" />
<Skeleton className="h-9 w-full rounded-md" />
</div>
<div className="space-y-2">
<Skeleton className="h-3.5 w-16" />
<Skeleton className="h-9 w-full rounded-md" />
</div>
</div>
</div>
</div>

<div className="space-y-4">
<div className="flex gap-1">
{SIX.map((k) => (
<Skeleton key={k} className="h-9 w-24 rounded-md" />
))}
</div>
<div className="space-y-4 pt-2">
<div className="grid gap-4 sm:grid-cols-2">
{FOUR.map((k) => (
<div key={k} className="rounded-xl border p-4">
<div className="space-y-3">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-8 w-20" />
<Skeleton className="h-3 w-24" />
</div>
</div>
))}
</div>
<Skeleton className="h-72 w-full rounded-xl" />
</div>
</div>
</div>
);
}
Loading
Loading