diff --git a/.env.example b/.env.example index a74a33b..753d3ad 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,10 @@ GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= NODE_ENV="development" WS_URL= + +#For sending email +SMTP_HOST= +SMTP_PORT= +SMTP_USERNAME= +SMTP_PASSWORD= +EMAIL_FROM="support@rails.com" \ No newline at end of file diff --git a/package.json b/package.json index 116ac7f..7f1554f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "build:1-generate": "prisma generate", "build:2-next": "cross-env NODE_ENV=production next build", "build:3-server": "tsc --project tsconfig.server.json", - "build:4-migrate": "prisma migrate deploy", "build": "run-s build:*", "lint": "next lint", "start": "next start", @@ -34,6 +33,7 @@ "next": "^13.4.19", "next-auth": "^4.23.1", "node-fetch": "^3.3.2", + "nodemailer": "^6.9.4", "react": "^18.2.0", "react-dom": "^18.2.0", "recharts": "^2.7.3", @@ -46,7 +46,8 @@ "devDependencies": { "@faker-js/faker": "^8.0.2", "@playwright/test": "^1.37.1", - "@types/node": "^20.5.1", + "@types/node": "^20.5.3", + "@types/nodemailer": "^6.4.9", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@types/ws": "^8.5.5", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4ee431f..3bffc2b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,18 +65,19 @@ model VerificationToken { } model Workspace { - id String @id @default(cuid()) - name String - shortName String - description String? - website String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - createdById String - color String - projects Project[] - members User[] - createdBy User @relation("createdBy", fields: [createdById], references: [id]) + id String @id @default(cuid()) + name String + shortName String @unique + description String? + website String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + createdById String + color String + projects Project[] + members User[] + createdBy User @relation("createdBy", fields: [createdById], references: [id]) + invitationTokens InvitationToken[] } model Project { @@ -206,6 +207,17 @@ model Sprint { project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) } +model InvitationToken { + id String @id @default(cuid()) + expiresAt DateTime + email String + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + workspaceId String + token String @unique @default(cuid()) + createdAt DateTime @default(now()) + createdByEmail String +} + enum ProjectType { KANBAN SCRUM diff --git a/src/components/AddUserPopUp.tsx/AddUserPopUp.tsx b/src/components/AddUserPopUp.tsx/AddUserPopUp.tsx deleted file mode 100644 index c1d7f25..0000000 --- a/src/components/AddUserPopUp.tsx/AddUserPopUp.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, {useState} from 'react'; -import {Button, Modal, Form, Input, Select} from 'antd'; -import { UserPlusIcon } from '@heroicons/react/24/outline'; - -interface AddUserPopUpProps { - render?: React.ReactNode; -} - -const AddUserPopUp = (props:AddUserPopUpProps) => { - const [open, setOpen] = useState(false); - const [confirmLoading, setConfirmLoading] = useState(false); - - const showModal = () => { - setOpen(true); - }; - - const handleOk = () => { - setConfirmLoading(true); - setTimeout(() => { - setOpen(false); - setConfirmLoading(false); - }, 2000); - }; - - const handleCancel = () => { - setOpen(false); - }; - - const [form] = Form.useForm(); - - const onFinish = (values: any) => { - console.log(values); - }; - - return ( -
- {
{props.render}
?? } - -
- - - - - - -
-
-
- ); -}; - -export default AddUserPopUp; - -const {Option} = Select; - -const layout = { - labelCol: {span: 8}, - wrapperCol: {span: 16}, -}; - -const tailLayout = { - wrapperCol: {offset: 8, span: 16}, -}; diff --git a/src/components/AddUserPopUp/AddUserPopUp.tsx b/src/components/AddUserPopUp/AddUserPopUp.tsx new file mode 100644 index 0000000..c8079a7 --- /dev/null +++ b/src/components/AddUserPopUp/AddUserPopUp.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react'; +import { Button, Modal, Form, Input, message } from 'antd'; +import { UserPlusIcon } from '@heroicons/react/24/outline'; +import { api } from '../../utils/api'; +import { useWorkspaceStore } from '../../store/workspace.store'; + +interface AddUserPopUpProps { + render?: React.ReactNode; +} + +const AddUserPopUp = (props: AddUserPopUpProps) => { + const [open, setOpen] = useState(false); + + const currentWorkspace = useWorkspaceStore(state => state.currentWorkspace); + + const { mutate: sendWorkspaceInvite, isLoading } = api.workspace.sendWorkspaceInvite.useMutation({ + onSuccess() { + form.resetFields(); + setOpen(false); + message.success('Invite sent successfully'); + }, + onError() { + message.error('Something went wrong'); + } + }); + + const showModal = () => { + setOpen(true); + }; + + const handleOk = () => { + form.submit(); + }; + + const handleCancel = () => { + setOpen(false); + }; + + const [form] = Form.useForm(); + + const onFinish = (values: any) => { + if (!currentWorkspace) return; + sendWorkspaceInvite({ workspaceId: currentWorkspace!.id, email: values.email }); + }; + + return ( +
+ {props.render ?
{props.render}
: } + +
+ + + +
+
+
+ ); +}; + +export default AddUserPopUp; \ No newline at end of file diff --git a/src/components/ButtonMenu/ButtonMenu.tsx b/src/components/ButtonMenu/ButtonMenu.tsx index 6c5ea28..d645ecb 100644 --- a/src/components/ButtonMenu/ButtonMenu.tsx +++ b/src/components/ButtonMenu/ButtonMenu.tsx @@ -25,7 +25,6 @@ const ButtonMenu = (props: Props) => { title={props.title} extra={ */} - {/* */} + )} - options={items.map((item) => ({label: item.title, value: item.title}))} - /> + > + {project!.labels.map((item) => ( +