diff --git a/MyApp.Client/pages/admin.tsx b/MyApp.Client/app/admin/page.tsx
similarity index 98%
rename from MyApp.Client/pages/admin.tsx
rename to MyApp.Client/app/admin/page.tsx
index d5cd8ed..da6be96 100644
--- a/MyApp.Client/pages/admin.tsx
+++ b/MyApp.Client/app/admin/page.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import { SecondaryButton } from "@servicestack/react"
import Page from "@/components/layout-page"
import { ValidateAuth, appAuth } from "@/lib/auth"
diff --git a/MyApp.Client/pages/bookings-auto.tsx b/MyApp.Client/app/bookings-auto/page.tsx
similarity index 88%
rename from MyApp.Client/pages/bookings-auto.tsx
rename to MyApp.Client/app/bookings-auto/page.tsx
index 01ba69c..18854d5 100644
--- a/MyApp.Client/pages/bookings-auto.tsx
+++ b/MyApp.Client/app/bookings-auto/page.tsx
@@ -1,12 +1,12 @@
+'use client'
+
import Link from "next/link"
import { AutoQueryGrid } from "@servicestack/react"
import Page from "@/components/layout-page"
import { ValidateAuth } from "@/lib/auth"
import SrcPage from "@/components/src-page"
-function Index() {
-
-
+function BookingsAuto() {
return (
@@ -26,5 +26,4 @@ function Index() {
)
}
-export default ValidateAuth(Index, {role: 'Employee'})
-
+export default ValidateAuth(BookingsAuto, {role: 'Employee'})
diff --git a/MyApp.Client/pages/bookings-custom.tsx b/MyApp.Client/app/bookings-custom/page.tsx
similarity index 96%
rename from MyApp.Client/pages/bookings-custom.tsx
rename to MyApp.Client/app/bookings-custom/page.tsx
index 128df9b..cc724ec 100644
--- a/MyApp.Client/pages/bookings-custom.tsx
+++ b/MyApp.Client/app/bookings-custom/page.tsx
@@ -1,11 +1,13 @@
-import { useState, useEffect } from "react"
+'use client'
+
+import { useState } from "react"
import Link from "next/link"
import { useFormatters, AutoQueryGrid, TextLink, PreviewFormat, AutoEditForm, Icon } from "@servicestack/react"
import Page from "@/components/layout-page"
import { ValidateAuth } from "@/lib/auth"
import SrcPage from "@/components/src-page"
-function Index() {
+function BookingsCustom() {
const { currency } = useFormatters()
const [coupon, setCoupon] = useState
(null)
@@ -76,7 +78,7 @@ function Index() {
onSave={() => setCoupon(null)}
/>
)}
-
+
@@ -85,5 +87,4 @@ function Index() {
)
}
-export default ValidateAuth(Index, {role: 'Employee'})
-
+export default ValidateAuth(BookingsCustom, {role: 'Employee'})
diff --git a/MyApp.Client/pages/features.mdx b/MyApp.Client/app/features/page.mdx
similarity index 98%
rename from MyApp.Client/pages/features.mdx
rename to MyApp.Client/app/features/page.mdx
index 0cdf101..7837dec 100644
--- a/MyApp.Client/pages/features.mdx
+++ b/MyApp.Client/app/features/page.mdx
@@ -1,4 +1,4 @@
-import Layout from "../components/layout-article"
+import Layout from "@/components/layout-article"
export const meta = {
title: 'Template Features'
diff --git a/MyApp.Client/pages/forbidden.tsx b/MyApp.Client/app/forbidden/page.tsx
similarity index 86%
rename from MyApp.Client/pages/forbidden.tsx
rename to MyApp.Client/app/forbidden/page.tsx
index 16c6a8a..ebb16d6 100644
--- a/MyApp.Client/pages/forbidden.tsx
+++ b/MyApp.Client/app/forbidden/page.tsx
@@ -1,5 +1,5 @@
-export default () => {
- return (<>
+export default function Forbidden() {
+ return (
403
@@ -7,7 +7,6 @@ export default () => {
You do not have access to this page.
-
- >
+
)
}
diff --git a/MyApp.Client/app/layout.tsx b/MyApp.Client/app/layout.tsx
new file mode 100644
index 0000000..984b5e8
--- /dev/null
+++ b/MyApp.Client/app/layout.tsx
@@ -0,0 +1,42 @@
+import "../styles/index.css"
+import "../styles/main.css"
+import "../styles/prism-dark-blue.css"
+import type { Metadata } from 'next'
+import Providers from './providers'
+
+export const metadata: Metadata = {
+ title: 'Next.js Example',
+ description: 'Next.js App Router Example',
+}
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/MyApp.Client/app/page.tsx b/MyApp.Client/app/page.tsx
new file mode 100644
index 0000000..1b3977b
--- /dev/null
+++ b/MyApp.Client/app/page.tsx
@@ -0,0 +1,71 @@
+import Container from "@/components/container"
+import MoreStories from "@/components/more-stories"
+import HeroPost from "@/components/hero-post"
+import Intro from "@/components/intro"
+import Layout from "@/components/layout"
+import { getAllPosts } from "@/lib/api"
+import { CMS_NAME } from "@/lib/constants"
+import Post from "@/types/post"
+import GettingStarted from "@/components/getting-started"
+import BuiltInUis from "@/components/builtin-uis"
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: `Next.js Example with ${CMS_NAME}`,
+}
+
+export default function Index() {
+ const allPosts = getAllPosts([
+ 'title',
+ 'date',
+ 'slug',
+ 'author',
+ 'coverImage',
+ 'excerpt',
+ ]) as unknown as Post[]
+
+ const heroPost = allPosts[0]
+ const morePosts = allPosts.slice(1)
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ Manage your ServiceStack App and explore, discover, query and call APIs instantly with
+ built-in Auto UIs dynamically generated from the rich metadata of your App's typed C# APIs & DTOs
+
+
+
+
+
+ {heroPost && (
+
+ )}
+ {morePosts.length > 0 && }
+
+
+ )
+}
diff --git a/MyApp.Client/app/posts/[slug]/page.tsx b/MyApp.Client/app/posts/[slug]/page.tsx
new file mode 100644
index 0000000..640cbc6
--- /dev/null
+++ b/MyApp.Client/app/posts/[slug]/page.tsx
@@ -0,0 +1,75 @@
+import { notFound } from "next/navigation"
+import Container from "@/components/container"
+import PostBody from "@/components/post-body"
+import Header from "@/components/header"
+import PostHeader from "@/components/post-header"
+import Layout from "@/components/layout"
+import { getPostBySlug, getAllPosts } from "@/lib/api"
+import PostTitle from "@/components/post-title"
+import { CMS_NAME } from "@/lib/constants"
+import markdownToHtml from "@/lib/markdownToHtml"
+import type { Metadata } from 'next'
+import PostType from "@/types/post"
+
+type Props = {
+ params: Promise<{
+ slug: string
+ }>
+}
+
+export async function generateMetadata({ params }: Props): Promise {
+ const { slug } = await params
+ const post = getPostBySlug(slug, ['title', 'ogImage']) as unknown as PostType
+ const title = `${post.title} | Next.js Example with ${CMS_NAME}`
+
+ return {
+ title,
+ openGraph: {
+ images: [post.ogImage.url],
+ },
+ }
+}
+
+export async function generateStaticParams() {
+ const posts = getAllPosts(['slug'])
+
+ return posts.map((post) => ({
+ slug: post.slug,
+ }))
+}
+
+export default async function Post({ params }: Props) {
+ const { slug } = await params
+ const post = getPostBySlug(slug, [
+ 'title',
+ 'date',
+ 'slug',
+ 'author',
+ 'content',
+ 'ogImage',
+ 'coverImage',
+ ]) as unknown as PostType
+
+ if (!post?.slug) {
+ notFound()
+ }
+
+ const content = await markdownToHtml(post.content || '')
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/MyApp.Client/app/posts/page.tsx b/MyApp.Client/app/posts/page.tsx
new file mode 100644
index 0000000..30a4b9f
--- /dev/null
+++ b/MyApp.Client/app/posts/page.tsx
@@ -0,0 +1,38 @@
+import React from "react"
+import Layout from "@/components/layout"
+import { getAllPosts } from "@/lib/api"
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Markdown Posts',
+}
+
+export default function Posts() {
+ const allPosts = getAllPosts([
+ 'title',
+ 'slug',
+ 'excerpt',
+ ])
+
+ return (
+
+
+
+
Markdown Posts
+
+ List of Markdown Posts in /pages
+
+ {allPosts.map((post) => (
+
+
{post.title}
+ {!post.excerpt ? null :
+ {post.excerpt}
+
}
+
+ ))}
+
+
+
+ )
+}
diff --git a/MyApp.Client/pages/profile.tsx b/MyApp.Client/app/profile/page.tsx
similarity index 95%
rename from MyApp.Client/pages/profile.tsx
rename to MyApp.Client/app/profile/page.tsx
index 34ac75a..feeeff6 100644
--- a/MyApp.Client/pages/profile.tsx
+++ b/MyApp.Client/app/profile/page.tsx
@@ -1,5 +1,7 @@
+'use client'
+
import React from "react"
-import Page from "../components/layout-page"
+import Page from "@/components/layout-page"
import {SecondaryButton} from "@servicestack/react"
import {appAuth, ValidateAuth} from "@/lib/auth"
diff --git a/MyApp.Client/app/providers.tsx b/MyApp.Client/app/providers.tsx
new file mode 100644
index 0000000..9520798
--- /dev/null
+++ b/MyApp.Client/app/providers.tsx
@@ -0,0 +1,23 @@
+'use client'
+
+import { useEffect } from "react"
+import Link from 'next/link'
+import { setLinkComponent, ClientContext } from '@servicestack/react'
+import { client, init } from "@/lib/gateway"
+
+// Adapter component to convert react-router 'to' prop to Next.js 'href' prop
+const NextLink = ({ to, ...props }: any) =>
+
+setLinkComponent(NextLink)
+
+export default function Providers({ children }: { children: React.ReactNode }) {
+ useEffect(() => {
+ (async () => init())()
+ }, [])
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/MyApp.Client/pages/shadcn-ui.tsx b/MyApp.Client/app/shadcn-ui/page.tsx
similarity index 98%
rename from MyApp.Client/pages/shadcn-ui.tsx
rename to MyApp.Client/app/shadcn-ui/page.tsx
index 8c2d7b2..5b61df8 100644
--- a/MyApp.Client/pages/shadcn-ui.tsx
+++ b/MyApp.Client/app/shadcn-ui/page.tsx
@@ -1,10 +1,10 @@
-import Layout from "../components/layout"
-import { Button } from "../components/ui/button"
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/ui/card"
-import { Badge } from "../components/ui/badge"
-import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs"
-import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../components/ui/accordion"
+import Layout from "@/components/layout"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Terminal, Rocket, Package, ExternalLink, Info, Download, Code2 } from "lucide-react"
const ShadcnUiDemo = () => {
@@ -216,7 +216,7 @@ const ShadcnUiDemo = () => {
Basic Example:
- import { Button } from "../components/ui/button"
+ import { Button } from "@/components/ui/button"
export default function Page() {
return <Button>Click me</Button>
}
@@ -314,4 +314,3 @@ const ShadcnUiDemo = () => {
}
export default ShadcnUiDemo
-
diff --git a/MyApp.Client/pages/signin.tsx b/MyApp.Client/app/signin/page.tsx
similarity index 92%
rename from MyApp.Client/pages/signin.tsx
rename to MyApp.Client/app/signin/page.tsx
index 70bd8d9..3ea618b 100644
--- a/MyApp.Client/pages/signin.tsx
+++ b/MyApp.Client/app/signin/page.tsx
@@ -1,6 +1,8 @@
+'use client'
+
import {serializeToObject} from "@servicestack/client"
import {SyntheticEvent, useEffect, useState} from "react"
-import Router, {useRouter} from "next/router"
+import {useRouter, useSearchParams} from "next/navigation"
import Link from "next/link"
import Page from "@/components/layout-page"
@@ -9,7 +11,7 @@ import {Authenticate} from "@/lib/dtos"
import {appAuth, Redirecting} from "@/lib/auth"
import {getRedirect} from "@/lib/gateway"
-export default () => {
+export default function SignIn() {
const client = useClient()
const [username, setUsername] = useState()
@@ -20,10 +22,14 @@ export default () => {
setPassword('p@55wOrd')
}
const router = useRouter()
+ const searchParams = useSearchParams()
const {user, revalidate} = appAuth()
useEffect(() => {
- if (user) Router.replace(getRedirect(router.query) || "/")
+ if (user) {
+ const redirect = getRedirect(Object.fromEntries(searchParams.entries())) || "/"
+ router.replace(redirect)
+ }
}, [user]);
if (user) return
diff --git a/MyApp.Client/pages/signup-confirm.tsx b/MyApp.Client/app/signup-confirm/page.tsx
similarity index 73%
rename from MyApp.Client/pages/signup-confirm.tsx
rename to MyApp.Client/app/signup-confirm/page.tsx
index 1efc69e..5f33b2e 100644
--- a/MyApp.Client/pages/signup-confirm.tsx
+++ b/MyApp.Client/app/signup-confirm/page.tsx
@@ -1,9 +1,11 @@
+'use client'
+
import Page from "@/components/layout-page"
-import { useRouter } from "next/router"
+import { useSearchParams } from "next/navigation"
-export default () => {
- const router = useRouter()
- const confirmLink = router.query.confirmLink as string | undefined
+export default function SignUpConfirm() {
+ const searchParams = useSearchParams()
+ const confirmLink = searchParams.get('confirmLink')
return (
diff --git a/MyApp.Client/pages/signup.tsx b/MyApp.Client/app/signup/page.tsx
similarity index 92%
rename from MyApp.Client/pages/signup.tsx
rename to MyApp.Client/app/signup/page.tsx
index b2d7f19..c53b010 100644
--- a/MyApp.Client/pages/signup.tsx
+++ b/MyApp.Client/app/signup/page.tsx
@@ -1,13 +1,15 @@
+'use client'
+
import { SyntheticEvent, useEffect, useState } from "react"
import { useClient, FormLoading, ErrorSummary, TextInput, PrimaryButton, SecondaryButton, ApiStateContext } from "@servicestack/react"
import { serializeToObject, leftPart, rightPart, toPascalCase } from "@servicestack/client"
-import Router, { useRouter } from "next/router"
+import {useRouter, useSearchParams} from "next/navigation"
import Page from "@/components/layout-page"
import { getRedirect } from "@/lib/gateway"
import { Register, RegisterResponse } from "@/lib/dtos"
import { appAuth, Redirecting } from "@/lib/auth"
-export default () => {
+export default function SignUp() {
const client = useClient()
const [displayName, setDisplayName] = useState
()
@@ -15,6 +17,7 @@ export default () => {
const [password, setPassword] = useState()
const [confirmPassword, setConfirmPassword] = useState()
const router = useRouter()
+ const searchParams = useSearchParams()
const { user, revalidate } = appAuth()
const setUser = (email: string) => {
@@ -27,7 +30,10 @@ export default () => {
}
useEffect(() => {
- if (user) Router.replace(getRedirect(router.query) || "/")
+ if (user) {
+ const redirect = getRedirect(Object.fromEntries(searchParams.entries())) || "/"
+ router.replace(redirect)
+ }
}, [user])
if (user) return
@@ -47,7 +53,7 @@ export default () => {
if (redirectUrl) {
location.href = redirectUrl
} else {
- Router.push("/signin")
+ router.push("/signin")
}
}
}
diff --git a/MyApp.Client/pages/todomvc.tsx b/MyApp.Client/app/todomvc/page.tsx
similarity index 98%
rename from MyApp.Client/pages/todomvc.tsx
rename to MyApp.Client/app/todomvc/page.tsx
index 9f83873..827629a 100644
--- a/MyApp.Client/pages/todomvc.tsx
+++ b/MyApp.Client/app/todomvc/page.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import { useState, type ReactNode, KeyboardEvent, MouseEvent, useEffect } from "react"
import { classNames, ResponseStatus } from "@servicestack/client"
import { TextInput } from "@servicestack/react"
@@ -7,7 +9,7 @@ import { CreateTodo, DeleteTodo, DeleteTodos, QueryTodos, Todo, UpdateTodo } fro
export type Filter = "all" | "finished" | "unfinished"
-const TodosMvc = () => {
+export default function TodosMvc() {
const [newTodo, setNewTodo] = useState('')
@@ -139,8 +141,8 @@ type FilterTabProps = {
}
function FilterTab({active, className, onClick, children}: FilterTabProps) {
- return ( {
@@ -148,5 +150,3 @@ function FilterTab({active, className, onClick, children}: FilterTabProps) {
onClick(e)
}}>{children} )
}
-
-export default TodosMvc
\ No newline at end of file
diff --git a/MyApp.Client/components/builtin-uis.tsx b/MyApp.Client/components/builtin-uis.tsx
index 5a3dcba..1f226f0 100644
--- a/MyApp.Client/components/builtin-uis.tsx
+++ b/MyApp.Client/components/builtin-uis.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { useState, useEffect, useCallback } from 'react'
import { BaseUrl } from "@/lib/gateway"
diff --git a/MyApp.Client/components/getting-started.tsx b/MyApp.Client/components/getting-started.tsx
index 83016d8..4b8bee5 100644
--- a/MyApp.Client/components/getting-started.tsx
+++ b/MyApp.Client/components/getting-started.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import { ChangeEvent, useMemo, useState } from "react"
import ShellCommand from "./shell-command"
diff --git a/MyApp.Client/components/intro.tsx b/MyApp.Client/components/intro.tsx
index 818fdba..0789c1f 100644
--- a/MyApp.Client/components/intro.tsx
+++ b/MyApp.Client/components/intro.tsx
@@ -1,6 +1,8 @@
+'use client'
+
import { useState } from "react"
import { TextInput } from "@servicestack/react"
-import { swrClient } from "@/lib/gateway"
+import { swrClient } from "@/lib/gateway.client"
import { Hello } from "@/lib/dtos"
import { CMS_NAME } from "@/lib/constants"
diff --git a/MyApp.Client/components/nav.tsx b/MyApp.Client/components/nav.tsx
index 0aa2305..a8b7cb3 100644
--- a/MyApp.Client/components/nav.tsx
+++ b/MyApp.Client/components/nav.tsx
@@ -1,5 +1,7 @@
+'use client'
+
import Link from "next/link"
-import { useRouter } from "next/router"
+import { usePathname } from "next/navigation"
import { useAuth, PrimaryButton, SecondaryButton, DarkModeToggle } from "@servicestack/react"
import { appAuth } from "@/lib/auth"
@@ -12,7 +14,8 @@ type NavItem = {
onClick?:() => void
}
-export default function () {
+export default function Nav() {
+ const pathname = usePathname()
const items:NavItem[] = [
{ href: '/shadcn-ui', name: 'shadcn/ui'},
@@ -25,11 +28,9 @@ export default function () {
const { user, hasRole, signOut } = appAuth()
const navClass = (path:string) => [
"p-4 flex items-center justify-start mw-full hover:text-sky-500 dark:hover:text-sky-400",
- router.asPath === path || router.asPath.startsWith(path + '/') ? "text-link-dark dark:text-link-dark" : "",
+ pathname === path || pathname.startsWith(path + '/') ? "text-link-dark dark:text-link-dark" : "",
].join(" ")
- const router = useRouter()
-
return (
@@ -41,7 +42,7 @@ export default function () {
{items.map(x => {
- const isActive = router.asPath === x.href || router.asPath.startsWith(x.href + '/')
+ const isActive = pathname === x.href || pathname.startsWith(x.href + '/')
return ({x.type === 'Button'
? {x.name}
: x.type == 'PrimaryButton'
diff --git a/MyApp.Client/components/shell-command.tsx b/MyApp.Client/components/shell-command.tsx
index 12d2433..0bdf675 100644
--- a/MyApp.Client/components/shell-command.tsx
+++ b/MyApp.Client/components/shell-command.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import { FC, MouseEvent, useState, ReactNode, useRef } from "react"
import { Terminal, Copy, Check } from "lucide-react"
import { cn } from "../lib/utils"
diff --git a/MyApp.Client/lib/auth.tsx b/MyApp.Client/lib/auth.tsx
index 21b660f..6ca6c32 100644
--- a/MyApp.Client/lib/auth.tsx
+++ b/MyApp.Client/lib/auth.tsx
@@ -1,5 +1,7 @@
+'use client'
+
import React, { useEffect } from "react"
-import Router, { useRouter } from "next/router"
+import { useRouter, usePathname } from "next/navigation"
import { useAuth, Loading } from "@servicestack/react"
import { client, Routes } from "./gateway"
import { Authenticate } from "@/lib/dtos"
@@ -17,6 +19,7 @@ export function ValidateAuth(Component:React.FC = (props) => {
const router = useRouter()
+ const pathname = usePathname()
const authProps = useAuth()
const { user, isAuthenticated, hasRole } = authProps
useEffect(() => {
@@ -26,7 +29,7 @@ export function ValidateAuth(Component:React.FC !isAuthenticated
? Routes.signin(redirectTo)
@@ -47,6 +50,7 @@ export function ValidateAuth(Component:React.FC(fn: () => IReturn | string) {
+ return useSWR(() => {
+ let request = fn()
+ return appendQueryString(`SwrClient:${nameOf(request)}`, request)
+ }, _ => this.client.get(fn()))
+ }
+}
+
+export const swrClient = new SwrClient(client)
diff --git a/MyApp.Client/lib/gateway.ts b/MyApp.Client/lib/gateway.ts
index ef9fed8..80e8266 100644
--- a/MyApp.Client/lib/gateway.ts
+++ b/MyApp.Client/lib/gateway.ts
@@ -1,6 +1,4 @@
-import { useMetadata, authContext } from "@servicestack/react"
-import { appendQueryString, nameOf, IReturn, JsonServiceClient, combinePaths } from "@servicestack/client"
-import useSWR from "swr"
+import { JsonServiceClient, combinePaths } from "@servicestack/client"
import { Authenticate } from "@/lib/dtos"
export const Routes = {
@@ -8,18 +6,26 @@ export const Routes = {
forbidden: () => '/forbidden',
}
-export const BaseUrl = process.env.apiBaseUrl || ''
+export const BaseUrl = typeof window === 'undefined'
+ ? (process.env.apiBaseUrl || '')
+ : (process.env.apiBaseUrl || '')
export function apiUrl(path: string) {
- return combinePaths(process.env.apiBaseUrl || '', path)
+ const base = typeof window === 'undefined' ? process.env.apiBaseUrl : process.env.apiBaseUrl
+ return combinePaths(base || '', path)
}
export const client = new JsonServiceClient()
-export const metadata = useMetadata(client)
// Load Metadata & Auth State on Startup
+// This needs to be called on client side only
export async function init() {
+ if (typeof window === 'undefined') return
+
+ const { useMetadata, authContext } = await import("@servicestack/react")
+ const metadata = useMetadata(client)
const authCtx = authContext()
+
return await Promise.all([
metadata.loadMetadata(),
client.post(new Authenticate())
@@ -39,21 +45,3 @@ export function getRedirect(searchParams: URLSearchParams | Record(fn: () => IReturn | string) {
- return useSWR(() => {
- let request = fn()
- return appendQueryString(`SwrClient:${nameOf(request)}`, request)
- }, _ => this.client.get(fn()))
- }
-}
-
-export const swrClient = new SwrClient(client)
diff --git a/MyApp.Client/next-env.d.ts b/MyApp.Client/next-env.d.ts
index 1970904..b87975d 100644
--- a/MyApp.Client/next-env.d.ts
+++ b/MyApp.Client/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./dist/dev/types/routes.d.ts";
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/MyApp.Client/next.config.mjs b/MyApp.Client/next.config.mjs
index 82b4abd..53ddb93 100644
--- a/MyApp.Client/next.config.mjs
+++ b/MyApp.Client/next.config.mjs
@@ -20,7 +20,8 @@ console.log('next.config.mjs', process.env.NODE_ENV, buildLocal, API_URL, proces
* @type {import('next').NextConfig}
**/
const nextConfig = {
- pageExtensions: ['tsx','mdx','md'],
+ // Configure pageExtensions to include MDX files
+ pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// Enable static export (replaces next export command)
output: isProd ? 'export' : undefined,
diff --git a/MyApp.Client/npm-shrinkwrap.json b/MyApp.Client/npm-shrinkwrap.json
index 1b2843b..8f4318c 100644
--- a/MyApp.Client/npm-shrinkwrap.json
+++ b/MyApp.Client/npm-shrinkwrap.json
@@ -157,7 +157,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -507,7 +506,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -551,7 +549,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1536,7 +1533,6 @@
"resolved": "https://registry.npmjs.org/@mdx-js/loader/-/loader-3.1.1.tgz",
"integrity": "sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@mdx-js/mdx": "^3.0.0",
"source-map": "^0.7.0"
@@ -1596,7 +1592,6 @@
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz",
"integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/mdx": "^2.0.0"
},
@@ -2857,7 +2852,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -3005,7 +3001,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.4.tgz",
"integrity": "sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -3016,7 +3011,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -3170,7 +3164,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3203,6 +3196,7 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -3213,6 +3207,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -3235,6 +3230,7 @@
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
@@ -3358,7 +3354,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -3686,7 +3681,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/electron-to-chromium": {
"version": "1.5.251",
@@ -4602,7 +4598,6 @@
"integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.23",
"@asamuzakjp/dom-selector": "^6.7.4",
@@ -4983,6 +4978,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -6273,7 +6269,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -6310,6 +6305,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -6353,7 +6349,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6363,7 +6358,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -6376,7 +6370,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/react-refresh": {
"version": "0.18.0",
@@ -6984,8 +6979,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@@ -7056,7 +7050,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -7398,7 +7391,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -7492,7 +7484,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
diff --git a/MyApp.Client/pages/_app.tsx b/MyApp.Client/pages/_app.tsx
deleted file mode 100644
index e9cf55e..0000000
--- a/MyApp.Client/pages/_app.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { AppProps } from "next/app"
-import "../styles/index.css"
-import "../styles/main.css"
-import "../styles/prism-dark-blue.css"
-
-import Link from 'next/link'
-import { setLinkComponent, Loading, ClientContext } from '@servicestack/react'
-import { client, init } from "@/lib/gateway"
-import { useEffect } from "react";
-
-// Adapter component to convert react-router 'to' prop to Next.js 'href' prop
-const NextLink = ({ to, ...props }: any) =>
-
-setLinkComponent(NextLink)
-
-export default function MyApp({ Component, pageProps }: AppProps) {
- useEffect(() => {
- (async () => init())()
- }, [])
- return (
-
-
- )
-}
diff --git a/MyApp.Client/pages/_document.tsx b/MyApp.Client/pages/_document.tsx
deleted file mode 100644
index 5f70562..0000000
--- a/MyApp.Client/pages/_document.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import Document, { Html, Head, Main, NextScript } from "next/document"
-
-export default class MyDocument extends Document {
- render() {
- return (
-
-
-
-
-
-
-
-
- )
- }
-}
diff --git a/MyApp.Client/pages/index.tsx b/MyApp.Client/pages/index.tsx
deleted file mode 100644
index a503795..0000000
--- a/MyApp.Client/pages/index.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import Container from "@/components/container"
-import MoreStories from "@/components/more-stories"
-import HeroPost from "@/components/hero-post"
-import Intro from "@/components/intro"
-import Layout from "@/components/layout"
-import { getAllPosts } from "@/lib/api"
-import Head from "next/head"
-import { CMS_NAME } from "@/lib/constants"
-import Post from "@/types/post"
-import GettingStarted from "@/components/getting-started"
-import BuiltInUis from "@/components/builtin-uis"
-
-type Props = {
- allPosts: Post[]
-}
-
-const Index = ({ allPosts }: Props) => {
- const heroPost = allPosts[0]
- const morePosts = allPosts.slice(1)
- const title = `Next.js Example with ${CMS_NAME}`
-
- return (
- <>
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
- Manage your ServiceStack App and explore, discover, query and call APIs instantly with
- built-in Auto UIs dynamically generated from the rich metadata of your App's typed C# APIs & DTOs
-
-
-
-
-
- {heroPost && (
-
- )}
- {morePosts.length > 0 && }
-
-
- >
- )
-}
-
-export default Index
-
-export const getStaticProps = async () => {
- const allPosts = getAllPosts([
- 'title',
- 'date',
- 'slug',
- 'author',
- 'coverImage',
- 'excerpt',
- ])
-
- return {
- props: { allPosts },
- }
-}
diff --git a/MyApp.Client/pages/posts.tsx b/MyApp.Client/pages/posts.tsx
deleted file mode 100644
index 6070e25..0000000
--- a/MyApp.Client/pages/posts.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from "react"
-import Layout from "../components/layout"
-import { getAllPosts } from "../lib/api"
-import Post from "../types/post"
-
-type Props = {
- allPosts: Post[]
-}
-const Posts = ({ allPosts }: Props) => {
- return (
-
-
-
Markdown Posts
-
- List of Markdown Posts in /pages
-
- {allPosts.map((post) => (
-
-
{post.title}
- {!post.excerpt ? null :
- {post.excerpt}
-
}
-
- ))}
-
-
- )
-}
-export default Posts
-
-export const getStaticProps = async () => {
- const allPosts = getAllPosts([
- 'title',
- 'slug',
- 'excerpt',
- ])
- return {
- props: { allPosts },
- }
-}
diff --git a/MyApp.Client/pages/posts/[slug].tsx b/MyApp.Client/pages/posts/[slug].tsx
deleted file mode 100644
index f7e62bc..0000000
--- a/MyApp.Client/pages/posts/[slug].tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { useRouter } from "next/router"
-import ErrorPage from "next/error"
-import Container from "../../components/container"
-import PostBody from "../../components/post-body"
-import Header from "../../components/header"
-import PostHeader from "../../components/post-header"
-import Layout from "../../components/layout"
-import { getPostBySlug, getAllPosts } from "../../lib/api"
-import PostTitle from "../../components/post-title"
-import Head from "next/head"
-import { CMS_NAME } from "../../lib/constants"
-import markdownToHtml from "../../lib/markdownToHtml"
-import PostType from "../../types/post"
-
-type Props = {
- post: PostType
- morePosts: PostType[]
- preview?: boolean
-}
-
-const Post = ({ post, morePosts, preview }: Props) => {
- const router = useRouter()
- if (!router.isFallback && !post?.slug) {
- return
- }
- const title = `${post.title} | Next.js Example with ${CMS_NAME}`
- return (
-
-
-
- {router.isFallback ? (
- Loading…
- ) : (
- <>
-
-
- {title}
-
-
-
-
-
- >
- )}
-
-
- )
-}
-
-export default Post
-
-type Params = {
- params: {
- slug: string
- }
-}
-
-export async function getStaticProps({ params }: Params) {
- const post = getPostBySlug(params.slug, [
- 'title',
- 'date',
- 'slug',
- 'author',
- 'content',
- 'ogImage',
- 'coverImage',
- ])
- const content = await markdownToHtml(post.content || '')
-
- return {
- props: {
- post: {
- ...post,
- content,
- },
- },
- }
-}
-
-export async function getStaticPaths() {
- const posts = getAllPosts(['slug'])
-
- return {
- paths: posts.map((post) => {
- return {
- params: {
- slug: post.slug,
- },
- }
- }),
- fallback: false,
- }
-}
diff --git a/MyApp.Client/tsconfig.json b/MyApp.Client/tsconfig.json
index 3d63fe6..e148955 100644
--- a/MyApp.Client/tsconfig.json
+++ b/MyApp.Client/tsconfig.json
@@ -20,8 +20,15 @@
"incremental": true,
"baseUrl": ".",
"paths": {
- "@/*": ["./*"]
- }
+ "@/*": [
+ "./*"
+ ]
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
},
"exclude": [
"node_modules",
@@ -30,6 +37,10 @@
"include": [
"next-env.d.ts",
"**/*.ts",
- "**/*.tsx"
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "dist/types/**/*.ts",
+ "dist/dev/types/**/*.ts"
]
}