Added a create-dashboard and sharable view dashboard page

This commit is contained in:
Pico2x 2022-08-26 12:10:14 +01:00
parent 5682e5aab2
commit 760a112c3b
4 changed files with 263 additions and 2 deletions

View File

@ -0,0 +1,46 @@
import { LinkIcon } from '@heroicons/react/outline'
import toast from 'react-hot-toast'
import { copyToClipboard } from 'web/lib/util/copy'
import { track } from 'web/lib/service/analytics'
import { Modal } from './layout/modal'
import { Col } from './layout/col'
import { Title } from './title'
import { Button } from './button'
import { TweetButton } from './tweet-button'
import { Row } from './layout/row'
export function ShareDashboardModal(props: {
shareUrl: string
isOpen: boolean
setOpen: (open: boolean) => void
}) {
const { isOpen, setOpen, shareUrl } = props
const linkIcon = <LinkIcon className="mr-2 h-6 w-6" aria-hidden="true" />
return (
<Modal open={isOpen} setOpen={setOpen} size="md">
<Col className="gap-4 rounded bg-white p-4">
<Title className="!mt-0 !mb-2" text="Share this dashboard" />
<Button
size="2xl"
color="gradient"
className={'mb-2 flex max-w-xs self-center'}
onClick={() => {
copyToClipboard(shareUrl)
toast.success('Link copied!', {
icon: linkIcon,
})
track('copy share link')
}}
>
{linkIcon} Copy link
</Button>
<Row className="z-0 justify-start gap-4 self-center">
<TweetButton className="self-start" tweetText={shareUrl} />
</Row>
</Col>
</Modal>
)
}

View File

@ -1,11 +1,18 @@
import { deleteDoc, doc, updateDoc } from 'firebase/firestore'
import {
deleteDoc,
doc,
getDocs,
query,
updateDoc,
where,
} from 'firebase/firestore'
import { Dashboard } from 'common/dashboard'
import { coll, getValue } from './utils'
export const dashboards = coll<Dashboard>('dashboards')
export function dashboardPath(dashboardSlug: string) {
return `/dashboard/${dashboardSlug}}`
return `/dashboard/${dashboardSlug}`
}
export function updateDashboard(
@ -22,3 +29,10 @@ export function deleteDashboard(dashboard: Dashboard) {
export function getDashboard(dashboardId: string) {
return getValue<Dashboard>(doc(dashboards, dashboardId))
}
export async function getDashboardBySlug(slug: string) {
const q = query(dashboards, where('slug', '==', slug))
const docs = (await getDocs(q)).docs
console.log(docs.length === 0 ? null : docs[0].data())
return docs.length === 0 ? null : docs[0].data()
}

View File

@ -0,0 +1,94 @@
import { useState } from 'react'
import { Spacer } from 'web/components/layout/spacer'
import { Page } from 'web/components/page'
import { Title } from 'web/components/title'
import Textarea from 'react-expanding-textarea'
import { useTracking } from 'web/hooks/use-tracking'
import { TextEditor, useTextEditor } from 'web/components/editor'
import { createDashboard } from 'web/lib/firebase/api'
import clsx from 'clsx'
import { useRouter } from 'next/router'
import { Dashboard } from 'common/dashboard'
import { dashboardPath } from 'web/lib/firebase/dashboards'
export default function CreateDashboard() {
useTracking('view create dashboards page')
const [name, setName] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const router = useRouter()
const { editor, upload } = useTextEditor({
max: 1000,
defaultValue: '',
disabled: isSubmitting,
})
async function saveDashboard(name: string) {
if (!editor) return
const newDashboard = {
name: name,
content: editor.getJSON(),
}
const result = await createDashboard(newDashboard).catch((e) => {
console.error(e)
return e
})
console.log(result.dashboard as Dashboard)
await router.push(dashboardPath((result.dashboard as Dashboard).slug))
}
return (
<Page>
<div className="mx-auto w-full max-w-2xl">
<div className="rounded-lg px-6 py-4 sm:py-0">
<Title className="!mt-0" text="Create a dashboard" />
<form>
<div className="form-control w-full">
<label className="label">
<span className="mb-1">
Name<span className={'text-red-700'}>*</span>
</span>
</label>
<Textarea
placeholder="e.g. Elon Mania Dashboard"
className="input input-bordered resize-none"
autoFocus
maxLength={100}
value={name}
onChange={(e) => setName(e.target.value || '')}
/>
<Spacer h={6} />
<label className="label">
<span className="mb-1">
Content<span className={'text-red-700'}>*</span>
</span>
</label>
<TextEditor editor={editor} upload={upload} />
<Spacer h={6} />
<button
type="submit"
className={clsx(
'btn btn-primary normal-case',
isSubmitting && 'loading disabled'
)}
disabled={isSubmitting || upload.isLoading}
onClick={async () => {
setIsSubmitting(true)
await saveDashboard(name)
setIsSubmitting(false)
}}
>
{isSubmitting ? 'Creating...' : 'Create a dashboard'}
</button>
</div>
</form>
<Spacer h={6} />
</div>
</div>
</Page>
)
}

View File

@ -0,0 +1,107 @@
import { Page } from 'web/components/page'
import { fromPropz, usePropz } from 'web/hooks/use-propz'
import { dashboardPath, getDashboardBySlug } from 'web/lib/firebase/dashboards'
import { Dashboard } from 'common/dashboard'
import { Title } from 'web/components/title'
import { Spacer } from 'web/components/layout/spacer'
import { Content } from 'web/components/editor'
import { UserLink } from 'web/components/user-page'
import { getUser, User } from 'web/lib/firebase/users'
import { ShareIcon } from '@heroicons/react/solid'
import clsx from 'clsx'
import { Button } from 'web/components/button'
import { useState } from 'react'
import { ShareDashboardModal } from 'web/components/share-dashboard-modal'
import { useRouter } from 'next/router'
import { Row } from 'web/components/layout/row'
import { Col } from 'web/components/layout/col'
import { ENV_CONFIG } from 'common/envs/constants'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
const { slugs } = props.params
const dashboard = await getDashboardBySlug(slugs[0])
const creatorPromise = dashboard ? getUser(dashboard.creatorId) : null
const creator = await creatorPromise
return {
props: {
dashboard: dashboard,
creator: creator,
},
revalidate: 60, // regenerate after a minute
}
}
export async function getStaticPaths() {
return { paths: [], fallback: 'blocking' }
}
export default function DashboardPage(props: {
dashboard: Dashboard
creator: User
}) {
props = usePropz(props, getStaticPropz) ?? {
dashboard: null,
}
const shareUrl = `https://${ENV_CONFIG.domain}${dashboardPath(
props?.dashboard.slug
)}`
const [isShareOpen, setShareOpen] = useState(false)
return (
<Page>
<div className="mx-auto w-full max-w-3xl ">
<Spacer h={1} />
<Title className="!mt-0" text={props.dashboard?.name ?? ''} />
<Row>
<Col className=" flex-1">
<div className={'items-right inline-flex'}>
<div className="mr-1 text-gray-500">Created by</div>
<UserLink
className="text-neutral"
name={props.creator.name}
username={props.creator.username}
/>
</div>
</Col>
<Col>
<Button
size="lg"
color="gray-white"
className={'flex'}
onClick={() => {
setShareOpen(true)
}}
>
<ShareIcon
className={clsx('mr-2 h-[24px] w-5')}
aria-hidden="true"
/>
Share
<ShareDashboardModal
isOpen={isShareOpen}
setOpen={setShareOpen}
shareUrl={shareUrl}
/>
</Button>
</Col>
</Row>
<Spacer h={1} />
<Spacer h={1} />
<div className="rounded-lg bg-white px-6 py-4 sm:py-0">
<div className="form-control w-full">
<Spacer h={6} />
<Content content={props.dashboard?.content ?? ''} />
<Spacer h={6} />
</div>
</div>
</div>
</Page>
)
}