Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major UI refactor #38

Merged
merged 4 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"clsx": "^2.1.0",
"crypto-js": "^4.2.0",
"jest-environment-jsdom": "^29.7.0",
"lodash": "^4.17.21",
"lucide-react": "^0.352.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -35,6 +36,7 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/crypto-js": "^4.2.2",
"@types/jest": "^29.5.11",
"@types/lodash": "^4.17.0",
"@types/node": "^20.11.24",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
Expand Down
11 changes: 10 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/components/AddDeviceFooter/AddDeviceFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useDevicesLocalStorage } from '@/hooks';
import { Pax } from '@/pax';
import { useState } from 'react';

import { SUPPORTED_DEVICES } from '../DevicesModal/constants';
import { Button } from '../ui/button';
Expand All @@ -13,6 +12,10 @@ const INPUT_PLACEHOLDER = `Insert ${SERIAL_SIZE} digits serial`;
interface Props {
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
serialInput?: string;
setSerialInput: React.Dispatch<React.SetStateAction<string | undefined>>;
deviceValue: Pax.lib.Devices;
setDeviceValue: React.Dispatch<React.SetStateAction<Pax.lib.Devices>>;
}

const buildOptions = (devices: Pax.lib.Devices[]) => {
Expand All @@ -29,10 +32,7 @@ const addNewDeviceButtonDisabled = (serialInput: string | undefined) => {
};

const AddDeviceFooter = (props: Props) => {
const defaultDevice = SUPPORTED_DEVICES[0];
const [serialInput, setSerialInput] = useState<string | undefined>(undefined);
const [deviceValue, setDeviceValue] =
useState<Pax.lib.Devices>(defaultDevice);
const { serialInput, setSerialInput, deviceValue, setDeviceValue } = props;

const deviceStore = useDevicesLocalStorage();

Expand Down
16 changes: 15 additions & 1 deletion src/components/DevicesModal/DevicesModal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { Modal } from '@/components';
import { useDevicesLocalStorage } from '@/hooks';
import { Pax } from '@/pax';
import { TriangleAlert } from 'lucide-react';
import { useState } from 'react';

import AddDeviceFooter from '../AddDeviceFooter';
import DeviceCard from './DeviceCard';
import { SUPPORTED_DEVICES } from './constants';

export interface DevicesModalProps {
open: boolean;
onOpenChange: () => void;
}

const DevicesModal = ({ open, onOpenChange }: DevicesModalProps) => {
const [serialInput, setSerialInput] = useState<string | undefined>(undefined);
const defaultDevice = SUPPORTED_DEVICES[0];
const [deviceValue, setDeviceValue] =
useState<Pax.lib.Devices>(defaultDevice);
const deviceStore = useDevicesLocalStorage();

const renderCards = () => {
Expand All @@ -34,7 +41,14 @@ const DevicesModal = ({ open, onOpenChange }: DevicesModalProps) => {
};

const renderFooter = () => {
return <AddDeviceFooter />;
return (
<AddDeviceFooter
serialInput={serialInput}
setSerialInput={setSerialInput}
deviceValue={deviceValue}
setDeviceValue={setDeviceValue}
/>
);
};

return (
Expand Down
71 changes: 71 additions & 0 deletions src/components/Graphics/PaxPairingSvg/PairingLight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useEffect, useState } from 'react';

interface PairingLightProps {
radius?: string;
color: string;
speed?: 'slow' | 'normal' | 'fast';
}

const PairingLight = (props: PairingLightProps) => {
const { speed = 'normal', color, radius } = props;
const [isBloomed, setIsBloomed] = useState(false);
const getSpeed = (speed: 'fast' | 'normal' | 'slow') => {
switch (speed) {
case 'fast':
return 0.5;
case 'slow':
return 1.5;
default:
return 1;
}
};
const speedInSeconds = getSpeed(speed);

useEffect(() => {
const interval = setInterval(() => {
setIsBloomed(prevState => !prevState);
}, speedInSeconds * 1000);
return () => clearInterval(interval);
}, [speedInSeconds]);

const animationStyle: React.CSSProperties = {
width: radius ? radius : '100px',
height: radius ? radius : '100px',
borderRadius: '50%',
backgroundColor: color,
boxShadow: isBloomed ? `0 0 50px 10px ${color}` : 'none',
transition: `box-shadow ${speedInSeconds}s ease-in-out`,
animation: 'blink 1s step-end infinite',
};

const buildCircle = (x: string, y: string) => {
return (
<div
style={{
...animationStyle,
position: 'absolute',
left: x,
top: y,
transform: 'translate(-50%, -50%)',
}}
></div>
);
};

return (
<div
style={{
position: 'relative',
width: '50px',
height: '50px',
}}
>
{buildCircle('60%', '50%')}
{buildCircle('40%', '50%')}
{buildCircle('50%', '60%')}
{buildCircle('50%', '40%')}
</div>
);
};

export default PairingLight;
45 changes: 45 additions & 0 deletions src/components/Graphics/PaxPairingSvg/PaxAddSerial.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import ParallaxComponent from './ParallaxComponent';
import Pax3DeviceSvg from './Svg/Pax3DeviceSvg';
import './shakingAnimation.css';

interface Props {
parallax?: boolean;
serial?: { serial?: string; device: string };
}

const PaxAddSerial = (props: Props) => {
const svgFillColor = '#666666';

const renderWithParallax = (
multiplier: number,
children: React.ReactNode,
parallax?: boolean,
) => {
if (!parallax) {
return children;
}

return (
<ParallaxComponent multiplier={multiplier}>{children}</ParallaxComponent>
);
};

return renderWithParallax(
7,
<div className="relative flex h-72 items-center justify-center">
<div className="absolute top-14"></div>
<Pax3DeviceSvg fillColor={svgFillColor} showShadow />
<div className="absolute bottom-20">
<h1 className="text-md text-center font-bold opacity-60">
{props.serial?.device}
</h1>
</div>
<div className="absolute bottom-16">
<h1 className="text-center text-[0.6rem]">{props.serial?.serial}</h1>
</div>
</div>,
props.parallax,
);
};

export default PaxAddSerial;
15 changes: 14 additions & 1 deletion src/components/Graphics/PaxPairingSvg/PaxPairing.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import ParallaxComponent from './ParallaxComponent';
import PulsatingLight from './PulsatingLight';
import Pax3DeviceSvg from './Svg/Pax3DeviceSvg';
import './shakingAnimation.css';

interface Props {
parallax?: boolean;
pulsatingLightSpeed?: 'slow' | 'normal' | 'fast';
pairingAnimation?: boolean;
}

const PaxPairing = (props: Props) => {
const { pairingAnimation = false } = props;
const svgFillColor = '#666666';
const lightColor = '#00FFFF';

Expand All @@ -26,7 +29,17 @@ const PaxPairing = (props: Props) => {
};

return (
<div className="relative flex h-72 items-center justify-center">
<div
className="relative flex h-72 items-center justify-center"
style={
pairingAnimation
? {
animation: 'shake ease-in-out 1s alternate 2',
transformOrigin: 'bottom',
}
: {}
}
>
<div className="absolute top-14"></div>
{renderWithParallax(
7,
Expand Down
1 change: 1 addition & 0 deletions src/components/Graphics/PaxPairingSvg/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from './PaxPairing';
export * from './PaxAddSerial';
5 changes: 5 additions & 0 deletions src/components/Graphics/PaxPairingSvg/shakingAnimation.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@keyframes shake {
0%, 100% { transform: rotate(0deg); }
20%, 80% { transform: rotate(-10deg); }
40%, 60% { transform: rotate(10deg); }
}
1 change: 1 addition & 0 deletions src/components/Graphics/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as PaxPairing } from './PaxPairingSvg';
export { default as PaxAddSerial } from './PaxPairingSvg/PaxAddSerial';
4 changes: 3 additions & 1 deletion src/components/MainContent/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const MainContent = () => {
currentDevice: Pax.lib.PaxSerial | undefined,
) => {
return !currentDevice ? (
<NoSelectedDevice />
<div className="mx-3 flex flex-grow justify-center">
<NoSelectedDevice />
</div>
) : (
<SelectedDevice
currentDevice={currentDevice}
Expand Down
30 changes: 23 additions & 7 deletions src/components/MainContent/NoSelectedDevice.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import { useIsMobile } from '@/hooks';
import { Pax } from '@/pax';
import { Terminal } from 'lucide-react';
import { useState } from 'react';

import AddDeviceFooter from '../AddDeviceFooter';
import { PaxPairing } from '../Graphics';
import { SUPPORTED_DEVICES } from '../DevicesModal/constants';
import { PaxAddSerial } from '../Graphics';
import { Alert, AlertDescription, AlertTitle } from '../ui/alert';

export const NoSelectedDevice = () => {
const isMobile = useIsMobile();
const [isInputOnFocus, setIsInputOnFocus] = useState(false);
const [serialInput, setSerialInput] = useState<string | undefined>(undefined);
const defaultDevice = SUPPORTED_DEVICES[0];
const [deviceValue, setDeviceValue] =
useState<Pax.lib.Devices>(defaultDevice);
return (
<div className="mx-auto flex flex-col gap-6 self-center">
<PaxPairing
<div className="flex flex-col gap-6 self-center">
<PaxAddSerial
parallax={!isMobile}
pulsatingLightSpeed={isInputOnFocus ? 'fast' : 'slow'}
serial={{ serial: serialInput, device: deviceValue }}
/>
<AddDeviceFooter
onFocus={() => setIsInputOnFocus(true)}
onBlur={() => setIsInputOnFocus(false)}
serialInput={serialInput}
setSerialInput={setSerialInput}
deviceValue={deviceValue}
setDeviceValue={setDeviceValue}
/>
<Alert>
<Terminal className="h-4 w-4" />
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
Make sure to add the correct serial from the back of your device.
</AlertDescription>
</Alert>
</div>
);
};
Loading