Skip to content

Commit

Permalink
refactor: remix-routes
Browse files Browse the repository at this point in the history
coji committed Mar 6, 2024
1 parent 570a24e commit 69f2652
Showing 17 changed files with 353 additions and 54 deletions.
7 changes: 4 additions & 3 deletions app/routes/$handle+/_index.tsx
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import {
} from '@remix-run/react'
import { MoreVerticalIcon, PlusIcon } from 'lucide-react'
import React from 'react'
import { $path } from 'remix-routes'
import { AppHeadingSection } from '~/components/AppHeadingSection'
import {
Button,
@@ -45,13 +46,13 @@ export const clientAction = async ({
request,
}: ClientActionFunctionArgs) => {
const handle = params.handle
const user = await requireUser(request, { failureRedirect: '/' })
const user = await requireUser(request, { failureRedirect: $path('/') })
if (user.handle !== handle) {
throw new Error('Unauthorized')
}

const newPost = await addUserPost(user.handle)
return redirect(`/${handle}/posts/${newPost.id}/edit`)
return redirect($path('/:handle/posts/:id', { handle, id: newPost.id }))
}

const PostCard = ({ handle, post }: { handle: string; post: Post }) => {
@@ -67,7 +68,7 @@ const PostCard = ({ handle, post }: { handle: string; post: Post }) => {
className="relative rounded-xl border-none bg-slate-100"
>
<Link
to={`posts/${post.id}`}
to={$path('/:handle/posts/:id', { handle: post.handle, id: post.id })}
className="absolute inset-0"
prefetch="intent"
>
12 changes: 9 additions & 3 deletions app/routes/$handle+/posts.$id._index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ClientLoaderFunctionArgs, Link, useLoaderData } from '@remix-run/react'
import { ArrowLeftIcon, PencilIcon } from 'lucide-react'
import { $path } from 'remix-routes'
import { AppHeadingSection } from '~/components/AppHeadingSection'
import { Button } from '~/components/ui'
import { dayjs } from '~/libs/dayjs'
@@ -27,15 +28,18 @@ export default function PostPage() {
{handle === user?.handle && (
<nav className="flex px-4 py-2">
<Button variant="ghost" size="sm" className="rounded-full" asChild>
<Link to={`/${handle}`} prefetch="intent">
<Link to={$path('/:handle', { handle })} prefetch="intent">
<ArrowLeftIcon className="h-4 w-4" />
</Link>
</Button>

<div className="flex-1" />

<Button size="sm" variant="ghost" asChild>
<Link to={`/${handle}/posts/${id}/edit`} prefetch="intent">
<Link
to={$path('/:handle/posts/:id/edit', { handle, id })}
prefetch="intent"
>
<PencilIcon className="mr-2 h-4 w-4" />
記事を編集
</Link>
@@ -48,7 +52,9 @@ export default function PostPage() {

<div className="flex items-center gap-1 text-slate-500">
<div>
<Link to={`/${post.handle}`}>{post.handle}</Link>
<Link to={$path('/:handle', { handle: post.handle })}>
{post.handle}
</Link>
</div>
<div>·</div>
<div>{dayjs(post.publishedAt).format('YYYY/MM/DD')}</div>
9 changes: 5 additions & 4 deletions app/routes/$handle+/posts.$id.delete.tsx
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import {
redirect,
useFetcher,
} from '@remix-run/react'
import { $path } from 'remix-routes'
import {
AlertDialog,
AlertDialogAction,
@@ -25,12 +26,12 @@ export const clientAction = async ({
}: ClientActionFunctionArgs) => {
const { handle, id } = params
if (!id) throw json({ message: 'Not found' }, { status: 404 })
const user = await requireUser(request, { failureRedirect: '/' })
const user = await requireUser(request, { failureRedirect: $path('/') })
if (user.handle !== handle) {
throw json({ message: 'Unauthorized' }, { status: 401 })
}
await deleteUserPost(handle, id)
return redirect(`/${handle}`)
return redirect($path('/:handle', { handle }))
}

interface PostDeleteMenuItemProps {
@@ -54,7 +55,7 @@ export const PostDeleteMenuItem = ({
{},
{
method: 'POST',
action: `/${handle}/posts/${post.id}/delete`,
action: $path('/:handle/posts/:id/delete', { handle, id: post.id }),
},
)
}}
@@ -84,7 +85,7 @@ export const DeleteAlertDialog = ({
{},
{
method: 'POST',
action: `/${handle}/posts/${post.id}/delete`,
action: $path('/:handle/posts/:id/delete', { handle, id: post.id }),
},
)
}
14 changes: 9 additions & 5 deletions app/routes/$handle+/posts.$id.edit.tsx
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import {
useLoaderData,
} from '@remix-run/react'
import { ArrowLeftIcon } from 'lucide-react'
import { $path } from 'remix-routes'
import { z } from 'zod'
import { AppHeadingSection } from '~/components/AppHeadingSection'
import { Button, Input, Label, Textarea } from '~/components/ui'
@@ -39,7 +40,7 @@ export const clientLoader = async ({
if (!handle || !id) throw json({ message: 'Not found', status: 404 })

// 本人の投稿以外は編集できない / 存在確認
const user = await requireUser(request, { failureRedirect: '/' })
const user = await requireUser(request, { failureRedirect: $path('/') })
if (handle !== user.handle) throw json({ message: 'Forbidden', status: 403 })

const post = await getUserPostById(handle, id)
@@ -55,15 +56,15 @@ export const clientAction = async ({
if (!handle || !id) throw json({ message: 'Not found', status: 404 })

// 本人の投稿以外は編集できない / 存在確認
const user = await requireUser(request, { failureRedirect: '/' })
const user = await requireUser(request, { failureRedirect: $path('/') })
if (handle !== user.handle) throw json({ message: 'Forbidden', status: 403 })

const formData = await request.formData()

// 削除
if (String(formData.get('intent')) === 'delete') {
await deleteUserPost(handle, id)
return redirect(`/${handle}`)
return redirect($path('/:handle', { handle }))
}

// 更新
@@ -80,7 +81,7 @@ export const clientAction = async ({
content: submission.value.content,
publishedAt: null,
})
return redirect(`/${handle}/posts/${id}`)
return redirect($path('/:handle/posts/:id', { handle, id }))
}

export default function PostEditPage() {
@@ -103,7 +104,10 @@ export default function PostEditPage() {
<nav className="sticky top-0 flex flex-row gap-4 px-4 py-2 sm:justify-between">
{post.publishedAt ? (
<Button variant="ghost" size="sm" className="rounded-full" asChild>
<Link to={`/${handle}/posts/${id}`} prefetch="intent">
<Link
to={$path('/:handle/posts/:id', { handle, id })}
prefetch="intent"
>
<ArrowLeftIcon className="h-4 w-4" />
</Link>
</Button>
9 changes: 6 additions & 3 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
useLoaderData,
} from '@remix-run/react'
import { ExternalLink } from 'lucide-react'
import { $path } from 'remix-routes'
import { AppFooter } from '~/components/AppFooter'
import { AppHeadingSection } from '~/components/AppHeadingSection'
import { Button } from '~/components/ui'
@@ -26,7 +27,7 @@ export const meta: MetaFunction = () => {
export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
const user = await isAuthenticated(request)
if (user?.handle) {
return redirect(`/${user.handle}`)
return redirect($path('/:handle', { handle: user.handle }))
}
return user
}
@@ -41,7 +42,9 @@ export default function IndexPage() {

{user?.handle ? (
<Button variant="outline" className="rounded-full" asChild>
<Link to={`/${user.handle}`}>自分のページへ</Link>
<Link to={$path('/:handle', { handle: user.handle })}>
自分のページへ
</Link>
</Button>
) : (
<SignInModal />
@@ -73,7 +76,7 @@ export default function IndexPage() {
</div>

<Link
to="/coji"
to={$path('/:handle', { handle: 'coji' })}
className="text-muted-foreground underline"
prefetch="intent"
>
13 changes: 8 additions & 5 deletions app/routes/_public+/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link, Outlet, useLocation } from '@remix-run/react'
import { $path } from 'remix-routes'
import { AppFooter } from '~/components/AppFooter'
import { AppHeadingSection } from '~/components/AppHeadingSection'
import { Button, Tabs, TabsList, TabsTrigger } from '~/components/ui'
@@ -12,18 +13,20 @@ export default function PublicPageLayout() {
return (
<div>
<nav className="flex items-center px-4 py-2">
<Link to="/" className="flex-1">
<Link to={$path('/')} className="flex-1">
しずかな Remix SPA Example
</Link>
<div>
{user ? (
{user?.handle ? (
<Button
size="sm"
variant="outline"
className="rounded-full"
asChild
>
<Link to={`/${user.handle}`}>自分のページへ</Link>
<Link to={$path('/:handle', { handle: user.handle })}>
自分のページへ
</Link>
</Button>
) : (
<SignInModal />
@@ -38,10 +41,10 @@ export default function PublicPageLayout() {
<Tabs defaultValue={pathname.split('/').at(1)}>
<TabsList>
<TabsTrigger value="license" asChild>
<Link to="/license">利用規約</Link>
<Link to={$path('/license')}>利用規約</Link>
</TabsTrigger>
<TabsTrigger value="privacy" asChild>
<Link to="/privacy">プライバシーポリシー</Link>
<Link to={$path('/privacy')}>プライバシーポリシー</Link>
</TabsTrigger>
</TabsList>
</Tabs>
5 changes: 3 additions & 2 deletions app/routes/auth+/google.callback.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { redirect } from '@remix-run/react'
import { $path } from 'remix-routes'
import { toast } from '~/components/ui'
import { authenticateCallback } from '~/services/google-auth'

@@ -11,9 +12,9 @@ export const clientLoader = async () => {
title: 'サインインしました',
description: `${user.displayName} さん、ようこそ!`,
})
return redirect(`/${user.handle}`)
return redirect($path('/:handle', { handle: user.handle }))
}
return redirect('/')
return redirect($path('/'))
}

// clientLoader だけでは動かないのでダミーの route コンポーネント
7 changes: 4 additions & 3 deletions app/routes/auth+/sign_in.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import {
redirect,
useFetcher,
} from '@remix-run/react'
import { $path } from 'remix-routes'
import {
Button,
Card,
@@ -25,7 +26,7 @@ import { authenticate } from '~/services/google-auth'
export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
const user = await isAuthenticated(request)
if (user?.handle) {
return redirect(`/${user.handle}`)
return redirect($path('/:handle', { handle: user.handle }))
}
return null
}
@@ -42,7 +43,7 @@ const SignInForm = () => {
{},
{
method: 'POST',
action: '/auth/sign_in',
action: $path('/auth/sign_in'),
},
)
}
@@ -97,7 +98,7 @@ export default function SignInPage() {

<div className="mx-auto text-center">
<Button variant="link" asChild>
<Link to="/">トップページに戻る</Link>
<Link to={$path('/')}>トップページに戻る</Link>
</Button>
</div>
</div>
7 changes: 4 additions & 3 deletions app/routes/auth+/sign_out.tsx
Original file line number Diff line number Diff line change
@@ -4,11 +4,12 @@ import {
redirect,
useFetcher,
} from '@remix-run/react'
import { $path } from 'remix-routes'
import { Button, toast } from '~/components/ui'
import { requireAuth, signOut } from '~/services/auth'

export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
await requireAuth(request, { failureRedirect: '/' })
await requireAuth(request, { failureRedirect: $path('/') })
return null
}

@@ -18,14 +19,14 @@ export const clientAction = async () => {
title: 'サインアウトしました',
description: 'またのご利用をお待ちしております。',
})
return redirect('/')
return redirect($path('/'))
}

export const useSignOut = () => {
const fetcher = useFetcher()

const signOut = () => {
fetcher.submit({}, { method: 'POST', action: '/auth/sign_out' })
fetcher.submit({}, { method: 'POST', action: $path('/auth/sign_out') })
}

return { signOut }
3 changes: 2 additions & 1 deletion app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ClientLoaderFunctionArgs } from '@remix-run/react'
import { $path } from 'remix-routes'
import { requireUser } from '~/services/auth'
import IndexPage from './_index'

export const clientLoader = ({ request }: ClientLoaderFunctionArgs) => {
const user = requireUser(request, { failureRedirect: '/' })
const user = requireUser(request, { failureRedirect: $path('/') })
return user
}

11 changes: 6 additions & 5 deletions app/routes/welcome+/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ClientLoaderFunctionArgs, Link, redirect } from '@remix-run/react'
import { $path } from 'remix-routes'
import { AppHeadingSection } from '~/components/AppHeadingSection'
import { Button, Stack } from '~/components/ui'
import { useSignOut } from '~/routes/auth+/sign_out'
import { requireAuth } from '~/services/auth'

export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
const user = await requireAuth(request, { failureRedirect: '/' })
const user = await requireAuth(request, { failureRedirect: $path('/') })
if (user.handle) {
return redirect(`/${user.handle}`)
return redirect($path('/:handle', { handle: user.handle }))
}
return null
}
@@ -20,18 +21,18 @@ export default function WelcomeIndexPage() {

<Stack className="rounded-3xl bg-slate-100 p-6">
<div className="text-slate-700">
<Link className="underline" to="/license" target="_blank">
<Link className="underline" to={$path('/license')} target="_blank">
利用規約
</Link>
<Link className="underline" to="/privacy" target="_blank">
<Link className="underline" to={$path('/privacy')} target="_blank">
プライバシーポリシー
</Link>
をご確認ください。
</div>

<Button variant="outline" size="lg" asChild>
<Link to="/welcome/create_account" prefetch="render">
<Link to={$path('/welcome/create_account')} prefetch="render">
同意する
</Link>
</Button>
Loading

0 comments on commit 69f2652

Please sign in to comment.