Skip to content

Commit 0beba89

Browse files
authored
Chore/custom content example projects (supabase#38086)
* Init custom content hook * Implement useCustomContent hook similarly to useIsFeatureEnabled, and implement extension of organization documents * Attempt to type things nicely * Add support for custom content example projects * Fix types
1 parent bc1c050 commit 0beba89

File tree

8 files changed

+113
-58
lines changed

8 files changed

+113
-58
lines changed

apps/studio/components/interfaces/Home/ExampleProject.tsx

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,39 @@ import { useParams } from 'common'
66
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
77
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
88
import { BASE_PATH } from 'lib/constants'
9+
import { cn } from 'ui'
910

1011
interface ExampleProjectProps {
11-
framework: string
1212
title: string
1313
description: string
1414
url: string
15+
framework?: string
16+
iconUrl?: string
1517
}
1618

17-
const ExampleProject = ({ framework, title, description, url }: ExampleProjectProps) => {
19+
export const ExampleProject = ({
20+
framework,
21+
title,
22+
description,
23+
url,
24+
iconUrl,
25+
}: ExampleProjectProps) => {
1826
const { resolvedTheme } = useTheme()
1927
const { ref: projectRef } = useParams()
2028
const { data: org } = useSelectedOrganizationQuery()
2129

2230
const { mutate: sendEvent } = useSendEventMutation()
31+
const iconImgSrc = iconUrl
32+
? iconUrl
33+
: !!framework
34+
? `${BASE_PATH}/img/libraries/${framework.toLowerCase()}${
35+
['expo', 'nextjs'].includes(framework.toLowerCase())
36+
? resolvedTheme?.includes('dark')
37+
? '-dark'
38+
: ''
39+
: ''
40+
}-icon.svg`
41+
: ''
2342

2443
return (
2544
<Link
@@ -35,23 +54,17 @@ const ExampleProject = ({ framework, title, description, url }: ExampleProjectPr
3554
}
3655
>
3756
<div
38-
className={[
57+
className={cn(
3958
'group relative',
4059
'border bg-surface-100 border-overlay',
4160
'flex h-32 flex-row rounded-md p-4 hover:bg-overlay-hover',
42-
'transition duration-150 ease-in-out',
43-
].join(' ')}
61+
'transition duration-150 ease-in-out'
62+
)}
4463
>
4564
<div className="mr-4 flex flex-col">
4665
<img
4766
className="transition-all group-hover:scale-110"
48-
src={`${BASE_PATH}/img/libraries/${framework.toLowerCase()}${
49-
['expo', 'nextjs'].includes(framework.toLowerCase())
50-
? resolvedTheme?.includes('dark')
51-
? '-dark'
52-
: ''
53-
: ''
54-
}-icon.svg`}
67+
src={iconImgSrc}
5568
alt={`${framework} logo`}
5669
width={26}
5770
height={26}
@@ -62,22 +75,15 @@ const ExampleProject = ({ framework, title, description, url }: ExampleProjectPr
6275
<p className="text-sm text-foreground-light">{description}</p>
6376
</div>
6477
<div
65-
className="
66-
absolute
67-
right-4
68-
top-3
69-
text-foreground-lighter
70-
transition-all
71-
duration-200
72-
group-hover:right-3
73-
group-hover:text-foreground
74-
"
78+
className={cn(
79+
'absolute right-4 top-3',
80+
'text-foreground-lighter transition-all duration-200',
81+
'group-hover:right-3 group-hover:text-foreground'
82+
)}
7583
>
7684
<ChevronRight />
7785
</div>
7886
</div>
7987
</Link>
8088
)
8189
}
82-
83-
export default ExampleProject
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export { default as ClientLibrary } from './ClientLibrary'
2-
export { default as ExampleProject } from './ExampleProject'

apps/studio/components/layouts/ProjectLayout/BuildingState.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useMemo } from 'react'
55

66
import { useParams } from 'common'
77
import ClientLibrary from 'components/interfaces/Home/ClientLibrary'
8-
import ExampleProject from 'components/interfaces/Home/ExampleProject'
8+
import { ExampleProject } from 'components/interfaces/Home/ExampleProject'
99
import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants'
1010
import { DisplayApiSettings, DisplayConfigSettings } from 'components/ui/ProjectSettings'
1111
import { invalidateProjectDetailsQuery } from 'data/projects/project-detail-query'

apps/studio/hooks/custom-content/CustomContent.types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,10 @@ export type CustomContentTypes = {
55
description: string
66
action: { text: string; url: string }
77
}[]
8+
projectHomepageExampleProjects: {
9+
title: string
10+
description: string
11+
iconUrl: string
12+
url: string
13+
}[]
814
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"$schema": "./custom-content.schema.json",
33

4-
"organization:legal_documents": null
4+
"organization:legal_documents": null,
5+
6+
"project_homepage:example_projects": null
57
}

apps/studio/hooks/custom-content/custom-content.sample.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,20 @@
2020
"url": "https://supabase.com"
2121
}
2222
}
23+
],
24+
25+
"project_homepage:example_projects": [
26+
{
27+
"title": "Framework 1",
28+
"description": "This is a description of Framework 1",
29+
"iconUrl": "https://supabase.com/dashboard/img/supabase-logo.svg",
30+
"url": "https://supabase.com"
31+
},
32+
{
33+
"title": "Framework 2",
34+
"description": "This is a description of Framework 2",
35+
"iconUrl": "https://supabase.com/dashboard/img/supabase-logo.svg",
36+
"url": "https://supabase.com"
37+
}
2338
]
2439
}

apps/studio/hooks/custom-content/custom-content.schema.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,21 @@
2424
}
2525
}
2626
}
27+
},
28+
"project_homepage:example_projects": {
29+
"type": ["array", "null"],
30+
"description": "Renders a provided set of example projects under the project's home page",
31+
"items": {
32+
"type": "object",
33+
"properties": {
34+
"title": { "type": "string" },
35+
"description": { "type": "string" },
36+
"iconUrl": { "type": "string" },
37+
"url": { "type": "string" }
38+
}
39+
}
2740
}
2841
},
29-
"required": ["organization:legal_documents"],
42+
"required": ["organization:legal_documents", "project_homepage:example_projects"],
3043
"additionalProperties": false
3144
}

apps/studio/pages/project/[ref]/index.tsx

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import Link from 'next/link'
33
import { useEffect, useMemo, useRef } from 'react'
44

55
import { useParams } from 'common'
6-
import { ClientLibrary, ExampleProject } from 'components/interfaces/Home'
6+
import { ClientLibrary } from 'components/interfaces/Home'
77
import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget'
8+
import { ExampleProject } from 'components/interfaces/Home/ExampleProject'
89
import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants'
910
import { NewProjectPanel } from 'components/interfaces/Home/NewProjectPanel/NewProjectPanel'
1011
import { ProjectUsageSection } from 'components/interfaces/Home/ProjectUsageSection'
@@ -19,6 +20,7 @@ import { useBranchesQuery } from 'data/branches/branches-query'
1920
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
2021
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
2122
import { useTablesQuery } from 'data/tables/tables-query'
23+
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
2224
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
2325
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
2426
import {
@@ -50,6 +52,8 @@ const Home: NextPageWithLayout = () => {
5052
const snap = useAppStateSnapshot()
5153
const { ref, enableBranching } = useParams()
5254

55+
const { projectHomepageExampleProjects } = useCustomContent(['project_homepage:example_projects'])
56+
5357
const {
5458
projectHomepageShowAllClientLibraries: showAllClientLibraries,
5559
projectHomepageShowInstanceSize: showInstanceSize,
@@ -243,36 +247,46 @@ const Home: NextPageWithLayout = () => {
243247
</div>
244248
</div>
245249
{showExamples && (
246-
<div className="space-y-8">
250+
<div className="flex flex-col gap-y-8">
247251
<h4 className="text-lg">Example projects</h4>
248-
<div className="flex justify-center">
249-
<Tabs_Shadcn_ defaultValue="app" className="w-full">
250-
<TabsList_Shadcn_ className="flex gap-4 mb-8">
251-
<TabsTrigger_Shadcn_ value="app">App Frameworks</TabsTrigger_Shadcn_>
252-
<TabsTrigger_Shadcn_ value="mobile">
253-
Mobile Frameworks
254-
</TabsTrigger_Shadcn_>
255-
</TabsList_Shadcn_>
256-
<TabsContent_Shadcn_ value="app">
257-
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
258-
{EXAMPLE_PROJECTS.filter((project) => project.type === 'app')
259-
.sort((a, b) => a.title.localeCompare(b.title))
260-
.map((project) => (
261-
<ExampleProject key={project.url} {...project} />
262-
))}
263-
</div>
264-
</TabsContent_Shadcn_>
265-
<TabsContent_Shadcn_ value="mobile">
266-
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
267-
{EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile')
268-
.sort((a, b) => a.title.localeCompare(b.title))
269-
.map((project) => (
270-
<ExampleProject key={project.url} {...project} />
271-
))}
272-
</div>
273-
</TabsContent_Shadcn_>
274-
</Tabs_Shadcn_>
275-
</div>
252+
{!!projectHomepageExampleProjects ? (
253+
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
254+
{projectHomepageExampleProjects
255+
.sort((a, b) => a.title.localeCompare(b.title))
256+
.map((project) => (
257+
<ExampleProject key={project.url} {...project} />
258+
))}
259+
</div>
260+
) : (
261+
<div className="flex justify-center">
262+
<Tabs_Shadcn_ defaultValue="app" className="w-full">
263+
<TabsList_Shadcn_ className="flex gap-4 mb-8">
264+
<TabsTrigger_Shadcn_ value="app">App Frameworks</TabsTrigger_Shadcn_>
265+
<TabsTrigger_Shadcn_ value="mobile">
266+
Mobile Frameworks
267+
</TabsTrigger_Shadcn_>
268+
</TabsList_Shadcn_>
269+
<TabsContent_Shadcn_ value="app">
270+
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
271+
{EXAMPLE_PROJECTS.filter((project) => project.type === 'app')
272+
.sort((a, b) => a.title.localeCompare(b.title))
273+
.map((project) => (
274+
<ExampleProject key={project.url} {...project} />
275+
))}
276+
</div>
277+
</TabsContent_Shadcn_>
278+
<TabsContent_Shadcn_ value="mobile">
279+
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
280+
{EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile')
281+
.sort((a, b) => a.title.localeCompare(b.title))
282+
.map((project) => (
283+
<ExampleProject key={project.url} {...project} />
284+
))}
285+
</div>
286+
</TabsContent_Shadcn_>
287+
</Tabs_Shadcn_>
288+
</div>
289+
)}
276290
</div>
277291
)}
278292
</>

0 commit comments

Comments
 (0)