-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #305 from NIAEFEUP/251-create-collaborative-sessio…
…n-frontend-components 251 create collaborative session frontend components
- Loading branch information
Showing
9 changed files
with
420 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
src/components/planner/sidebar/sessionController/CollabModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import React, { useContext, useState, useEffect, Fragment } from 'react'; | ||
import { Dialog, Transition } from '@headlessui/react'; | ||
import { XMarkIcon } from '@heroicons/react/24/solid'; | ||
import CollabPickSession from './CollabPickSession'; | ||
import CollabSession from './CollabSession'; | ||
import CollabSessionContext from '../../../../contexts/CollabSessionContext'; | ||
|
||
const PICK_SESSION = 'PICK_SESSION'; | ||
const SESSION = 'SESSION'; | ||
const generateUniqueId = () => Date.now(); | ||
const CollabModal = ({ isOpen, closeModal }) => { | ||
const { sessions, setSessions, currentSessionId, setcurrentSessionId } = useContext(CollabSessionContext); | ||
const [currentView, setCurrentView] = useState(PICK_SESSION); //Defines in which modal we are | ||
|
||
useEffect(() => { | ||
if (isOpen) { | ||
if (currentSessionId !== null && sessions.find(s => s.id === currentSessionId)) { | ||
setCurrentView(SESSION); | ||
} else { | ||
setCurrentView(PICK_SESSION); | ||
} | ||
} | ||
}, [isOpen, currentSessionId, sessions]); | ||
|
||
const currentSession = sessions.find(s => s.id === currentSessionId) || null; | ||
|
||
const handleStartSession = (sessionId) => { | ||
setcurrentSessionId(sessionId); | ||
setCurrentView(SESSION); | ||
}; | ||
|
||
const handleCreateSession = () => { //Dummy function to create a session... | ||
const newSession = { | ||
id: generateUniqueId(), | ||
name: Math.random().toString(36).substr(2, 9), | ||
lastEdited: new Date().toLocaleDateString(), | ||
lifeSpan: 30, | ||
currentUser: 'TheCreator', | ||
link: `https://collab.app/session/${Date.now().toString()}`, | ||
participants: ['TheCreator'], | ||
}; | ||
console.log('CollabModal -> newSession', newSession); | ||
setSessions(prevSessions => [...prevSessions, newSession]); | ||
setcurrentSessionId(newSession.id); | ||
setCurrentView(SESSION); | ||
}; | ||
|
||
const handleExitSession = () => { | ||
setcurrentSessionId(null); | ||
setCurrentView(PICK_SESSION); | ||
}; | ||
|
||
const handleDeleteSession = (sessionId) => { | ||
setSessions(prevSessions => prevSessions.filter(session => session.id !== sessionId)); | ||
if (currentSession?.id === sessionId) { | ||
handleExitSession(); | ||
} | ||
}; | ||
|
||
const handleUpdateUser = (updatedUser) => { | ||
if (currentSession) { | ||
const updatedSession = { | ||
...currentSession, | ||
currentUser: updatedUser, | ||
participants: currentSession.participants.map(participant => | ||
participant === currentSession.currentUser ? updatedUser : participant | ||
) | ||
}; | ||
setSessions(prevSessions => | ||
prevSessions.map(session => | ||
session.id === currentSession.id ? updatedSession : session | ||
) | ||
); | ||
} | ||
}; | ||
return ( | ||
<Transition appear show={isOpen} as={Fragment}> | ||
<Dialog as="div" className="relative z-10" onClose={closeModal}> | ||
<Transition.Child | ||
as={Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0" | ||
enterTo="opacity-100" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100" | ||
leaveTo="opacity-0" | ||
> | ||
<div className="fixed inset-0 bg-black bg-opacity-25" /> | ||
</Transition.Child> | ||
|
||
<div className="fixed inset-0 overflow-y-auto"> | ||
<div className="flex min-h-full items-center justify-center p-4 text-center"> | ||
<Transition.Child | ||
as={Fragment} | ||
enter="ease-out duration-300" | ||
enterFrom="opacity-0 scale-95" | ||
enterTo="opacity-100 scale-100" | ||
leave="ease-in duration-200" | ||
leaveFrom="opacity-100 scale-100" | ||
leaveTo="opacity-0 scale-95" | ||
> | ||
<Dialog.Panel className="w-full max-w-xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"> | ||
<div className="flex justify-end"> | ||
<button | ||
type="button" | ||
className="text-gray-400 hover:text-gray-600" | ||
onClick={closeModal} | ||
> | ||
<XMarkIcon className="h-6 w-6" /> | ||
</button> | ||
</div> | ||
|
||
{currentView === PICK_SESSION && ( | ||
<CollabPickSession | ||
sessions={sessions} | ||
onStartSession={handleStartSession} | ||
onCreateSession={handleCreateSession} | ||
onDeleteSession={handleDeleteSession} | ||
/> | ||
)} | ||
|
||
{currentView === SESSION && currentSession && ( | ||
<CollabSession | ||
session={currentSession} | ||
onExitSession={handleExitSession} | ||
onUpdateUser={handleUpdateUser} | ||
/> | ||
)} | ||
</Dialog.Panel> | ||
</Transition.Child> | ||
</div> | ||
</div> | ||
</Dialog> | ||
</Transition> | ||
); | ||
}; | ||
|
||
export default CollabModal; |
63 changes: 63 additions & 0 deletions
63
src/components/planner/sidebar/sessionController/CollabPickSession.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import React from 'react'; | ||
import { PlayCircleIcon, UserGroupIcon } from '@heroicons/react/20/solid'; | ||
import { Button } from '../../../ui/button'; | ||
|
||
const CollabPickSession = ({ sessions, onStartSession, onCreateSession, onDeleteSession }) => ( | ||
<div className="text-center"> | ||
<UserGroupIcon className="h-40 w-40 mx-auto text-primary" /> | ||
<h3 className="text-xl font-bold leading-6 text-primary"> | ||
Colaboração ao vivo... | ||
</h3> | ||
|
||
<p className="mt-6 text-sm text-gray-600"> | ||
<span className="font-bold block text-gray-800"> | ||
Podes convidar amigos para as tuas opções para colaborar contigo. | ||
</span> | ||
Não te preocupes, todas as tuas opções continuam a guardar localmente no teu dispositivo. | ||
</p> | ||
|
||
<div className="flex justify-center mt-6 space-x-4 "> | ||
<Button | ||
variant="icon" | ||
className="flex items-center rounded-lg bg-primary py-6" | ||
onClick={onCreateSession} | ||
> | ||
<PlayCircleIcon className="h-8 w-8 mr-2 " /> | ||
Iniciar nova sessão | ||
</Button> | ||
</div> | ||
|
||
<div className="mt-6 text-center"> | ||
<h4 className="text-md font-bold ">Sessões anteriores</h4> | ||
<p className="text-sm text-gray-600"> | ||
As sessões têm um tempo de vida, pelo que se não quiseres perder as tuas opções, terás de guardar para o teu dispositivo localmente. | ||
</p> | ||
<ul className="mt-4 flex flex-col sm:grid sm:grid-cols-1 sm:gap-y-4"> | ||
{sessions.map((session) => ( | ||
<li key={session.id} className="sm:grid sm:grid-cols-7 flex flex-col sm:mt-0 mt-6 items-center text-sm text-gray-800 gap-4"> | ||
<span className="col-span-2 truncate whitespace-nowrap font-bold">{session.name}</span> | ||
<span className="col-span-2 text-gray-600 truncate whitespace-nowrap">editado {session.lastEdited}</span> | ||
<span className="col-span-2 text-gray-600 truncate whitespace-nowrap">expira em {session.lifeSpan} dias</span> | ||
<div className="col-span-1 flex justify-end space-x-4"> | ||
<a | ||
href="#" | ||
className="text-primary hover:underline" | ||
onClick={() => onStartSession(session.id)} | ||
> | ||
Entrar | ||
</a> | ||
<button | ||
className="text-primary hover:text-red-800" | ||
onClick={() => onDeleteSession(session.id)} | ||
> | ||
× | ||
</button> | ||
</div> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
</div> | ||
); | ||
|
||
export default CollabPickSession; |
127 changes: 127 additions & 0 deletions
127
src/components/planner/sidebar/sessionController/CollabSession.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import React, { useState } from 'react'; | ||
import { DocumentDuplicateIcon, CheckIcon } from '@heroicons/react/24/outline'; | ||
import { StopIcon } from '@heroicons/react/24/solid'; | ||
import { Button } from '../../../ui/button'; | ||
import { useToast } from '../../../ui/use-toast'; | ||
|
||
const pastelColors = [ //Colors for the participants | ||
'bg-orange-200 text-orange-700', | ||
'bg-blue-200 text-blue-800', | ||
'bg-yellow-200 text-yellow-800', | ||
'bg-green-200 text-green-800', | ||
'bg-purple-200 text-purple-800', | ||
]; | ||
|
||
const CollabSession = ({ session, onExitSession, onUpdateUser }) => { | ||
const { toast } = useToast(); | ||
const [copied, setCopied] = useState(false); | ||
const [lastValidUser, setLastValidUser] = useState(session.currentUser); | ||
|
||
const handleCopyLink = () => { | ||
navigator.clipboard.writeText(session.link); | ||
toast({ | ||
title: 'Link copiado', | ||
description: 'Podes partilhar o link com amigos para colaborar contigo.', | ||
}); | ||
setCopied(true); | ||
setTimeout(() => { | ||
setCopied(false); | ||
}, 2000); | ||
}; | ||
|
||
const handleUserChange = (e) => { | ||
const newValue = e.target.value.trim(); | ||
if (newValue !== '') { | ||
setLastValidUser(newValue); | ||
} | ||
}; | ||
|
||
const handleUserBlur = (e) => { | ||
const newValue = e.target.value.trim(); | ||
if (newValue !== '') { | ||
onUpdateUser(newValue); | ||
} else { | ||
onUpdateUser(lastValidUser); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="text-left"> | ||
<h3 className="text-xl font-bold leading-6 mb-6">Colaboração ao vivo...</h3> | ||
|
||
<div className="mt-4"> | ||
<label className="block text-sm font-medium text-gray-700 mb-1">O teu nome</label> | ||
<input | ||
type="text" | ||
defaultValue={session.currentUser} | ||
onChange={handleUserChange} | ||
onBlur={handleUserBlur} | ||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm" | ||
/> | ||
</div> | ||
|
||
<div className="mt-4"> | ||
<label className="block text-sm font-medium text-gray-700 mb-1">Link</label> | ||
<div className="flex items-center mt-1"> | ||
<input | ||
type="text" | ||
value={session.link} | ||
className="flex-1 block w-full rounded-md bg-red-50 border-gray-300 shadow-sm sm:text-sm" | ||
readOnly | ||
/> | ||
<Button | ||
variant="icon" | ||
className={`ml-2 px-3 py-1 flex items-center ${copied ? 'bg-green-200 text-white' : 'bg-primary text-white'} text-sm font-medium rounded-lg min-w-[120px]`} | ||
onClick={handleCopyLink} | ||
> | ||
{copied ? ( | ||
<CheckIcon className="h-5 w-5 text-green-700" /> | ||
) : ( | ||
<DocumentDuplicateIcon className="h-5 w-5" /> | ||
)} | ||
{copied ? '' : ' Copiar link'} | ||
</Button> | ||
</div> | ||
</div> | ||
<div className="mt-6"> | ||
<h4 className="text-md font-medium text-gray-900 mb-2">Participantes</h4> | ||
<div className="flex flex-wrap space-x-2"> | ||
{session.participants.map((user, index) => ( | ||
<div key={index} className="relative group mb-2"> | ||
<div | ||
className={`rounded-full h-10 w-10 flex items-center justify-center ${ | ||
pastelColors[index % pastelColors.length] | ||
}`} | ||
> | ||
{user[0]} | ||
</div> | ||
{user && ( | ||
<div className="absolute bottom-12 left-1/2 transform -translate-x-1/2 invisible opacity-0 group-hover:visible group-hover:opacity-100 transition-opacity duration-200 bg-gray-900 text-white text-xs rounded-md px-2 py-1"> | ||
{user} | ||
</div> | ||
)} | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
|
||
|
||
<p className="mt-6 text-sm text-gray-600"> | ||
Interromper a sessão irá desconectar-te da sala, mas podes continuar a criar e ver mais combinações de horários localmente | ||
</p> | ||
|
||
<div className="mt-6 text-center"> | ||
<Button | ||
type="button" | ||
className="px-4 py-2 bg-white border border-primary text-primary font-medium rounded-lg hover:bg-primary hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500" | ||
onClick={onExitSession} | ||
> | ||
<StopIcon className="h-5 w-5 mr-2" /> | ||
Sair da sessão | ||
</Button> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default CollabSession; |
Oops, something went wrong.