Merge pull request #157 from sambecker/next-15-1

Upgrade to Next.js 15
This commit is contained in:
Sam Becker 2025-01-05 17:47:31 -06:00 committed by GitHub
commit 6e72c02769
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 2612 additions and 2685 deletions

View File

@ -45,7 +45,6 @@
"thephotoblog",
"trpc",
"Turbopack",
"undici",
"unnest",
"upstash",
"UsKSGcbt",

View File

@ -9,57 +9,58 @@
"analyze": "ANALYZE=true next build"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.62",
"@aws-sdk/client-s3": "3.658.1",
"@aws-sdk/s3-request-presigner": "3.658.1",
"@next/bundle-analyzer": "14.2.13",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@ai-sdk/openai": "^1.0.13",
"@aws-sdk/client-s3": "3.722.0",
"@aws-sdk/s3-request-presigner": "3.722.0",
"@next/bundle-analyzer": "15.1.3",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-visually-hidden": "^1.1.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.13",
"@types/node": "^22.7.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.5",
"@types/pg": "^8.11.10",
"@types/react": "18.3.9",
"@types/react-dom": "18.3.0",
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"@types/sanitize-html": "^2.13.0",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"@upstash/ratelimit": "^2.0.3",
"@vercel/analytics": "^1.3.1",
"@vercel/blob": "^0.24.0",
"@vercel/kv": "^2.0.0",
"@vercel/speed-insights": "^1.0.12",
"ai": "^3.4.4",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"@upstash/ratelimit": "^2.0.5",
"@vercel/analytics": "^1.4.1",
"@vercel/blob": "^0.27.0",
"@vercel/kv": "^3.0.0",
"@vercel/speed-insights": "^1.1.0",
"ai": "^4.0.26",
"autoprefixer": "10.4.20",
"camelcase-keys": "^9.1.3",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"cmdk": "^1.0.4",
"date-fns": "^4.1.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.13",
"eslint": "9.17.0",
"eslint-config-next": "15.1.3",
"exifr": "^7.1.3",
"framer-motion": "^11.8.0",
"framer-motion": "^11.15.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"nanoid": "^5.0.7",
"next": "14.2.13",
"next-auth": "5.0.0-beta.21",
"next-themes": "^0.3.0",
"pg": "^8.13.0",
"postcss": "8.4.47",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-icons": "^5.3.0",
"sanitize-html": "^2.13.0",
"nanoid": "^5.0.9",
"next": "15.1.3",
"next-auth": "5.0.0-beta.25",
"next-themes": "^0.4.4",
"pg": "^8.13.1",
"postcss": "8.4.49",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-icons": "^5.4.0",
"sanitize-html": "^2.14.0",
"sharp": "^0.33.5",
"sonner": "^1.5.0",
"swr": "^2.2.5",
"tailwindcss": "3.4.13",
"sonner": "^1.7.1",
"swr": "^2.3.0",
"tailwindcss": "3.4.17",
"ts-exif-parser": "^0.2.2",
"typescript": "5.6.2",
"undici": "^6.19.8",
"use-debounce": "^10.0.3"
"typescript": "5.7.2",
"use-debounce": "^10.0.4"
}
}

4922
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -46,13 +46,14 @@ export default function AdminAddAllUploads({
const router = useRouter();
const addedUploadCount = useRef(0);
const addUploadUrls = async (uploadUrls: string[]) => {
const addUploadUrls = async (uploadUrls: string[], isFinalBatch: boolean) => {
try {
const stream = await addAllUploadsAction({
uploadUrls,
tags: showTags ? tags : undefined,
takenAtLocal: generateLocalPostgresString(),
takenAtNaiveLocal: generateLocalNaivePostgresString(),
shouldRevalidateAllKeysAndPaths: isFinalBatch,
});
for await (const data of readStreamableValue(stream)) {
setButtonText(addedUploadCount.current === 0
@ -152,8 +153,11 @@ export default function AdminAddAllUploads({
const uploadsToAdd = storageUrls.slice();
try {
while (uploadsToAdd.length > 0) {
const nextBatch = uploadsToAdd
.splice(0, UPLOAD_BATCH_SIZE);
await addUploadUrls(
uploadsToAdd.splice(0, UPLOAD_BATCH_SIZE),
nextBatch,
uploadsToAdd.length === 0,
);
}
setButtonText('Complete');

View File

@ -11,10 +11,12 @@ import { blurImageFromUrl, resizeImageFromUrl } from '@/photo/server';
import { getNextImageUrlForManipulation } from '@/services/next-image';
export default async function PhotoEditPage({
params: { photoId },
params,
}: {
params: { photoId: string }
params: Promise<{ photoId: string }>
}) {
const { photoId } = await params;
const photo = await getPhotoNoStore(photoId, true);
if (!photo) { redirect(PATH_ADMIN); }

View File

@ -10,12 +10,14 @@ import AdminTagBadge from '@/admin/AdminTagBadge';
const MAX_PHOTO_TO_SHOW = 6;
interface Props {
params: { tag: string }
params: Promise<{ tag: string }>
}
export default async function PhotoPageEdit({
params: { tag: tagFromParams } }: Props
) {
params,
}: Props) {
const { tag: tagFromParams } = await params;
const tag = decodeURIComponent(tagFromParams);
const [

View File

@ -12,10 +12,12 @@ import {
export const maxDuration = 60;
interface Params {
params: { uploadPath: string }
params: Promise<{ uploadPath: string }>
}
export default async function UploadPage({ params: { uploadPath } }: Params) {
export default async function UploadPage({ params }: Params) {
const { uploadPath } = await params;
const {
blobId,
photoFormExif,

View File

@ -12,8 +12,10 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
export async function GET(
_: Request,
{ params: { key } }: { params: { key: string } },
{ params }: { params: Promise<{ key: string }> },
) {
const { key } = await params;
const session = await auth();
if (session?.user && key) {
const url = await getSignedUrl(

View File

@ -28,12 +28,14 @@ const getPhotosNearIdCachedCached = cache((
));
interface PhotoFilmSimulationProps {
params: { photoId: string, simulation: FilmSimulation }
params: Promise<{ photoId: string, simulation: FilmSimulation }>
}
export async function generateMetadata({
params: { photoId, simulation },
params,
}: PhotoFilmSimulationProps): Promise<Metadata> {
const { photoId, simulation } = await params;
const { photo } = await getPhotosNearIdCachedCached(photoId, simulation);
if (!photo) { return {}; }
@ -62,9 +64,11 @@ export async function generateMetadata({
}
export default async function PhotoFilmSimulationPage({
params: { photoId, simulation },
params,
children,
}: PhotoFilmSimulationProps & { children: ReactNode }) {
const { photoId, simulation } = await params;
const { photo, photos, photosGrid, indexNumber } =
await getPhotosNearIdCachedCached(photoId, simulation);

View File

@ -5,10 +5,12 @@ import { PATH_ROOT } from '@/site/paths';
import { redirect } from 'next/navigation';
export default async function Share({
params: { photoId, simulation },
params,
}: {
params: { photoId: string, simulation: FilmSimulation }
params: Promise<{ photoId: string, simulation: FilmSimulation }>
}) {
const { photoId, simulation } = await params;
const photo = await getPhotoCached(photoId);
if (!photo) { return redirect(PATH_ROOT); }

View File

@ -12,9 +12,9 @@ import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
export async function GET(
_: Request,
context: { params: { simulation: FilmSimulation } },
context: { params: Promise<{ simulation: FilmSimulation }> },
) {
const { simulation } = context.params;
const { simulation } = await context.params;
const [
photos,

View File

@ -9,12 +9,14 @@ const getPhotosFilmSimulationDataCachedCached =
cache(getPhotosFilmSimulationDataCached);
interface FilmSimulationProps {
params: { simulation: FilmSimulation }
params: Promise<{ simulation: FilmSimulation }>
}
export async function generateMetadata({
params: { simulation },
params,
}: FilmSimulationProps): Promise<Metadata> {
const { simulation } = await params;
const [
photos,
{ count, dateRange },
@ -48,8 +50,10 @@ export async function generateMetadata({
}
export default async function FilmSimulationPage({
params: { simulation },
params,
}: FilmSimulationProps) {
const { simulation } = await params;
const [
photos,
{ count, dateRange },

View File

@ -13,12 +13,14 @@ const getPhotosFilmSimulationDataCachedCached =
}));
interface FilmSimulationProps {
params: { simulation: FilmSimulation }
params: Promise<{ simulation: FilmSimulation }>
}
export async function generateMetadata({
params: { simulation },
params,
}: FilmSimulationProps): Promise<Metadata> {
const { simulation } = await params;
const [
photos,
{ count, dateRange },
@ -49,8 +51,10 @@ export async function generateMetadata({
}
export default async function Share({
params: { simulation },
params,
}: FilmSimulationProps) {
const { simulation } = await params;
const [
photos,
{ count, dateRange },

View File

@ -23,12 +23,14 @@ const getPhotosNearIdCachedCached = cache((photoId: string, focal: number) =>
));
interface PhotoFocalLengthProps {
params: { photoId: string, focal: string }
params: Promise<{ photoId: string, focal: string }>
}
export async function generateMetadata({
params: { photoId, focal: focalString },
params,
}: PhotoFocalLengthProps): Promise<Metadata> {
const { photoId, focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const { photo } = await getPhotosNearIdCachedCached(photoId, focal);
@ -59,9 +61,11 @@ export async function generateMetadata({
}
export default async function PhotoFocalLengthPage({
params: { photoId, focal: focalString },
params,
children,
}: PhotoFocalLengthProps & { children: ReactNode }) {
const { photoId, focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const { photo, photos, photosGrid, indexNumber } =

View File

@ -5,10 +5,12 @@ import { PATH_ROOT } from '@/site/paths';
import { redirect } from 'next/navigation';
export default async function Share({
params: { photoId, focal: focalString },
params,
}: {
params: { photoId: string, focal: string }
params: Promise<{ photoId: string, focal: string }>
}) {
const { photoId, focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const photo = await getPhotoCached(photoId);

View File

@ -12,9 +12,11 @@ import { getFocalLengthFromString } from '@/focal';
export async function GET(
_: Request,
context: { params: { focal: string } },
context: { params: Promise<{ focal: string }> },
) {
const focal = getFocalLengthFromString(context.params.focal);
const focalString = (await context.params).focal;
const focal = getFocalLengthFromString(focalString);
const [
photos,

View File

@ -14,12 +14,14 @@ const getPhotosFocalDataCachedCached = cache((focal: number) =>
}));
interface FocalLengthProps {
params: { focal: string }
params: Promise<{ focal: string }>
}
export async function generateMetadata({
params: { focal: focalString },
params,
}: FocalLengthProps): Promise<Metadata> {
const { focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const [
@ -54,8 +56,10 @@ export async function generateMetadata({
}
export default async function TagPage({
params: { focal: focalString },
params,
}:FocalLengthProps) {
const { focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const [

View File

@ -13,12 +13,14 @@ const getPhotosFocalLengthDataCachedCached = cache((focal: number) =>
}));
interface FocalLengthProps {
params: { focal: string }
params: Promise<{ focal: string }>
}
export async function generateMetadata({
params: { focal: focalString },
params,
}: FocalLengthProps): Promise<Metadata> {
const { focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const [
@ -51,8 +53,10 @@ export async function generateMetadata({
}
export default async function Share({
params: { focal: focalString },
params,
}: FocalLengthProps) {
const { focal: focalString } = await params;
const focal = getFocalLengthFromString(focalString);
const [

View File

@ -21,14 +21,16 @@ if (STATICALLY_OPTIMIZED_OG_IMAGES && IS_PRODUCTION) {
export async function GET(
_: Request,
context: { params: { photoId: string } },
context: { params: Promise<{ photoId: string }> },
) {
const { photoId } = await context.params;
const [
photo,
{ fontFamily, fonts },
headers,
] = await Promise.all([
getPhotoCached(context.params.photoId),
getPhotoCached(photoId),
getIBMPlexMonoMedium(),
getImageResponseCacheControlHeaders(),
]);

View File

@ -33,12 +33,13 @@ if (STATICALLY_OPTIMIZED_PAGES && IS_PRODUCTION) {
}
interface PhotoProps {
params: { photoId: string }
params: Promise<{ photoId: string }>
}
export async function generateMetadata({
params: { photoId },
params,
}:PhotoProps): Promise<Metadata> {
const { photoId } = await params;
const { photo } = await getPhotosNearIdCachedCached(photoId);
if (!photo) { return {}; }
@ -67,9 +68,10 @@ export async function generateMetadata({
}
export default async function PhotoPage({
params: { photoId },
params,
children,
}: PhotoProps & { children: ReactNode }) {
const { photoId } = await params;
const { photo, photos, photosGrid } =
await getPhotosNearIdCachedCached(photoId);

View File

@ -4,10 +4,12 @@ import { PATH_ROOT } from '@/site/paths';
import { redirect } from 'next/navigation';
export default async function Share({
params: { photoId },
params,
}: {
params: { photoId: string }
params: Promise<{ photoId: string }>
}) {
const { photoId } = await params;
const photo = await getPhotoCached(photoId);
if (!photo) { return redirect(PATH_ROOT); }

View File

@ -35,8 +35,10 @@ const getPhotosNearIdCachedCached = cache((
));
export async function generateMetadata({
params: { photoId, make, model },
params,
}: PhotoCameraProps): Promise<Metadata> {
const { photoId, make, model } = await params;
const { photo } = await getPhotosNearIdCachedCached(photoId, make, model);
if (!photo) { return {}; }
@ -68,9 +70,11 @@ export async function generateMetadata({
}
export default async function PhotoCameraPage({
params: { photoId, make, model },
params,
children,
}: PhotoCameraProps & { children: ReactNode }) {
const { photoId, make, model } = await params;
const { photo, photos, photosGrid, indexNumber } =
await getPhotosNearIdCachedCached(photoId, make, model);

View File

@ -5,8 +5,10 @@ import { PATH_ROOT } from '@/site/paths';
import { redirect } from 'next/navigation';
export default async function Share({
params: { photoId, make, model },
params,
}: PhotoCameraProps) {
const { photoId, make, model } = await params;
const photo = await getPhotoCached(photoId);
if (!photo) { return redirect(PATH_ROOT); }

View File

@ -13,7 +13,7 @@ export async function GET(
_: Request,
context: CameraProps,
) {
const camera = getCameraFromParams(context.params);
const camera = getCameraFromParams(await context.params);
const [
photos,

View File

@ -16,8 +16,10 @@ const getPhotosCameraDataCachedCached = cache((
));
export async function generateMetadata({
params: { make, model },
params,
}: CameraProps): Promise<Metadata> {
const { make, model } = await params;
const [
photos,
{ count, dateRange },
@ -49,8 +51,10 @@ export async function generateMetadata({
}
export default async function CameraPage({
params: { make, model },
params,
}: CameraProps) {
const { make, model } = await params;
const [
photos,
{ count, dateRange },

View File

@ -17,8 +17,10 @@ const getPhotosCameraDataCachedCached = cache((
));
export async function generateMetadata({
params: { make, model },
params,
}: CameraProps): Promise<Metadata> {
const { make, model } = await params;
const [
photos,
{ count, dateRange },
@ -49,7 +51,9 @@ export async function generateMetadata({
};
}
export default async function Share({ params: { make, model } }: CameraProps) {
export default async function Share({ params }: CameraProps) {
const { make, model } = await params;
const [
photos,
{ count, dateRange },

View File

@ -22,12 +22,14 @@ const getPhotosNearIdCachedCached = cache((photoId: string, tag: string) =>
));
interface PhotoTagProps {
params: { photoId: string, tag: string }
params: Promise<{ photoId: string, tag: string }>
}
export async function generateMetadata({
params: { photoId, tag },
params,
}: PhotoTagProps): Promise<Metadata> {
const { photoId, tag } = await params;
const { photo } = await getPhotosNearIdCachedCached(photoId, tag);
if (!photo) { return {}; }
@ -56,9 +58,10 @@ export async function generateMetadata({
}
export default async function PhotoTagPage({
params: { photoId, tag },
params,
children,
}: PhotoTagProps & { children: ReactNode }) {
const { photoId, tag } = await params;
const { photo, photos, photosGrid, indexNumber } =
await getPhotosNearIdCachedCached(photoId, tag);

View File

@ -4,10 +4,12 @@ import { PATH_ROOT } from '@/site/paths';
import { redirect } from 'next/navigation';
export default async function Share({
params: { photoId, tag },
params,
}: {
params: { photoId: string, tag: string }
params: Promise<{ photoId: string, tag: string }>
}) {
const { photoId, tag } = await params;
const photo = await getPhotoCached(photoId);
if (!photo) { return redirect(PATH_ROOT); }

View File

@ -10,9 +10,9 @@ import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
export async function GET(
_: Request,
context: { params: { tag: string } },
context: { params: Promise<{ tag: string }> },
) {
const { tag } = context.params;
const { tag } = await context.params;
const [
photos,

View File

@ -11,12 +11,14 @@ const getPhotosTagDataCachedCached = cache((tag: string) =>
getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_INITIAL}));
interface TagProps {
params: { tag: string }
params: Promise<{ tag: string }>
}
export async function generateMetadata({
params: { tag: tagFromParams },
params,
}: TagProps): Promise<Metadata> {
const { tag: tagFromParams } = await params;
const tag = decodeURIComponent(tagFromParams);
const [
@ -51,8 +53,10 @@ export async function generateMetadata({
}
export default async function TagPage({
params: { tag: tagFromParams },
params,
}:TagProps) {
const { tag: tagFromParams } = await params;
const tag = decodeURIComponent(tagFromParams);
const [

View File

@ -10,12 +10,14 @@ const getPhotosTagDataCachedCached = cache((tag: string) =>
getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_INITIAL }));
interface TagProps {
params: { tag: string }
params: Promise<{ tag: string }>
}
export async function generateMetadata({
params: { tag: tagFromParams },
params,
}: TagProps): Promise<Metadata> {
const { tag: tagFromParams } = await params;
const tag = decodeURIComponent(tagFromParams);
const [
@ -48,8 +50,10 @@ export async function generateMetadata({
}
export default async function Share({
params: { tag: tagFromParams },
params,
}: TagProps) {
const { tag: tagFromParams } = await params;
const tag = decodeURIComponent(tagFromParams);
const [

View File

@ -21,12 +21,14 @@ const getPhotosNearIdCachedCached = cache((photoId: string) =>
));
interface PhotoTagProps {
params: { photoId: string }
params: Promise<{ photoId: string }>
}
export async function generateMetadata({
params: { photoId },
params,
}: PhotoTagProps): Promise<Metadata> {
const { photoId } = await params;
const { photo } = await getPhotosNearIdCachedCached(photoId);
if (!photo) { return {}; }
@ -52,8 +54,10 @@ export async function generateMetadata({
}
export default async function PhotoTagHiddenPage({
params: { photoId },
params,
}: PhotoTagProps) {
const { photoId } = await params;
const { photo, photos, photosGrid, indexNumber } =
await getPhotosNearIdCachedCached(photoId);

View File

@ -3,9 +3,14 @@
import FieldSetWithStatus from '@/components/FieldSetWithStatus';
import Container from '@/components/Container';
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import {
useActionState,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { getAuthAction, signInAction } from './actions';
import { useFormState } from 'react-dom';
import ErrorNote from '@/components/ErrorNote';
import { KEY_CALLBACK_URL, KEY_CREDENTIALS_SIGN_IN_ERROR } from '.';
import { useSearchParams } from 'next/navigation';
@ -20,7 +25,7 @@ export default function SignInForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [response, action] = useFormState(signInAction, undefined);
const [response, action] = useActionState(signInAction, undefined);
const emailRef = useRef<HTMLInputElement>(null);
useLayoutEffect(() => {

View File

@ -43,7 +43,7 @@ export const signInAction = async (
export const signOutAndRedirectAction = async () =>
signOut({ redirectTo: PATH_ROOT });
export const getAuthAction = () => auth();
export const getAuthAction = async () => auth();
export const logClientAuthUpdate = (data: Session | null | undefined) =>
export const logClientAuthUpdate = async (data: Session | null | undefined) =>
console.log('Client auth update', data);

View File

@ -9,11 +9,11 @@ export type Camera = {
};
export interface CameraProps {
params: Camera
params: Promise<Camera>
}
export interface PhotoCameraProps {
params: Camera & { photoId: string }
params: Promise<Camera & { photoId: string }>
}
export type CameraWithCount = {

View File

@ -10,7 +10,7 @@ export default function HeaderList({
}: {
title?: string,
className?: string,
icon?: JSX.Element,
icon?: ReactNode,
items: ReactNode[]
}) {
return (

View File

@ -4,7 +4,7 @@ import Modal from '@/components/Modal';
import { TbPhotoShare } from 'react-icons/tb';
import { clsx } from 'clsx/lite';
import { BiCopy } from 'react-icons/bi';
import { ReactNode } from 'react';
import { JSX, ReactNode } from 'react';
import { shortenUrl } from '@/utility/url';
import { toastSuccess } from '@/toast';
import { PiXLogo } from 'react-icons/pi';

View File

@ -1,5 +1,5 @@
import { clsx } from 'clsx/lite';
import { RefObject } from 'react';
import { JSX, RefObject } from 'react';
/*
MAX WIDTHS
@ -19,7 +19,7 @@ export default function SiteGrid({
sideFirstOnMobile,
sideHiddenOnMobile,
}: {
containerRef?: RefObject<HTMLDivElement>
containerRef?: RefObject<HTMLDivElement | null>
className?: string
contentMain: JSX.Element
contentSide?: JSX.Element

View File

@ -1,6 +1,7 @@
import Link from 'next/link';
import { clsx } from 'clsx/lite';
import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config';
import { JSX } from 'react';
export default function SwitcherItem({
icon,

View File

@ -46,6 +46,11 @@ import { FaTag } from 'react-icons/fa';
import { formatCount, formatCountDescriptive } from '@/utility/string';
import CommandKItem from './CommandKItem';
import { GRID_HOMEPAGE_ENABLED } from '@/site/config';
import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog';
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
const DIALOG_TITLE = 'Global Command-K Menu';
const DIALOG_DESCRIPTION = 'For searching photos, views, and settings';
const LISTENER_KEYDOWN = 'keydown';
const MINIMUM_QUERY_LENGTH = 2;
@ -343,7 +348,6 @@ export default function CommandKClient({
<Command.Dialog
open={isOpen}
onOpenChange={setIsOpen}
label="Global Command Menu"
filter={(value, search, keywords) => {
const searchFormatted = search.trim().toLocaleLowerCase();
return (
@ -360,6 +364,10 @@ export default function CommandKClient({
>
<div className="space-y-1.5">
<div className="relative">
<VisuallyHidden.Root>
<DialogTitle>{DIALOG_TITLE}</DialogTitle>
<DialogDescription>{DIALOG_DESCRIPTION}</DialogDescription>
</VisuallyHidden.Root>
<Command.Input
onChangeCapture={(e) => setQueryLive(e.currentTarget.value)}
className={clsx(
@ -442,7 +450,7 @@ export default function CommandKClient({
if (path) {
if (path !== pathname) {
setKeyPending(key);
startTransition(async () => {
startTransition(() => {
shouldCloseAfterPending.current = true;
router.push(path, { scroll: true });
});

View File

@ -9,11 +9,11 @@ export type Lens = {
};
export interface LensProps {
params: Lens
params: Promise<Lens>
}
export interface PhotoLensProps {
params: Lens & { photoId: string }
params: Promise<Lens & { photoId: string }>
}
export type LensWithCount = {

View File

@ -10,6 +10,7 @@ import { TAG_HIDDEN } from '@/tag';
import HiddenHeader from '@/tag/HiddenHeader';
import FocalLengthHeader from '@/focal/FocalLengthHeader';
import PhotoHeader from './PhotoHeader';
import { JSX } from 'react';
export default function PhotoDetailPage({
photo,

View File

@ -7,6 +7,7 @@ import AnimateItems from '@/components/AnimateItems';
import { GRID_ASPECT_RATIO } from '@/site/config';
import { useAppState } from '@/state/AppState';
import SelectTileOverlay from '@/components/SelectTileOverlay';
import { JSX } from 'react';
export default function PhotoGrid({
photos,

View File

@ -5,7 +5,7 @@ import PhotoGrid from './PhotoGrid';
import PhotoGridInfinite from './PhotoGridInfinite';
import { clsx } from 'clsx/lite';
import AnimateItems from '@/components/AnimateItems';
import { ComponentProps, useCallback, useState } from 'react';
import { JSX, ComponentProps, useCallback, useState } from 'react';
export default function PhotoGridContainer({
cacheKey,

View File

@ -16,7 +16,10 @@ import { useAppState } from '@/state/AppState';
import { useMemo } from 'react';
import HiddenTag from '@/tag/HiddenTag';
import { SITE_ABOUT } from '@/site/config';
import { htmlHasBrParagraphBreaks, safelyParseFormattedHtml } from '@/utility/html';
import {
htmlHasBrParagraphBreaks,
safelyParseFormattedHtml,
} from '@/utility/html';
import { clsx } from 'clsx/lite';
export default function PhotoGridSidebar({

View File

@ -49,6 +49,7 @@ import { createStreamableValue } from 'ai/rsc';
import { convertUploadToPhoto } from './storage';
import { UrlAddStatus } from '@/admin/AdminUploadsClient';
import { convertStringToArray } from '@/utility/string';
import { after } from 'next/server';
// Private actions
@ -77,11 +78,13 @@ export const addAllUploadsAction = async ({
tags,
takenAtLocal,
takenAtNaiveLocal,
shouldRevalidateAllKeysAndPaths = true,
}: {
uploadUrls: string[]
tags?: string
takenAtLocal: string
takenAtNaiveLocal: string
shouldRevalidateAllKeysAndPaths?: boolean
}) =>
runAuthenticatedAdminServerAction(async () => {
const PROGRESS_TASK_COUNT = AI_TEXT_GENERATION_ENABLED ? 5 : 4;
@ -169,10 +172,13 @@ export const addAllUploadsAction = async ({
// eslint-disable-next-line max-len
stream.error(`${error.message} (${addedUploadUrls.length} of ${uploadUrls.length} photos successfully added)`);
}
revalidateAllKeysAndPaths();
stream.done();
})();
if (shouldRevalidateAllKeysAndPaths) {
after(revalidateAllKeysAndPaths)
}
return stream.value;
});
@ -205,7 +211,7 @@ export const updatePhotoAction = async (formData: FormData) =>
redirect(PATH_ADMIN_PHOTOS);
});
export const tagMultiplePhotosAction = (
export const tagMultiplePhotosAction = async (
tags: string,
photoIds: string[],
) =>

View File

@ -61,7 +61,7 @@ export default function useAiImageQueries(
const request = useCallback(async (
fields = ALL_AI_AUTO_GENERATED_FIELDS,
) => {
if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV !== 'production') {
console.log('RUNNING AI QUERIES', fields);
}
hasRunAllQueriesOnce.current = true;

View File

@ -282,7 +282,11 @@ export default function PhotoForm({
? createPhotoAction
: updatePhotoAction
)(data)
.catch(e => setFormActionErrorMessage(e.message))}
.catch(e => {
if (e.message !== 'NEXT_REDIRECT') {
setFormActionErrorMessage(e.message);
}
})}
onSubmit={() => {
setFormActionErrorMessage('');
(document.activeElement as HTMLElement)?.blur?.();

View File

@ -9,6 +9,7 @@ import { cleanUpAiTextResponse } from '@/photo/ai';
const RATE_LIMIT_IDENTIFIER = 'openai-image-query';
const RATE_LIMIT_MAX_QUERIES_PER_HOUR = 100;
const MODEL = 'gpt-4o';
const openai = AI_TEXT_GENERATION_ENABLED
? createOpenAI({ apiKey: process.env.OPENAI_SECRET_KEY })
@ -45,7 +46,7 @@ const getImageTextArgs = (
Parameters<typeof streamText>[0] &
Parameters<typeof generateText>[0]
) | undefined => openai ? {
model: openai('gpt-4o'),
model: openai(MODEL),
messages: [{
'role': 'user',
'content': [
@ -102,7 +103,7 @@ export const testOpenAiConnection = async () => {
if (openai) {
return generateText({
model: openai('gpt-4o'),
model: openai(MODEL),
messages: [{
'role': 'user',
'content': [

View File

@ -5,7 +5,7 @@ import { cwd } from 'process';
const FONT_FAMILY_IBM_PLEX_MONO = 'IBMPlexMono';
const getFontData = async () => {
let data: ArrayBuffer;
let data;
if (typeof fs !== 'undefined') {
data = fs.readFileSync(path.join(
cwd(),

View File

@ -4,8 +4,8 @@ import { usePathname } from 'next/navigation';
const usePathnames = () => {
const pathname = usePathname();
const currentRef = useRef<string>();
const previousRef = useRef<string>();
const currentRef = useRef('');
const previousRef = useRef('');
useEffect(() => {
previousRef.current = currentRef.current;