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
5 changes: 5 additions & 0 deletions .changeset/good-shirts-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'outstatic': patch
---

Separate content from settings by @mathieudutour
3 changes: 2 additions & 1 deletion packages/outstatic/src/client/client.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { AdminArea, Dashboard, OstClient } from './pages'
export { Main as Dashboard } from './pages' // for backward compatibility
export { AdminArea, OstClient } from './pages'
export { MediaSettings } from './pages/settings/_components/media-settings'
export { GitHubBranchSearch } from '@/components/ui/outstatic/github-branch-search'
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { useEffect, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { slugify } from 'transliteration'
import * as z from 'zod'
import * as z from 'zod/v4'
import GithubExplorer from '@/components/ui/outstatic/github-explorer'
import PathBreadcrumbs from '@/components/ui/outstatic/path-breadcrumb'
import {
Expand Down Expand Up @@ -93,10 +93,13 @@ export default function NewCollectionModal({
})

const createCollectionSchema = z.object({
name: z.string().refine(
(val) => !pages.some((page) => page.toLowerCase() === val.toLowerCase()),
(val) => ({ message: `${val} is a reserved name.` })
),
name: z
.string()
.refine(
(val) =>
!pages.some((page) => page.toLowerCase() === val.toLowerCase()),
{ error: (val) => ({ message: `${val} is a reserved name.` }) }
),
contentPath: z.string().optional()
})

Expand Down
68 changes: 68 additions & 0 deletions packages/outstatic/src/client/pages/dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { AdminLayout } from '@/components/admin-layout'
import { AdminLoading } from '@/components/admin-loading'
import { Button } from '@/components/ui/shadcn/button'
import { useCollections } from '@/utils/hooks/useCollections'
import { Card, CardContent } from '@/components/ui/shadcn/card'
import Link from 'next/link'
import { useOutstatic } from '@/utils/hooks/useOutstatic'
import { Settings, Plus } from 'lucide-react'
import CollectionOnboarding from '../collections/_components/collection-onboarding'
import LineBackground from '@/components/ui/outstatic/line-background'
import { singular } from 'pluralize'

export default function Collections() {
const { data: collections, isPending } = useCollections()
const { dashboardRoute } = useOutstatic()

if (isPending) return <AdminLoading />

return (
<AdminLayout title="Collections">
{!collections || collections.length === 0 ? (
<LineBackground>
<CollectionOnboarding />
</LineBackground>
) : (
<>
<div className="mb-8 flex h-12 items-center">
<h1 className="mr-12 text-2xl text-foreground">Collections</h1>
<Button asChild size="icon" variant="ghost">
<Link href={`${dashboardRoute}/collections`}>
<span className="sr-only">Edit Collections</span>
<Settings className="w-6 h-6" />
</Link>
</Button>
</div>
<div className="max-w-5xl w-full grid md:grid-cols-3 gap-6">
{collections &&
collections.map((collection) => (
<Card
key={collection.slug}
className="hover:border-gray-500 transition-all duration-300"
>
<CardContent className="relative flex justify-between items-center">
<Link href={`${dashboardRoute}/${collection.slug}`}>
<h5 className="text-2xl cursor-pointer font-bold tracking-tight text-foreground">
{collection.title}
<span className="absolute top-0 bottom-0 left-0 right-16"></span>
</h5>
</Link>
<div className="z-10 flex gap-2">
<Button asChild size="icon" variant="ghost">
<Link href={`${dashboardRoute}/${collection.slug}/new`}>
<span className="sr-only">
New {singular(collection.title)}
</span>
<Plus className="w-6 h-6" />
</Link>
</Button>
</div>
</CardContent>
</Card>
))}
</div>
</>
)}
</AdminLayout>
)
}
2 changes: 2 additions & 0 deletions packages/outstatic/src/client/pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Dashboard from './dashboard'
export default Dashboard
4 changes: 2 additions & 2 deletions packages/outstatic/src/client/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const AdminArea = ({ params }: { params: { ost: string[] } }) => {

<div className="dark:bg-background bg-background mx-auto flex w-full flex-col overflow-y-auto">
<div className={'flex flex-1 overflow-y-auto'}>
<Dashboard params={params} />
<Main params={params} />
</div>
</div>
</div>
Expand All @@ -54,7 +54,7 @@ export const AdminArea = ({ params }: { params: { ost: string[] } }) => {
)
}

export const Dashboard = ({ params }: { params: { ost: string[] } }) => {
export const Main = ({ params }: { params: { ost: string[] } }) => {
const { repoSlug, repoOwner, repoBranch, isPending, session } = useOutstatic()
const { data: repository } = useGetRepository()
const { setData, data, isPending: localPending } = useLocalData()
Expand Down
7 changes: 4 additions & 3 deletions packages/outstatic/src/client/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import List from './pages/list'
import Settings from './pages/settings'
import MediaLibrary from './pages/media-library'
import { EditorProvider } from '@/components/editor/editor-context'
import Dashboard from './pages/dashboard'

const DEFAULT_PAGES: { [key: string]: ReactElement | undefined } = {
settings: <Settings />,
'media-library': <MediaLibrary />,
collections: undefined
collections: <Collections />
}

interface RouterProps {
Expand Down Expand Up @@ -75,9 +76,9 @@ const renderRoute = ({
collections,
pages
}: RouteParams): ReactElement | undefined => {
// Default route - show collections
// Default route - show dashboard
if (!slug) {
return <Collections />
return <Dashboard />
}

// Content routes (edit document or list)
Expand Down
81 changes: 47 additions & 34 deletions packages/outstatic/src/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useOutstatic } from '@/utils/hooks/useOutstatic'
import { z } from 'zod'
import { z } from 'zod/v4'
import {
Sidebar as SidebarUI,
SidebarContent,
Expand All @@ -20,6 +20,9 @@ export const Sidebar = () => {
const { dashboardRoute } = useOutstatic()
const { data: collections } = useCollections()

const hasCollections = collections && collections.length > 0
const hasContentTypes = hasCollections

const routes = [
{
label: 'Dashboard',
Expand All @@ -32,39 +35,49 @@ export const Sidebar = () => {
}
]
},
...(collections && collections.length > 0
...(hasContentTypes
? [
{
label: 'Collections',
collapsible: true,
children: collections.map((collection) => ({
label: collection.title,
path: `${dashboardRoute}/${collection.slug}`,
Icon: <Folder className={'w-4'} />,
renderAction: (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Link
href={`/outstatic/${collection.slug}/new`}
className="invisible group-hover/menu-item:visible"
aria-label={`Create new item in collection ${collection.title}`}
>
<Plus className="w-3 h-3 pointer-events-none" />
</Link>
</TooltipTrigger>
<TooltipContent className="pointer-events-none">
<p>
Create new{' '}
<span className="inline-block">
{singular(collection.title)}
</span>
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}))
label: 'Content',
collapsible: false,
children: [
...(collections && collections.length > 0
? [
{
label: 'Collections',
collapsible: true,
children: collections.map((collection) => ({
label: collection.title,
path: `${dashboardRoute}/${collection.slug}`,
Icon: <Folder className={'w-4'} />,
renderAction: (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Link
href={`${dashboardRoute}/${collection.slug}/new`}
className="invisible group-hover/sub-menu-item:visible"
aria-label={`Create new item in collection ${collection.title}`}
>
<Plus className="w-3 h-3 pointer-events-none" />
</Link>
</TooltipTrigger>
<TooltipContent className="pointer-events-none">
<p>
Create new{' '}
<span className="inline-block">
{singular(collection.title)}
</span>
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}))
}
]
: [])
]
}
]
: []),
Expand All @@ -76,7 +89,7 @@ export const Sidebar = () => {
path: `${dashboardRoute}/media-library`,
Icon: <Images className={'w-4'} />
}
].filter((route) => !!route)
]
},
{
label: 'Settings',
Expand All @@ -86,7 +99,7 @@ export const Sidebar = () => {
path: `${dashboardRoute}/settings`,
Icon: <Settings className={'w-4'} />
}
].filter((route) => !!route)
]
}
] satisfies z.infer<typeof NavigationConfigSchema>['routes']

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from 'zod'
import { z } from 'zod/v4'

const RouteMatchingEnd = z
.union([z.boolean(), z.function().args(z.string()).returns(z.boolean())])
.union([z.boolean(), z.custom<(input: string) => boolean>()])
.default(false)
.optional()

Expand Down Expand Up @@ -34,10 +34,14 @@ const RouteGroup = z.object({
label: z.string(),
collapsible: z.boolean().optional(),
collapsed: z.boolean().optional(),
children: z.array(RouteChild),
Icon: z.custom<React.ReactNode>().optional(),
get children(): z.ZodUnion<[z.ZodArray<typeof RouteChild>, z.ZodArray<typeof RouteGroup>]> {
return z.union([z.array(RouteChild), z.array(RouteGroup)])
},
renderAction: z.custom<React.ReactNode>().optional()
})

export const NavigationConfigSchema = z.object({
// @ts-ignore
routes: z.array(z.union([RouteGroup, Divider]))
})
Loading