feat: account page content
This commit is contained in:
@@ -9,11 +9,20 @@ module.exports = function (sequelize) {
|
|||||||
|
|
||||||
router.get('/', verifySignedIn, async (req, res) => {
|
router.get('/', verifySignedIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const projects = await Project.findAll({
|
let projects;
|
||||||
where: {
|
if (req.query.onlyUserProjects === 'true') {
|
||||||
[Op.or]: [{ isPublic: true }, { userId: req.userId }],
|
projects = await Project.findAll({
|
||||||
},
|
where: {
|
||||||
});
|
userId: req.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
projects = await Project.findAll({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [{ isPublic: true }, { userId: req.userId }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
res.json(projects);
|
res.json(projects);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
19
frontend/components/PublicityChip.tsx
Normal file
19
frontend/components/PublicityChip.tsx
Normal file
@@ -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 ? (
|
||||||
|
<Chip size="sm" variant="bordered">
|
||||||
|
{publicText}
|
||||||
|
</Chip>
|
||||||
|
) : (
|
||||||
|
<Chip size="sm" variant="bordered">
|
||||||
|
{privateText}
|
||||||
|
</Chip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -53,7 +53,10 @@
|
|||||||
"email_not_exist": "Email address not found",
|
"email_not_exist": "Email address not found",
|
||||||
"signup_error": "Sign up failed",
|
"signup_error": "Sign up failed",
|
||||||
"signin_error": "Sign in 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": {
|
"Admin": {
|
||||||
"user_management": "User Management",
|
"user_management": "User Management",
|
||||||
|
|||||||
@@ -52,7 +52,10 @@
|
|||||||
"email_not_exist": "メールアドレスが見つかりません",
|
"email_not_exist": "メールアドレスが見つかりません",
|
||||||
"signup_error": "新規登録に失敗しました",
|
"signup_error": "新規登録に失敗しました",
|
||||||
"signin_error": "サインインに失敗しました",
|
"signin_error": "サインインに失敗しました",
|
||||||
"your_projects": "保有プロジェクト"
|
"your_projects": "保有プロジェクト",
|
||||||
|
"public": "パブリック",
|
||||||
|
"private": "プライベート",
|
||||||
|
"no_projects_found": "プロジェクトがありません"
|
||||||
},
|
},
|
||||||
"Admin": {
|
"Admin": {
|
||||||
"user_management": "ユーザー管理",
|
"user_management": "ユーザー管理",
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useContext } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import { Card, CardHeader, CardBody, Divider } from '@nextui-org/react';
|
import { Link, NextUiLinkClasses } from '@/src/navigation';
|
||||||
|
import { Card, CardHeader, CardFooter } from '@nextui-org/react';
|
||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import Avatar from 'boring-avatars';
|
import Avatar from 'boring-avatars';
|
||||||
|
import { fetchMyProjects } from '@/utils/projectsControl';
|
||||||
|
import { ProjectType } from '@/types/project';
|
||||||
|
import PublicityChip from '@/components/PublicityChip';
|
||||||
|
|
||||||
type AccountPageMessages = {
|
type AccountPageMessages = {
|
||||||
yourProjects: string;
|
yourProjects: string;
|
||||||
|
public: string;
|
||||||
|
private: string;
|
||||||
|
noProjectsFound: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -15,28 +22,69 @@ type Props = {
|
|||||||
|
|
||||||
export default function AccountPage({ messages, locale }: Props) {
|
export default function AccountPage({ messages, locale }: Props) {
|
||||||
const context = useContext(TokenContext);
|
const context = useContext(TokenContext);
|
||||||
|
const [myProjects, setMyProjects] = useState<ProjectType[]>([]);
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{context.isSignedIn() && (
|
{context.isSignedIn() && (
|
||||||
<Card className="w-[600px] mt-16 mx-3">
|
<div className="container mx-auto max-w-3xl pt-6 px-6 flex-grow">
|
||||||
<CardHeader className="flex gap-6">
|
<div className="w-full p-3 flex items-center justify-between">
|
||||||
<Avatar
|
<Card className="w-[600px]">
|
||||||
size={48}
|
<CardHeader className="flex gap-6">
|
||||||
name={context.token.user.username}
|
<Avatar
|
||||||
variant="beam"
|
size={48}
|
||||||
colors={['#0A0310', '#49007E', '#FF005B', '#FF7D10', '#FFB238']}
|
name={context.token.user.username}
|
||||||
/>
|
variant="beam"
|
||||||
<div className="flex flex-col">
|
colors={['#0A0310', '#49007E', '#FF005B', '#FF7D10', '#FFB238']}
|
||||||
<p className="text-2xl font-bold">{context.token.user.username}</p>
|
/>
|
||||||
<p className="text-lg text-default-500">{context.token.user.email}</p>
|
<div className="flex flex-col">
|
||||||
</div>
|
<p className="text-xl font-bold">{context.token.user.username}</p>
|
||||||
</CardHeader>
|
<p className="text-lg text-default-500">{context.token.user.email}</p>
|
||||||
<Divider />
|
</div>
|
||||||
<CardBody className="overflow-visible px-4 pb-4">
|
</CardHeader>
|
||||||
<p className="text-default-500">{messages.yourProjects}</p>
|
</Card>
|
||||||
</CardBody>
|
</div>
|
||||||
</Card>
|
|
||||||
|
<div className="w-full p-3">
|
||||||
|
<h3 className="font-bold mb-2">{messages.yourProjects}</h3>
|
||||||
|
{myProjects.length > 0 ? (
|
||||||
|
myProjects.map((myProject, myProjectsIndex) => {
|
||||||
|
return (
|
||||||
|
<Card key={myProject.id} className={`w-[600px] ${myProjectsIndex !== 0 ? 'mt-2' : ''}`}>
|
||||||
|
<CardHeader className="flex gap-6 pb-0">
|
||||||
|
<Link href={`/projects/${myProject.id}/home`} locale={locale} className={NextUiLinkClasses}>
|
||||||
|
{myProject.name}
|
||||||
|
</Link>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="justify-between pt-0">
|
||||||
|
<p className="text-small text-default-500">{myProject.detail}</p>
|
||||||
|
<PublicityChip isPublic={true} publicText={messages.public} privateText={messages.private} />
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div className="text-default-500">{messages.noProjectsFound}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export default function Page(params: { locale: string }) {
|
|||||||
const t = useTranslations('Auth');
|
const t = useTranslations('Auth');
|
||||||
const messages = {
|
const messages = {
|
||||||
yourProjects: t('your_projects'),
|
yourProjects: t('your_projects'),
|
||||||
|
public: t('public'),
|
||||||
|
private: t('private'),
|
||||||
|
noProjectsFound: t('no_projects_found'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AccountPage messages={messages} locale={params.locale} />;
|
return <AccountPage messages={messages} locale={params.locale} />;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
TableCell,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
|
||||||
DropdownTrigger,
|
DropdownTrigger,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -18,6 +17,7 @@ import { Link, NextUiLinkClasses } from '@/src/navigation';
|
|||||||
import { MoreVertical } from 'lucide-react';
|
import { MoreVertical } from 'lucide-react';
|
||||||
import { ProjectType, ProjectsMessages } from '@/types/project';
|
import { ProjectType, ProjectsMessages } from '@/types/project';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import PublicityChip from '@/components/PublicityChip';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projects: ProjectType[];
|
projects: ProjectType[];
|
||||||
@@ -62,15 +62,7 @@ export default function ProjectsTable({ projects, onEditProject, onDeleteProject
|
|||||||
case 'id':
|
case 'id':
|
||||||
return <span>{cellValue}</span>;
|
return <span>{cellValue}</span>;
|
||||||
case 'isPublic':
|
case 'isPublic':
|
||||||
return cellValue ? (
|
return <PublicityChip isPublic={cellValue} publicText={messages.public} privateText={messages.private} />;
|
||||||
<Chip size="sm" variant="bordered">
|
|
||||||
{messages.public}
|
|
||||||
</Chip>
|
|
||||||
) : (
|
|
||||||
<Chip size="sm" variant="bordered">
|
|
||||||
{messages.private}
|
|
||||||
</Chip>
|
|
||||||
);
|
|
||||||
case 'name':
|
case 'name':
|
||||||
const maxLength = 30;
|
const maxLength = 30;
|
||||||
const truncatedDetail = truncateText(project.detail, maxLength);
|
const truncatedDetail = truncateText(project.detail, maxLength);
|
||||||
|
|||||||
@@ -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) {
|
async function fetchProjects(jwt: string) {
|
||||||
const url = `${apiServer}/projects`;
|
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
|
* 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 };
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ async function fetchMyRoles(jwt: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isProjectManager(projectRoles: ProjectRoleType[], projectId: number) {
|
function isProjectManager(projectRoles: ProjectRoleType[], projectId: number) {
|
||||||
|
if (!projectRoles) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const found = projectRoles.find((role) => {
|
const found = projectRoles.find((role) => {
|
||||||
return role.projectId === projectId;
|
return role.projectId === projectId;
|
||||||
});
|
});
|
||||||
@@ -83,6 +87,10 @@ function isProjectManager(projectRoles: ProjectRoleType[], projectId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isProjectDeveloper(projectRoles: ProjectRoleType[], projectId: number) {
|
function isProjectDeveloper(projectRoles: ProjectRoleType[], projectId: number) {
|
||||||
|
if (!projectRoles) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const found = projectRoles.find((role) => {
|
const found = projectRoles.find((role) => {
|
||||||
return role.projectId === projectId;
|
return role.projectId === projectId;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user