diff --git a/.vscode/settings.json b/.vscode/settings.json
index 27747e68..e664ce2a 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -14,6 +14,7 @@
"favs",
"ghijklmnopqrstuv",
"Hasselblad",
+ "headlessui",
"hgetall",
"hset",
"Lightbox",
diff --git a/package.json b/package.json
index 53c6716b..b5e37b83 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@aws-sdk/client-s3": "3.485.0",
"@aws-sdk/s3-request-presigner": "3.485.0",
+ "@headlessui/react": "2.0.0-alpha.4",
"@next/bundle-analyzer": "14.0.4",
"@tailwindcss/forms": "^0.5.7",
"@testing-library/jest-dom": "^6.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b6162f91..4ab6cca2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,9 @@ dependencies:
'@aws-sdk/s3-request-presigner':
specifier: 3.485.0
version: 3.485.0
+ '@headlessui/react':
+ specifier: 2.0.0-alpha.4
+ version: 2.0.0-alpha.4(react-dom@18.2.0)(react@18.2.0)
'@next/bundle-analyzer':
specifier: 14.0.4
version: 14.0.4
@@ -1159,6 +1162,62 @@ packages:
engines: {node: '>=14'}
dev: false
+ /@floating-ui/core@1.5.3:
+ resolution: {integrity: sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==}
+ dependencies:
+ '@floating-ui/utils': 0.2.1
+ dev: false
+
+ /@floating-ui/dom@1.5.4:
+ resolution: {integrity: sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==}
+ dependencies:
+ '@floating-ui/core': 1.5.3
+ '@floating-ui/utils': 0.2.1
+ dev: false
+
+ /@floating-ui/react-dom@2.0.5(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@floating-ui/dom': 1.5.4
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@floating-ui/react@0.26.5(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-LJeSQa+yOwV0Tdpc/C3Vr92QMrwRqRMTk4yOwsRJKc57x3Lcw317GE0EV+ECM7+Z89yEAPBe7nzbDEWfkWCrBA==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@floating-ui/react-dom': 2.0.5(react-dom@18.2.0)(react@18.2.0)
+ '@floating-ui/utils': 0.2.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ tabbable: 6.2.0
+ dev: false
+
+ /@floating-ui/utils@0.2.1:
+ resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
+ dev: false
+
+ /@headlessui/react@2.0.0-alpha.4(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-spykSTXskDUYjSFhdId97Bqclo1F9Ky2pgLmyNKdV4f7aRDncdc/mjMPx67eEWRN2xQobksaUCcnn5K/AcRXsg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16 || ^17 || ^18
+ react-dom: ^16 || ^17 || ^18
+ dependencies:
+ '@floating-ui/react': 0.26.5(react-dom@18.2.0)(react@18.2.0)
+ '@react-aria/focus': 3.16.0(react@18.2.0)
+ '@react-aria/interactions': 3.0.0-nightly.2584(react@18.2.0)
+ '@tanstack/react-virtual': 3.0.0-beta.60(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@humanwhocodes/config-array@0.11.13:
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
engines: {node: '>=10.10.0'}
@@ -1593,6 +1652,123 @@ packages:
resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==}
dev: false
+ /@react-aria/focus@3.16.0(react@18.2.0):
+ resolution: {integrity: sha512-GP6EYI07E8NKQQcXHjpIocEU0vh0oi0Vcsd+/71fKS0NnTR0TUOEeil0JuuQ9ymkmPDTu51Aaaa4FxVsuN/23A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/interactions': 3.20.1(react@18.2.0)
+ '@react-aria/utils': 3.23.0(react@18.2.0)
+ '@react-types/shared': 3.22.0(react@18.2.0)
+ '@swc/helpers': 0.5.2
+ clsx: 2.1.0
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/interactions@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-6DqYQx8XnbCfIen33uLz4kdgevrXLW6aoxsBOTY/Mzq9n0LHzbG/5H87obrOxRNVYh62RcQolo/qfqEpXZ7bVA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1-nightly.4295(react@18.2.0)
+ '@react-aria/utils': 3.0.0-nightly.2584(react@18.2.0)
+ '@react-types/shared': 3.0.0-nightly.2584(react@18.2.0)
+ '@swc/helpers': 0.5.2
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/interactions@3.20.1(react@18.2.0):
+ resolution: {integrity: sha512-PLNBr87+SzRhe9PvvF9qvzYeP4ofTwfKSorwmO+hjr3qoczrSXf4LRQlb27wB6hF10C7ZE/XVbUI1lj4QQrZ/g==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1(react@18.2.0)
+ '@react-aria/utils': 3.23.0(react@18.2.0)
+ '@react-types/shared': 3.22.0(react@18.2.0)
+ '@swc/helpers': 0.5.2
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/ssr@3.9.1(react@18.2.0):
+ resolution: {integrity: sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.2
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/ssr@3.9.1-nightly.4295(react@18.2.0):
+ resolution: {integrity: sha512-cv0+RaS3LJeZiSJ4pVGqSAyiyL+rieLiR3ctyoU7EwkArY1W7fI3NSkMEbNhHe4YoqqjPy1ZzAcpSA11EceiBg==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.2
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/utils@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-A6NP3Yc9MMA+PiRBMTpMlx5plaiK7ejl3cppdkKiNPHtFmZrzxn6o9WHth4NToqIUkJRWHIrpTK8a/gBgVFPOg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1-nightly.4295(react@18.2.0)
+ '@react-stately/utils': 3.0.0-nightly.2584(react@18.2.0)
+ '@react-types/shared': 3.0.0-nightly.2584(react@18.2.0)
+ '@swc/helpers': 0.5.2
+ clsx: 1.2.1
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/utils@3.23.0(react@18.2.0):
+ resolution: {integrity: sha512-fJA63/VU4iQNT8WUvrmll3kvToqMurD69CcgVmbQ56V7ZbvlzFi44E7BpnoaofScYLLtFWRjVdaHsohT6O/big==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1(react@18.2.0)
+ '@react-stately/utils': 3.9.0(react@18.2.0)
+ '@react-types/shared': 3.22.0(react@18.2.0)
+ '@swc/helpers': 0.5.2
+ clsx: 2.1.0
+ react: 18.2.0
+ dev: false
+
+ /@react-stately/utils@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-UOW2P+H3O7goB1mNEIwUdxr28CVHrKKvi+N1CQ0TGDwr+Bp6oIZK2aXE6aQluzgwZ36aRvLPW5dAoovpzTTcQQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.2
+ react: 18.2.0
+ dev: false
+
+ /@react-stately/utils@3.9.0(react@18.2.0):
+ resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.2
+ react: 18.2.0
+ dev: false
+
+ /@react-types/shared@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-SVqvg7B3rtzN1ypQni5g6sfpUNf4wODRDtiOalBFSJ02YuaUIr7gXVjafPYIXOC1BkJbZtPun/Pv4mCwNHFNbA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
+ /@react-types/shared@3.22.0(react@18.2.0):
+ resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/@rushstack/eslint-patch@1.6.1:
resolution: {integrity: sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==}
dev: false
@@ -2127,6 +2303,19 @@ packages:
tailwindcss: 3.4.1
dev: false
+ /@tanstack/react-virtual@3.0.0-beta.60(react@18.2.0):
+ resolution: {integrity: sha512-F0wL9+byp7lf/tH6U5LW0ZjBqs+hrMXJrj5xcIGcklI0pggvjzMNW9DdIBcyltPNr6hmHQ0wt8FDGe1n1ZAThA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ '@tanstack/virtual-core': 3.0.0-beta.60
+ react: 18.2.0
+ dev: false
+
+ /@tanstack/virtual-core@3.0.0-beta.60:
+ resolution: {integrity: sha512-QlCdhsV1+JIf0c0U6ge6SQmpwsyAT0oQaOSZk50AtEeAyQl9tQrd6qCHAslxQpgphrfe945abvKG8uYvw3hIGA==}
+ dev: false
+
/@testing-library/dom@9.3.3:
resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==}
engines: {node: '>=14'}
@@ -2996,6 +3185,11 @@ packages:
wrap-ansi: 7.0.0
dev: false
+ /clsx@1.2.1:
+ resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
+ engines: {node: '>=6'}
+ dev: false
+
/clsx@2.1.0:
resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==}
engines: {node: '>=6'}
@@ -6305,6 +6499,10 @@ packages:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: false
+ /tabbable@6.2.0:
+ resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+ dev: false
+
/tailwindcss@3.4.1:
resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
engines: {node: '>=14.0.0'}
diff --git a/src/admin/AdminPhotoMenu.tsx b/src/admin/AdminPhotoMenu.tsx
new file mode 100644
index 00000000..82126b4e
--- /dev/null
+++ b/src/admin/AdminPhotoMenu.tsx
@@ -0,0 +1,10 @@
+import { authCached } from '@/cache';
+import AdminPhotoMenuClient, { AdminPhotoMenuClientProps }
+ from './AdminPhotoMenuClient';
+
+export default async function AdminPhotoMenu(props: AdminPhotoMenuClientProps) {
+ const session = await authCached();
+ return Boolean(session?.user?.email)
+ ?