diff --git a/frontend/src/app/(dashboard)/settings/account/page.tsx b/frontend/src/app/(dashboard)/settings/account/page.tsx new file mode 100644 index 0000000..caf06aa --- /dev/null +++ b/frontend/src/app/(dashboard)/settings/account/page.tsx @@ -0,0 +1,23 @@ +'use client'; + +import AccountNameForm from '@/features/user/components/account-name-form'; +import ChangePassword from "@/features/user/components/change-password"; +import ChangeEmail from "@/features/user/components/change-email"; +import { Separator } from "@/components/ui/separator"; +import React from "react"; + +export default function Home() { + + return ( + <> + + + + + + + + + + ); +} diff --git a/frontend/src/app/(dashboard)/settings/layout.tsx b/frontend/src/app/(dashboard)/settings/layout.tsx new file mode 100644 index 0000000..f083d3f --- /dev/null +++ b/frontend/src/app/(dashboard)/settings/layout.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from 'react'; + +export default function SettingsLayout({ children }: { children: ReactNode }) { + return ( +
+
{children}
+
+ ); +} diff --git a/frontend/src/app/(dashboard)/settings/page.tsx b/frontend/src/app/(dashboard)/settings/page.tsx new file mode 100644 index 0000000..2c7e396 --- /dev/null +++ b/frontend/src/app/(dashboard)/settings/page.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; + +export default function Home() { + const router = useRouter(); + + useEffect(() => { + router.push('/settings/account'); + }, [router]); + + return <>; +} diff --git a/frontend/src/app/(dashboard)/settings/workspace/members/page.tsx b/frontend/src/app/(dashboard)/settings/workspace/members/page.tsx new file mode 100644 index 0000000..80c350a --- /dev/null +++ b/frontend/src/app/(dashboard)/settings/workspace/members/page.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { Separator } from "@/components/ui/separator"; +import WorkspaceInviteSection from "@/features/workspace/components/workspace-invite-section"; +import React from "react"; +import WorkspaceInviteDialog from "@/features/workspace/components/workspace-invite-dialog"; + +const WorkspaceMembersTable = React.lazy(() => import('@/features/workspace/components/workspace-members-table')); + +export default function WorkspaceMembers() { + return ( + <> + + + + +
+

Members

+ + + + + +
+ + ); +} diff --git a/frontend/src/app/(dashboard)/settings/workspace/page.tsx b/frontend/src/app/(dashboard)/settings/workspace/page.tsx new file mode 100644 index 0000000..790e6ad --- /dev/null +++ b/frontend/src/app/(dashboard)/settings/workspace/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import WorkspaceNameForm from "@/features/workspace/components/workspace-name-form"; + + +export default function Home() { + + return (); +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 7ffd6e9..3d54eaa 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -3,17 +3,21 @@ import type { Metadata } from 'next' import { Inter } from 'next/font/google' import { cn } from "@/lib/utils"; import { ThemeProvider } from "@/components/providers/theme-provider"; -import { Toaster } from "@/components/ui/toaster"; import { TanstackProvider } from "@/components/providers/tanstack-provider"; +import CustomToaster from "@/components/ui/custom-toaster"; const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', + viewport: { + width: 'device-width', + initialScale: 1, + maximumScale: 1, + }, } - export default function RootLayout({ children, }: { @@ -26,7 +30,7 @@ export default function RootLayout({ {children} - + diff --git a/frontend/src/components/providers/tanstack-provider.tsx b/frontend/src/components/providers/tanstack-provider.tsx index fd6a426..5a01ac8 100644 --- a/frontend/src/components/providers/tanstack-provider.tsx +++ b/frontend/src/components/providers/tanstack-provider.tsx @@ -1,9 +1,16 @@ -"use client" +'use client' -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import React from "react"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnMount: false, + refetchOnWindowFocus: false + } + } +}); export function TanstackProvider({ children }: React.PropsWithChildren) { return ( diff --git a/frontend/src/features/auth/components/sign-up-form.tsx b/frontend/src/features/auth/components/sign-up-form.tsx index 6583ce3..93cf8eb 100644 --- a/frontend/src/features/auth/components/sign-up-form.tsx +++ b/frontend/src/features/auth/components/sign-up-form.tsx @@ -15,11 +15,10 @@ import { IRegister } from "@/features/auth/types/auth.types"; const formSchema = z.object({ email: z.string({ required_error: "email is required" }).email({ message: "Invalid email address" }), - password: z.string({ required_error: "password is required" }), + password: z.string({ required_error: "password is required" }).min(8), }); -interface UserAuthFormProps extends React.HTMLAttributes { -} +interface UserAuthFormProps extends React.HTMLAttributes {} export function SignUpForm({ className, ...props }: UserAuthFormProps) { const { register, handleSubmit, formState: { errors } } diff --git a/frontend/src/features/auth/hooks/use-auth.ts b/frontend/src/features/auth/hooks/use-auth.ts index d192825..38ca33f 100644 --- a/frontend/src/features/auth/hooks/use-auth.ts +++ b/frontend/src/features/auth/hooks/use-auth.ts @@ -1,11 +1,11 @@ import { useState } from "react"; -import { toast } from "@/components/ui/use-toast"; import { login, register } from "@/features/auth/services/auth-service"; import { useRouter } from "next/navigation"; import { useAtom } from "jotai"; import { authTokensAtom } from "@/features/auth/atoms/auth-tokens-atom"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; import { ILogin, IRegister } from "@/features/auth/types/auth.types"; +import toast from "react-hot-toast"; export default function useAuth() { const [isLoading, setIsLoading] = useState(false); @@ -25,10 +25,7 @@ export default function useAuth() { router.push("/home"); } catch (err) { setIsLoading(false); - toast({ - description: err.response?.data.message, - variant: "destructive", - }); + toast.error(err.response?.data.message) } }; @@ -44,10 +41,7 @@ export default function useAuth() { router.push("/home"); } catch (err) { setIsLoading(false); - toast({ - description: err.response?.data.message, - variant: "destructive", - }); + toast.error(err.response?.data.message) } }; @@ -57,7 +51,7 @@ export default function useAuth() { const handleLogout = async () => { setAuthToken(null); - setCurrentUser(''); + setCurrentUser(null); } return { signIn: handleSignIn, signUp: handleSignUp, isLoading, hasTokens }; diff --git a/frontend/src/features/user/atoms/current-user-atom.ts b/frontend/src/features/user/atoms/current-user-atom.ts index bc606e9..cc3aaf1 100644 --- a/frontend/src/features/user/atoms/current-user-atom.ts +++ b/frontend/src/features/user/atoms/current-user-atom.ts @@ -1,4 +1,6 @@ import { atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; + import { ICurrentUserResponse } from "@/features/user/types/user.types"; -export const currentUserAtom = atom(null); +export const currentUserAtom = atomWithStorage("currentUser", null); diff --git a/frontend/src/features/user/components/account-name-form.tsx b/frontend/src/features/user/components/account-name-form.tsx new file mode 100644 index 0000000..5f9cf43 --- /dev/null +++ b/frontend/src/features/user/components/account-name-form.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useAtom } from 'jotai'; +import { focusAtom } from 'jotai-optics'; +import { useForm } from 'react-hook-form'; +import * as z from 'zod'; +import { currentUserAtom } from '../atoms/current-user-atom'; +import { updateUser } from '../services/user-service'; +import { IUser } from '../types/user.types'; +import { useState } from 'react'; +import { Icons } from '@/components/icons'; +import toast from "react-hot-toast"; + +const profileFormSchema = z.object({ + name: z.string().min(2).max(40), +}); + +type ProfileFormValues = z.infer; + +const userAtom = focusAtom(currentUserAtom, (optic) => optic.prop('user')); + +export default function AccountNameForm() { + const [isLoading, setIsLoading] = useState(false); + const [currentUser] = useAtom(currentUserAtom); + const [, setUser] = useAtom(userAtom); + + const defaultValues: Partial = { + name: currentUser?.user?.name, + }; + + const form = useForm({ + resolver: zodResolver(profileFormSchema), + defaultValues, + }); + + async function onSubmit(data: Partial) { + setIsLoading(true); + + try { + const updatedUser = await updateUser(data); + setUser(updatedUser); + toast.success('Updated successfully'); + } catch (err) { + console.log(err); + toast.error('Failed to update data.') + } + + setIsLoading(false); + } + + return ( +
+ + ( + + Name + + + + + This is the name that will be displayed on your account and in + emails. + + + + )} + /> + + + + + ); +} diff --git a/frontend/src/features/user/components/change-email.tsx b/frontend/src/features/user/components/change-email.tsx new file mode 100644 index 0000000..2d04834 --- /dev/null +++ b/frontend/src/features/user/components/change-email.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { Dialog, DialogTrigger } from "@radix-ui/react-dialog"; +import { Button } from "@/components/ui/button"; +import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import * as z from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Icons } from "@/components/icons"; +import { useState } from "react"; +import { useAtom } from "jotai"; +import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; + + +export default function ChangeEmail() { + const [currentUser] = useAtom(currentUserAtom); + + return ( +
+
+

Email

+

{currentUser.user.email}

+
+ +
+ ); +} + +function ChangeEmailDialog() { + return ( + + + + + + + + Change email + + + To change your email, you have to enter your password and new email. + + + + + + + + + ); +} + +const changeEmailSchema = z.object({ + password: z.string({ required_error: "your current password is required" }).min(8), + email: z.string({ required_error: "New email is required" }).email() +}); + +type ChangeEmailFormValues = z.infer + +function ChangePasswordForm() { + const [isLoading, setIsLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(changeEmailSchema), + defaultValues: { + password: "", + email: "", + }, + }); + + function onSubmit(data: ChangeEmailFormValues) { + setIsLoading(true); + console.log(data); + } + + return ( +
+ + ( + + Password + + + + + + )} + /> + + ( + + New email + + + + Enter your new preferred email + + + )} + /> + +
+ +
+ + + + ); +} diff --git a/frontend/src/features/user/components/change-password.tsx b/frontend/src/features/user/components/change-password.tsx new file mode 100644 index 0000000..418d40f --- /dev/null +++ b/frontend/src/features/user/components/change-password.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { Dialog, DialogTrigger } from "@radix-ui/react-dialog"; +import { Button } from "@/components/ui/button"; +import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import * as z from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Icons } from "@/components/icons"; +import { useState } from "react"; + +export default function ChangePassword() { + return ( +
+
+

Password

+

You can change your password here.

+
+ +
+ ); +} + +function ChangePasswordDialog() { + return ( + + + + + + + + Change password + + + Your password must be at least a minimum of 8 characters. + + + + + + + + + ); +} + +const changePasswordSchema = z.object({ + current: z.string({ required_error: "your current password is required" }).min(1), + password: z.string({ required_error: "New password is required" }).min(8), + confirm_password: z.string({ required_error: "Password confirmation is required" }).min(8), +}).refine(data => data.password === data.confirm_password, { + message: "Your new password and confirmation does not match.", + path: ["confirm_password"], +}); + +type ChangePasswordFormValues = z.infer + +function ChangePasswordForm() { + const [isLoading, setIsLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(changePasswordSchema), + defaultValues: { + current: "", + password: "", + confirm_password: "", + }, + }); + + function onSubmit(data: ChangePasswordFormValues) { + setIsLoading(true); + console.log(data); + } + + return ( +
+ + ( + + Current password + + + + + + )} + /> + + ( + + New password + + + + + + )} + /> + + ( + + Repeat new password + + + + + + )} + /> + +
+ +
+ + + + ); +} diff --git a/frontend/src/features/user/services/user-service.ts b/frontend/src/features/user/services/user-service.ts index 8c824d8..f820799 100644 --- a/frontend/src/features/user/services/user-service.ts +++ b/frontend/src/features/user/services/user-service.ts @@ -1,12 +1,18 @@ -import api from "@/lib/api-client"; -import { ICurrentUserResponse, IUser } from "@/features/user/types/user.types"; +import api from '@/lib/api-client'; +import { ICurrentUserResponse, IUser } from '@/features/user/types/user.types'; -export async function getMe(): Promise{ - const req = await api.get("/user/me"); +export async function getMe(): Promise { + const req = await api.get('/user/me'); return req.data as IUser; } -export async function getUserInfo(): Promise{ - const req = await api.get("/user/info"); +export async function getUserInfo(): Promise { + const req = await api.get('/user/info'); return req.data as ICurrentUserResponse; } + +export async function updateUser(data: Partial) { + const req = await api.post('/user/update', data); + + return req.data as IUser; +} diff --git a/frontend/src/features/user/types/user.types.ts b/frontend/src/features/user/types/user.types.ts index 765c437..001b14f 100644 --- a/frontend/src/features/user/types/user.types.ts +++ b/frontend/src/features/user/types/user.types.ts @@ -12,6 +12,7 @@ export interface IUser { lastLoginIp: string; createdAt: Date; updatedAt: Date; + workspaceRole?: string; } export interface ICurrentUserResponse { diff --git a/frontend/src/features/user/user-provider.tsx b/frontend/src/features/user/user-provider.tsx index 9e6a0dc..67bb232 100644 --- a/frontend/src/features/user/user-provider.tsx +++ b/frontend/src/features/user/user-provider.tsx @@ -1,25 +1,25 @@ -"use client" +'use client'; -import { useAtom } from "jotai"; -import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; -import { useEffect } from "react"; -import useCurrentUser from "@/features/user/hooks/use-current-user"; +import { useAtom } from 'jotai'; +import { currentUserAtom } from '@/features/user/atoms/current-user-atom'; +import { useEffect } from 'react'; +import useCurrentUser from '@/features/user/hooks/use-current-user'; export function UserProvider({ children }: React.PropsWithChildren) { const [, setCurrentUser] = useAtom(currentUserAtom); const { data, isLoading, error } = useCurrentUser(); useEffect(() => { - if (data && data.user){ + if (data && data.user) { setCurrentUser(data); } }, [data, isLoading, setCurrentUser]); if (isLoading) return <>; - if (error){ - return <>an error occurred + if (error) { + return <>an error occurred; } - return <>{children} + return <>{children}; } diff --git a/frontend/src/features/workspace/components/workspace-invite-dialog.tsx b/frontend/src/features/workspace/components/workspace-invite-dialog.tsx new file mode 100644 index 0000000..f39d32b --- /dev/null +++ b/frontend/src/features/workspace/components/workspace-invite-dialog.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import ButtonWithIcon from "@/components/ui/button-with-icon"; +import { IconUserPlus } from "@tabler/icons-react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { WorkspaceInviteForm } from "@/features/workspace/components/workspace-invite-form"; + +export default function WorkspaceInviteDialog() { + + return ( + <> + + + } + className="font-medium"> + Invite Members + + + + + + Invite new members + + + Here you can invite new members. + + + + + + + + + + + ); +} diff --git a/frontend/src/features/workspace/components/workspace-invite-form.tsx b/frontend/src/features/workspace/components/workspace-invite-form.tsx new file mode 100644 index 0000000..9612de1 --- /dev/null +++ b/frontend/src/features/workspace/components/workspace-invite-form.tsx @@ -0,0 +1,154 @@ +"use client"; + +import * as z from "zod"; +import { useFieldArray, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { IconTrashX } from "@tabler/icons-react"; +import ButtonWithIcon from "@/components/ui/button-with-icon"; +import { Button } from "@/components/ui/button"; + +enum UserRole { + GUEST = "guest", + MEMBER = "member", + OWNER = "owner", +} + +const inviteFormSchema = z.object({ + members: z + .array( + z.object({ + email: z.string({ + required_error: "Email is required", + }).email({ message: "Please enter a valid email" }), + role: z + .string({ + required_error: "Please select a role", + }), + }), + ), +}); + +type InviteFormValues = z.infer + +const defaultValues: Partial = { + members: [ + { email: "user@example.com", role: "member" }, + ], +}; + +export function WorkspaceInviteForm() { + + const form = useForm({ + resolver: zodResolver(inviteFormSchema), + defaultValues, + mode: "onChange", + }); + + const { fields, append, remove } = useFieldArray({ + name: "members", + control: form.control, + }); + + function onSubmit(data: InviteFormValues) { + console.log(data); + } + + return ( + +
+ +
+ { + fields.map((field, index) => { + const key = index.toString(); + return ( +
+ +
+ {index === 0 && Email} + ( + + + + + + + )} + /> +
+ +
+ {index === 0 && Role} + + ( + + + + + )} /> +
+ +
+ {index != 0 && + } + variant="secondary" + onClick={() => remove(index)} + /> + } +
+ +
+ ); + }) + } + + + +
+ +
+ +
+
+ + + ); + +} diff --git a/frontend/src/features/workspace/components/workspace-invite-section.tsx b/frontend/src/features/workspace/components/workspace-invite-section.tsx new file mode 100644 index 0000000..f484e37 --- /dev/null +++ b/frontend/src/features/workspace/components/workspace-invite-section.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useAtom } from "jotai/index"; +import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; +import { useEffect, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import toast from "react-hot-toast"; + + +export default function WorkspaceInviteSection() { + const [currentUser] = useAtom(currentUserAtom); + const [inviteLink, setInviteLink] = useState(""); + + useEffect(() => { + setInviteLink(`${window.location.origin}/invite/${currentUser.workspace.inviteCode}`); + }, [currentUser.workspace.inviteCode]); + + function handleCopy(): void { + try { + navigator.clipboard?.writeText(inviteLink); + toast.success("Link copied successfully"); + } catch (err) { + toast.error("Failed to copy to clipboard"); + } + } + + return ( + <> +
+

Invite members

+

+ Anyone with the link can join this workspace. +

+
+ +
+ + +
+ + ); +} diff --git a/frontend/src/features/workspace/components/workspace-members-table.tsx b/frontend/src/features/workspace/components/workspace-members-table.tsx new file mode 100644 index 0000000..9fb9bca --- /dev/null +++ b/frontend/src/features/workspace/components/workspace-members-table.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useAtom } from "jotai/index"; +import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; +import { useQuery } from "@tanstack/react-query"; +import { getWorkspaceUsers } from "@/features/workspace/services/workspace-service"; +import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; + +export default function WorkspaceMembersTable() { + const [currentUser] = useAtom(currentUserAtom); + + const workspaceUsers = useQuery({ + queryKey: ["workspaceUsers", currentUser.workspace.id], + queryFn: async () => { + return await getWorkspaceUsers(); + }, + }); + + const { data, isLoading, isSuccess } = workspaceUsers; + + return ( + <> + {isSuccess && + + + Your workspace members will appear here. + + + Name + Email + Role + + + + + { + data['users']?.map((user, index) => ( + + {user.name} + {user.email} + {user.workspaceRole} + + )) + } + + +
+ } + + ); +} diff --git a/frontend/src/features/workspace/components/workspace-name-form.tsx b/frontend/src/features/workspace/components/workspace-name-form.tsx new file mode 100644 index 0000000..ffe6cae --- /dev/null +++ b/frontend/src/features/workspace/components/workspace-name-form.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useAtom } from "jotai"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import toast from "react-hot-toast"; +import { updateUser } from "@/features/user/services/user-service"; +import { useState } from "react"; +import { focusAtom } from "jotai-optics"; +import { updateWorkspace } from "@/features/workspace/services/workspace-service"; +import { IWorkspace } from "@/features/workspace/types/workspace.types"; + +const profileFormSchema = z.object({ + name: z.string(), +}); + +type ProfileFormValues = z.infer; + +const workspaceAtom = focusAtom(currentUserAtom, (optic) => optic.prop("workspace")); + +export default function WorkspaceNameForm() { + const [isLoading, setIsLoading] = useState(false); + const [currentUser] = useAtom(currentUserAtom); + const [, setWorkspace] = useAtom(workspaceAtom); + + const defaultValues: Partial = { + name: currentUser?.workspace?.name, + }; + + const form = useForm({ + resolver: zodResolver(profileFormSchema), + defaultValues, + }); + + async function onSubmit(data: Partial) { + setIsLoading(true); + + try { + const updatedWorkspace = await updateWorkspace(data); + setWorkspace(updatedWorkspace); + toast.success("Updated successfully"); + } catch (err) { + console.log(err); + toast.error("Failed to update data."); + } + + setIsLoading(false); + + } + + return ( +
+ + ( + + Name + + + + + Your workspace name. + + + + )} + /> + + + + + ); +} diff --git a/frontend/src/features/workspace/services/workspace-service.ts b/frontend/src/features/workspace/services/workspace-service.ts new file mode 100644 index 0000000..7661842 --- /dev/null +++ b/frontend/src/features/workspace/services/workspace-service.ts @@ -0,0 +1,19 @@ +import api from '@/lib/api-client'; +import { ICurrentUserResponse, IUser } from '@/features/user/types/user.types'; +import { IWorkspace } from '../types/workspace.types'; + +export async function getWorkspace(): Promise { + const req = await api.get('/workspace'); + return req.data as IWorkspace; +} + +export async function getWorkspaceUsers(): Promise { + const req = await api.get('/workspace/members'); + return req.data as IUser[]; +} + +export async function updateWorkspace(data: Partial) { + const req = await api.post('/workspace/update', data); + + return req.data as IWorkspace; +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 44eea8a..f15c41a 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -5,6 +5,7 @@ "allowJs": true, "skipLibCheck": true, "strict": false, + "strictNullChecks": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true,