Skip to content

Commit

Permalink
Ingawei/dashboard page (manifoldmarkets#2035)
Browse files Browse the repository at this point in the history
* dashboard pages
  • Loading branch information
ingawei authored Sep 13, 2023
1 parent fdb6a84 commit a93351e
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 8 deletions.
2 changes: 2 additions & 0 deletions backend/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import { getsimilargroupstocontract } from 'api/get-similar-groups-to-contract'
import { followUser } from './follow-user'
import { report } from './report'
import { createdashboard } from './create-dashboard'
import { getyourdashboards } from './get-your-dashboards'

const allowCors: RequestHandler = cors({
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_VERCEL, CORS_ORIGIN_LOCALHOST],
Expand Down Expand Up @@ -207,6 +208,7 @@ app.post('/follow-user', ...apiRoute(followUser))
app.post('/report', ...apiRoute(report))

app.post('/createdashboard', ...apiRoute(createdashboard))
app.post('/getyourdashboards', ...apiRoute(getyourdashboards))

// Catch 404 errors - this should be the last route
app.use(allowCors, (req, res) => {
Expand Down
19 changes: 16 additions & 3 deletions backend/api/src/create-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,25 @@ export const createdashboard = authEndpoint(async (req, auth) => {
slug = `${slug}-${randomString(8)}`
}

const { data: user } = await pg.one(`select data from users where id = $1`, [
auth.uid,
])

// create if not exists the group invite link row
const { id } = await pg.one(
`insert into dashboards(slug, creator_id, description, title, items)
values ($1, $2, $3,$4, $5)
`insert into dashboards(slug, creator_id, description, title, items, creator_username, creator_name, creator_avatar_url)
values ($1, $2, $3,$4, $5, $6, $7, $8)
returning id, slug`,
[slug, auth.uid, description, title, JSON.stringify(items)]
[
slug,
auth.uid,
description,
title,
JSON.stringify(items),
user.username,
user.name,
user.avatarUrl,
]
)

// return something
Expand Down
22 changes: 22 additions & 0 deletions backend/api/src/get-your-dashboards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
createSupabaseClient,
createSupabaseDirectClient,
} from 'shared/supabase/init'
import { z } from 'zod'
import { authEndpoint, validate } from './helpers'
import { run } from 'common/supabase/utils'

export const getyourdashboards = authEndpoint(async (req, auth) => {
if (!auth.uid) {
return { dashboards: [] }
}
const db = createSupabaseClient()
const { data } = await run(
db.from('dashboards').select('*').eq('creator_id', auth.uid)
)

if (data && data.length > 0) {
return { dashboards: data }
}
return { dashboards: [] }
})
7 changes: 5 additions & 2 deletions backend/supabase/dashboards/create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ create table if not exists
creator_id text not null,
foreign key (creator_id) references users (id),
created_time timestamptz not null default now(),
views numeric not null default 0,
description json,
title text not null,
items jsonb default '[]'::jsonb
items jsonb default '[]'::jsonb,
visibility text default 'public',
creator_username text not null,
creator_name text not null,
creator_avatar_url text not null
);
5 changes: 5 additions & 0 deletions backend/supabase/dashboards/rls.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
alter table dashboards enable row level security;

create policy "Enable read access for admin" on public.dashboards for
select
to service_role using (true);
3 changes: 3 additions & 0 deletions common/src/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export type Dashboard = {
description: JSONContent
title: string
items: DashboardItem[]
creator_username: string
creator_name: string
creator_avatar_url: string
}

export type DashboardItem = DashboardQuestionItem | DashboardLinkItem
Expand Down
3 changes: 3 additions & 0 deletions common/src/supabase/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SupabaseClient } from '@supabase/supabase-js'
import { run } from './utils'
import { Dashboard } from 'common/dashboard'

export async function getDashboardFromSlug(slug: string, db: SupabaseClient) {
const { data: dashboard } = await run(
Expand All @@ -11,3 +12,5 @@ export async function getDashboardFromSlug(slug: string, db: SupabaseClient) {
}
return null
}


4 changes: 4 additions & 0 deletions web/lib/firebase/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,7 @@ export function createDashboard(params: {
}) {
return call(getApiUrl('createdashboard'), 'POST', params)
}

export function getYourDashboards() {
return call(getApiUrl('getyourdashboards'), 'POST')
}
7 changes: 6 additions & 1 deletion web/pages/dashboard/[dashboardSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ import { DashboardSidebar } from 'web/components/dashboard/dashboard-sidebar'
import { Col } from 'web/components/layout/col'
import { Page } from 'web/components/layout/page'
import { Title } from 'web/components/widgets/title'
import { initSupabaseAdmin } from 'web/lib/supabase/admin-db'
import { db } from 'web/lib/supabase/db'

export async function getStaticProps(ctx: {
params: { dashboardSlug: string }
}) {
const { dashboardSlug } = ctx.params
const adminDb = await initSupabaseAdmin()

try {
const dashboard: Dashboard = await getDashboardFromSlug(dashboardSlug, db)
const dashboard: Dashboard = await getDashboardFromSlug(
dashboardSlug,
adminDb
)
return { props: { dashboard } }
} catch (e) {
if (typeof e === 'object' && e !== null && 'code' in e && e.code === 404) {
Expand Down
65 changes: 63 additions & 2 deletions web/pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,36 @@ import { Col } from 'web/components/layout/col'
import { Page } from 'web/components/layout/page'
import { Row } from 'web/components/layout/row'
import { useRedirectIfSignedOut } from 'web/hooks/use-redirect-if-signed-out'
import { useUser } from 'web/hooks/use-user'
import { useIsAuthorized, useUser } from 'web/hooks/use-user'
import Custom404 from '../404'
import { useEffect, useState } from 'react'
import { getYourDashboards } from 'web/lib/firebase/api'
import { Dashboard } from 'common/dashboard'
import Link from 'next/link'
import { getUser } from 'web/lib/supabase/user'
import { Avatar } from 'web/components/widgets/avatar'
import { useUserById } from 'web/hooks/use-user-supabase'
import { UserLink } from 'web/components/widgets/user-link'
import clsx from 'clsx'

export default function DashboardPage() {
useRedirectIfSignedOut()
const user = useUser()

const isAuth = useIsAuthorized()

const [yourDashboards, setYourDashboards] = useState<Dashboard[]>([])

useEffect(() => {
if (!isAuth) return
getYourDashboards().then((results) => {
setYourDashboards(results.dashboards as Dashboard[])
})
}, [isAuth])

if (!DASHBOARD_ENABLED) {
return <Custom404 />
}

return (
<Page trackPageView={'dashboards page'}>
<Col className="items-center">
Expand All @@ -23,8 +42,50 @@ export default function DashboardPage() {
<span className={'text-primary-600 text-2xl'}>Dashboards</span>
{user && <CreateDashboardButton />}
</Row>
<DashboardPreviews dashboards={yourDashboards} />
</Col>
</Col>
</Page>
)
}

function DashboardPreviews(props: { dashboards?: Dashboard[] }) {
const { dashboards } = props
if (!dashboards || dashboards.length === 0) return null

return (
<Col className="gap-2">
{dashboards.map((dashboard: Dashboard) => (
<DashboardPreview key={dashboard.id} dashboard={dashboard} />
))}
</Col>
)
}

function DashboardPreview(props: { dashboard: Dashboard }) {
const { dashboard } = props
const { slug, creator_avatar_url, creator_username, creator_name } = dashboard
return (
<Link
href={`/dashboard/${slug}`}
className=" bg-canvas-0 border-canvas-0 hover:border-primary-300 flex flex-col gap-2 rounded-lg border px-4 py-2 transition-colors"
>
<Row className={'text-ink-500 items-center gap-1 text-sm'}>
<Avatar
size={'xs'}
className={'mr-0.5'}
avatarUrl={creator_avatar_url}
username={creator_username}
/>
<UserLink
name={creator_name}
username={creator_username}
className={clsx(
'w-full max-w-[10rem] text-ellipsis sm:max-w-[12rem]'
)}
/>
</Row>
<div className="text-lg">{dashboard.title}</div>
</Link>
)
}

0 comments on commit a93351e

Please sign in to comment.