Skip to content

Commit

Permalink
Create demo app UI using React and TS (#63)
Browse files Browse the repository at this point in the history
* Create demo app UI using React and TS
  • Loading branch information
esme authored Feb 14, 2023
1 parent 2e7a7bd commit 7b47430
Show file tree
Hide file tree
Showing 105 changed files with 8,712 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
node_modules
demo/build
karma.conf.js
karma.conf.js
2 changes: 2 additions & 0 deletions demo-v1/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
visitor-ui-component-library
20 changes: 20 additions & 0 deletions demo-v1/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript"
],
"rules": {
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
]
}
}
4 changes: 4 additions & 0 deletions demo-v1/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore artifacts:
dist
node_modules
visitor-ui-component-library
1 change: 1 addition & 0 deletions demo-v1/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
8 changes: 8 additions & 0 deletions demo-v1/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
plugins: ["babel-plugin-styled-components"],
presets: [
"@babel/preset-typescript",
"@babel/preset-react",
"@babel/preset-env",
],
};
37 changes: 37 additions & 0 deletions demo-v1/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "calling-integration-sdk-demo-v1",
"version": "0.1.0",
"description": "HubSpot calling integration sdk test app",
"private": true,
"scripts": {
"build": "webpack",
"start": "npm i && webpack-dev-server --open"
},
"license": "ISC",
"dependencies": {
"@hubspot/calling-extensions-sdk": "^0.0.10",
"react": "^18.2.0",
"react-aria": "^3.22.0",
"react-dom": "^18.2.0",
"styled-components": "^5.3.6"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/styled-components": "^5.1.26",
"babel-loader": "^9.1.2",
"babel-plugin-styled-components": "^2.0.7",
"prettier": "2.8.4",
"prop-types": "^15.8.1",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
},
"resolutions": {
"styled-components": "^5.3.6"
}
}
144 changes: 144 additions & 0 deletions demo-v1/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { useState, useMemo, MouseEventHandler } from "react";
import { ThemeProvider } from "styled-components";
import { createTheme } from "../visitor-ui-component-library/theme/createTheme";
import {
setDisabledBackgroundColor,
setPrimaryColor,
setTextColor,
} from "../visitor-ui-component-library/theme/defaultThemeOperators";
import { setTooltipBackgroundColor } from "../visitor-ui-component-library/tooltip/theme/tooltipThemeOperators";
import LoginScreen from "./screens/LoginScreen";
import KeypadScreen from "./screens/KeypadScreen";
import DialingScreen from "./screens/DialingScreen";
import CallingScreen from "./screens/CallingScreen";
import CallEndedScreen from "./screens/CallEndedScreen";
import { useCti } from "../hooks/useCti";
import { useCallDurationTimer } from "../hooks/useTimer";
import { WHITE } from "../visitor-ui-component-library/theme/ColorConstants";

export interface ScreenProps {
handleNextScreen: Function;
handlePreviousScreen: Function;
handleNavigateToScreen: Function;
cti: any;
phoneNumber: string;
engagementId: string;
dialNumber: string;
setDialNumber: Function;
notes: string;
setNotes: Function;
callDurationString: string;
startTimer: Function;
stopTimer: Function;
handleEndCall: MouseEventHandler<HTMLButtonElement>;
handleSaveCall: MouseEventHandler<HTMLButtonElement>;
}

export enum ScreenNames {
Login,
Keypad,
Dialing,
Calling,
CallEnded,
}

export const screens = [
LoginScreen,
KeypadScreen,
DialingScreen,
CallingScreen,
CallEndedScreen,
];

export const formatTime = (totalSeconds: number) => {
const getTimeStr = (time: number) =>
time < 10 ? `0${time}` : time.toString();
const hour = Math.floor(totalSeconds / 3600);
const minute = Math.floor((totalSeconds - hour * 3600) / 60);
const second = totalSeconds - (hour * 3600 + minute * 60);
return `${getTimeStr(hour)}:${getTimeStr(minute)}:${getTimeStr(second)}`;
};

function App() {
const { cti, phoneNumber, engagementId } = useCti();
const [screenIndex, setScreenIndex] = useState(0);
const [dialNumber, setDialNumber] = useState("+1");
const [notes, setNotes] = useState("");
const { callDurationString, startTimer, stopTimer } = useCallDurationTimer();

const handleNextScreen = () => {
if (screenIndex === screens.length - 1) {
setScreenIndex(1);
return;
}
setScreenIndex(screenIndex + 1);
};

const handleNavigateToScreen = (screenIndex: ScreenNames) => {
setScreenIndex(screenIndex);
};

const handlePreviousScreen = () => {
if (screenIndex !== 0) {
setScreenIndex(screenIndex + 1);
}
};

const resetInputs = () => {
setDialNumber("+1");
setNotes("");
};

const handleEndCall = () => {
stopTimer();
cti.callAnswered();
cti.callEnded();
handleNavigateToScreen(ScreenNames.CallEnded);
};

const handleSaveCall = () => {
resetInputs();
handleNavigateToScreen(ScreenNames.Keypad);
};

const screenComponent = useMemo(() => {
const Component = screens[screenIndex];
if (!Component) {
return <></>;
}
return (
<Component
handleNextScreen={handleNextScreen}
handlePreviousScreen={handlePreviousScreen}
handleNavigateToScreen={handleNavigateToScreen}
cti={cti}
phoneNumber={phoneNumber}
engagementId={engagementId}
dialNumber={dialNumber}
setDialNumber={setDialNumber}
notes={notes}
setNotes={setNotes}
callDurationString={callDurationString}
startTimer={startTimer}
stopTimer={stopTimer}
handleEndCall={handleEndCall}
handleSaveCall={handleSaveCall}
/>
);
}, [screenIndex, dialNumber, notes, callDurationString]);

return (
<ThemeProvider
theme={createTheme(
setPrimaryColor("#05a3bd"),
setTextColor("#516f91"),
setDisabledBackgroundColor("#eaf0f6"),
setTooltipBackgroundColor(WHITE)
)}
>
<div>{screenComponent}</div>
</ThemeProvider>
);
}

export default App;
25 changes: 25 additions & 0 deletions demo-v1/src/components/CallDurationTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useCallback, useEffect, useState } from "react";
import { millisecondsToFormattedDuration } from "../utils/millisecondsToFormattedDuration";

export const CallDurationString = ({
callStartTime,
}: {
callStartTime: number;
}) => {
const [callDurationString, setCallDurationString] = useState(
millisecondsToFormattedDuration(0)
);
const tick = useCallback(() => {
setCallDurationString(
millisecondsToFormattedDuration(Date.now() - callStartTime)
);
}, [callStartTime]);

useEffect(() => {
const timerId = setInterval(tick, 1000);
return () => {
clearInterval(timerId);
};
}, [tick]);
return callDurationString;
};
84 changes: 84 additions & 0 deletions demo-v1/src/components/Components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import styled from "styled-components";
import VizExButton from "../visitor-ui-component-library/button/VizExButton";
// @ts-expect-error module not typed
import VizExInput from "../visitor-ui-component-library/input/VizExInput";
import VizExLink from "../visitor-ui-component-library/link/VizExLink";
// @ts-expect-error module not typed
import VizExTooltip from "../visitor-ui-component-library/tooltip/VizExTooltip";
import { setPrimaryColor } from "../visitor-ui-component-library/theme/defaultThemeOperators";
import { DEFAULT_INPUT_BORDER_COLOR } from "../visitor-ui-component-library/theme/ColorConstants";
import { createTheme } from "../visitor-ui-component-library/theme/createTheme";

/**
* This file has a dependency on visitor-ui-component-library. Do not directly edit files in the library!
*/

export const TextArea = styled.textarea`
border: 1px solid ${DEFAULT_INPUT_BORDER_COLOR};
`;

export const Wrapper = styled.div`
margin: 10px 40px;
`;

export const Input = styled(VizExInput).attrs({
backgroundColor: "white",
})``;

export const KeypadInput = styled(VizExInput).attrs({
containerStyles: { width: "225px" },
})``;

export const RoundedInput = styled(VizExInput).attrs({
containerStyles: { borderRadius: "25px", marginBottom: "10px" },
})``;

export const Button = styled(VizExButton).attrs((props) => ({
disabled: props.disabled,
}))``;

export const RoundedButton = styled(Button).attrs((props) => ({
disabled: props.disabled,
}))`
border-radius: 25px;
min-width: 150px;
`;

export const EndCallButton = styled(RoundedButton).attrs({
theme: createTheme(setPrimaryColor("#d94c53")),
})``;

export const Row = styled.div`
display: flex;
justify-content: space-evenly;
`;

export const Link = styled(VizExLink)``;

export const Key = styled(VizExButton).attrs({
use: "transparent-on-primary",
theme: createTheme(setPrimaryColor("#516f90")),
})`
width: 65px;
height: 65px;
text-align: center;
font-size: 24px;
margin: 0 0 10px 0;
border: none;
`;

export const CallActionButton = styled(VizExButton)`
width: 40px;
height: 40px;
line-height: 40px;
padding: 0;
border-radius: 50%;
`;

export const Timer = styled.div`
margin-bottom: 20px;
`;

export const Tooltip = styled(VizExTooltip).attrs({
placement: "bottom right",
})``;
55 changes: 55 additions & 0 deletions demo-v1/src/components/Keypad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Key, Row } from "./Components";

export function Keypad({ addToDialNumber }: { addToDialNumber: Function }) {
return (
<div>
<Row>
<Key onClick={() => addToDialNumber("1")}>1</Key>
<Key onClick={() => addToDialNumber("2")}>2</Key>
<Key onClick={() => addToDialNumber("3")}>3</Key>
</Row>
<Row>
<Key onClick={() => addToDialNumber("4")}>4</Key>
<Key onClick={() => addToDialNumber("5")}>5</Key>
<Key onClick={() => addToDialNumber("6")}>6</Key>
</Row>
<Row>
<Key onClick={() => addToDialNumber("7")}>7</Key>
<Key onClick={() => addToDialNumber("8")}>8</Key>
<Key onClick={() => addToDialNumber("9")}>9</Key>
</Row>
<Row>
<Key onClick={() => addToDialNumber("*")}>*</Key>
<Key onClick={() => addToDialNumber("0")}>0</Key>
<Key onClick={() => addToDialNumber("#")}>#</Key>
</Row>
</div>
);
}

export function KeypadPopover() {
return (
<div>
<Row>
<Key>1</Key>
<Key>2</Key>
<Key>3</Key>
</Row>
<Row>
<Key>4</Key>
<Key>5</Key>
<Key>6</Key>
</Row>
<Row>
<Key>7</Key>
<Key>8</Key>
<Key>9</Key>
</Row>
<Row>
<Key>*</Key>
<Key>0</Key>
<Key>#</Key>
</Row>
</div>
);
}
Loading

0 comments on commit 7b47430

Please sign in to comment.