Skip to content

Commit c981d02

Browse files
authored
Hono に移行 (#46)
* refactor: main.ts を Hono に書き換え * refactor: POST /projects を Hono に * refactor: hono/client 導入 * refactor: GET /projects/mine, GET /projects/:projectId * refactor: PUT, DELETE /projects/:projectId * refactor: POST /submissions, PUT /submissions/mine * refactor: Submission.tsx * refactor: 一旦 useData を削除し hono RPC で fetch * refactor: uninstall express * refactor: rename to validator.ts * refactor: string → Date の変換を整理 * chore: server/tsconfig の設定を client に合わせて修正 * chore: update bun.lock
1 parent 15303ee commit c981d02

File tree

18 files changed

+695
-1872
lines changed

18 files changed

+695
-1872
lines changed

bun.lock

Lines changed: 11 additions & 203 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@vitejs/plugin-react": "^4.3.4",
4444
"daisyui": "^5.0.3",
4545
"globals": "^15.15.0",
46+
"hono": "^4.9.6",
4647
"typescript": "~5.7.2",
4748
"typescript-eslint": "^8.24.1",
4849
"vite": "^6.3.5"

client/src/components/Calendar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import type {
1414
import type React from "react";
1515
import { useCallback, useEffect, useMemo, useRef } from "react";
1616
import { Tooltip } from "react-tooltip";
17-
import type { ProjectRes } from "../../../common/schema";
17+
import type { Project } from "../types";
1818

1919
dayjs.locale("ja");
2020

2121
type Props = {
22-
project: ProjectRes;
22+
project: Project;
2323
myGuestId: string;
2424
mySlotsRef: React.RefObject<{ from: Date; to: Date }[]>;
2525
editMode: boolean;

client/src/hooks.ts

Lines changed: 0 additions & 90 deletions
This file was deleted.

client/src/pages/Home.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
1+
import { hc } from "hono/client";
2+
import { useEffect, useState } from "react";
13
import { HiOutlineCalendar, HiOutlineCog, HiOutlinePlus, HiOutlineUser, HiOutlineUsers } from "react-icons/hi";
24
import { NavLink } from "react-router";
3-
import { type InvolvedProjects, involvedProjectsResSchema } from "../../../common/schema";
5+
import type { AppType } from "../../../server/src/main";
46
import Header from "../components/Header";
5-
import { useData } from "../hooks";
7+
import { briefProjectReviver } from "../revivers";
8+
import type { BriefProject } from "../types";
69
import { API_ENDPOINT } from "../utils";
710

11+
const client = hc<AppType>(API_ENDPOINT);
12+
813
export default function HomePage() {
9-
const { data: involvedProjects, loading } = useData(`${API_ENDPOINT}/projects/mine`, involvedProjectsResSchema);
14+
const [involvedProjects, setInvolvedProjects] = useState<BriefProject[] | null>(null);
15+
const [loading, setLoading] = useState(true);
16+
17+
useEffect(() => {
18+
const fetchInvolvedProjects = async () => {
19+
setLoading(true);
20+
try {
21+
const res = await client.projects.mine.$get({}, { init: { credentials: "include" } });
22+
if (res.status === 200) {
23+
const data = await res.json();
24+
const parsedData = data.map((p) => briefProjectReviver(p));
25+
setInvolvedProjects(parsedData);
26+
} else {
27+
setInvolvedProjects(null);
28+
}
29+
} catch (error) {
30+
console.error("Error fetching involved projects:", error);
31+
setInvolvedProjects(null);
32+
} finally {
33+
setLoading(false);
34+
}
35+
};
36+
fetchInvolvedProjects();
37+
}, []);
1038

1139
return (
1240
<>
@@ -28,7 +56,7 @@ export default function HomePage() {
2856
);
2957
}
3058

31-
function ProjectDashboard({ involvedProjects }: { involvedProjects: InvolvedProjects }) {
59+
function ProjectDashboard({ involvedProjects }: { involvedProjects: BriefProject[] }) {
3260
const sortedProjects = [...involvedProjects].sort((a, b) => {
3361
if (a.isHost !== b.isHost) {
3462
return a.isHost ? -1 : 1;
@@ -76,7 +104,7 @@ function ProjectDashboard({ involvedProjects }: { involvedProjects: InvolvedProj
76104
);
77105
}
78106

79-
function ProjectCard({ project }: { project: InvolvedProjects[0] }) {
107+
function ProjectCard({ project }: { project: BriefProject }) {
80108
return (
81109
<NavLink
82110
to={`/${project.id}`}

client/src/pages/Project.tsx

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { zodResolver } from "@hookform/resolvers/zod";
22
import dayjs from "dayjs";
3+
import { hc } from "hono/client";
34
import { useEffect, useState } from "react";
45
import { useFieldArray, useForm } from "react-hook-form";
56
import {
@@ -11,22 +12,54 @@ import {
1112
} from "react-icons/hi";
1213
import { NavLink, useNavigate, useParams } from "react-router";
1314
import type { z } from "zod";
14-
import { editReqSchema, projectReqSchema, projectResSchema } from "../../../common/schema";
15+
import { editReqSchema, projectReqSchema } from "../../../common/validators";
16+
import type { AppType } from "../../../server/src/main";
1517
import Header from "../components/Header";
16-
import { useData } from "../hooks";
18+
import { projectReviver } from "../revivers";
19+
import type { Project } from "../types";
1720
import { API_ENDPOINT, FRONTEND_ORIGIN } from "../utils";
1821

22+
const client = hc<AppType>(API_ENDPOINT);
23+
1924
export default function ProjectPage() {
2025
const { eventId } = useParams();
2126
const navigate = useNavigate();
2227

2328
const formSchema = eventId ? editReqSchema : projectReqSchema;
2429
type FormSchemaType = z.infer<typeof formSchema>;
2530

26-
const { data: project, loading: projectLoading } = useData(
27-
eventId ? `${API_ENDPOINT}/projects/${eventId}` : null,
28-
projectResSchema,
29-
);
31+
const [project, setProject] = useState<Project | null>(null);
32+
const [projectLoading, setProjectLoading] = useState(true);
33+
useEffect(() => {
34+
const fetchProject = async () => {
35+
if (!eventId) {
36+
setProject(null);
37+
setProjectLoading(false);
38+
return;
39+
}
40+
setProjectLoading(true);
41+
try {
42+
const res = await client.projects[":projectId"].$get(
43+
{
44+
param: { projectId: eventId },
45+
},
46+
{
47+
init: { credentials: "include" },
48+
},
49+
);
50+
if (res.status === 200) {
51+
const data = await res.json();
52+
const parsedData = projectReviver(data);
53+
setProject(parsedData);
54+
}
55+
} catch (error) {
56+
console.error(error);
57+
} finally {
58+
setProjectLoading(false);
59+
}
60+
};
61+
fetchProject();
62+
}, [eventId]);
3063

3164
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
3265
const loading = projectLoading || submitLoading;
@@ -109,46 +142,46 @@ export default function ProjectPage() {
109142
} satisfies z.infer<typeof projectReqSchema>;
110143

111144
if (!project) {
112-
const res = await fetch(`${API_ENDPOINT}/projects`, {
113-
method: "POST",
114-
headers: { "Content-Type": "application/json" },
115-
body: JSON.stringify(eventData),
116-
credentials: "include",
117-
});
118-
119-
const { id: projectId, name: projectName } = await res.json(); // TODO:
145+
const res = await client.projects.$post(
146+
{
147+
json: eventData,
148+
},
149+
{
150+
init: { credentials: "include" },
151+
},
152+
);
120153

121154
setSubmitLoading(false);
122-
if (res.ok) {
155+
if (res.status === 201) {
156+
const { id, name } = await res.json();
123157
setDialogStatus({
124-
projectId: projectId,
125-
projectName: projectName,
158+
projectId: id,
159+
projectName: name,
126160
});
127161
} else {
162+
const { message } = await res.json();
128163
setToast({
129-
message: "送信に失敗しました",
164+
message,
130165
variant: "error",
131166
});
132167
setTimeout(() => setToast(null), 3000);
133168
}
134169
} else {
135-
const res = await fetch(`${API_ENDPOINT}/projects/${eventId}`, {
136-
method: "PUT",
137-
headers: { "Content-Type": "application/json" },
138-
body: JSON.stringify(eventData),
139-
credentials: "include",
140-
});
141-
170+
const res = await client.projects[":projectId"].$put(
171+
{ param: { projectId: project.id }, json: eventData },
172+
{ init: { credentials: "include" } },
173+
);
142174
setSubmitLoading(false);
143175
if (res.ok) {
176+
// TODO: 更新したデータで再レンダリング
144177
setToast({
145178
message: "更新しました。",
146179
variant: "success",
147180
});
148181
setTimeout(() => setToast(null), 3000);
149182
} else {
150183
setToast({
151-
message: res.status === 403 ? "認証に失敗しました。" : "更新に失敗しました。",
184+
message: res.status === 403 ? "権限がありません。" : "更新に失敗しました。",
152185
variant: "error",
153186
});
154187
setTimeout(() => setToast(null), 3000);
@@ -376,10 +409,11 @@ export default function ProjectPage() {
376409
onClick={async () => {
377410
if (confirm("本当にこのイベントを削除しますか?")) {
378411
try {
379-
const response = await fetch(`${API_ENDPOINT}/projects/${project.id}`, {
380-
method: "DELETE",
381-
});
382-
if (!response.ok) {
412+
const res = await client.projects[":projectId"].$delete(
413+
{ param: { projectId: project.id } },
414+
{ init: { credentials: "include" } },
415+
);
416+
if (!res.ok) {
383417
throw new Error("削除に失敗しました。");
384418
}
385419
navigate("/home");

0 commit comments

Comments
 (0)