Added a create-dashboard and sharable view dashboard page
This commit is contained in:
parent
5682e5aab2
commit
760a112c3b
46
web/components/share-dashboard-modal.tsx
Normal file
46
web/components/share-dashboard-modal.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
94
web/pages/create-dashboard.tsx
Normal file
94
web/pages/create-dashboard.tsx
Normal 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>
|
||||
)
|
||||
}
|
107
web/pages/dashboard/[...slugs]/index.tsx
Normal file
107
web/pages/dashboard/[...slugs]/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user