add game
This commit is contained in:
		| @@ -0,0 +1,4 @@ | ||||
|             <Button asChild> | ||||
|               <Link href="/post/game">Game</Link> | ||||
|             </Button> | ||||
|  | ||||
| @@ -0,0 +1,26 @@ | ||||
| "use server"; | ||||
|  | ||||
| import { DID } from "@/lib/data/atproto/did"; | ||||
| import { getVerifiedHandle } from "@/lib/data/atproto/identity"; | ||||
| import { putPost } from "@/lib/data/atproto/game"; | ||||
| import { uncached_doesPostExist } from "@/lib/data/db/post"; | ||||
| import { DataLayerError } from "@/lib/data/error"; | ||||
| import { ensureUser } from "@/lib/data/user"; | ||||
| import { redirect } from "next/navigation"; | ||||
|  | ||||
| export async function newPostAction(_prevState: unknown) { | ||||
| 	"use server"; | ||||
| 	const user = await ensureUser(); | ||||
| 	const [handle] = await Promise.all([ | ||||
| 		getVerifiedHandle(user.did), | ||||
| 	]); | ||||
|  | ||||
| 	const account = `at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.game.user/${handle}`; | ||||
| 	try { | ||||
| 		const { rkey } = await putPost({ account }); | ||||
| 		redirect(`https://at.syu.is/at/${user.did}/ai.syui.game/self`); | ||||
| 	} catch (error) { | ||||
| 		if (!(error instanceof DataLayerError)) throw error; | ||||
| 		return { error: "Failed to create post" }; | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,36 @@ | ||||
| "use client"; | ||||
|  | ||||
| import { startTransition, useActionState, useId, useState } from "react"; | ||||
| import { newPostAction } from "./_action"; | ||||
| import { Label } from "@/lib/components/ui/label"; | ||||
| import { Input } from "@/lib/components/ui/input"; | ||||
| import { Button } from "@/lib/components/ui/button"; | ||||
| import { Alert, AlertDescription, AlertTitle } from "@/lib/components/ui/alert"; | ||||
| import { Spinner } from "@/lib/components/ui/spinner"; | ||||
| import { InputLengthIndicator } from "@/lib/components/input-length-indicator"; | ||||
|  | ||||
| export function NewPostButton() { | ||||
|   const [state, action, isPending] = useActionState(newPostAction, null); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
|  | ||||
|   const handleClick = async () => { | ||||
|     try { | ||||
|       const result = await action({}); | ||||
|       if (result && 'error' in result) { | ||||
|         setError(result.error); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       console.error('Error creating post:', e); | ||||
|       setError('予期しないエラーが発生しました'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|       <button onClick={handleClick} disabled={isPending}> | ||||
|         {isPending ? '投稿中...' : '新規投稿'} | ||||
|       </button> | ||||
|       {error && <p style={{ color: 'red' }}>{error}</p>} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| import { Metadata } from "next"; | ||||
| import { NewPostButton } from "./_client"; | ||||
|  | ||||
| export const metadata: Metadata = { | ||||
|   title: "New post | Frontpage", | ||||
|   robots: "noindex, nofollow", | ||||
| }; | ||||
|  | ||||
| export default function NewPost() { | ||||
|   return ( | ||||
|     <main className="flex flex-col gap-3"> | ||||
|       <h2 className="text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-100"> | ||||
|       </h2> | ||||
|       <NewPostButton /> | ||||
|     </main> | ||||
|   ); | ||||
| } | ||||
| @@ -0,0 +1,62 @@ | ||||
| import "server-only"; | ||||
| import { | ||||
|   atprotoPutRecord, | ||||
|   atprotoCreateRecord, | ||||
|   atprotoDeleteRecord, | ||||
|   atprotoGetRecord, | ||||
| } from "./record"; | ||||
| import { z } from "zod"; | ||||
| import { DataLayerError } from "../error"; | ||||
| import { DID, getPdsUrl } from "./did"; | ||||
|  | ||||
| export const PostCollection = "ai.syui.game"; | ||||
|  | ||||
| export const PostRecord = z.object({ | ||||
|   account: z.string(), | ||||
|   createdAt: z.string(), | ||||
| }); | ||||
|  | ||||
| export type Post = z.infer<typeof PostRecord>; | ||||
|  | ||||
| type PostInput = { | ||||
|   account: string; | ||||
| }; | ||||
|  | ||||
| export async function putPost({ account }: PostInput) { | ||||
|   const record = { account, createdAt: new Date().toISOString() }; | ||||
|   PostRecord.parse(record); | ||||
|  | ||||
|   const result = await atprotoPutRecord({ | ||||
|     rkey: "self", | ||||
|     record, | ||||
|     collection: PostCollection, | ||||
|   }); | ||||
|  | ||||
|   return { | ||||
|     rkey: "self", | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export async function deletePost(rkey: string) { | ||||
|   await atprotoDeleteRecord({ | ||||
|     rkey, | ||||
|     collection: PostCollection, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export async function getPost({ rkey, repo }: { rkey: string; repo: DID }) { | ||||
|   const service = await getPdsUrl(repo); | ||||
|  | ||||
|   if (!service) { | ||||
|     throw new DataLayerError("Failed to get service url"); | ||||
|   } | ||||
|  | ||||
|   const { value } = await atprotoGetRecord({ | ||||
|     serviceEndpoint: service, | ||||
|     repo, | ||||
|     collection: PostCollection, | ||||
|     rkey, | ||||
|   }); | ||||
|  | ||||
|   return PostRecord.parse(value); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user