Make project table associated with the user

This commit is contained in:
Takeshi Kimata
2024-05-22 22:33:21 +09:00
parent 704f20239e
commit 7a0bb63e7b
17 changed files with 174 additions and 24 deletions

View File

@@ -62,11 +62,15 @@
"actions": "Actions",
"project_name": "Project Name",
"project_detail": "Project Detail",
"public": "Public",
"if_you_make_public": "If you make project public, everyone who has access to this site will be able to view it.",
"close": "Close",
"create": "Create",
"update": "Update",
"please_enter": "Please enter project name",
"no_projects_found": "No projects found"
"no_projects_found": "No projects found",
"you_need_signed_in": "You need signed in",
"sign_in": "Sign in"
},
"Project": {
"home": "Home",

View File

@@ -61,11 +61,15 @@
"actions": "アクション",
"project_name": "プロジェクト名",
"project_detail": "プロジェクト詳細",
"public": "パブリック",
"if_you_make_public": "プロジェクトをパブリックにすると、このサイトにアクセスできるすべてのユーザーがプロジェクトを見ることができます。",
"close": "閉じる",
"create": "作成",
"update": "更新",
"please_enter": "プロジェクト名を入力してください",
"no_projects_found": "プロジェクトがありません"
"no_projects_found": "プロジェクトがありません",
"you_need_signed_in": "サインインが必要です",
"sign_in": "サインイン"
},
"Project": {
"home": "ホーム",

View File

@@ -5,6 +5,7 @@ import { useContext } from 'react';
import { TokenContext } from './TokenProvider';
import { useRouter } from '@/src/navigation';
import { AccountDropDownMessages } from '@/types/user';
import Avatar from 'boring-avatars';
type Props = {
messages: AccountDropDownMessages;
@@ -22,11 +23,23 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props
router.push(`/`, { locale: locale });
};
let userAvatar =
context.token && context.token.user ? (
<Avatar
size={16}
name={context.token.user.username}
variant="beam"
colors={['#0A0310', '#49007E', '#FF005B', '#FF7D10', '#FFB238']}
/>
) : (
<User size={16} />
);
const signinItems = [
{
uid: 'account',
title: messages.account,
icon: <User size={16} />,
icon: userAvatar,
onPress: () => {
router.push('/account', { locale: locale });
onItemPress();
@@ -67,7 +80,7 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props
return (
<Dropdown>
<DropdownTrigger>
<Button size="sm" variant="light" startContent={<User size={16} />} endContent={<ChevronDown size={16} />}>
<Button size="sm" variant="light" startContent={userAvatar} endContent={<ChevronDown size={16} />}>
{context.token && context.token.user ? context.token.user.username : messages.signIn}
</Button>
</DropdownTrigger>

View File

@@ -6,7 +6,7 @@ import { Link } from '@/src/navigation';
import { ChevronRight, Eye, EyeOff } from 'lucide-react';
import { UserType, AuthMessages } from '@/types/user';
import { roles } from '@/config/selection';
import { signUp, signIn, getRandomAvatarPath } from './authControl';
import { signUp, signIn } from './authControl';
import { isValidEmail, isValidPassword } from './validate';
import { TokenContext } from '../TokenProvider';
import { useRouter } from '@/src/navigation';

View File

@@ -0,0 +1,31 @@
'use client';
import { ProjectsMessages } from '@/types/project';
import { Link } from '@/src/navigation';
import { Button, Modal, ModalContent, ModalHeader, ModalFooter } from '@nextui-org/react';
type Props = {
isOpen: boolean;
onCancel: () => void;
messages: ProjectsMessages;
locale: string;
};
export default function NeedSignedInDialog({ isOpen, onCancel, messages, locale }: Props) {
return (
<Modal
isOpen={isOpen}
onOpenChange={() => {
onCancel();
}}
>
<ModalContent>
<ModalHeader className="flex flex-col gap-1">{messages.needSignedIn}</ModalHeader>
<ModalFooter>
<Link href={'/account/signin'} locale={locale}>
<Button color="primary">{messages.signIn}</Button>
</Link>
</ModalFooter>
</ModalContent>
</Modal>
);
}

View File

@@ -1,14 +1,24 @@
'use client';
import React from 'react';
import { useState, useEffect } from 'react';
import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
import {
Button,
Input,
Textarea,
Checkbox,
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
} from '@nextui-org/react';
import { ProjectType, ProjectsMessages } from '@/types/project';
type Props = {
isOpen: boolean;
editingProject: ProjectType;
onCancel: () => void;
onSubmit: (name: string, detail: string) => void;
onSubmit: (name: string, detail: string, isPublic: boolean) => void;
messages: ProjectsMessages;
};
@@ -25,6 +35,8 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm
errorMessage: '',
});
const [isProjectPublic, setIsProjectPublic] = useState(editingProject ? editingProject.isPublic : true);
useEffect(() => {
if (editingProject) {
setProjectName({
@@ -36,6 +48,8 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm
...projectDetail,
text: editingProject.detail ? editingProject.detail : '',
});
setIsProjectPublic(editingProject.isPublic);
} else {
setProjectName({
...projectName,
@@ -46,6 +60,8 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm
...projectDetail,
text: '',
});
setIsProjectPublic(true);
}
}, [editingProject]);
@@ -73,7 +89,7 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm
return;
}
onSubmit(projectName.text, projectDetail.text);
onSubmit(projectName.text, projectDetail.text, isProjectPublic);
clear();
};
@@ -112,6 +128,10 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm
});
}}
/>
<Checkbox isSelected={isProjectPublic} onValueChange={setIsProjectPublic}>
{messages.public}
</Checkbox>
<div className="text-small text-default-500">{messages.ifYouMakePublic}</div>
</ModalBody>
<ModalFooter>
<Button variant="light" onPress={onCancel}>

View File

@@ -1,10 +1,12 @@
'use client';
import { useEffect, useState } from 'react';
import { useEffect, useState, useContext } from 'react';
import { Button } from '@nextui-org/react';
import { Plus } from 'lucide-react';
import { TokenContext } from '@/src/app/[locale]/TokenProvider';
import { ProjectType, ProjectsMessages } from '@/types/project';
import ProjectsTable from './ProjectsTable';
import ProjectDialog from './ProjectDialog';
import NeedSignedInDialog from './NeedSignedInDialog';
import { fetchProjects, createProject, updateProject, deleteProject } from './projectsControl';
export type Props = {
@@ -13,6 +15,7 @@ export type Props = {
};
export default function ProjectsPage({ messages, locale }: Props) {
const context = useContext(TokenContext);
const [projects, setProjects] = useState([]);
useEffect(() => {
@@ -29,9 +32,15 @@ export default function ProjectsPage({ messages, locale }: Props) {
}, []);
// dialog
const [isNeedSignedInDialogOpen, setIsNeedSignedInDialogOpen] = useState(false);
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false);
const [editingProject, setEditingProject] = useState<ProjectType | null>(null);
const openDialogForCreate = () => {
if (!context.token || !context.token.user) {
setIsNeedSignedInDialogOpen(true);
return;
}
setIsProjectDialogOpen(true);
setEditingProject(null);
};
@@ -41,13 +50,13 @@ export default function ProjectsPage({ messages, locale }: Props) {
setEditingProject(null);
};
const onSubmit = async (name: string, detail: string) => {
const onSubmit = async (name: string, detail: string, isPublic: boolean) => {
if (editingProject) {
const updatedProject = await updateProject(editingProject.id, name, detail);
const updatedProject = await updateProject(editingProject.id, name, detail, isPublic);
const updatedProjects = projects.map((project) => (project.id === updatedProject.id ? updatedProject : project));
setProjects(updatedProjects);
} else {
const newProject = await createProject(name, detail);
const newProject = await createProject(name, detail, isPublic, context.token.user.id);
setProjects([...projects, newProject]);
}
closeDialog();
@@ -93,6 +102,13 @@ export default function ProjectsPage({ messages, locale }: Props) {
onSubmit={onSubmit}
messages={messages}
/>
<NeedSignedInDialog
isOpen={isNeedSignedInDialogOpen}
onCancel={() => setIsNeedSignedInDialogOpen(false)}
messages={messages}
locale={locale}
/>
</div>
);
}

View File

@@ -16,11 +16,15 @@ export default function Page(params: { locale: string }) {
actions: t('actions'),
projectName: t('project_name'),
projectDetail: t('project_detail'),
public: t('public'),
ifYouMakePublic: t('if_you_make_public'),
close: t('close'),
create: t('create'),
update: t('update'),
pleaseEnter: t('please_enter'),
noProjectsFound: t('no_projects_found'),
needSignedIn: t('you_need_signed_in'),
signIn: t('sign_in'),
};
return (
<>

View File

@@ -29,10 +29,12 @@ async function fetchProjects() {
/**
* Create project
*/
async function createProject(name: string, detail: string) {
async function createProject(name: string, detail: string, isPublic: boolean, userId: number) {
const newProjectData = {
name: name,
detail: detail,
name,
detail,
isPublic,
userId,
};
const fetchOptions = {
@@ -61,10 +63,11 @@ async function createProject(name: string, detail: string) {
/**
* Update project
*/
async function updateProject(projectId: number, name: string, detail: string) {
async function updateProject(projectId: number, name: string, detail: string, isPublic: boolean) {
const updatedProjectData = {
name: name,
detail: detail,
name,
detail,
isPublic,
};
const fetchOptions = {

View File

@@ -5,6 +5,8 @@ export type ProjectType = {
id: number;
name: string;
detail: string;
isPublic: string;
userId: number;
createdAt: string;
updatedAt: string;
Folders: FolderType[]; // additional property
@@ -23,11 +25,15 @@ export type ProjectsMessages = {
actions: string;
projectName: string;
projectDetail: string;
public: string;
ifYouMakePublic: string;
close: string;
create: string;
update: string;
pleaseEnter: string;
noProjectsFound: string;
needSignedIn: string;
signIn: string;
};
export type ProjectMessages = {