Skip to content

Commit

Permalink
Merge pull request #11 from dumpsayamrat/gallery
Browse files Browse the repository at this point in the history
Add gallery page with admin panel
  • Loading branch information
dumpsayamrat authored Nov 25, 2023
2 parents 768bbcc + 44c23a1 commit 5384ec9
Show file tree
Hide file tree
Showing 49 changed files with 3,300 additions and 257 deletions.
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
package.json
package-lock.json
public
node_modules
.contentlayer
.next
.vercel
15 changes: 13 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ const { withContentlayer } = require('next-contentlayer')
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
experimental: {
serverActions: true,
images: {
imageSizes: [200, 400, 1050],
remotePatterns: [
{
protocol: 'https',
hostname: `${
process.env.AWS_BUCKET || 'me-blog'
}.s3.ap-southeast-1.wasabisys.com`,
port: '',
pathname: '/**',
},
],
minimumCacheTTL: 31536000,
},
}

Expand Down
2,010 changes: 1,792 additions & 218 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,36 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "NODE_OPTIONS='--dns-result-order=ipv4first' next dev -p 8080",
"build": "next build",
"postbuild": "node src/scripts/rss.mjs && echo 'Build process is completed'",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.441.0",
"@aws-sdk/s3-request-presigner": "^3.441.0",
"@vercel/kv": "^0.2.3",
"clsx": "^2.0.0",
"contentlayer": "^0.3.4",
"date-fns": "^2.30.0",
"feed": "^4.2.2",
"framer-motion": "^10.16.4",
"next": "13.5.6",
"nanoid": "^5.0.3",
"next": "^14.0.1",
"next-auth": "^4.24.4",
"next-contentlayer": "^0.3.4",
"next-themes": "^0.2.1",
"react": "^18",
"react-dom": "^18",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-wrap-balancer": "^1.1.0",
"rehype-autolink-headings": "^7.0.0",
"rehype-pretty-code": "^0.10.1",
"rehype-slug": "^6.0.0",
"remark-gfm": "^3.0.1",
"sass": "^1.69.4",
"server-only": "^0.0.1"
"server-only": "^0.0.1",
"stackblur-canvas": "^2.6.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
Expand All @@ -36,7 +41,7 @@
"@types/react-dom": "^18",
"autoprefixer": "^10",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"eslint-config-next": "^14.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.3",
"typescript": "^5"
Expand Down
Binary file added public/images/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/images/site.webmanifest
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"Iconik IA Icons","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
{"name":"dumpsayamrat's Icons","short_name":"","icons":[{"src":"android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
107 changes: 107 additions & 0 deletions src/app/(auth-needed)/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use client'
import ThemeSwitcher from '@/components/ThemeSwitcher'
import clsx from 'clsx'
import Link from 'next/link'
import { usePathname } from 'next/navigation'

export default function AdminLayout({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
return (
<>
<button
data-drawer-target="default-sidebar"
data-drawer-toggle="default-sidebar"
aria-controls="default-sidebar"
type="button"
className="inline-flex items-center p-2 mt-2 ml-3 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
>
<span className="sr-only">Open sidebar</span>
<svg
className="w-6 h-6"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
fillRule="evenodd"
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"
></path>
</svg>
</button>

<aside
id="default-sidebar"
className="fixed top-0 left-0 z-40 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0"
aria-label="Sidebar"
>
<div className="h-full px-3 py-4 overflow-y-auto bg-gray-50 dark:bg-gray-800">
<ul className="space-y-2 font-medium">
<li>
<Link
href="/admin/upload"
className="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group"
>
<svg
className={clsx(
'flex-shrink-0 w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white',
{
'text-gray-900 dark:text-white':
pathname.includes('/upload'),
}
)}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 18 18"
>
<path d="M6.143 0H1.857A1.857 1.857 0 0 0 0 1.857v4.286C0 7.169.831 8 1.857 8h4.286A1.857 1.857 0 0 0 8 6.143V1.857A1.857 1.857 0 0 0 6.143 0Zm10 0h-4.286A1.857 1.857 0 0 0 10 1.857v4.286C10 7.169 10.831 8 11.857 8h4.286A1.857 1.857 0 0 0 18 6.143V1.857A1.857 1.857 0 0 0 16.143 0Zm-10 10H1.857A1.857 1.857 0 0 0 0 11.857v4.286C0 17.169.831 18 1.857 18h4.286A1.857 1.857 0 0 0 8 16.143v-4.286A1.857 1.857 0 0 0 6.143 10Zm10 0h-4.286A1.857 1.857 0 0 0 10 11.857v4.286c0 1.026.831 1.857 1.857 1.857h4.286A1.857 1.857 0 0 0 18 16.143v-4.286A1.857 1.857 0 0 0 16.143 10Z" />
</svg>
<span className="flex-1 ml-3 whitespace-nowrap">Upload</span>
</Link>
</li>
<li>
<div className="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
<ThemeSwitcher showText />
</div>
</li>
<li>
<Link
href="/api/auth/signout"
className="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group"
>
<svg
className="flex-shrink-0 w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 16"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1 8h11m0 0L8 4m4 4-4 4m4-11h3a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-3"
/>
</svg>
<span className="flex-1 ml-3 whitespace-nowrap">Sign out</span>
</Link>
</li>
</ul>
</div>
</aside>

<div className="p-4 sm:ml-64">
<div className="p-4 border-2 border-gray-200 border-dashed rounded-lg dark:border-gray-700">
{children}
</div>
</div>
</>
)
}
30 changes: 30 additions & 0 deletions src/app/(auth-needed)/admin/upload/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use server'

import { kv } from '@vercel/kv'
import { PhotoFormData } from '@/type'
import {
generateBlobPhotoPrefix,
generateID,
generatePhotoKey,
} from '@/utils/common'
import { convertFormDataToPhoto } from '@/utils/photo'
import { copy } from '@/s3'

export const savePhoto = async (formData: FormData): Promise<void> => {
const {
fileName,
fileURL,
submit: _unused,
...data
} = Object.fromEntries(formData) as PhotoFormData
const id = generateID()
const blobName = generateBlobPhotoPrefix(`${id}.${fileName.split('.')[1]}`)
await copy(fileURL, blobName)
const photo = convertFormDataToPhoto({
...data,
url: blobName,
id,
createdAt: Date.now(),
} as PhotoFormData)
await kv.hmset(generatePhotoKey(id), photo)
}
13 changes: 13 additions & 0 deletions src/app/(auth-needed)/admin/upload/list/action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

import { generatePhotoKey } from '@/utils/common'
import { kv } from '@vercel/kv'

export const getPhotoList = async () => {
const keys = await kv.keys(generatePhotoKey('*'))
return Promise.all(
keys.map(key =>
kv.hmget(key, ...['id', 'title', 'takenAt', 'createdAt', 'url'])
)
)
}
27 changes: 27 additions & 0 deletions src/app/(auth-needed)/admin/upload/list/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getPhotoList } from './action'
import ResetDevelopmentButton from '@/components/photo/ResetDevelopmentButton'

const ListPage = async () => {
const photos = await getPhotoList()
return (
<>
<div>
<span>{photos.length} photos</span>
<div>
<ResetDevelopmentButton />
</div>
{photos.map((photo: any) => (
<ul key={photo.id} className="py-3">
{Object.keys(photo).map((key, index) => (
<li key={index}>
<strong>{key}:</strong> {photo[key]}
</li>
))}
</ul>
))}
</div>
</>
)
}

export default ListPage
Loading

0 comments on commit 5384ec9

Please sign in to comment.