Skip to content
This repository has been archived by the owner on May 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5 from f-lab-edu/feat/3/start-activity
Browse files Browse the repository at this point in the history
사용자가 시작 버튼으로 시작가능
  • Loading branch information
tolluset authored Jan 30, 2024
2 parents 523e4a1 + 4691d22 commit 7903009
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 12 deletions.
45 changes: 45 additions & 0 deletions src/app/activities/[activity-id]/start/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import BasicLayout from "~/components/ui/BasicLayout";
import { Textarea } from "~/components/ui/textarea";
import { getActivity } from "~/models/activity";

export default function ActivitiesStartPage({
params,
}: {
params: { "activity-id": string };
}) {
return (
<BasicLayout>
<div className="flex flex-col items-center w-full gap-y-4">
<Timer />
<Editor activityId={params["activity-id"]} />
</div>
</BasicLayout>
);
}

function Timer() {
return (
<>
00:00:00
<hr className="text-gray-500 w-full" />
</>
);
}

async function Editor({ activityId }: { activityId: string }) {
if (!activityId) {
return "존재하지 않는 활동이에요!";
}

const activity = await getActivity({ activityId });

return (
<section className="w-full">
<div className="pb-4">
<h1 className="font-bold">{activity.name}</h1>
<p className="text-sm text-gray-500">{activity.description}</p>
</div>
<Textarea autoFocus className="h-[80vh]" />
</section>
);
}
6 changes: 3 additions & 3 deletions src/app/activities/features/ListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
CardTitle,
} from "~/components/ui/card";
import PlayButton from "./PlayButton";
import type { Activity } from "~/models/activity";
import { type Activity } from "~/models/activity";

export default function ListCard({ activity }: { activity: Activity }) {
return (
Expand All @@ -16,9 +16,9 @@ export default function ListCard({ activity }: { activity: Activity }) {
<CardTitle>{activity.name}</CardTitle>
<CardDescription>{activity.description}</CardDescription>
</CardHeader>
<CardContent></CardContent>
<CardContent />
<CardFooter className="justify-end">
<PlayButton />
<PlayButton activity={activity} />
</CardFooter>
</Card>
);
Expand Down
23 changes: 21 additions & 2 deletions src/app/activities/features/PlayButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
"use client";

import { useRouter } from "next/navigation";
import React from "react";
import { PlayIcon } from "~/components/icons";
import { Button } from "~/components/ui/button";
import { Activity, startActivity } from "~/models/activity";

export default function PlayButton(
props: React.ComponentPropsWithoutRef<"button"> & { activity: Activity },
) {
const { activity, ...rest } = props;

const router = useRouter();

async function handleStartActivity() {
await startActivity({
activity: { ...activity, started_at: new Date().getTime() },
});

router.push(`/activities/${activity.id}/start`);
}

export default function PlayButton() {
return (
<Button>
<Button {...rest} onClick={handleStartActivity}>
<PlayIcon />
</Button>
);
Expand Down
5 changes: 3 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import AddButton from "~/app/activities/features/AddButton";
import AcgivityList from "./activities/widgets/ActivityList";
import BasicLayout from "~/components/ui/BasicLayout";

export default async function HomePage() {
return (
<main className="flex max-w-96 m-auto min-h-screen flex-col items-center justify-between p-8">
<BasicLayout>
<div className="flex flex-col items-center w-full gap-y-4">
00:00:00
<hr className="text-gray-500 w-full" />
<AddButton />
<AcgivityList />
</div>
</main>
</BasicLayout>
);
}
13 changes: 13 additions & 0 deletions src/components/ui/BasicLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";

export default function BasicLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<main className="flex max-w-96 m-auto min-h-screen flex-col items-center justify-between p-8">
{children}
</main>
);
}
24 changes: 24 additions & 0 deletions src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react"

import { cn } from "~/lib/utils"

export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}

const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"

export { Textarea }
23 changes: 20 additions & 3 deletions src/mocks/http.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from "express";
import cors from "cors";
import { Activity } from "~/models/activity";

const app = express();
const port = 9090;
Expand All @@ -13,13 +14,13 @@ const genId = () => {
count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString();
};

const data = {
const data: { activities: Activity[] } = {
activities: [
{
id: "0",
name: "사이드 프로젝트/giroker",
description: "시간단위로 기록하는 프로젝트",
started_at: -1,
},
],
};
Expand All @@ -30,11 +31,27 @@ const PATHS = {

app.listen(port, () => console.log(`Mock server is running on port: ${port}`));

app.get(PATHS.activities + "/:activity_id", (req, res) => {
const id = req.params["activity_id"];
const activity = data.activities.find((activity) => activity.id === id);

res.json({ data: activity });
});

app.get(PATHS.activities, (_, res) => {
res.json({ data });
});

app.post(PATHS.activities, (req, res) => {
data.activities.unshift({ id: genId(), ...req.body });
data.activities.unshift({ id: genId(), started_at: -1, ...req.body });
res.status(201).end();
});

app.post(PATHS.activities + "/:activity_id/start", (req, res) => {
const id = req.params["activity_id"];
const index = data.activities.findIndex((activity) => activity.id === id);

data.activities[index] = { ...req.body };

res.end();
});
29 changes: 27 additions & 2 deletions src/models/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Activity = {
id: string;
name: string;
description: string;
started_at: number;
};

export const ACTIVITES = "activities";
Expand All @@ -19,6 +20,18 @@ const activitySchema = z.object({
description: z.string().max(80, { message: "80자 이하로 설정해주세요" }),
});

async function getActivity({
activityId,
}: {
activityId: Activity["id"];
}): Promise<Activity> {
const result = await fetch(`${API_ENDPOINT}/${ACTIVITES}/${activityId}`, {
cache: "no-store",
}).then((d) => d.json());

return result.data;
}

async function getActivities(): Promise<Activity[]> {
const result = await fetch(`${API_ENDPOINT}/${ACTIVITES}`, {
cache: "no-store",
Expand All @@ -29,7 +42,6 @@ async function getActivities(): Promise<Activity[]> {

async function createActivity({ body }: { body: Omit<Activity, "id"> }) {
await fetch(`${API_ENDPOINT}/${ACTIVITES}`, {
cache: "no-store",
method: "post",
headers: {
Accept: "application/json",
Expand All @@ -39,10 +51,23 @@ async function createActivity({ body }: { body: Omit<Activity, "id"> }) {
});
}

async function startActivity({ activity }: { activity: Activity }) {
await fetch(`${API_ENDPOINT}/${ACTIVITES}/${activity.id}/start`, {
method: "post",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(activity),
});
}

export {
type Activity,
type ActivitySchema,
activitySchema,
createActivity,
getActivity,
getActivities,
createActivity,
startActivity,
};

0 comments on commit 7903009

Please sign in to comment.