Skip to content

Commit

Permalink
Media upload modal (#8539)
Browse files Browse the repository at this point in the history
* check all folders for images

* add upload modal
  • Loading branch information
smallbrownbike authored May 28, 2024
1 parent 0c40ef5 commit 4fce2eb
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 7 deletions.
11 changes: 7 additions & 4 deletions gatsby/onCreateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ export const onPreInit: GatsbyNode['onPreInit'] = async function ({ actions }) {
await fetchCloudinaryImages()
}

function getPublicID(image: string) {
const imagePath = image.split('/upload/')[1]
return imagePath.substring(0, imagePath.lastIndexOf('.'))
}

export const onCreateNode: GatsbyNode['onCreateNode'] = async ({
node,
getNode,
Expand All @@ -107,9 +112,7 @@ export const onCreateNode: GatsbyNode['onCreateNode'] = async ({
const imageFields = ['featuredImage', 'thumbnail', 'logo', 'logoDark', 'icon']
imageFields.forEach((field) => {
if (node.frontmatter?.[field] && node.frontmatter?.[field].includes('res.cloudinary.com')) {
const publicId = `posthog.com/contents${
node.frontmatter?.[field].split('posthog.com/contents')[1].split('.')[0]
}`
const publicId = getPublicID(node.frontmatter?.[field])
const cloudinaryData = cloudinaryCache[publicId]
if (!cloudinaryData) {
console.warn(`Cloudinary data not found for ${publicId}`)
Expand All @@ -130,7 +133,7 @@ export const onCreateNode: GatsbyNode['onCreateNode'] = async ({
const images = node.frontmatter?.images
if (images?.length > 0) {
node.frontmatter.images = images.map((image) => {
const publicId = `posthog.com/contents${image.split('posthog.com/contents')[1].split('.')[0]}`
const publicId = getPublicID(image)
const cloudinaryData = cloudinaryCache[publicId]
if (!cloudinaryData) {
console.warn(`Cloudinary data not found for ${publicId}`)
Expand Down
21 changes: 18 additions & 3 deletions src/components/MainNav/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { layoutLogic } from '../../logic/layoutLogic'
import {
IconApp,
IconBrightness,
IconChat,
IconMessage,
IconSearch,
IconTextWidth,
IconUser,
IconChevronDown,
IconLetter,
IconUpload,
} from '@posthog/icons'

import { Placement } from '@popperjs/core'
Expand All @@ -32,6 +32,7 @@ import { useInView } from 'react-intersection-observer'
import { usePopper } from 'react-popper'
import getAvatarURL from 'components/Squeak/util/getAvatar'
import { StaticImage } from 'gatsby-plugin-image'
import MediaUploadModal from 'components/MediaUploadModal'

export const Avatar = (props: { className?: string; src?: string }) => {
return (
Expand Down Expand Up @@ -379,7 +380,6 @@ const Notifications = () => {

export const Main = () => {
const { user } = useUser()

const { open } = useSearch()
const {
menu,
Expand All @@ -395,6 +395,7 @@ export const Main = () => {
const { websiteTheme } = useValues(layoutLogic)
const { setWebsiteTheme } = useActions(layoutLogic)
const [posthogInstance, setPosthogInstance] = useState<string>()
const [mediaModalOpen, setMediaModalOpen] = useState(false)
const posthog = usePostHog()

useEffect(() => {
Expand All @@ -421,8 +422,11 @@ export const Main = () => {
}
}

const isModerator = user?.role?.type === 'moderator'

return (
<div>
<MediaUploadModal open={mediaModalOpen} setOpen={setMediaModalOpen} />
<div className="border-b border-light dark:border-dark bg-accent dark:bg-accent-dark mb-1">
<div
className={`flex mx-auto px-2 md:px-0 mdlg:px-5 justify-between transition-all ${
Expand Down Expand Up @@ -551,6 +555,17 @@ export const Main = () => {
My profile
</Link>
</li>
{isModerator && (
<li className="px-1">
<button
className="group/item flex items-center text-sm px-2 py-2 rounded-sm hover:bg-border dark:hover:bg-border-dark w-full"
onClick={() => setMediaModalOpen(true)}
>
<IconUpload className="opacity-50 inline-block w-6 group-hover/parent:opacity-75 mr-2" />
Upload media
</button>
</li>
)}
</>
)}
<li className="bg-border/20 dark:bg-border-dark/20 border-y border-light dark:border-dark text-[13px] px-2 py-1.5 !my-1 text-primary/50 dark:text-primary-dark/60 z-20 m-0 font-semibold">
Expand All @@ -571,7 +586,6 @@ export const Main = () => {
<Toggle checked={fullWidthContent} />
</button>
</li>
<Orders />
{pathname === '/' && (
<li className="px-1 whitespace-nowrap">
<button
Expand Down Expand Up @@ -621,6 +635,7 @@ export const Main = () => {
</button>
</li>
)}
<Orders />
</ul>
)
}}
Expand Down
121 changes: 121 additions & 0 deletions src/components/MediaUploadModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { IconCheck, IconCopy, IconUpload, IconX } from '@posthog/icons'
import Modal from 'components/Modal'
import uploadImage from 'components/Squeak/util/uploadImage'
import { useUser } from 'hooks/useUser'
import React, { useState } from 'react'
import { useDropzone } from 'react-dropzone'

const Image = ({ name, previewUrl, provider_metadata: { public_id, resource_type }, ext }) => {
const [copied, setCopied] = useState(false)
const mediaURL = `https://res.cloudinary.com/${process.env.GATSBY_CLOUDINARY_CLOUD_NAME}/${resource_type}/upload/${public_id}${ext}`
const handleClick = () => {
setCopied(true)
navigator.clipboard.writeText(mediaURL)
setTimeout(() => {
setCopied(false)
}, 3000)
}
return (
<li className="flex space-x-2 items-center">
<div className="overflow-hidden size-16 flex flex-shrink-0 justify-center items-center bg-accent dark:bg-accent-dark rounded-sm border border-border dark:border-dark">
<img src={resource_type === 'video' ? previewUrl : mediaURL} />
</div>
<div className="flex-grow line-clamp-1">
<p className="m-0 font-bold">{name}</p>
<div className="flex space-x-2">
<p className="text-sm line-clamp-1 m-0" title={mediaURL}>
{mediaURL}
</p>
<button onClick={handleClick} className="flex-shrink-0 size-5">
{copied ? <IconCheck className="text-green" /> : <IconCopy />}
</button>
</div>
</div>
</li>
)
}

export default function MediaUploadModal({ open, setOpen }) {
const { getJwt, user } = useUser()
const [loading, setLoading] = useState(0)
const [images, setImages] = useState([])
const isModerator = user?.role?.type === 'moderator'

const onDrop = async (acceptedFiles) => {
const profileID = user?.profile?.id
const jwt = await getJwt()
if (isModerator && profileID && jwt) {
await Promise.all(
acceptedFiles.map(async (file) => {
setLoading((loadingNumber) => loadingNumber + 1)
const uploadedImage = await uploadImage(file, jwt, {
field: 'images',
id: profileID,
type: 'api::profile.profile',
})
setLoading((loadingNumber) => loadingNumber - 1)
setImages((images) => [...images, uploadedImage])
})
).catch((err) => console.error(err))
}
}

const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })

return isModerator ? (
<Modal open={open} setOpen={setOpen}>
{open && (
<div className="max-w-6xl w-full mx-auto relative p-5 pt-12">
<div className="bg-white dark:bg-dark p-4 rounded-md border border-border dark:border-dark relative grid grid-cols-2 gap-x-6">
<button
onClick={() => setOpen(false)}
className="absolute right-0 top-0 bg-white dark:bg-accent-dark rounded-full p-2 border border-border dark:border-dark translate-x-1/2 -translate-y-1/2"
>
<IconX className="size-4 opacity-70 hover:opacity-100 click" />
</button>
<div className="flex flex-col">
<h3 className="m-0">Upload media</h3>
<p className="m-0 mt-1">
Add images or videos to our CDN (Cloudinary) that can be linked in docs or blog posts.
</p>
<div
{...getRootProps()}
className={`mt-4 flex-grow w-full rounded-md border border-dashed border-border dark:border-dark ${
isDragActive ? 'bg-accent/70 dark:bg-accent-dark/70' : ''
}`}
>
<p
className={`m-0 flex justify-center items-center font-bold space-x-1 h-full text-lg ${
isDragActive ? '' : 'opacity-70'
}`}
>
<IconUpload className="size-7" />
<span>{isDragActive ? 'Drop' : 'Drag'} media</span>
</p>
<input {...getInputProps()} />
</div>
</div>
<div>
<h3>Your uploads</h3>
<ul className="list-none m-0 p-0 space-y-2 overflow-auto pr-4 flex-grow max-h-[450px]">
{loading > 0 &&
Array.from({ length: loading }).map((_, index) => (
<li
key={index}
className="w-full h-20 bg-accent dark:bg-accent-dark rounded-md animate-pulse mt-2"
/>
))}
{images.map((image) => {
return <Image key={image.id} {...image} />
})}
{user?.profile?.images?.map((image) => {
return <Image key={image.id} {...image} />
})}
</ul>
</div>
</div>
</div>
)}
</Modal>
) : null
}
3 changes: 3 additions & 0 deletions src/hooks/useUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
populate: {
profile: {
populate: {
images: {
sort: ['createdAt:desc'],
},
avatar: true,
questionSubscriptions: {
filters: {
Expand Down

0 comments on commit 4fce2eb

Please sign in to comment.