diff --git a/web/components/share-dashboard-modal.tsx b/web/components/share-dashboard-modal.tsx new file mode 100644 index 00000000..c52f372c --- /dev/null +++ b/web/components/share-dashboard-modal.tsx @@ -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 = + + return ( + + + + { + copyToClipboard(shareUrl) + toast.success('Link copied!', { + icon: linkIcon, + }) + track('copy share link') + }} + > + {linkIcon} Copy link + + + + + + + + ) +} diff --git a/web/lib/firebase/dashboards.ts b/web/lib/firebase/dashboards.ts index 8bcdf0da..f231b784 100644 --- a/web/lib/firebase/dashboards.ts +++ b/web/lib/firebase/dashboards.ts @@ -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('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(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() +} diff --git a/web/pages/create-dashboard.tsx b/web/pages/create-dashboard.tsx new file mode 100644 index 00000000..c27f8834 --- /dev/null +++ b/web/pages/create-dashboard.tsx @@ -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 ( + + + + + + + + + + Name* + + + setName(e.target.value || '')} + /> + + + + Content* + + + + + + { + setIsSubmitting(true) + await saveDashboard(name) + setIsSubmitting(false) + }} + > + {isSubmitting ? 'Creating...' : 'Create a dashboard'} + + + + + + + + ) +} diff --git a/web/pages/dashboard/[...slugs]/index.tsx b/web/pages/dashboard/[...slugs]/index.tsx new file mode 100644 index 00000000..8d4728b9 --- /dev/null +++ b/web/pages/dashboard/[...slugs]/index.tsx @@ -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 ( + + + + + + + + Created by + + + + + { + setShareOpen(true) + }} + > + + Share + + + + + + + + + + + + + + + + + ) +}