Skip to content

Commit

Permalink
feat/nextjs: basic add tasks (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonitienda authored Mar 29, 2024
2 parents c074948 + 789eb54 commit b23461c
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 38 deletions.
34 changes: 5 additions & 29 deletions webapp-nextjs/src/api/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
import { getAccessToken } from "@auth0/nextjs-auth0";

function buildUrl(path: string) {
return `${process.env.BACKEND_BASE_URL}${path}`;
}

export async function getTasks() {
const accessTokenResponse = await getAccessToken({
authorizationParams: {
audience: process.env.AUTH0_AUDIENCE || "",
},
});

const res = await fetch(buildUrl("/v0/tasks"), {
cache: "no-store",
headers: {
Authorization: "Bearer " + accessTokenResponse.accessToken || "",
},
});
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
const res = await fetch("/api/tasks");

if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
Expand All @@ -29,18 +10,13 @@ export async function getTasks() {
return res.json();
}

export async function addTask() {
const res = await fetch(buildUrl("/v0/tasks"), {
export async function addTask(title: string, description: string) {
const res = await fetch("/api/tasks", {
method: "POST",
body: JSON.stringify({
title: "New task",
description: "This is a new task",
title,
description,
}),

headers: {
"X-User-ID": "b78fe6be-0642-4cfa-9f19-9cc8e53b129d",
"Content-Type": "application/json",
},
});
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
Expand Down
56 changes: 56 additions & 0 deletions webapp-nextjs/src/app/api/tasks/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { getAccessToken } from "@auth0/nextjs-auth0";

function buildUrl(path: string) {
return `${process.env.BACKEND_BASE_URL}${path}`;
}

async function getAuthHeader() {
const accessTokenResponse = await getAccessToken({
authorizationParams: {
audience: process.env.AUTH0_AUDIENCE || "",
},
});

return "Bearer " + accessTokenResponse.accessToken || "";
}

export const GET = async function getTasks() {
const res = await fetch(buildUrl("/v0/tasks"), {
cache: "no-store",
headers: {
Authorization: await getAuthHeader(),
},
});

if (!res.ok) {
const b = await res.json();
throw new Error("Failed to fetch data");
}

const data = await res.json();
return Response.json(data);
};

export const POST = async function addTask(req: Request) {
const reqData = await req.json();

const res = await fetch(buildUrl("/v0/tasks"), {
method: "POST",
body: JSON.stringify({
title: reqData.title,
description: reqData.description,
}),

headers: {
Authorization: await getAuthHeader(),
},
});

if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error("Failed to add task");
}

const resData = res.json();
return Response.json(resData);
};
82 changes: 73 additions & 9 deletions webapp-nextjs/src/components/TaskList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Divider from "@mui/material/Divider";
import Input from "@mui/material/Input";
import ListItemText from "@mui/material/ListItemText";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import { getTasks } from "@/api/tasks";
import { getTasks, addTask } from "@/api/tasks";
import PendingIcon from "@mui/icons-material/Pending";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import Typography from "@mui/material/Typography";
import { FormGroup } from "@mui/material";
import { Button } from "@mui/base";

type TaskStatusIconMap = {
[key: string]: JSX.Element;
Expand Down Expand Up @@ -37,14 +40,24 @@ type Task = {
status: string;
};

export default async function TasksList() {
let tasks = [];
try {
tasks = await getTasks();
} catch (error) {
console.error(error);
return null;
}
type TaskRequest = {
title: string;
description: string;
};

export default function TasksList() {
const [addingTask, setAddingTask] = React.useState(false);
const [tasks, setTasks] = React.useState<Task[]>([]);
const [newTask, setNewTask] = React.useState<TaskRequest>({
title: "",
description: "",
});

const refreshTasks = () => getTasks().then(setTasks);

React.useEffect(() => {
refreshTasks();
}, []);

return (
<>
Expand All @@ -64,6 +77,57 @@ export default async function TasksList() {
</>
))}
</List>
{!addingTask && (
<Button
color="primary"
onClick={() => {
setAddingTask(true);
}}
>
Add Task
</Button>
)}
{
// Form to add a new task
}
{addingTask && (
<FormGroup>
<Input
placeholder="Title"
onChange={(e) =>
setNewTask((t) => ({ ...t, title: e.target.value }))
}
/>
<Input
placeholder="Description"
onChange={(e) =>
setNewTask((t) => ({ ...t, description: e.target.value }))
}
/>
<Button
color="primary"
onClick={() => {
setAddingTask(false);
setNewTask({ title: "", description: "" });
}}
>
Cancel
</Button>
<Button
color="primary"
onClick={async () => {
// FIXME: handle potential error
await addTask(newTask.title, newTask.description).then(
refreshTasks
);
setAddingTask(false);
setNewTask({ title: "", description: "" });
}}
>
Save
</Button>
</FormGroup>
)}
</>
);
}

0 comments on commit b23461c

Please sign in to comment.