commit
6e72c02769
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -45,7 +45,6 @@
|
||||
"thephotoblog",
|
||||
"trpc",
|
||||
"Turbopack",
|
||||
"undici",
|
||||
"unnest",
|
||||
"upstash",
|
||||
"UsKSGcbt",
|
||||
|
||||
79
package.json
79
package.json
@ -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
4922
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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');
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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 } =
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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(),
|
||||
]);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -13,7 +13,7 @@ export async function GET(
|
||||
_: Request,
|
||||
context: CameraProps,
|
||||
) {
|
||||
const camera = getCameraFromParams(context.params);
|
||||
const camera = getCameraFromParams(await context.params);
|
||||
|
||||
const [
|
||||
photos,
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -10,7 +10,7 @@ export default function HeaderList({
|
||||
}: {
|
||||
title?: string,
|
||||
className?: string,
|
||||
icon?: JSX.Element,
|
||||
icon?: ReactNode,
|
||||
items: ReactNode[]
|
||||
}) {
|
||||
return (
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 });
|
||||
});
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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[],
|
||||
) =>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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?.();
|
||||
|
||||
@ -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': [
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user