Skip to content

Commit

Permalink
Merge pull request #200 from svobik7/feature/catch-all-non-optional
Browse files Browse the repository at this point in the history
feat(routes): added support for catch all non-optional routes
  • Loading branch information
svobik7 authored Mar 4, 2024
2 parents 6b6814d + 57d800e commit d01b64f
Show file tree
Hide file tree
Showing 24 changed files with 440 additions and 97 deletions.
10 changes: 10 additions & 0 deletions examples/basic/src/features/blog/utils/getBookLinkParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { BookTranslation } from 'src/server/db/types'
import { getBooksDetailHref } from 'src/server/router'

export function getBookLinkParams(book: BookTranslation) {
return {
locale: book.locale,
name: book.title,
href: getBooksDetailHref(book),
}
}
12 changes: 12 additions & 0 deletions examples/basic/src/features/blog/utils/getBookMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Metadata } from 'next'
import type { Book } from 'src/server/db/types'
import { getBookTranslation } from './getBookTranslation'

export function getBookMetadata(book: Book, locale: string): Metadata {
const bookTranslation = getBookTranslation({ book, locale })

return {
title: bookTranslation.title,
description: bookTranslation.content?.slice(0, 255),
}
}
27 changes: 27 additions & 0 deletions examples/basic/src/features/blog/utils/getBookTranslation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Book, BookTranslation } from '../../../server/db/types'
import { getTranslationFactory } from '../../common/utils/getTranslation'

type GetBookTranslationProps = {
book: Book
locale: string
}

export function getBookTranslation({ book, locale }: GetBookTranslationProps) {
const translate = getTranslationFactory(locale)

return {
...book,
title: translate(book, 'title'),
content: translate(book, 'content'),
slug: translate(book, 'slug'),
locale,
}
}

export function getAllBookTranslations(book: Book) {
return book.slug.map(({ locale }) => getBookTranslation({ book, locale }))
}

export function getBookTranslationFactory(locale: string) {
return (book: Book): BookTranslation => getBookTranslation({ locale, book })
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import type { ReactNode } from 'react'
import type { ProductTranslation } from 'src/server/db/types'
import { getDictionary } from 'src/server/utils/getDictionary'

type ProductDetailProps = {
product: ProductTranslation
type DetailProps = {
title: string
content: string
alternatives?: ReactNode
buttonBack?: ReactNode
}

export async function ProductDetail({
product,
export async function Detail({
title,
content,
alternatives,
buttonBack,
}: ProductDetailProps) {
const t = await getDictionary(product.locale)
}: DetailProps) {
return (
<div className="relative isolate overflow-hidden bg-white p-6 sm:py-8 lg:px-0">
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 lg:mx-0 lg:max-w-none lg:grid-cols-2 lg:items-start lg:gap-y-10">
<div className="lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:mx-auto lg:grid lg:w-full lg:max-w-7xl lg:grid-cols-2 lg:gap-x-8 lg:px-8">
<div className="lg:pr-4">
<div className="lg:max-w-lg">
<h1 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
{product.title}
{title}
</h1>

<p className="mt-2 text-xl leading-8 text-gray-700">
{product.content}
</p>
<p className="mt-2 text-xl leading-8 text-gray-700">{content}</p>
{buttonBack && <p className="mt-6">{buttonBack}</p>}
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions examples/basic/src/features/common/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Nav } from './Nav'
import 'src/features/common/styles.css'
import {
getAboutHref,
getBooksHref,
getContactsHref,
getHomeHref,
getProductsHref,
Expand All @@ -22,6 +23,7 @@ async function getNavigation(locale: string) {
return [
{ name: t('nav.Home'), href: getHomeHref(locale) },
{ name: t('nav.Products'), href: getProductsHref({ locale }) },
{ name: t('nav.Books'), href: getBooksHref(locale) },
{ name: t('nav.About'), href: getAboutHref(locale) },
{ name: t('nav.Contacts'), href: getContactsHref(locale) },
]
Expand Down
18 changes: 18 additions & 0 deletions examples/basic/src/features/common/components/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { BookTranslation } from 'src/server/db/types'
import { ListItem, ListItemProps } from './ListItem'

type Item = ListItemProps & { id: string | number }

type ListProps = {
items: Item[]
}

export function List({ items }: ListProps) {
return (
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-4 lg:mx-0 lg:max-w-none lg:grid-cols-3">
{items.map((item) => (
<ListItem key={item.id} {...item} />
))}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import Image from 'next/image'
import Link from 'next/link'
import type { ProductTranslation } from 'src/server/db/types'
import { getProductsHref, getAuthorHref } from 'src/server/router'
import type { BookTranslation } from 'src/server/db/types'
import { getBooksDetailHref, getBooksHref } from 'src/server/router'

type ProductsListItemProps = { product: ProductTranslation }

export function ProductsListItem({ product }: ProductsListItemProps) {
const hrefProduct = getProductsHref({ product })
export type ListItemProps = {
title: string
createdAt: string
content: string
href: string
}

export function ListItem({ content, createdAt, title, href }: ListItemProps) {
return (
<article className="flex max-w-xl flex-col items-start justify-between bg-white p-4">
<div className="flex items-center gap-x-4 text-xs">
<time dateTime="2020-03-16" className="text-gray-500">
{product.createdAt}
{createdAt}
</time>
</div>
<div className="group relative">
<h3 className="mt-3 text-lg font-semibold leading-6 text-gray-900 group-hover:text-gray-600">
<Link href={hrefProduct}>
<Link href={href}>
<span className="absolute inset-0"></span>
{product.title}
{title}
</Link>
</h3>
<p className="mt-5 text-sm leading-6 text-gray-600">
{product.content.slice(0, 160)}...
{content.slice(0, 160)}...
</p>
</div>
</article>
Expand Down
16 changes: 0 additions & 16 deletions examples/basic/src/features/products/components/ProductsList.tsx

This file was deleted.

103 changes: 103 additions & 0 deletions examples/basic/src/routes/books/[...slugs]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { Metadata } from 'next'
import type {
GeneratePageMetadataProps,
GenerateStaticParamsProps,
PageProps,
} from 'next-roots'
import Link from 'next/link'
import { notFound, redirect } from 'next/navigation'
import { getBookLinkParams } from 'src/features/blog/utils/getBookLinkParams'
import { getBookMetadata } from 'src/features/blog/utils/getBookMetadata'
import {
getAllBookTranslations,
getBookTranslation,
} from 'src/features/blog/utils/getBookTranslation'
import { Detail } from 'src/features/common/components/Detail'
import { Links } from 'src/features/common/components/Links'
import { fetchBookBySlug, fetchBooks, fetchProductBySlug } from 'src/server/db'
import { getBooksDetailHref, getHomeHref, router } from 'src/server/router'
import { getDictionary } from 'src/server/utils/getDictionary'

type BookParam = { slugs: string[] }

export default async function BookPage({
params,
pageHref,
}: PageProps<BookParam>) {
const pageLocale = router.getLocaleFromHref(pageHref)
const t = await getDictionary(pageLocale)

const book = await fetchBookBySlug(params.slugs)

if (!book) {
return notFound()
}

const allBookTranslations = getAllBookTranslations(book)
const currentBookTranslation = getBookTranslation({
book,
locale: pageLocale,
})

if (!currentBookTranslation) {
return notFound()
}

const href = getBooksDetailHref(currentBookTranslation)

if (pageHref !== href) {
return redirect(href)
}

return (
<Detail
title={currentBookTranslation.title}
content={currentBookTranslation.content}
alternatives={
<Links
header={t('common.NotYourLanguage?')}
items={allBookTranslations.map(getBookLinkParams)}
/>
}
buttonBack={
<Link
href={getHomeHref(currentBookTranslation.locale)}
role="button"
className="rounded bg-indigo-600 px-4 py-2 text-base font-semibold leading-7 text-white"
>
{t('article.BtnBack')}
</Link>
}
/>
)
}

export async function generateMetadata({
pageHref,
params,
}: GeneratePageMetadataProps<BookParam>): Promise<Metadata> {
const pageLocale = router.getLocaleFromHref(pageHref)
const t = await getDictionary(pageLocale)

const book = await fetchProductBySlug(params.slugs)

if (!book) {
return {}
}

return getBookMetadata(book, pageLocale)
}

export async function generateStaticParams({
pageLocale,
}: GenerateStaticParamsProps) {
const books = await fetchBooks()

return books
.map((book) => ({
slugs: book.slug
.find((slug) => slug.locale === pageLocale)
?.value.split('/'),
}))
.filter((book) => book.slugs)
}
5 changes: 5 additions & 0 deletions examples/basic/src/routes/books/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports.routeNames = [
{ locale: 'en', path: 'books' },
{ locale: 'cs', path: 'knihy' },
{ locale: 'es', path: 'libros' },
]
35 changes: 35 additions & 0 deletions examples/basic/src/routes/books/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Metadata } from 'next'
import type { GeneratePageMetadataProps, PageProps } from 'next-roots'
import { getBookTranslationFactory } from 'src/features/blog/utils/getBookTranslation'
import { List } from 'src/features/common/components/List'
import { fetchBooks } from 'src/server/db'
import { getBooksDetailHref, router } from 'src/server/router'
import { getDictionary } from 'src/server/utils/getDictionary'

export default async function BooksPage({ pageHref }: PageProps) {
const pageLocale = router.getLocaleFromHref(pageHref)
const translateBook = getBookTranslationFactory(pageLocale)

const books = await fetchBooks()

return (
<List
items={books.map(translateBook).map((book) => ({
id: book.id,
title: book.title,
href: getBooksDetailHref(book),
content: book.content,
createdAt: book.createdAt,
}))}
/>
)
}

export async function generateMetadata({
pageHref,
}: GeneratePageMetadataProps): Promise<Metadata> {
const pageLocale = router.getLocaleFromHref(pageHref)
const t = await getDictionary(pageLocale)

return { title: t('books.title'), description: t('books.content') }
}
Loading

0 comments on commit d01b64f

Please sign in to comment.