diff --git a/src/components/forms/CreateEstimationSessionForm.tsx b/src/components/forms/CreateEstimationSessionForm.tsx index 8f3ad2b..2f39737 100644 --- a/src/components/forms/CreateEstimationSessionForm.tsx +++ b/src/components/forms/CreateEstimationSessionForm.tsx @@ -1,5 +1,5 @@ import { useForm } from '@tanstack/react-form'; -import { useEstimationSessions } from '../../lib/context/estimationSession'; +import { useEstimationsList } from '../../lib/context/estimationsList'; import { useUser } from '../../lib/context/user'; import Input from '../Input'; import Button from '../Button'; @@ -12,13 +12,13 @@ const CreateEstimationSessionForm: React.FC< CreateEstimationSessionFormProps > = ({ onCreated }) => { const user = useUser(); - const estimationSessions = useEstimationSessions(); + const estimationsList = useEstimationsList(); const form = useForm({ defaultValues: { name: '', }, onSubmit: async ({ value }) => { - await estimationSessions?.add({ + await estimationsList?.add({ name: value.name, userId: user.current?.$id, }); diff --git a/src/lib/context/estimation.tsx b/src/lib/context/estimation.tsx index a2da560..61ee263 100644 --- a/src/lib/context/estimation.tsx +++ b/src/lib/context/estimation.tsx @@ -14,6 +14,7 @@ import { import { DatabaseModels, EntityModels } from '../types'; import { useUser } from './user'; import { EstimationSessionTicket } from '../types/entityModels'; +import { mapDatabaseToEntity } from '../mappers/estimationSession'; interface EstimationContextType { setSessionId: (sessionId: string) => void; @@ -32,43 +33,6 @@ export const useEstimationContext = () => { return useContext(EstimationContext); }; -const mapEstimationSession = ( - data: DatabaseModels.EstimationSession, - { userId }: { userId?: string }, -) => { - const sessionState: EntityModels.SessionState = data.sessionState - ? JSON.parse(data.sessionState) - : { - votes: [], - }; - - const tickets = data.tickets - ? data.tickets.map((ticket) => - JSON.parse(ticket), - ) - : []; - - const result: EntityModels.EstimationSession = { - id: data.$id, - name: data.name, - userId: data.userId, - tickets, - sessionState: { - ...sessionState, - currentPlayerVote: sessionState.votes.find((x) => x.userId === userId) - ?.estimate, - currentTicket: tickets.find((x) => x.id === sessionState.currentTicketId), - }, - }; - - console.log({ - result, - userId, - }); - - return result; -}; - export const EstimationContextProvider = (props: PropsWithChildren) => { const [sessionId, setSessionId] = useState(''); const [currentSessionData, setCurrentSessionData] = @@ -89,7 +53,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => { ) .then((payload) => { const userId = userData?.$id ?? ''; // TODO: Not sure if this is the user id or session - setCurrentSessionData(mapEstimationSession(payload, { userId })); + setCurrentSessionData(mapDatabaseToEntity(payload, { userId })); }); return client.subscribe( @@ -98,7 +62,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => { ], ({ payload }) => { const userId = userData?.$id ?? ''; // TODO: Not sure if this is the user id or session - setCurrentSessionData(mapEstimationSession(payload, { userId })); + setCurrentSessionData(mapDatabaseToEntity(payload, { userId })); }, ); }, [sessionId, userData]); diff --git a/src/lib/context/estimationSession.tsx b/src/lib/context/estimationSession.tsx deleted file mode 100644 index 941f9ff..0000000 --- a/src/lib/context/estimationSession.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import { - createContext, - PropsWithChildren, - useContext, - useEffect, - useState, -} from 'react'; -import { - client, - DATABASE_ID, - databases, - ESTIMATION_SESSION_COLLECTION_ID, -} from '../appwrite'; -import { ID, Models, Query } from 'appwrite'; - -interface EstimationSessionType extends Models.Document { - userId: string; - name: string; - tickets: string[]; - sessionState: string; -} - -interface EstimationSessionTicket { - id: string; - name: string; -} - -interface SessionStateType { - currentTicketId: string; - votesRevealed: boolean; - votes: { - userId: string; - estimate: number; - }[]; -} - -interface EstimationSessionsContextType { - current: EstimationSessionType[]; - add: ( - estimationSession: Omit, - ) => Promise; - remove: (id: string) => Promise; - addTicket: ( - sessionId: string, - ticket: Omit, - ) => Promise; - getTickets: (sessionId: string) => EstimationSessionTicket[]; - selectTicket: (sessionId: string, ticketId: string) => Promise; - getState: (sessionId: string) => SessionStateType; - voteEstimate: ( - sessionId: string, - ticketId: string, - estimate: number, - userId: string, - ) => Promise; - revealVotes: (sessionId: string) => Promise; -} - -const EstimationSessionsContext = createContext< - EstimationSessionsContextType | undefined ->(undefined); - -export function useEstimationSessions() { - return useContext(EstimationSessionsContext); -} - -export function EstimationSessionProvider(props: PropsWithChildren) { - const [estimationSessions, setEstimationSessions] = useState< - EstimationSessionType[] - >([]); - - const add = async ( - estimationSession: Omit, - ) => { - const response = await databases.createDocument( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - ID.unique(), - estimationSession, - ); - setEstimationSessions((estimationSessions) => - [response, ...estimationSessions].slice(0, 10), - ); - }; - - const remove = async (id: string) => { - await databases.deleteDocument( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - id, - ); - setEstimationSessions((estimationSessions) => - estimationSessions.filter( - (estimationSession) => estimationSession.$id !== id, - ), - ); - await init(); - }; - - const addTicket = async ( - sessionId: string, - ticket: Omit, - ) => { - const currentSession = estimationSessions.find((x) => x.$id === sessionId); - const response = await databases.updateDocument( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - sessionId, - { - tickets: currentSession?.tickets.concat([ - JSON.stringify({ - ...ticket, - id: crypto.randomUUID(), - }), - ]), - }, - ); - setEstimationSessions((estimationSessions) => - estimationSessions - .filter((x) => x.$id != sessionId) - .concat([response]) - .slice(0, 10), - ); - }; - - const getTickets = (sessionId: string) => { - return ( - estimationSessions - .find((x) => x.$id === sessionId) - ?.tickets.map((x) => JSON.parse(x)) ?? [] - ); - }; - - const selectTicket = async (sessionId: string, ticketId: string) => { - const response = await databases.updateDocument( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - sessionId, - { - sessionState: JSON.stringify({ - currentTicketId: ticketId, - }), - }, - ); - setEstimationSessions((estimationSessions) => - estimationSessions - .filter((x) => x.$id != sessionId) - .concat([response]) - .slice(0, 10), - ); - }; - - const getState = (sessionId: string): SessionStateType => { - return JSON.parse( - estimationSessions.find((x) => x.$id === sessionId)?.sessionState ?? '{}', - ); - }; - - const voteEstimate = async ( - sessionId: string, - ticketId: string, - estimate: number, - userId: string, - ) => { - const currentState = getState(sessionId); - const newVotes = (currentState.votes ?? []) - .filter((x) => x.userId !== userId) - .concat([ - { - estimate: estimate, - userId: userId, - }, - ]); - const response = await databases.updateDocument( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - sessionId, - { - sessionState: JSON.stringify({ - currentTicketId: ticketId, - votes: newVotes, - }), - }, - ); - setEstimationSessions((estimationSessions) => - estimationSessions - .filter((x) => x.$id != sessionId) - .concat([response]) - .slice(0, 10), - ); - }; - - const revealVotes = async (sessionId: string) => { - const currentState = getState(sessionId); - const response = await databases.updateDocument( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - sessionId, - { - sessionState: JSON.stringify({ - ...currentState, - VotesRevealed: true, - }), - }, - ); - setEstimationSessions((estimationSessions) => - estimationSessions - .filter((x) => x.$id != sessionId) - .concat([response]) - .slice(0, 10), - ); - }; - - const init = async () => { - const response = await databases.listDocuments( - DATABASE_ID, - ESTIMATION_SESSION_COLLECTION_ID, - [Query.orderDesc('$createdAt'), Query.limit(10)], - ); - setEstimationSessions(response.documents); - }; - - useEffect(() => { - init(); - - return client.subscribe( - [ - `databases.${DATABASE_ID}.collections.${ESTIMATION_SESSION_COLLECTION_ID}.documents`, - ], - (payload) => { - setEstimationSessions((estimationSessions) => - estimationSessions - .filter((x) => x.$id != payload.payload.$id) - .concat([payload.payload]) - .slice(0, 10), - ); - }, - ); - }, []); - - return ( - - {props.children} - - ); -} diff --git a/src/lib/context/estimationsList.tsx b/src/lib/context/estimationsList.tsx new file mode 100644 index 0000000..62346b0 --- /dev/null +++ b/src/lib/context/estimationsList.tsx @@ -0,0 +1,114 @@ +import { + createContext, + PropsWithChildren, + useContext, + useEffect, + useState, +} from 'react'; +import { + client, + DATABASE_ID, + databases, + ESTIMATION_SESSION_COLLECTION_ID, +} from '../appwrite'; +import { ID, Models, Query } from 'appwrite'; +import { EntityModels } from '../types'; +import { mapDatabaseToEntity } from '../mappers/estimationSession'; + +interface EstimationSessionType extends Models.Document { + userId: string; + name: string; + tickets: string[]; + sessionState: string; +} + +interface EstimationsListContextType { + current: EntityModels.EstimationSession[]; + add: ( + estimationSession: Omit, + ) => Promise; + remove: (id: string) => Promise; +} + +const EstimationsListContext = createContext< + EstimationsListContextType | undefined +>(undefined); + +export function useEstimationsList() { + return useContext(EstimationsListContext); +} + +export function EstimationsListContextProvider(props: PropsWithChildren) { + const [estimationSessions, setEstimationSessions] = useState< + EntityModels.EstimationSession[] + >([]); + + const add = async ( + estimationSession: Omit, + ) => { + const response = await databases.createDocument( + DATABASE_ID, + ESTIMATION_SESSION_COLLECTION_ID, + ID.unique(), + estimationSession, + ); + setEstimationSessions((estimationSessions) => + [mapDatabaseToEntity(response, {}), ...estimationSessions].slice(0, 10), + ); + }; + + const remove = async (id: string) => { + await databases.deleteDocument( + DATABASE_ID, + ESTIMATION_SESSION_COLLECTION_ID, + id, + ); + setEstimationSessions((estimationSessions) => + estimationSessions.filter( + (estimationSession) => estimationSession.id !== id, + ), + ); + await init(); + }; + + const init = async () => { + const response = await databases.listDocuments( + DATABASE_ID, + ESTIMATION_SESSION_COLLECTION_ID, + [Query.orderDesc('$createdAt'), Query.limit(10)], + ); + setEstimationSessions( + response.documents.map((document) => mapDatabaseToEntity(document, {})), + ); + }; + + useEffect(() => { + init(); + + return client.subscribe( + [ + `databases.${DATABASE_ID}.collections.${ESTIMATION_SESSION_COLLECTION_ID}.documents`, + ], + (payload) => { + setEstimationSessions((estimationSessions) => + estimationSessions + .filter((x) => x.id != payload.payload.$id) + .concat([mapDatabaseToEntity(payload.payload, {})]) + .slice(0, 10), + ); + }, + ); + }, []); + + return ( + + {props.children} + + ); +} diff --git a/src/lib/mappers/estimationSession.ts b/src/lib/mappers/estimationSession.ts new file mode 100644 index 0000000..1270bfc --- /dev/null +++ b/src/lib/mappers/estimationSession.ts @@ -0,0 +1,33 @@ +import { DatabaseModels, EntityModels } from '../types'; + +export const mapDatabaseToEntity = ( + data: DatabaseModels.EstimationSession, + { userId }: { userId?: string }, +) => { + const sessionState: EntityModels.SessionState = data.sessionState + ? JSON.parse(data.sessionState) + : { + votes: [], + }; + + const tickets = data.tickets + ? data.tickets.map((ticket) => + JSON.parse(ticket), + ) + : []; + + const result: EntityModels.EstimationSession = { + id: data.$id, + name: data.name, + userId: data.userId, + tickets, + sessionState: { + ...sessionState, + currentPlayerVote: sessionState.votes.find((x) => x.userId === userId) + ?.estimate, + currentTicket: tickets.find((x) => x.id === sessionState.currentTicketId), + }, + }; + + return result; +}; diff --git a/src/main.tsx b/src/main.tsx index eaa50d4..3e9ddba 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -12,7 +12,7 @@ import { import Home from './pages/Home'; import { UserContextType, UserProvider, useUser } from './lib/context/user'; import Login from './pages/Login'; -import { EstimationSessionProvider } from './lib/context/estimationSession'; +import { EstimationsListContextProvider } from './lib/context/estimationsList'; import { EstimationContextProvider } from './lib/context/estimation'; import Estimation from './pages/Estimation/Estimation'; import Header from './components/Header'; @@ -115,12 +115,12 @@ const InnerApp = () => { createRoot(document.getElementById('root')!).render( - + {/* TODO: Move ctx providers to layout */} - + , ); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index ca43ceb..0d64590 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,7 +1,7 @@ import './Home.css'; import { getRouteApi, Link } from '@tanstack/react-router'; import { useUser } from '../lib/context/user'; -import { useEstimationSessions } from '../lib/context/estimationSession'; +import { useEstimationsList } from '../lib/context/estimationsList'; import { Card, CreateEstimationSessionForm, @@ -15,7 +15,7 @@ const route = getRouteApi('/_authenticated/'); function Home() { const user = useUser(); const navigate = route.useNavigate(); - const estimationSessions = useEstimationSessions(); + const estimationsList = useEstimationsList(); const [isDrawerOpen, setDrawerOpen] = useState(false); return ( @@ -33,16 +33,16 @@ function Home() {

Estimation sessions

( { navigate({ to: '/estimate/session/$sessionId', - params: { sessionId: item.$id }, + params: { sessionId: item.id }, }); }} />