feature: add team creation & project deletion

This commit is contained in:
Jordan Knott 2020-06-20 17:49:11 -05:00
parent 5c3afaba7c
commit fd7c006b73
27 changed files with 1590 additions and 98 deletions

File diff suppressed because it is too large Load Diff

View File

@ -36,10 +36,29 @@ type CreateTaskChecklistItem struct {
Position float64 `json:"position"` Position float64 `json:"position"`
} }
type CreateTeamMember struct {
UserID uuid.UUID `json:"userID"`
TeamID uuid.UUID `json:"teamID"`
}
type CreateTeamMemberPayload struct {
Team *pg.Team `json:"team"`
TeamMember *ProjectMember `json:"teamMember"`
}
type DeleteProject struct {
ProjectID uuid.UUID `json:"projectID"`
}
type DeleteProjectLabel struct { type DeleteProjectLabel struct {
ProjectLabelID uuid.UUID `json:"projectLabelID"` ProjectLabelID uuid.UUID `json:"projectLabelID"`
} }
type DeleteProjectPayload struct {
Ok bool `json:"ok"`
Project *pg.Project `json:"project"`
}
type DeleteTaskChecklistItem struct { type DeleteTaskChecklistItem struct {
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"` TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
} }
@ -123,8 +142,8 @@ type NewTaskLocation struct {
} }
type NewTeam struct { type NewTeam struct {
Name string `json:"name"` Name string `json:"name"`
OrganizationID string `json:"organizationID"` OrganizationID uuid.UUID `json:"organizationID"`
} }
type NewUserAccount struct { type NewUserAccount struct {

View File

@ -55,6 +55,7 @@ type Team {
id: ID! id: ID!
createdAt: Time! createdAt: Time!
name: String! name: String!
members: [ProjectMember!]!
} }
type Project { type Project {
@ -117,7 +118,13 @@ input FindTask {
taskID: UUID! taskID: UUID!
} }
type Organization {
id: ID!
name: String!
}
type Query { type Query {
organizations: [Organization!]!
users: [UserAccount!]! users: [UserAccount!]!
findUser(input: FindUser!): UserAccount! findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!): Project! findProject(input: FindProject!): Project!
@ -143,7 +150,7 @@ input NewUserAccount {
input NewTeam { input NewTeam {
name: String! name: String!
organizationID: String! organizationID: UUID!
} }
input NewProject { input NewProject {
@ -330,6 +337,25 @@ input UpdateTaskChecklistItemName {
name: String! name: String!
} }
input CreateTeamMember {
userID: UUID!
teamID: UUID!
}
type CreateTeamMemberPayload {
team: Team!
teamMember: ProjectMember!
}
input DeleteProject {
projectID: UUID!
}
type DeleteProjectPayload {
ok: Boolean!
project: Project!
}
type Mutation { type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken! createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -338,7 +364,10 @@ type Mutation {
createTeam(input: NewTeam!): Team! createTeam(input: NewTeam!): Team!
clearProfileAvatar: UserAccount! clearProfileAvatar: UserAccount!
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
createProject(input: NewProject!): Project! createProject(input: NewProject!): Project!
deleteProject(input: DeleteProject!): DeleteProjectPayload!
updateProjectName(input: UpdateProjectName): Project! updateProjectName(input: UpdateProjectName): Project!
createProjectLabel(input: NewProjectLabel!): ProjectLabel! createProjectLabel(input: NewProjectLabel!): ProjectLabel!

View File

@ -41,12 +41,8 @@ func (r *mutationResolver) CreateUserAccount(ctx context.Context, input NewUserA
} }
func (r *mutationResolver) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) { func (r *mutationResolver) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) {
organizationID, err := uuid.Parse(input.OrganizationID)
if err != nil {
return &pg.Team{}, err
}
createdAt := time.Now().UTC() createdAt := time.Now().UTC()
team, err := r.Repository.CreateTeam(ctx, pg.CreateTeamParams{organizationID, createdAt, input.Name}) team, err := r.Repository.CreateTeam(ctx, pg.CreateTeamParams{input.OrganizationID, createdAt, input.Name})
return &team, err return &team, err
} }
@ -65,12 +61,49 @@ func (r *mutationResolver) ClearProfileAvatar(ctx context.Context) (*pg.UserAcco
return &user, nil return &user, nil
} }
func (r *mutationResolver) CreateTeamMember(ctx context.Context, input CreateTeamMember) (*CreateTeamMemberPayload, error) {
addedDate := time.Now().UTC()
team, err := r.Repository.GetTeamByID(ctx, input.TeamID)
if err != nil {
return &CreateTeamMemberPayload{}, err
}
_, err = r.Repository.CreateTeamMember(ctx, pg.CreateTeamMemberParams{TeamID: input.TeamID, UserID: input.UserID, Addeddate: addedDate})
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
if err != nil {
return &CreateTeamMemberPayload{}, err
}
var url *string
if user.ProfileAvatarUrl.Valid {
url = &user.ProfileAvatarUrl.String
}
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
return &CreateTeamMemberPayload{
Team: &team,
TeamMember: &ProjectMember{
ID: user.UserID,
FullName: user.FullName,
ProfileIcon: profileIcon,
}}, nil
}
func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) (*pg.Project, error) { func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) (*pg.Project, error) {
createdAt := time.Now().UTC() createdAt := time.Now().UTC()
project, err := r.Repository.CreateProject(ctx, pg.CreateProjectParams{input.UserID, input.TeamID, createdAt, input.Name}) project, err := r.Repository.CreateProject(ctx, pg.CreateProjectParams{input.UserID, input.TeamID, createdAt, input.Name})
return &project, err return &project, err
} }
func (r *mutationResolver) DeleteProject(ctx context.Context, input DeleteProject) (*DeleteProjectPayload, error) {
project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
if err != nil {
return &DeleteProjectPayload{Ok: false}, err
}
err = r.Repository.DeleteProjectByID(ctx, input.ProjectID)
if err != nil {
return &DeleteProjectPayload{Ok: false}, err
}
return &DeleteProjectPayload{Project: &project, Ok: true}, err
}
func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) { func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) {
project, err := r.Repository.UpdateProjectNameByID(ctx, pg.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name}) project, err := r.Repository.UpdateProjectNameByID(ctx, pg.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name})
if err != nil { if err != nil {
@ -409,6 +442,10 @@ func (r *mutationResolver) LogoutUser(ctx context.Context, input LogoutUser) (bo
return true, err return true, err
} }
func (r *organizationResolver) ID(ctx context.Context, obj *pg.Organization) (uuid.UUID, error) {
return obj.OrganizationID, nil
}
func (r *projectResolver) ID(ctx context.Context, obj *pg.Project) (uuid.UUID, error) { func (r *projectResolver) ID(ctx context.Context, obj *pg.Project) (uuid.UUID, error) {
return obj.ProjectID, nil return obj.ProjectID, nil
} }
@ -475,6 +512,10 @@ func (r *projectLabelResolver) Name(ctx context.Context, obj *pg.ProjectLabel) (
return name, nil return name, nil
} }
func (r *queryResolver) Organizations(ctx context.Context) ([]pg.Organization, error) {
return r.Repository.GetAllOrganizations(ctx)
}
func (r *queryResolver) Users(ctx context.Context) ([]pg.UserAccount, error) { func (r *queryResolver) Users(ctx context.Context) ([]pg.UserAccount, error) {
return r.Repository.GetAllUserAccounts(ctx) return r.Repository.GetAllUserAccounts(ctx)
} }
@ -683,6 +724,31 @@ func (r *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error)
return obj.TeamID, nil return obj.TeamID, nil
} }
func (r *teamResolver) Members(ctx context.Context, obj *pg.Team) ([]ProjectMember, error) {
teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID)
var projectMembers []ProjectMember
if err != nil {
return projectMembers, err
}
for _, teamMember := range teamMembers {
user, err := r.Repository.GetUserAccountByID(ctx, teamMember.UserID)
if err != nil {
return projectMembers, err
}
var url *string
if user.ProfileAvatarUrl.Valid {
url = &user.ProfileAvatarUrl.String
}
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
projectMembers = append(projectMembers, ProjectMember{
ID: user.UserID,
FullName: user.FullName,
ProfileIcon: profileIcon,
})
}
return projectMembers, nil
}
func (r *userAccountResolver) ID(ctx context.Context, obj *pg.UserAccount) (uuid.UUID, error) { func (r *userAccountResolver) ID(ctx context.Context, obj *pg.UserAccount) (uuid.UUID, error) {
return obj.UserID, nil return obj.UserID, nil
} }
@ -702,6 +768,9 @@ func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{
// Mutation returns MutationResolver implementation. // Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
// Organization returns OrganizationResolver implementation.
func (r *Resolver) Organization() OrganizationResolver { return &organizationResolver{r} }
// Project returns ProjectResolver implementation. // Project returns ProjectResolver implementation.
func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} } func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} }
@ -737,6 +806,7 @@ func (r *Resolver) UserAccount() UserAccountResolver { return &userAccountResolv
type labelColorResolver struct{ *Resolver } type labelColorResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver } type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver }
type projectResolver struct{ *Resolver } type projectResolver struct{ *Resolver }
type projectLabelResolver struct{ *Resolver } type projectLabelResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }

View File

@ -0,0 +1,8 @@
CREATE TABLE team_member (
team_member_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
team_id uuid NOT NULL REFERENCES team(team_id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES user_account(user_id) ON DELETE CASCADE,
UNIQUE(team_id, user_id),
addedDate timestamptz NOT NULL
);

View File

@ -0,0 +1,6 @@
ALTER TABLE project_label DROP CONSTRAINT project_label_project_id_fkey;
ALTER TABLE project_label
ADD CONSTRAINT project_label_project_id_fkey
FOREIGN KEY (project_id)
REFERENCES project(project_id)
ON DELETE CASCADE;

View File

@ -0,0 +1,6 @@
ALTER TABLE task_label DROP CONSTRAINT task_label_project_label_id_fkey;
ALTER TABLE task_label
ADD CONSTRAINT task_label_project_label_id_fkey
FOREIGN KEY (project_label_id)
REFERENCES project_label(project_label_id)
ON DELETE CASCADE;

View File

@ -103,6 +103,13 @@ type Team struct {
OrganizationID uuid.UUID `json:"organization_id"` OrganizationID uuid.UUID `json:"organization_id"`
} }
type TeamMember struct {
TeamMemberID uuid.UUID `json:"team_member_id"`
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
Addeddate time.Time `json:"addeddate"`
}
type UserAccount struct { type UserAccount struct {
UserID uuid.UUID `json:"user_id"` UserID uuid.UUID `json:"user_id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`

View File

@ -7,11 +7,17 @@ import (
) )
type Repository interface { type Repository interface {
CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error)
DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error
GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error)
CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error) CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error)
GetAllTeams(ctx context.Context) ([]Team, error) GetAllTeams(ctx context.Context) ([]Team, error)
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error
CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error)
GetAllProjects(ctx context.Context) ([]Project, error) GetAllProjects(ctx context.Context) ([]Project, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)

View File

@ -39,6 +39,15 @@ func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (P
return i, err return i, err
} }
const deleteProjectByID = `-- name: DeleteProjectByID :exec
DELETE FROM project WHERE project_id = $1
`
func (q *Queries) DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteProjectByID, projectID)
return err
}
const getAllProjects = `-- name: GetAllProjects :many const getAllProjects = `-- name: GetAllProjects :many
SELECT project_id, team_id, created_at, name, owner FROM project SELECT project_id, team_id, created_at, name, owner FROM project
` `

View File

@ -21,8 +21,10 @@ type Querier interface {
CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error) CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error)
CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error)
CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error) CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error)
CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error)
CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error) CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error)
DeleteExpiredTokens(ctx context.Context) error DeleteExpiredTokens(ctx context.Context) error
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error
DeleteProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) error DeleteProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) error
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
@ -34,6 +36,7 @@ type Querier interface {
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error) DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error
GetAllOrganizations(ctx context.Context) ([]Organization, error) GetAllOrganizations(ctx context.Context) ([]Organization, error)
GetAllProjects(ctx context.Context) ([]Project, error) GetAllProjects(ctx context.Context) ([]Project, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
@ -59,6 +62,7 @@ type Querier interface {
GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error)
GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error)
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error)
GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error)
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error) GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)

75
api/pg/team_member.sql.go Normal file
View File

@ -0,0 +1,75 @@
// Code generated by sqlc. DO NOT EDIT.
// source: team_member.sql
package pg
import (
"context"
"time"
"github.com/google/uuid"
)
const createTeamMember = `-- name: CreateTeamMember :one
INSERT INTO team_member (team_id, user_id, addedDate) VALUES ($1, $2, $3)
RETURNING team_member_id, team_id, user_id, addeddate
`
type CreateTeamMemberParams struct {
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
Addeddate time.Time `json:"addeddate"`
}
func (q *Queries) CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error) {
row := q.db.QueryRowContext(ctx, createTeamMember, arg.TeamID, arg.UserID, arg.Addeddate)
var i TeamMember
err := row.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
)
return i, err
}
const deleteTeamMemberByUserID = `-- name: DeleteTeamMemberByUserID :exec
DELETE FROM team_member WHERE user_id = $1
`
func (q *Queries) DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteTeamMemberByUserID, userID)
return err
}
const getTeamMembersForTeamID = `-- name: GetTeamMembersForTeamID :many
SELECT team_member_id, team_id, user_id, addeddate FROM team_member WHERE team_id = $1
`
func (q *Queries) GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error) {
rows, err := q.db.QueryContext(ctx, getTeamMembersForTeamID, teamID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TeamMember
for rows.Next() {
var i TeamMember
if err := rows.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -12,3 +12,6 @@ INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RE
-- name: UpdateProjectNameByID :one -- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *; UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *;
-- name: DeleteProjectByID :exec
DELETE FROM project WHERE project_id = $1;

View File

@ -0,0 +1,9 @@
-- name: CreateTeamMember :one
INSERT INTO team_member (team_id, user_id, addedDate) VALUES ($1, $2, $3)
RETURNING *;
-- name: GetTeamMembersForTeamID :many
SELECT * FROM team_member WHERE team_id = $1;
-- name: DeleteTeamMemberByUserID :exec
DELETE FROM team_member WHERE user_id = $1;

View File

@ -1,22 +1,47 @@
import React, { useState, useContext } from 'react'; import React, { useState, useContext } from 'react';
import TopNavbar from 'shared/components/TopNavbar'; import TopNavbar from 'shared/components/TopNavbar';
import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu'; import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu';
import ProjectSettings from 'shared/components/ProjectSettings'; import ProjectSettings, { DeleteProject } from 'shared/components/ProjectSettings';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import UserIDContext from 'App/context'; import UserIDContext from 'App/context';
import { useMeQuery } from 'shared/generated/graphql'; import { useMeQuery, useDeleteProjectMutation, GetProjectsDocument } from 'shared/generated/graphql';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
type GlobalTopNavbarProps = { type GlobalTopNavbarProps = {
projectID: string | null;
name: string | null; name: string | null;
projectMembers?: null | Array<TaskUser>; projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void; onSaveProjectName?: (projectName: string) => void;
}; };
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers, onSaveProjectName }) => { const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, projectMembers, onSaveProjectName }) => {
const { loading, data } = useMeQuery(); const { loading, data } = useMeQuery();
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup, setTab } = usePopup();
const history = useHistory(); const history = useHistory();
const { userID, setUserID } = useContext(UserIDContext); const { userID, setUserID } = useContext(UserIDContext);
const [deleteProject] = useDeleteProjectMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
query: GetProjectsDocument,
});
console.log(cacheData);
console.log(deleteData);
const newData = produce(cacheData, (draftState: any) => {
draftState.projects = draftState.projects.filter(
(project: any) => project.id !== deleteData.data.deleteProject.project.id,
);
});
client.writeQuery({
query: GetProjectsDocument,
data: {
...newData,
},
});
},
});
const onLogout = () => { const onLogout = () => {
fetch('http://localhost:3333/auth/logout', { fetch('http://localhost:3333/auth/logout', {
method: 'POST', method: 'POST',
@ -49,9 +74,27 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers,
const onOpenSettings = ($target: React.RefObject<HTMLElement>) => { const onOpenSettings = ($target: React.RefObject<HTMLElement>) => {
showPopup( showPopup(
$target, $target,
<Popup title={null} tab={0}> <>
<ProjectSettings /> <Popup title={null} tab={0}>
</Popup>, <ProjectSettings
onDeleteProject={() => {
setTab(1, 325);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteProject
name={name ?? ''}
onDeleteProject={() => {
if (projectID) {
deleteProject({ variables: { projectID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>,
185, 185,
); );
}; };

View File

@ -50,7 +50,7 @@ const Projects = () => {
} }
}} }}
/> />
<GlobalTopNavbar onSaveProjectName={() => {}} name={null} /> <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
{!loading && data && ( {!loading && data && (
<Settings <Settings
profile={data.me.profileIcon} profile={data.me.profileIcon}

View File

@ -472,7 +472,7 @@ const Project = () => {
if (loading) { if (loading) {
return ( return (
<> <>
<GlobalTopNavbar onSaveProjectName={projectName => {}} name="" /> <GlobalTopNavbar onSaveProjectName={projectName => {}} name="" projectID={null} />
</> </>
); );
} }
@ -510,6 +510,7 @@ const Project = () => {
updateProjectName({ variables: { projectID, name: projectName } }); updateProjectName({ variables: { projectID, name: projectName } });
}} }}
projectMembers={data.findProject.members} projectMembers={data.findProject.members}
projectID={projectID}
name={data.findProject.name} name={data.findProject.name}
/> />
<ProjectBar> <ProjectBar>

View File

@ -1,32 +1,183 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useContext, useEffect } from 'react';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import GlobalTopNavbar from 'App/TopNavbar'; import GlobalTopNavbar from 'App/TopNavbar';
import { useGetProjectsQuery, useCreateProjectMutation, GetProjectsDocument } from 'shared/generated/graphql'; import {
useCreateTeamMutation,
useGetProjectsQuery,
useCreateProjectMutation,
GetProjectsDocument,
} from 'shared/generated/graphql';
import ProjectGridItem, { AddProjectItem } from 'shared/components/ProjectGridItem'; import ProjectGridItem, { AddProjectItem } from 'shared/components/ProjectGridItem';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Navbar from 'App/Navbar'; import Navbar from 'App/Navbar';
import NewProject from 'shared/components/NewProject'; import NewProject from 'shared/components/NewProject';
import UserIDContext from 'App/context'; import UserIDContext from 'App/context';
import Button from 'shared/components/Button';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useForm } from 'react-hook-form';
import Input from 'shared/components/Input';
const MainContent = styled.div` const CreateTeamButton = styled(Button)`
padding: 0 0 50px 80px; width: 100%;
height: 100%; `;
background: #262c49; type CreateTeamData = { teamName: string };
type CreateTeamFormProps = {
onCreateTeam: (teamName: string) => void;
};
const CreateTeamFormContainer = styled.form``;
const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
const { register, handleSubmit, errors } = useForm<CreateTeamData>();
const createTeam = (data: CreateTeamData) => {
onCreateTeam(data.teamName);
};
return (
<CreateTeamFormContainer onSubmit={handleSubmit(createTeam)}>
<Input
width="100%"
label="Team name"
id="teamName"
name="teamName"
variant="alternate"
ref={register({ required: 'Team name is required' })}
/>
<CreateTeamButton type="submit">Create</CreateTeamButton>
</CreateTeamFormContainer>
);
};
const ProjectAddTile = styled.div`
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
background-size: cover;
background-position: 50%;
color: #fff;
line-height: 20px;
padding: 8px;
position: relative;
text-decoration: none;
border-radius: 3px;
display: block;
`; `;
const ProjectTile = styled(Link)<{ color: string }>`
background-color: ${props => props.color};
background-size: cover;
background-position: 50%;
color: #fff;
line-height: 20px;
padding: 8px;
position: relative;
text-decoration: none;
border-radius: 3px;
display: block;
`;
const ProjectTileFade = styled.div`
background-color: rgba(0, 0, 0, 0.15);
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
`;
const ProjectListItem = styled.li`
width: 23.5%;
padding: 0;
margin: 0 2% 2% 0;
box-sizing: border-box;
position: relative;
cursor: pointer;
&:hover ${ProjectTileFade} {
background-color: rgba(0, 0, 0, 0.25);
}
`;
const ProjectList = styled.ul`
display: flex;
flex-wrap: wrap;
& ${ProjectListItem}:nth-of-type(4n) {
margin-right: 0;
}
`;
const ProjectTileDetails = styled.div`
display: flex;
height: 80px;
position: relative;
flex-direction: column;
justify-content: space-between;
`;
const ProjectAddTileDetails = styled.div`
display: flex;
height: 80px;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
`;
const ProjectTileName = styled.div<{ centered?: boolean }>`
flex: 0 0 auto;
font-size: 16px;
font-weight: 700;
display: inline-block;
overflow: hidden;
max-height: 40px;
width: 100%;
word-wrap: break-word;
${props => props.centered && 'text-align: center;'}
`;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
`;
const ProjectSectionTitleWrapper = styled.div`
align-items: center;
display: flex;
height: 32px;
margin-bottom: 24px;
padding: 8px 0;
position: relative;
`;
const ProjectSectionTitle = styled.h3`
font-size: 16px;
color: rgba(${props => props.theme.colors.text.primary});
`;
const ProjectsContainer = styled.div`
margin: 40px 16px 0;
width: 100%;
max-width: 825px;
min-width: 288px;
`;
const ProjectGrid = styled.div` const ProjectGrid = styled.div`
width: 60%;
max-width: 780px; max-width: 780px;
margin: 25px auto;
display: grid; display: grid;
grid-template-columns: 240px 240px 240px; grid-template-columns: 240px 240px 240px;
gap: 20px 10px; gap: 20px 10px;
`; `;
const AddTeamButton = styled(Button)`
padding: 6px 12px;
float: right;
`;
const ProjectLink = styled(Link)``; const ProjectLink = styled(Link)``;
const Projects = () => { const Projects = () => {
const { showPopup } = usePopup();
const { loading, data } = useGetProjectsQuery(); const { loading, data } = useGetProjectsQuery();
useEffect(() => { useEffect(() => {
document.title = 'Citadel'; document.title = 'Citadel';
@ -53,6 +204,7 @@ const Projects = () => {
}); });
const [showNewProject, setShowNewProject] = useState(false); const [showNewProject, setShowNewProject] = useState(false);
const { userID, setUserID } = useContext(UserIDContext); const { userID, setUserID } = useContext(UserIDContext);
const [createTeam] = useCreateTeamMutation();
if (loading) { if (loading) {
return ( return (
<> <>
@ -60,39 +212,91 @@ const Projects = () => {
</> </>
); );
} }
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
if (data) { if (data) {
const { projects, teams } = data; const { projects, teams, organizations } = data;
const organizationID = organizations[0].id ?? null;
const projectTeams = teams.map(team => {
return {
id: team.id,
name: team.name,
projects: projects.filter(project => project.team.id === team.id),
};
});
return ( return (
<> <>
<GlobalTopNavbar onSaveProjectName={() => {}} name={null} /> <GlobalTopNavbar onSaveProjectName={() => {}} projectID={null} name={null} />
<ProjectGrid> <Wrapper>
{projects.map(project => ( <ProjectsContainer>
<ProjectLink key={project.id} to={`/projects/${project.id}`}> <AddTeamButton
<ProjectGridItem variant="outline"
project={{ ...project, projectID: project.id, teamTitle: project.team.name, taskGroups: [] }} onClick={$target => {
showPopup(
$target,
<Popup title="Create team" tab={0}>
<CreateTeamForm
onCreateTeam={teamName => {
if (organizationID) {
createTeam({ variables: { name: teamName, organizationID } });
}
}}
/>
</Popup>,
);
}}
>
Add Team
</AddTeamButton>
{projectTeams.map(team => {
return (
<div key={team.id}>
<ProjectSectionTitleWrapper>
<ProjectSectionTitle>{team.name}</ProjectSectionTitle>
</ProjectSectionTitleWrapper>
<ProjectList>
{team.projects.map((project, idx) => (
<ProjectListItem key={project.id}>
<ProjectTile color={colors[idx % 5]} to={`/projects/${project.id}`}>
<ProjectTileFade />
<ProjectTileDetails>
<ProjectTileName>{project.name}</ProjectTileName>
</ProjectTileDetails>
</ProjectTile>
</ProjectListItem>
))}
<ProjectListItem>
<ProjectAddTile
onClick={() => {
setShowNewProject(true);
}}
>
<ProjectTileFade />
<ProjectAddTileDetails>
<ProjectTileName centered>Create new project</ProjectTileName>
</ProjectAddTileDetails>
</ProjectAddTile>
</ProjectListItem>
</ProjectList>
</div>
);
})}
{showNewProject && (
<NewProject
onCreateProject={(name, teamID) => {
if (userID) {
createProject({ variables: { teamID, name, userID } });
setShowNewProject(false);
}
}}
onClose={() => {
setShowNewProject(false);
}}
teams={teams}
/> />
</ProjectLink> )}
))} </ProjectsContainer>
<AddProjectItem </Wrapper>
onAddProject={() => {
setShowNewProject(true);
}}
/>
</ProjectGrid>
{showNewProject && (
<NewProject
onCreateProject={(name, teamID) => {
if (userID) {
createProject({ variables: { teamID, name, userID } });
setShowNewProject(false);
}
}}
onClose={() => {
setShowNewProject(false);
}}
teams={teams}
/>
)}
</> </>
); );
} }

View File

@ -11,7 +11,7 @@ const InputWrapper = styled.div<{ width: string }>`
justify-content: center; justify-content: center;
margin-bottom: 2.2rem; margin-bottom: 2.2rem;
margin-top: 17px; margin-top: 24px;
`; `;
const InputLabel = styled.span<{ width: string }>` const InputLabel = styled.span<{ width: string }>`

View File

@ -1,4 +1,5 @@
import styled from 'styled-components'; import styled from 'styled-components';
import Button from 'shared/components/Button';
export const Wrapper = styled.div` export const Wrapper = styled.div`
background: #eff2f7; background: #eff2f7;
@ -70,21 +71,7 @@ export const FormError = styled.span`
color: rgb(234, 84, 85); color: rgb(234, 84, 85);
`; `;
export const LoginButton = styled.input` export const LoginButton = styled(Button)``;
padding: 0.75rem 2rem;
font-size: 1rem;
border-radius: 6px;
background: var(--color-button-background);
outline: none;
border: none;
cursor: pointer;
color: var(--color-button-text-hover);
&:disabled {
opacity: 0.5;
cursor: default;
pointer-events: none;
}
`;
export const ActionButtons = styled.div` export const ActionButtons = styled.div`
margin-top: 17.5px; margin-top: 17.5px;
@ -92,15 +79,7 @@ export const ActionButtons = styled.div`
justify-content: space-between; justify-content: space-between;
`; `;
export const RegisterButton = styled.button` export const RegisterButton = styled(Button)``;
padding: 0.679rem 2rem;
border-radius: 6px;
border: 1px solid rgb(115, 103, 240);
background: transparent;
font-size: 1rem;
color: var(--color-primary);
cursor: pointer;
`;
export const LogoTitle = styled.div` export const LogoTitle = styled.div`
font-size: 24px; font-size: 24px;

View File

@ -72,8 +72,10 @@ const Login = ({ onSubmit }: LoginProps) => {
{errors.password && <FormError>{errors.password.message}</FormError>} {errors.password && <FormError>{errors.password.message}</FormError>}
<ActionButtons> <ActionButtons>
<RegisterButton>Register</RegisterButton> <RegisterButton variant="outline">Register</RegisterButton>
<LoginButton type="submit" value="Login" disabled={!isComplete} /> <LoginButton type="submit" disabled={!isComplete}>
Login
</LoginButton>
</ActionButtons> </ActionButtons>
</Form> </Form>
</LoginFormContainer> </LoginFormContainer>

View File

@ -16,7 +16,7 @@ import {
type PopupContextState = { type PopupContextState = {
show: (target: RefObject<HTMLElement>, content: JSX.Element, width?: string | number) => void; show: (target: RefObject<HTMLElement>, content: JSX.Element, width?: string | number) => void;
setTab: (newTab: number) => void; setTab: (newTab: number, width?: number | string) => void;
getCurrentTab: () => number; getCurrentTab: () => number;
hide: () => void; hide: () => void;
}; };
@ -139,12 +139,14 @@ export const PopupProvider: React.FC = ({ children }) => {
}; };
const portalTarget = canUseDOM ? document.body : null; // appease flow const portalTarget = canUseDOM ? document.body : null; // appease flow
const setTab = (newTab: number) => { const setTab = (newTab: number, width?: number | string) => {
let newWidth = width ?? currentState.width;
setState((prevState: PopupState) => { setState((prevState: PopupState) => {
return { return {
...prevState, ...prevState,
previousTab: currentState.currentTab, previousTab: currentState.currentTab,
currentTab: newTab, currentTab: newTab,
width: newWidth,
}; };
}); });
}; };

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import Button from 'shared/components/Button';
export const ListActionsWrapper = styled.ul` export const ListActionsWrapper = styled.ul`
list-style-type: none; list-style-type: none;
@ -36,16 +37,64 @@ export const ListSeparator = styled.hr`
width: 100%; width: 100%;
`; `;
type Props = {}; type Props = {
const ProjectSettings: React.FC<Props> = () => { onDeleteProject: () => void;
};
const ProjectSettings: React.FC<Props> = ({ onDeleteProject }) => {
return ( return (
<> <>
<ListActionsWrapper> <ListActionsWrapper>
<ListActionItemWrapper onClick={() => {}}> <ListActionItemWrapper onClick={() => onDeleteProject()}>
<ListActionItem>Delete Project</ListActionItem> <ListActionItem>Delete Project</ListActionItem>
</ListActionItemWrapper> </ListActionItemWrapper>
</ListActionsWrapper> </ListActionsWrapper>
</> </>
); );
}; };
const ConfirmWrapper = styled.div``;
const ConfirmSubTitle = styled.h3`
font-size: 14px;
`;
const ConfirmDescription = styled.div`
font-size: 14px;
`;
const DeleteList = styled.ul`
margin-bottom: 12px;
`;
const DeleteListItem = styled.li`
padding: 6px 0;
list-style: disc;
margin-left: 12px;
`;
const ConfirmDeleteButton = styled(Button)`
width: 100%;
padding: 6px 12px;
`;
type DeleteProjectProps = {
name: string;
onDeleteProject: () => void;
};
const DeleteProject: React.FC<DeleteProjectProps> = ({ name, onDeleteProject }) => {
return (
<ConfirmWrapper>
<ConfirmDescription>
Deleting the project will also delete the following:
<DeleteList>
<DeleteListItem>Task groups and tasks</DeleteListItem>
</DeleteList>
</ConfirmDescription>
<ConfirmDeleteButton onClick={() => onDeleteProject()} color="danger">
Delete
</ConfirmDeleteButton>
</ConfirmWrapper>
);
};
export { DeleteProject };
export default ProjectSettings; export default ProjectSettings;

View File

@ -78,6 +78,7 @@ export type Team = {
id: Scalars['ID']; id: Scalars['ID'];
createdAt: Scalars['Time']; createdAt: Scalars['Time'];
name: Scalars['String']; name: Scalars['String'];
members: Array<ProjectMember>;
}; };
export type Project = { export type Project = {
@ -145,8 +146,15 @@ export type FindTask = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
}; };
export type Organization = {
__typename?: 'Organization';
id: Scalars['ID'];
name: Scalars['String'];
};
export type Query = { export type Query = {
__typename?: 'Query'; __typename?: 'Query';
organizations: Array<Organization>;
users: Array<UserAccount>; users: Array<UserAccount>;
findUser: UserAccount; findUser: UserAccount;
findProject: Project; findProject: Project;
@ -192,7 +200,7 @@ export type NewUserAccount = {
export type NewTeam = { export type NewTeam = {
name: Scalars['String']; name: Scalars['String'];
organizationID: Scalars['String']; organizationID: Scalars['UUID'];
}; };
export type NewProject = { export type NewProject = {
@ -390,13 +398,36 @@ export type UpdateTaskChecklistItemName = {
name: Scalars['String']; name: Scalars['String'];
}; };
export type CreateTeamMember = {
userID: Scalars['UUID'];
teamID: Scalars['UUID'];
};
export type CreateTeamMemberPayload = {
__typename?: 'CreateTeamMemberPayload';
team: Team;
teamMember: ProjectMember;
};
export type DeleteProject = {
projectID: Scalars['UUID'];
};
export type DeleteProjectPayload = {
__typename?: 'DeleteProjectPayload';
ok: Scalars['Boolean'];
project: Project;
};
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
createRefreshToken: RefreshToken; createRefreshToken: RefreshToken;
createUserAccount: UserAccount; createUserAccount: UserAccount;
createTeam: Team; createTeam: Team;
clearProfileAvatar: UserAccount; clearProfileAvatar: UserAccount;
createTeamMember: CreateTeamMemberPayload;
createProject: Project; createProject: Project;
deleteProject: DeleteProjectPayload;
updateProjectName: Project; updateProjectName: Project;
createProjectLabel: ProjectLabel; createProjectLabel: ProjectLabel;
deleteProjectLabel: ProjectLabel; deleteProjectLabel: ProjectLabel;
@ -443,11 +474,21 @@ export type MutationCreateTeamArgs = {
}; };
export type MutationCreateTeamMemberArgs = {
input: CreateTeamMember;
};
export type MutationCreateProjectArgs = { export type MutationCreateProjectArgs = {
input: NewProject; input: NewProject;
}; };
export type MutationDeleteProjectArgs = {
input: DeleteProject;
};
export type MutationUpdateProjectNameArgs = { export type MutationUpdateProjectNameArgs = {
input?: Maybe<UpdateProjectName>; input?: Maybe<UpdateProjectName>;
}; };
@ -869,7 +910,10 @@ export type GetProjectsQueryVariables = {};
export type GetProjectsQuery = ( export type GetProjectsQuery = (
{ __typename?: 'Query' } { __typename?: 'Query' }
& { teams: Array<( & { organizations: Array<(
{ __typename?: 'Organization' }
& Pick<Organization, 'id' | 'name'>
)>, teams: Array<(
{ __typename?: 'Team' } { __typename?: 'Team' }
& Pick<Team, 'id' | 'name' | 'createdAt'> & Pick<Team, 'id' | 'name' | 'createdAt'>
)>, projects: Array<( )>, projects: Array<(
@ -897,6 +941,23 @@ export type MeQuery = (
) } ) }
); );
export type DeleteProjectMutationVariables = {
projectID: Scalars['UUID'];
};
export type DeleteProjectMutation = (
{ __typename?: 'Mutation' }
& { deleteProject: (
{ __typename?: 'DeleteProjectPayload' }
& Pick<DeleteProjectPayload, 'ok'>
& { project: (
{ __typename?: 'Project' }
& Pick<Project, 'id'>
) }
) }
);
export type CreateTaskChecklistItemMutationVariables = { export type CreateTaskChecklistItemMutationVariables = {
taskChecklistID: Scalars['UUID']; taskChecklistID: Scalars['UUID'];
name: Scalars['String']; name: Scalars['String'];
@ -985,6 +1046,20 @@ export type UpdateTaskGroupNameMutation = (
) } ) }
); );
export type CreateTeamMutationVariables = {
name: Scalars['String'];
organizationID: Scalars['UUID'];
};
export type CreateTeamMutation = (
{ __typename?: 'Mutation' }
& { createTeam: (
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'createdAt' | 'name'>
) }
);
export type ToggleTaskLabelMutationVariables = { export type ToggleTaskLabelMutationVariables = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID']; projectLabelID: Scalars['UUID'];
@ -1690,6 +1765,10 @@ export type FindTaskLazyQueryHookResult = ReturnType<typeof useFindTaskLazyQuery
export type FindTaskQueryResult = ApolloReactCommon.QueryResult<FindTaskQuery, FindTaskQueryVariables>; export type FindTaskQueryResult = ApolloReactCommon.QueryResult<FindTaskQuery, FindTaskQueryVariables>;
export const GetProjectsDocument = gql` export const GetProjectsDocument = gql`
query getProjects { query getProjects {
organizations {
id
name
}
teams { teams {
id id
name name
@ -1768,6 +1847,41 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>; export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>; export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>; export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
export const DeleteProjectDocument = gql`
mutation deleteProject($projectID: UUID!) {
deleteProject(input: {projectID: $projectID}) {
ok
project {
id
}
}
}
`;
export type DeleteProjectMutationFn = ApolloReactCommon.MutationFunction<DeleteProjectMutation, DeleteProjectMutationVariables>;
/**
* __useDeleteProjectMutation__
*
* To run a mutation, you first call `useDeleteProjectMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteProjectMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteProjectMutation, { data, loading, error }] = useDeleteProjectMutation({
* variables: {
* projectID: // value for 'projectID'
* },
* });
*/
export function useDeleteProjectMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteProjectMutation, DeleteProjectMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteProjectMutation, DeleteProjectMutationVariables>(DeleteProjectDocument, baseOptions);
}
export type DeleteProjectMutationHookResult = ReturnType<typeof useDeleteProjectMutation>;
export type DeleteProjectMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMutation>;
export type DeleteProjectMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMutation, DeleteProjectMutationVariables>;
export const CreateTaskChecklistItemDocument = gql` export const CreateTaskChecklistItemDocument = gql`
mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) { mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) {
createTaskChecklistItem(input: {taskChecklistID: $taskChecklistID, name: $name, position: $position}) { createTaskChecklistItem(input: {taskChecklistID: $taskChecklistID, name: $name, position: $position}) {
@ -1980,6 +2094,41 @@ export function useUpdateTaskGroupNameMutation(baseOptions?: ApolloReactHooks.Mu
export type UpdateTaskGroupNameMutationHookResult = ReturnType<typeof useUpdateTaskGroupNameMutation>; export type UpdateTaskGroupNameMutationHookResult = ReturnType<typeof useUpdateTaskGroupNameMutation>;
export type UpdateTaskGroupNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskGroupNameMutation>; export type UpdateTaskGroupNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskGroupNameMutation>;
export type UpdateTaskGroupNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskGroupNameMutation, UpdateTaskGroupNameMutationVariables>; export type UpdateTaskGroupNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskGroupNameMutation, UpdateTaskGroupNameMutationVariables>;
export const CreateTeamDocument = gql`
mutation createTeam($name: String!, $organizationID: UUID!) {
createTeam(input: {name: $name, organizationID: $organizationID}) {
id
createdAt
name
}
}
`;
export type CreateTeamMutationFn = ApolloReactCommon.MutationFunction<CreateTeamMutation, CreateTeamMutationVariables>;
/**
* __useCreateTeamMutation__
*
* To run a mutation, you first call `useCreateTeamMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTeamMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createTeamMutation, { data, loading, error }] = useCreateTeamMutation({
* variables: {
* name: // value for 'name'
* organizationID: // value for 'organizationID'
* },
* });
*/
export function useCreateTeamMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTeamMutation, CreateTeamMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTeamMutation, CreateTeamMutationVariables>(CreateTeamDocument, baseOptions);
}
export type CreateTeamMutationHookResult = ReturnType<typeof useCreateTeamMutation>;
export type CreateTeamMutationResult = ApolloReactCommon.MutationResult<CreateTeamMutation>;
export type CreateTeamMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTeamMutation, CreateTeamMutationVariables>;
export const ToggleTaskLabelDocument = gql` export const ToggleTaskLabelDocument = gql`
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) { mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) { toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {

View File

@ -1,4 +1,8 @@
query getProjects { query getProjects {
organizations {
id
name
}
teams { teams {
id id
name name

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
export const DELETE_PROJECT_MUTATION = gql`
mutation deleteProject($projectID: UUID!) {
deleteProject(input: { projectID: $projectID }) {
ok
project {
id
}
}
}
`;
export default DELETE_PROJECT_MUTATION;

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag';
export const CREATE_TEAM_MUTATION = gql`
mutation createTeam($name: String!, $organizationID: UUID!) {
createTeam(input: { name: $name, organizationID: $organizationID }) {
id
createdAt
name
}
}
`;
export default CREATE_TEAM_MUTATION;