Skip to content

Commit

Permalink
Feat/multiple sessions one window (#225)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas Maupin <[email protected]>
  • Loading branch information
malmen237 and LucasMaupin authored Dec 13, 2024
1 parent 79611dc commit 08e4b92
Show file tree
Hide file tree
Showing 32 changed files with 1,772 additions and 1,571 deletions.
13 changes: 0 additions & 13 deletions aws/cloudfront-functions/README.md

This file was deleted.

39 changes: 0 additions & 39 deletions aws/cloudfront-functions/url-rewrite-spa.yaml

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@emotion/styled": "^11.11.5",
"@eyevinn/media-event-filter": "^3.4.0",
"@hookform/error-message": "^2.0.1",
"@martinstark/storage-ts": "^1.1.0",
"@types/react-router-dom": "^5.3.3",
"bowser": "^2.11.0",
"react": "^18.3.1",
Expand Down
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BrowserRouter, Routes, Route } from "react-router-dom";
import styled from "@emotion/styled";
import { useState } from "react";
import { ProductionLine } from "./components/production-line/production-line.tsx";
import { ErrorPage } from "./components/router-error.tsx";
import { useDevicePermissions } from "./use-device-permission.ts";
import { LandingPage } from "./components/landing-page/landing-page.tsx";
Expand All @@ -19,6 +18,7 @@ import { ManageProductions } from "./components/manage-productions/manage-produc
import { isValidBrowser } from "./bowser.ts";
import { DisplayContainerHeader } from "./components/landing-page/display-container-header.tsx";
import { NavigateToRootButton } from "./components/navigate-to-root-button/navigate-to-root-button.tsx";
import { CallsPage } from "./components/calls-page/calls-page.tsx";

const DisplayBoxPositioningContainer = styled(FlexContainer)`
justify-content: center;
Expand Down Expand Up @@ -126,8 +126,8 @@ const App = () => {
errorElement={<ErrorPage />}
/>
<Route
path="/production/:productionId/line/:lineId"
element={<ProductionLine />}
path="/production-calls/production/:productionId/line/:lineId"
element={<CallsPage />}
errorElement={<ErrorPage />}
/>
<Route path="*" element={<NotFound />} />
Expand Down
5 changes: 5 additions & 0 deletions src/assets/icons/campaign_off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/campaign_on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/components/accessing-local-storage/access-local-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useCallback } from "react";
import { createStorage, StorageType } from "@martinstark/storage-ts";

type Schema = {
username: string;
};

// Create a store of the desired type. If it is not available,
// in-memory storage will be used as a fallback.
const store = createStorage<Schema>({
type: StorageType.LOCAL,
prefix: "id",
silent: true,
});

export function useStorage<Key extends keyof Schema>(key: Key) {
const readFromStorage = useCallback((): Schema[Key] | null => {
return store.read(key);
}, [key]);

const writeToStorage = useCallback(
(value: Schema[Key]): void => {
store.write(key, value);
},
[key]
);

return { readFromStorage, writeToStorage };
}
151 changes: 151 additions & 0 deletions src/components/calls-page/calls-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import styled from "@emotion/styled";
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useGlobalState } from "../../global-state/context-provider";
import { JoinProduction } from "../landing-page/join-production";
import { ProductionLine } from "../production-line/production-line";
import { SecondaryButton } from "../landing-page/form-elements";
import { NavigateToRootButton } from "../navigate-to-root-button/navigate-to-root-button";
import { DisplayContainerHeader } from "../landing-page/display-container-header";
import { Modal } from "../modal/modal";
import { VerifyDecision } from "../verify-decision/verify-decision";
import { ModalConfirmationText } from "../modal/modal-confirmation-text";

const Container = styled.div`
display: flex;
flex-direction: column;
flex-wrap: wrap;
padding: 2rem;
`;

const CallsContainer = styled.div`
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 2rem;
`;

const CallContainer = styled.div`
display: flex;
flex-direction: column;
padding: 2rem;
max-width: 40rem;
min-width: 30rem;
`;

const AddCallContainer = styled.div`
display: flex;
flex-direction: column;
padding: 4rem;
max-width: 40rem;
min-width: 30rem;
`;

const ButtonWrapper = styled.div`
margin: 0 1rem 1rem 0;
:last-of-type {
margin: 0 0 4rem;
}
`;

export const CallsPage = () => {
const [productionId, setProductionId] = useState<string | null>(null);
const [addCallActive, setAddCallActive] = useState(false);
const [confirmExitModalOpen, setConfirmExitModalOpen] = useState(false);
const [{ calls, selectedProductionId }, dispatch] = useGlobalState();
const { productionId: paramProductionId, lineId: paramLineId } = useParams();
const navigate = useNavigate();

const isEmpty = Object.values(calls).length === 0;
const isSingleCall = Object.values(calls).length === 1;

useEffect(() => {
if (selectedProductionId) {
setProductionId(selectedProductionId);
}
}, [selectedProductionId]);

useEffect(() => {
if (isEmpty && !paramProductionId && !paramLineId) {
navigate("/");
}
}, [isEmpty, paramProductionId, paramLineId, navigate]);

const runExitAllCalls = async () => {
setProductionId(null);
navigate("/");
Object.entries(calls).forEach(([callId]) => {
if (callId) {
dispatch({
type: "REMOVE_CALL",
payload: { id: callId },
});
}
});
};

return (
<Container>
<ButtonWrapper>
<NavigateToRootButton
resetOnExitRequest={() => setConfirmExitModalOpen(true)}
/>
{confirmExitModalOpen && (
<Modal onClose={() => setConfirmExitModalOpen(false)}>
<DisplayContainerHeader>Confirm</DisplayContainerHeader>
<ModalConfirmationText>
Are you sure you want to leave all calls?
</ModalConfirmationText>
<VerifyDecision
confirm={runExitAllCalls}
abort={() => setConfirmExitModalOpen(false)}
/>
</Modal>
)}
</ButtonWrapper>
<CallsContainer>
{Object.entries(calls).map(
([callId, callState]) =>
callId &&
callState.joinProductionOptions && (
<CallContainer key={callId}>
<ProductionLine
id={callId}
callState={callState}
isSingleCall={isSingleCall}
/>
</CallContainer>
)
)}
{!isEmpty && (
<AddCallContainer>
<ButtonWrapper>
<SecondaryButton
type="button"
onClick={() => setAddCallActive(!addCallActive)}
>
Add Call
</SecondaryButton>
</ButtonWrapper>
{addCallActive && productionId && (
<JoinProduction
addAdditionalCallId={productionId}
closeAddCallView={() => setAddCallActive(false)}
/>
)}
</AddCallContainer>
)}
{isEmpty && paramProductionId && paramLineId && (
<CallContainer>
<JoinProduction
preSelected={{
preSelectedProductionId: paramProductionId,
preSelectedLineId: paramLineId,
}}
/>
</CallContainer>
)}
</CallsContainer>
</Container>
);
};
75 changes: 58 additions & 17 deletions src/components/error.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from "@emotion/styled";
import { FC } from "react";
import { FC, useEffect, useState } from "react";
import { errorColour } from "../css-helpers/defaults";
import { useGlobalState } from "../global-state/context-provider";

Expand Down Expand Up @@ -29,25 +29,66 @@ const CloseErrorButton = styled.button`
`;

export const ErrorBanner: FC = () => {
const [callError, setCallError] = useState<string[] | null>(null);
const [{ error }, dispatch] = useGlobalState();

useEffect(() => {
const displayedMessages = new Set<string>();
if (error.callErrors) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(error.callErrors).forEach(([_, singleError]) => {
// _ is needed to destructure the object
if (singleError && !displayedMessages.has(singleError.message)) {
console.error(`Error:`, singleError.message); // Display only unique errors
displayedMessages.add(singleError.message);
}
});
const uniqueErrors = Array.from(displayedMessages);
setCallError(uniqueErrors);
}
}, [error.callErrors]);

const resetError = () => {
if (error.callErrors) {
Object.entries(error.callErrors).forEach(([callId]) => {
setCallError(null);
dispatch({
type: "ERROR",
payload: { callId, error: null },
});
});
}
};

return (
error && (
<ErrorDisplay>
{`${error.name}: ${error.message}`}{" "}
<CloseErrorButton
type="button"
onClick={() =>
dispatch({
type: "ERROR",
payload: null,
})
}
>
close
</CloseErrorButton>
</ErrorDisplay>
)
<>
{error.globalError && (
<ErrorDisplay>
{`${error.globalError.name}: ${error.globalError.message}`}{" "}
<CloseErrorButton
type="button"
onClick={() =>
dispatch({
type: "ERROR",
payload: { error: null },
})
}
>
close
</CloseErrorButton>
</ErrorDisplay>
)}
{callError &&
error.callErrors &&
callError.map((uniqueErrors) => (
<ErrorDisplay key={uniqueErrors}>
{uniqueErrors}{" "}
<CloseErrorButton type="button" onClick={() => resetError()}>
close
</CloseErrorButton>
</ErrorDisplay>
))}
</>
);
};

Expand Down
Loading

0 comments on commit 08e4b92

Please sign in to comment.