From e4db79047a7979e0a4ea96302b9280d24ec1b2c9 Mon Sep 17 00:00:00 2001 From: Takeshi Kimata <117462761+kimatata@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:53:52 +0900 Subject: [PATCH] feat: account page content --- backend/routes/projects/index.js | 19 ++-- frontend/components/PublicityChip.tsx | 19 ++++ frontend/messages/en.json | 5 +- frontend/messages/ja.json | 5 +- .../src/app/[locale]/account/AccountPage.tsx | 88 ++++++++++++++----- frontend/src/app/[locale]/account/page.tsx | 3 + .../app/[locale]/projects/ProjectsTable.tsx | 14 +-- frontend/utils/projectsControl.ts | 30 ++++++- frontend/utils/token.ts | 8 ++ 9 files changed, 151 insertions(+), 40 deletions(-) create mode 100644 frontend/components/PublicityChip.tsx diff --git a/backend/routes/projects/index.js b/backend/routes/projects/index.js index f2ecaf9..8bde248 100644 --- a/backend/routes/projects/index.js +++ b/backend/routes/projects/index.js @@ -9,11 +9,20 @@ module.exports = function (sequelize) { router.get('/', verifySignedIn, async (req, res) => { try { - const projects = await Project.findAll({ - where: { - [Op.or]: [{ isPublic: true }, { userId: req.userId }], - }, - }); + let projects; + if (req.query.onlyUserProjects === 'true') { + projects = await Project.findAll({ + where: { + userId: req.userId, + }, + }); + } else { + projects = await Project.findAll({ + where: { + [Op.or]: [{ isPublic: true }, { userId: req.userId }], + }, + }); + } res.json(projects); } catch (error) { console.error(error); diff --git a/frontend/components/PublicityChip.tsx b/frontend/components/PublicityChip.tsx new file mode 100644 index 0000000..1968cee --- /dev/null +++ b/frontend/components/PublicityChip.tsx @@ -0,0 +1,19 @@ +import { Chip } from '@nextui-org/react'; + +type Props = { + isPublic: boolean; + publicText: string; + privateText: string; +}; + +export default function PublicityChip({ isPublic, publicText, privateText }: Props) { + return isPublic ? ( + + {publicText} + + ) : ( + + {privateText} + + ); +} diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 7d34430..9d258a0 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -53,7 +53,10 @@ "email_not_exist": "Email address not found", "signup_error": "Sign up failed", "signin_error": "Sign in failed", - "your_projects": "Your Projects" + "your_projects": "Your Projects", + "public": "Public", + "private": "Private", + "no_projects_found": "No projects found" }, "Admin": { "user_management": "User Management", diff --git a/frontend/messages/ja.json b/frontend/messages/ja.json index 9837e39..5b5952f 100644 --- a/frontend/messages/ja.json +++ b/frontend/messages/ja.json @@ -52,7 +52,10 @@ "email_not_exist": "メールアドレスが見つかりません", "signup_error": "新規登録に失敗しました", "signin_error": "サインインに失敗しました", - "your_projects": "保有プロジェクト" + "your_projects": "保有プロジェクト", + "public": "パブリック", + "private": "プライベート", + "no_projects_found": "プロジェクトがありません" }, "Admin": { "user_management": "ユーザー管理", diff --git a/frontend/src/app/[locale]/account/AccountPage.tsx b/frontend/src/app/[locale]/account/AccountPage.tsx index 55a0a55..4094256 100644 --- a/frontend/src/app/[locale]/account/AccountPage.tsx +++ b/frontend/src/app/[locale]/account/AccountPage.tsx @@ -1,11 +1,18 @@ 'use client'; -import { useContext } from 'react'; -import { Card, CardHeader, CardBody, Divider } from '@nextui-org/react'; +import { useState, useEffect, useContext } from 'react'; +import { Link, NextUiLinkClasses } from '@/src/navigation'; +import { Card, CardHeader, CardFooter } from '@nextui-org/react'; import { TokenContext } from '@/utils/TokenProvider'; import Avatar from 'boring-avatars'; +import { fetchMyProjects } from '@/utils/projectsControl'; +import { ProjectType } from '@/types/project'; +import PublicityChip from '@/components/PublicityChip'; type AccountPageMessages = { yourProjects: string; + public: string; + private: string; + noProjectsFound: string; }; type Props = { @@ -15,28 +22,69 @@ type Props = { export default function AccountPage({ messages, locale }: Props) { const context = useContext(TokenContext); + const [myProjects, setMyProjects] = useState([]); + + useEffect(() => { + async function fetchDataEffect() { + if (!context.isSignedIn()) { + return; + } + + try { + const data = await fetchMyProjects(context.token.access_token); + setMyProjects(data); + } catch (error: any) { + console.error('Error in effect:', error.message); + } + } + + fetchDataEffect(); + }, [context]); return ( <> {context.isSignedIn() && ( - - - - - {context.token.user.username} - {context.token.user.email} - - - - - {messages.yourProjects} - - + + + + + + + {context.token.user.username} + {context.token.user.email} + + + + + + + {messages.yourProjects} + {myProjects.length > 0 ? ( + myProjects.map((myProject, myProjectsIndex) => { + return ( + + + + {myProject.name} + + + + {myProject.detail} + + + + ); + }) + ) : ( + {messages.noProjectsFound} + )} + + )} > ); diff --git a/frontend/src/app/[locale]/account/page.tsx b/frontend/src/app/[locale]/account/page.tsx index cf7fad0..0f1f2a0 100644 --- a/frontend/src/app/[locale]/account/page.tsx +++ b/frontend/src/app/[locale]/account/page.tsx @@ -4,6 +4,9 @@ export default function Page(params: { locale: string }) { const t = useTranslations('Auth'); const messages = { yourProjects: t('your_projects'), + public: t('public'), + private: t('private'), + noProjectsFound: t('no_projects_found'), }; return ; diff --git a/frontend/src/app/[locale]/projects/ProjectsTable.tsx b/frontend/src/app/[locale]/projects/ProjectsTable.tsx index d8b0a57..63afc24 100644 --- a/frontend/src/app/[locale]/projects/ProjectsTable.tsx +++ b/frontend/src/app/[locale]/projects/ProjectsTable.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo, useCallback } from 'react'; +import { useState, useMemo, useCallback } from 'react'; import { Table, TableHeader, @@ -7,7 +7,6 @@ import { TableRow, TableCell, Button, - Chip, DropdownTrigger, Dropdown, DropdownMenu, @@ -18,6 +17,7 @@ import { Link, NextUiLinkClasses } from '@/src/navigation'; import { MoreVertical } from 'lucide-react'; import { ProjectType, ProjectsMessages } from '@/types/project'; import dayjs from 'dayjs'; +import PublicityChip from '@/components/PublicityChip'; type Props = { projects: ProjectType[]; @@ -62,15 +62,7 @@ export default function ProjectsTable({ projects, onEditProject, onDeleteProject case 'id': return {cellValue}; case 'isPublic': - return cellValue ? ( - - {messages.public} - - ) : ( - - {messages.private} - - ); + return ; case 'name': const maxLength = 30; const truncatedDetail = truncateText(project.detail, maxLength); diff --git a/frontend/utils/projectsControl.ts b/frontend/utils/projectsControl.ts index 585ac12..d0e8d0f 100644 --- a/frontend/utils/projectsControl.ts +++ b/frontend/utils/projectsControl.ts @@ -28,7 +28,7 @@ async function fetchProject(jwt: string, projectId: number) { } /** - * fetch project records + * fetch projects (public and user own projects) */ async function fetchProjects(jwt: string) { const url = `${apiServer}/projects`; @@ -53,6 +53,32 @@ async function fetchProjects(jwt: string) { } } +/** + * fetch projects (user own projects) + */ +async function fetchMyProjects(jwt: string) { + const url = `${apiServer}/projects?onlyUserProjects=true`; + + try { + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${jwt}`, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error: any) { + console.error('Error fetching data:', error.message); + } +} + /** * Create project */ @@ -146,4 +172,4 @@ async function deleteProject(jwt: string, projectId: number) { } } -export { fetchProject, fetchProjects, createProject, updateProject, deleteProject }; +export { fetchProject, fetchProjects, fetchMyProjects, createProject, updateProject, deleteProject }; diff --git a/frontend/utils/token.ts b/frontend/utils/token.ts index 34405ad..5b2ee99 100644 --- a/frontend/utils/token.ts +++ b/frontend/utils/token.ts @@ -62,6 +62,10 @@ async function fetchMyRoles(jwt: string) { } function isProjectManager(projectRoles: ProjectRoleType[], projectId: number) { + if (!projectRoles) { + return false; + } + const found = projectRoles.find((role) => { return role.projectId === projectId; }); @@ -83,6 +87,10 @@ function isProjectManager(projectRoles: ProjectRoleType[], projectId: number) { } function isProjectDeveloper(projectRoles: ProjectRoleType[], projectId: number) { + if (!projectRoles) { + return false; + } + const found = projectRoles.find((role) => { return role.projectId === projectId; });
{context.token.user.username}
{context.token.user.email}
{messages.yourProjects}
{myProject.detail}