Skip to content

Commit

Permalink
Progress on Workspace Member Management
Browse files Browse the repository at this point in the history
  • Loading branch information
R1c4rdCo5t4 committed Jun 19, 2024
1 parent b7a032c commit 5449bed
Show file tree
Hide file tree
Showing 33 changed files with 330 additions and 96 deletions.
8 changes: 5 additions & 3 deletions code/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { WorkspaceProvider } from '@ui/contexts/workspace/WorkspaceContext';
import Workspaces from '@ui/pages/workspaces/Workspaces';
import { CommunicationProvider } from '@ui/contexts/communication/CommunicationContext';
import Home from '@ui/pages/home/Home';
import Login from '@ui/pages/login/Login';
import AuthProvider from '@ui/contexts/auth/AuthContext';
import Profile from '@ui/pages/profile/Profile';
import Landing from '@ui/pages/landing/Landing';

function App() {
return (
Expand All @@ -23,16 +24,17 @@ function App() {
<Header />
<div className="content">
<Routes>
<Route path="/" element={<Landing />} />
<Route
path="/"
path="/home"
element={
<>
<Sidebar />
<Home />
</>
}
/>
<Route path="/login" element={<Login />} />
<Route path="/profile/:id" element={<Profile />} />
<Route
path="/workspaces/*"
element={
Expand Down
15 changes: 15 additions & 0 deletions code/client/src/domain/workspaces/useWorkspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ function useWorkspaces() {
return await service.updateWorkspace(id, newProps);
}

async function getWorkspaceMembers(id: string) {
return await service.getWorkspaceMembers(id);
}

async function addWorkspaceMember(id: string, email: string) {
return await service.addWorkspaceMember(id, email);
}

async function removeWorkspaceMember(id: string, email: string) {
return await service.removeWorkspaceMember(id, email);
}

useSocketListeners(socket, {
createdWorkspace: onCreateWorkspace,
deletedWorkspace: onDeleteWorkspace,
Expand All @@ -62,6 +74,9 @@ function useWorkspaces() {
createWorkspace,
deleteWorkspace,
updateWorkspace,
getWorkspaceMembers,
addWorkspaceMember,
removeWorkspaceMember,
},
};
}
Expand Down
6 changes: 4 additions & 2 deletions code/client/src/services/auth/authService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { UserData } from '@notespace/shared/src/users/types';
import { User, UserData } from '@notespace/shared/src/users/types';
import { HttpCommunication } from '@services/communication/http/httpCommunication';
import Cookies from 'js-cookie';

function authService(http: HttpCommunication) {
async function registerUser(id: string, data: UserData) {
await http.post('/users', { id, ...data });
}

async function getUser(id: string) {
async function getUser(id: string): Promise<User> {
return await http.get(`/users/${id}`);
}

Expand All @@ -16,6 +17,7 @@ function authService(http: HttpCommunication) {

async function deleteUser(id: string) {
await http.delete(`/users/${id}`);
Cookies.remove('token');
}

return {
Expand Down
17 changes: 17 additions & 0 deletions code/client/src/services/workspace/workspaceService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HttpCommunication } from '@services/communication/http/httpCommunication';
import { WorkspaceInputModel, WorkspaceMeta } from '@notespace/shared/src/workspace/types/workspace';
import { Workspace } from '@notespace/shared/src/workspace/types/workspace';
import { UserData } from '@notespace/shared/src/users/types';

function workspaceService(http: HttpCommunication) {
async function getWorkspace(id: string): Promise<Workspace> {
Expand All @@ -23,12 +24,28 @@ function workspaceService(http: HttpCommunication) {
await http.put(`/workspaces/${id}`, newProps);
}

async function getWorkspaceMembers(id: string): Promise<UserData[]> {
const workspace: WorkspaceMeta = await http.get(`/workspaces/${id}`);
return workspace.members;
}

async function addWorkspaceMember(id: string, email: string): Promise<void> {
await http.post(`/workspaces/${id}/members`, { email });
}

async function removeWorkspaceMember(id: string, email: string): Promise<void> {
await http.delete(`/workspaces/${id}/members`, { email });
}

return {
getWorkspace,
getWorkspaces,
createWorkspace,
deleteWorkspace,
updateWorkspace,
getWorkspaceMembers,
addWorkspaceMember,
removeWorkspaceMember,
};
}

Expand Down
4 changes: 3 additions & 1 deletion code/client/src/ui/components/dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ interface DialogProps {
fields: Field[];
onSubmit: (values: { [key: string]: any }) => void;
submitText?: string;
extraContent?: React.ReactNode;
children: React.ReactNode;
}

function Dialog({ title, fields, onSubmit, submitText, children }: DialogProps) {
function Dialog({ title, fields, onSubmit, submitText, extraContent, children }: DialogProps) {
const [open, setOpen] = useState(false);
const [values, setValues] = useState<{ [key: string]: any }>(
fields.reduce((obj, item) => ({ ...obj, [item.name]: item.type === 'checkbox' ? false : '' }), {})
Expand Down Expand Up @@ -82,6 +83,7 @@ function Dialog({ title, fields, onSubmit, submitText, children }: DialogProps)
/>
)
)}
{extraContent}
</DialogContent>
<DialogActions>
<button onClick={handleClose}>Cancel</button>
Expand Down
4 changes: 4 additions & 0 deletions code/client/src/ui/components/header/Header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
justify-content: space-between;
align-items: center;

a {
padding-left: 10vh;
}

div {
display: flex;
align-items: center;
Expand Down
18 changes: 9 additions & 9 deletions code/client/src/ui/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ function Header() {
const { currentUser, logout } = useAuth();
return (
<header className="header">
<p></p>
<Link to={currentUser ? '/home' : '/'}>NoteSpace</Link>
<div>
{currentUser ? (
<div className="account">
<p>{currentUser?.displayName}</p>
<button onClick={logout}>Logout</button>
</div>
) : (
<Link to="/login">Login</Link>
)}
<div className="account">
{currentUser && (
<>
<Link to={`/profile/${currentUser.uid}`}>{currentUser?.displayName}</Link>
<button onClick={logout}>Logout</button>
</>
)}
</div>
</div>
</header>
);
Expand Down
1 change: 0 additions & 1 deletion code/client/src/ui/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ function Sidebar() {
<IoMenu className="icon-menu" />
)}
</button>
<Link to="/">NoteSpace</Link>
</div>
<ul>
<li>
Expand Down
9 changes: 8 additions & 1 deletion code/client/src/ui/contexts/auth/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
const loginWithProvider = async (provider: Provider) => {
try {
const { user } = await signInWithPopup(auth, provider);
await registerUser(user.uid, { username: user.displayName!, email: user.email! });
await registerUser(user.uid, { name: user.displayName!, email: user.email! });
const token = await user.getIdToken();
Cookies.set('token', token, { expires: 1, secure: true, sameSite: 'Strict' });
} catch (e) {
Expand All @@ -56,6 +56,13 @@ export function AuthProvider({ children }: AuthProviderProps) {
});
}, []);

useEffect(() => {
console.log(currentUser);
if (!currentUser && window.location.pathname !== '/') {
window.location.href = '/';
}
}, [currentUser]);

return (
<AuthContext.Provider
value={{
Expand Down
22 changes: 0 additions & 22 deletions code/client/src/ui/contexts/auth/utils.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.login {
.landing {
display: flex;
flex-direction: column;
justify-content: center;
Expand All @@ -15,7 +15,7 @@
button {
background-color: white;
color: black;
padding: 1.5vh;
padding: 1.5vh 3vh 1.5vh 3vh;
font-size: larger;
border-radius: 50px;
border: 1px solid gray;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { useAuth } from '@ui/contexts/auth/useAuth';
import { FaGithub } from 'react-icons/fa6';
import googleIcon from '@assets/images/google-icon.png';
import './Login.scss';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
import googleIcon from '@assets/images/google-icon.png';
import { FaGithub } from 'react-icons/fa6';
import './Landing.scss';

function Login() {
function Landing() {
const { currentUser, loginWithGoogle, loginWithGithub } = useAuth();
const navigate = useNavigate();

useEffect(() => {
if (currentUser) {
navigate('/');
navigate('/home');
}
}, [currentUser, navigate]);

return (
<div className="login">
<h1>Login</h1>
<div className="landing">
<h1>Welcome to NoteSpace</h1>
<div>
<button onClick={loginWithGoogle}>
Login With Google
Expand All @@ -32,4 +33,4 @@ function Login() {
);
}

export default Login;
export default Landing;
7 changes: 7 additions & 0 deletions code/client/src/ui/pages/profile/Profile.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.profile {
button {
background: indianred;
color: white;
margin-top: 8vh;
}
}
44 changes: 44 additions & 0 deletions code/client/src/ui/pages/profile/Profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import useAuthService from '@services/auth/useAuthService';
import { User } from '@notespace/shared/src/users/types';
import { formatDate } from '@/utils/utils';
import { useAuth } from '@ui/contexts/auth/useAuth';
import './Profile.scss';

function Profile() {
const { id } = useParams();
const { getUser, deleteUser } = useAuthService();
const [user, setUser] = useState<User | null>(null);
const { currentUser } = useAuth();
const navigate = useNavigate();

useEffect(() => {
if (!id) return;
async function fetchUser() {
const user = await getUser(id!);
setUser(user);
}
fetchUser();
}, [id, getUser]);

async function onDeleteAccount() {
if (!currentUser || currentUser.uid !== user?.id) return;
await currentUser.delete();
await deleteUser(user.id);
navigate('/');
}

return (
user && (
<div className="profile">
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>Joined {formatDate(user.createdAt)}</p>
{currentUser?.uid === user.id && <button onClick={onDeleteAccount}>Delete Account</button>}
</div>
)
);
}

export default Profile;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Dialog from '@ui/components/dialog/Dialog';
import { FaCross, FaUsers } from 'react-icons/fa6';
import { UserData } from '@notespace/shared/src/users/types';

type ManageMembersDialog = {
members: UserData[];
onAddMember: (email: string) => void;
onRemoveMember: (email: string) => void;
};

function ManageMembersDialog({ members, onAddMember, onRemoveMember }: ManageMembersDialog) {
return (
<Dialog
title="Manage members in workspace"
fields={[{ name: 'Add new member', label: 'User email' }]}
onSubmit={values => {
onAddMember(values['Add new member']);
}}
submitText="Add Member"
extraContent={
<div>
<h3>Members</h3>
<ul>
{members?.map(member => (
<li key={member.email}>
<p>{member.email}</p>
<button onClick={() => onRemoveMember(member.email)}>
<FaCross />
</button>
</li>
))}
</ul>
</div>
}
>
<div>
<FaUsers />
Members
</div>
</Dialog>
);
}

export default ManageMembersDialog;
Loading

0 comments on commit 5449bed

Please sign in to comment.