Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/docs-sync-auto-troubleshooting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,16 @@ jobs:
repository: supabase/troubleshooting
path: troubleshooting-upstream

- name: Generate PR token
id: pr-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.GH_AUTOFIX_APP_ID }}
private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}

- name: Sync supabase/troubleshooting changes back to supabase/supabase
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.pr-token.outputs.token }}
run: |
git config user.name 'github-docs-bot'
git config user.email 'github-docs-bot@supabase.com'
Expand Down
38 changes: 38 additions & 0 deletions apps/learn/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

.contentlayer
54 changes: 54 additions & 0 deletions apps/learn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Plan:
Course 1: Supabase Foundations Learn the basics of Supabase: database, auth, and RLS. 5 chapters.

-

Course 2: Project: Smart Office 15 Build a realtime room-booking dashboard using Supabase. 15 chapters.
Course 3: Supabase Internals: Performance & Scaling. Learn how to profile queries, tune indexes, and scale Postgres with Supabase. 20 chapters.
Course 4: Supabase Internals: Debugging & Operations. Understand how to diagnose slow queries, use read replicas, and manage production workloads. 20 chapters.

———
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

## Supabase types

To regenerate the Supabase database types, run

```
supabase gen types --local > registry/default/fixtures/database.types.ts
```
158 changes: 158 additions & 0 deletions apps/learn/app/(app)/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { metadata as mainMetadata } from '@/app/layout'
import { ChapterCompletion } from '@/components/chapter-completion'
import { CourseHero } from '@/components/course-hero'
import { ExploreMore } from '@/components/explore-more'
import { Mdx } from '@/components/mdx-components'
import { NextUp } from '@/components/next-up'
import { DashboardTableOfContents } from '@/components/toc'
import { getTableOfContents } from '@/lib/toc'
import { getCurrentChapter } from '@/lib/get-current-chapter'
import { getNextPage } from '@/lib/get-next-page'
import { absoluteUrl, cn } from '@/lib/utils'
import '@/styles/code-block-variables.css'
import '@/styles/mdx.css'
import { allDocs } from 'contentlayer/generated'
import { ChevronRight } from 'lucide-react'
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import Balancer from 'react-wrap-balancer'
import { ScrollArea } from 'ui'

interface DocPageProps {
params: Promise<{
slug: string[]
}>
}

async function getDocFromParams({ params }: { params: { slug: string[] } }) {
const slug = params.slug?.join('/') || ''
const doc = allDocs.find((doc) => doc.slugAsParams === slug)

if (!doc) {
return null
}

return doc
}

export async function generateMetadata(props: DocPageProps): Promise<Metadata> {
const params = await props.params
const doc = await getDocFromParams({ params })
// get page params so we can check if it's the introduction page
const slugSegments = doc?.slugAsParams.split('/')
const isIntroductionPage = slugSegments?.[slugSegments.length - 1] === 'introduction'

if (!doc) {
return {}
}

const metadata: Metadata = {
...mainMetadata,
title: doc.title,
description: doc.description,
openGraph: {
...(mainMetadata.openGraph || {}),
title: doc.title,
description: doc.description,
type: 'article',
url: absoluteUrl(doc.slug),
},
}
return metadata
}

export async function generateStaticParams(): Promise<{ slug: string[] }[]> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split('/'),
}))
}

export default async function DocPage(props: DocPageProps) {
const params = await props.params
const doc = await getDocFromParams({ params })

if (!doc) {
notFound()
}

const toc = await getTableOfContents(doc.body.raw)
const nextPage = getNextPage(doc.slugAsParams)
const currentChapter = getCurrentChapter(doc.slugAsParams)
const slugSegments = doc.slugAsParams.split('/')
const isIntroductionPage = slugSegments[slugSegments.length - 1] === 'introduction'

const exploreItems = (
doc as {
explore?: Array<{ title: string; link: string; itemType?: string; description?: string }>
}
).explore

return (
<div className={cn('relative')}>
{isIntroductionPage && doc.courseHero && (
<CourseHero
title={doc.courseHero.title}
subtitle={doc.courseHero.subtitle}
description={doc.courseHero.description}
//instructors={doc.courseHero.instructors}
/>
)}

<div
className={cn(
'relative lg:gap-10 xl:grid xl:grid-cols-[1fr_200px] py-20',
isIntroductionPage ? 'px-0' : 'px-8 md:px-16'
)}
>
<div className="mx-auto w-full min-w-0 max-w-4xl">
<div className="mb-4 flex items-center space-x-1 text-sm text-foreground-muted">
<div className="overflow-hidden text-ellipsis whitespace-nowrap">Learn</div>
<ChevronRight className="h-4 w-4 text-foreground-muted" />
<div className="text-foreground-lighter">{doc.title}</div>
</div>
<div className="flex flex-col lg:flex-row lg:items-end justify-between mb-5">
<div className="space-y-2">
<h1 className={cn('scroll-m-20 text-2xl lg:text-4xl tracking-tight')}>{doc.title}</h1>
{doc.description && (
<p className="text-base lg:text-lg text-foreground-light">
<Balancer>{doc.description}</Balancer>
</p>
)}
</div>
</div>
<div className="pb-12">
<Mdx code={doc.body.code} />
</div>
{exploreItems && exploreItems.length > 0 && <ExploreMore items={exploreItems} />}
{currentChapter && nextPage && (
<ChapterCompletion
chapterNumber={currentChapter.chapterNumber!}
completionMessage={currentChapter.completionMessage}
/>
)}
{nextPage && (
<div className="flex w-2xl grow">
<NextUp
title={nextPage.title}
description={nextPage.description || ''}
href={nextPage.href}
chapterNumber={nextPage.chapterNumber}
/>
</div>
)}
</div>
{doc.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
<DashboardTableOfContents toc={toc} />
</div>
</ScrollArea>
</div>
</div>
)}
</div>
</div>
)
}
31 changes: 31 additions & 0 deletions apps/learn/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Sidebar } from '@/components/sidebar'
import { SiteFooter } from '@/components/site-footer'
import { TelemetryWrapper } from './telemetry-wrapper'

interface AppLayoutProps {
children: React.ReactNode
}

export default function AppLayout({ children }: AppLayoutProps) {
return (
<>
{/* main container */}
<div className="pt-10 md:pt-0">
{/* main content */}
<main className="flex-1 max-w-site mx-auto w-full p-0">
{/* {children} */}
<div className="border-b">
<div className="flex-1 items-start md:grid md:grid-cols-[240px_minmax(0,1fr)] lg:grid-cols-[280px_minmax(0,1fr)]">
<Sidebar />
<div vaul-drawer-wrapper="">
<div className="relative flex min-h-screen flex-col bg-background">{children}</div>
</div>
</div>
</div>
</main>
</div>
<SiteFooter />
<TelemetryWrapper />
</>
)
}
Loading
Loading