diff --git a/dotenv.js b/dotenv.js index c1a9818..91d7e7e 100644 --- a/dotenv.js +++ b/dotenv.js @@ -1,5 +1,3 @@ - - const dotenv = require('dotenv') const { ENABLE_FRONTEND_TUNNEL, ENABLE_BACKEND_TUNNEL, STAGE } = process.env diff --git a/package.json b/package.json index aac712c..c01e0ff 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "dynamodb-admin": "^5.1.3", "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "husky": "9.1.7", "lerna": "^7.4.2", diff --git a/packages/backend/src/services/user.service.ts b/packages/backend/src/services/user.service.ts index a66e7f7..19b8a62 100644 --- a/packages/backend/src/services/user.service.ts +++ b/packages/backend/src/services/user.service.ts @@ -11,6 +11,15 @@ const getAvatarURL = (emailHash: string, size?: number): string => { return size ? `${AVATAR_URL}${emailHash}?s=${size}` : `${AVATAR_URL}${emailHash}` } +async function hashEmail(email: string) { + const encoder = new TextEncoder() + const data = encoder.encode(email.toLowerCase().trim()) // normalize + const hashBuffer = await crypto.subtle.digest('SHA-256', data) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') + return hashHex +} + /** * Creates the username from the first and lastname * @param user User to generate username @@ -24,16 +33,17 @@ export const username = (user: Pick): string => * @param user User to get avatar from * @returns URL of the avatar */ -export const avatar = (user: Pick): string => { +export const avatar = async (user: Pick): Promise => { if (isDebug() || !AVATAR_URL) { - return `https://avatars.dicebear.com/v2/human/${user.email}.svg` + console.log(hashEmail(user.email)) + return `https://api.dicebear.com/9.x/identicon/svg?seed=${await hashEmail(user.email)}.` } const email = OLD_COMPANY_NAME && NEW_COMPANY_NAME ? user.email.toLowerCase().replace(OLD_COMPANY_NAME, NEW_COMPANY_NAME) : user.email.toLowerCase() - const emailHash = crypto.createHash('md5').update(email).digest('hex').toLowerCase() + const emailHash = await hashEmail(email) return getAvatarURL(emailHash, 300) } diff --git a/packages/components/src/accordion.ts b/packages/components/src/accordion.ts index 2f7afb2..3c83576 100644 --- a/packages/components/src/accordion.ts +++ b/packages/components/src/accordion.ts @@ -28,7 +28,9 @@ export const StyledAccordionHeader = styled.div` align-items: center; ` -export const StyledAccordionTitle = styled(Paragraph)` +export const StyledAccordionTitle = styled(Paragraph).withConfig({ + shouldForwardProp: (prop) => !['active'].includes(prop), +})` font-size: ${FontSizes.copy}; transition: font-weight 0.1s; user-select: none; @@ -44,7 +46,9 @@ export const StyledAccordionTitle = styled(Paragraph)` `} ` -export const StyledAccordionIcon = styled(StyledIcon)` +export const StyledAccordionIcon = styled(StyledIcon).withConfig({ + shouldForwardProp: (prop) => !['active'].includes(prop), +})` transform: rotate(${(props) => (props.active ? '180deg' : '0deg')}); transition: all 0.2s; ` diff --git a/packages/components/src/box.tsx b/packages/components/src/box.tsx new file mode 100644 index 0000000..b047e13 --- /dev/null +++ b/packages/components/src/box.tsx @@ -0,0 +1,8 @@ +import { Box as BaseBox } from '@rebass/grid' +import { styled } from 'styled-components' + +const ignoredProps = new Set(['mt', 'mb', 'ml', 'mr', 'mx', 'my', 'pt', 'pr', 'pl', 'pb', 'px', 'py']) + +export const Box = styled(BaseBox).withConfig({ + shouldForwardProp: (prop) => !ignoredProps.has(prop), +})`` diff --git a/packages/components/src/button-layout.tsx b/packages/components/src/button-layout.tsx index ea7371c..0d605c8 100644 --- a/packages/components/src/button-layout.tsx +++ b/packages/components/src/button-layout.tsx @@ -174,7 +174,9 @@ const buttonStyle = (props: ButtonLayoutProps) => css` ${props.fullsize && 'width: 100%'}; ` -const ButtonWrapper = styled.button` +const ButtonWrapper = styled.button.withConfig({ + shouldForwardProp: (prop) => !['styling', 'fullsize'].includes(prop), +})` font-size: ${FontSizes.button}; border: none; letter-spacing: 0.2px; @@ -223,7 +225,9 @@ const IconWrapper = styled.div` justify-content: center; ` -const TextWrapper = styled.div<{ hasIcon: boolean }>` +const TextWrapper = styled.div.withConfig({ + shouldForwardProp: (prop) => !['hasIcon'].includes(prop), +})<{ hasIcon: boolean }>` display: flex; align-items: center; justify-content: center; diff --git a/packages/components/src/container.ts b/packages/components/src/container.ts index 54be4b9..d9ebbfe 100644 --- a/packages/components/src/container.ts +++ b/packages/components/src/container.ts @@ -11,7 +11,9 @@ export interface ContainerProps { overflow?: 'hidden' | 'scroll' | 'visible' } -export const Container = styled.div` +export const Container = styled.div.withConfig({ + shouldForwardProp: (prop) => !['padding', 'paddingX', 'paddingY', 'hoverable', 'flat', 'overflow'].includes(prop), +})` display: block; overflow: ${(props) => props.overflow || 'hidden'}; background: ${(props) => props.theme.surface}; diff --git a/packages/components/src/day-input-layout.tsx b/packages/components/src/day-input-layout.tsx index 426b749..69be1bc 100644 --- a/packages/components/src/day-input-layout.tsx +++ b/packages/components/src/day-input-layout.tsx @@ -3,7 +3,9 @@ import styled from 'styled-components' import { FontSizes } from './font-size' import { Spacings } from './spacing' -const DayInputContainer = styled.div<{ showEntriesInput: boolean }>` +const DayInputContainer = styled.div.withConfig({ + shouldForwardProp: (prop) => !['showEntriesInput'].includes(prop), +})<{ showEntriesInput: boolean }>` padding-bottom: ${(props) => (props.showEntriesInput ? Spacings.xs : Spacings.l)}; padding-top: ${Spacings.l}; ` @@ -21,7 +23,9 @@ const DayStatusContainer = styled.div` justify-content: space-between; ` -const StatusContainer = styled.div<{ visible: boolean }>` +const StatusContainer = styled.div.withConfig({ + shouldForwardProp: (prop) => !['visible'].includes(prop), +})<{ visible: boolean }>` display: flex; align-items: center; transition: opacity 0.5s ease; @@ -33,7 +37,9 @@ const TotalContainer = styled.div` text-align: right; ` -export const StyledStatusLabel = styled.span<{ error: boolean }>` +export const StyledStatusLabel = styled.span.withConfig({ + shouldForwardProp: (prop) => !['error'].includes(prop), +})<{ error: boolean }>` font-size: ${FontSizes.label}; letter-spacing: 1.2px; color: ${(props) => (props.error ? props.theme.errorRed : props.theme.mediumFont)}; diff --git a/packages/components/src/dropdown-layout.tsx b/packages/components/src/dropdown-layout.tsx index c9ca7e3..2da5dd7 100644 --- a/packages/components/src/dropdown-layout.tsx +++ b/packages/components/src/dropdown-layout.tsx @@ -6,7 +6,9 @@ import { BorderRadii } from './border-radius' import { FontSizes } from './font-size' import { Spacings } from './spacing' -const DropdownContainer = styled.div<{ active?: boolean }>` +const DropdownContainer = styled.div.withConfig({ + shouldForwardProp: (prop) => !['active'].includes(prop), +})<{ active?: boolean }>` display: ${(props) => (props.active ? 'grid' : 'none')}; z-index: 6; position: fixed; diff --git a/packages/components/src/entry-input-layout.tsx b/packages/components/src/entry-input-layout.tsx index e4b9564..71a502a 100644 --- a/packages/components/src/entry-input-layout.tsx +++ b/packages/components/src/entry-input-layout.tsx @@ -17,7 +17,9 @@ interface StyledActionInterface { noMargin?: boolean } -export const StyledAction = styled.button` +export const StyledAction = styled.button.withConfig({ + shouldForwardProp: (prop) => !['noMargin', 'danger'].includes(prop), +})` display: flex; align-items: center; justify-content: center; @@ -71,7 +73,10 @@ interface EntryContainerProps { dragFromTopToBottom?: boolean } -export const StyledEntryContainer = styled.div` +export const StyledEntryContainer = styled.div.withConfig({ + shouldForwardProp: (prop) => + !['clickable', 'disabled', 'isDragged', 'isDraggedOver', 'dragFromTopToBottom'].includes(prop), +})` position: relative; border-right: 6px solid transparent; border-left: 6px solid transparent; diff --git a/packages/components/src/flex.tsx b/packages/components/src/flex.tsx new file mode 100644 index 0000000..031b4f4 --- /dev/null +++ b/packages/components/src/flex.tsx @@ -0,0 +1,25 @@ +import { Flex as BaseFlex } from '@rebass/grid' +import { styled } from 'styled-components' + +const ignoredProps = new Set([ + 'alignItems', + 'flexDirection', + 'justifyContent', + 'flexWrap', + 'mt', + 'mb', + 'ml', + 'mr', + 'mx', + 'my', + 'pt', + 'pr', + 'pl', + 'pb', + 'px', + 'py', +]) + +export const Flex = styled(BaseFlex).withConfig({ + shouldForwardProp: (prop) => !ignoredProps.has(prop), +})`` diff --git a/packages/components/src/heading.ts b/packages/components/src/heading.ts index 3152377..e8b08a1 100644 --- a/packages/components/src/heading.ts +++ b/packages/components/src/heading.ts @@ -16,19 +16,25 @@ const BaseHeader = styled.h1` user-select: none; ` -export const H1 = styled(BaseHeader)` +export const H1 = styled(BaseHeader).withConfig({ + shouldForwardProp: (prop) => !['center', 'noMargin'].includes(prop), +})` font-size: ${FontSizes.h1}; letter-spacing: 0.9px; ${(props) => props.noMargin && 'margin: 0;'}; ` -export const H2 = styled.h2` +export const H2 = styled.h2.withConfig({ + shouldForwardProp: (prop) => !['center', 'noMargin'].includes(prop), +})` font-size: ${FontSizes.h2}; letter-spacing: 0.9px; ${(props) => props.noMargin && 'margin: 0;'}; ` -export const Title = styled(BaseHeader)` +export const Title = styled(BaseHeader).withConfig({ + shouldForwardProp: (prop) => !['noMargin'].includes(prop), +})` font-size: ${FontSizes.copy}; ${(props) => props.noMargin && 'margin: 0;'}; font-family: ${Fonts.secondary}; diff --git a/packages/components/src/icons.tsx b/packages/components/src/icons.tsx index f9ec4ad..aac7cba 100644 --- a/packages/components/src/icons.tsx +++ b/packages/components/src/icons.tsx @@ -33,7 +33,9 @@ const findSpacing = (...args: (SpacingKey | undefined)[]) => { return Spacings[spacing] } -const IconContainer = styled.i` +const IconContainer = styled.i.withConfig({ + shouldForwardProp: (prop) => !['marginLeft', 'marginRight', 'marginTop', 'marginBottom'].includes(prop), +})` width: ${(props) => props.width || props.size || '100%'}; height: ${(props) => props.height || props.size || 'auto'}; display: block; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 1ce8775..bc5d4eb 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -71,4 +71,6 @@ export * from './settings' export * from './alexa-settings' export * from './splash-page' export * from './faq' +export * from './flex' +export * from './box' export { DefaultTheme, ThemeProvider } from 'styled-components' diff --git a/packages/components/src/loader.ts b/packages/components/src/loader.ts index 9a45b1c..8da0b0b 100644 --- a/packages/components/src/loader.ts +++ b/packages/components/src/loader.ts @@ -11,7 +11,9 @@ export enum LoaderSize { Big = '50px', } -export const StyledLoaderContainer = styled.div` +export const StyledLoaderContainer = styled.div.withConfig({ + shouldForwardProp: (prop) => !['padding'].includes(prop), +})` display: flex; flex: 1; justify-content: center; diff --git a/packages/components/src/modal-layout.tsx b/packages/components/src/modal-layout.tsx index d3a1e5b..79acbce 100644 --- a/packages/components/src/modal-layout.tsx +++ b/packages/components/src/modal-layout.tsx @@ -19,7 +19,9 @@ interface ModalLayoutProps extends ModelStylingProps { children: ReactNode } -const ModalOverlay = styled.div` +const ModalOverlay = styled.div.withConfig({ + shouldForwardProp: (prop) => !['show'].includes(prop), +})` position: fixed; display: ${(props) => (props.show ? 'flex' : 'none')}; align-items: center; @@ -33,7 +35,9 @@ const ModalOverlay = styled.div` padding: 0 ${Spacings.m}; ` -const ModalContainer = styled(Container)` +const ModalContainer = styled(Container).withConfig({ + shouldForwardProp: (prop) => !['large'].includes(prop), +})` display: flex; flex-direction: column; min-width: 300px; diff --git a/packages/components/src/navigation-button-link.tsx b/packages/components/src/navigation-button-link.tsx index f4e6e79..94e23bc 100644 --- a/packages/components/src/navigation-button-link.tsx +++ b/packages/components/src/navigation-button-link.tsx @@ -11,7 +11,11 @@ export interface StyledLinkProps extends LinkProps { // isLeft props needs to be removed from LinkProps -const StyledLink = styled(Link).attrs(({ isLeft: _isLeft }) => ({}))` +const StyledLink = styled(Link) + .withConfig({ + shouldForwardProp: (prop) => !['isLeft'].includes(prop), + }) + .attrs(({ isLeft: _isLeft }) => ({}))` display: flex; align-items: center; justify-content: center; diff --git a/packages/components/src/navigation.tsx b/packages/components/src/navigation.tsx index 35c05dd..3faf5bd 100644 --- a/packages/components/src/navigation.tsx +++ b/packages/components/src/navigation.tsx @@ -1,12 +1,14 @@ import { Link, NavLink, NavLinkProps } from 'react-router' import styled, { css } from 'styled-components' -import { Flex } from '@rebass/grid' +import { Flex } from './flex' import { FontSizes } from './font-size' import { Spacings } from './spacing' -export const StyledNavWrapper = styled(Flex)` +export const StyledNavWrapper = styled(Flex).withConfig({ + shouldForwardProp: (prop) => !['flexWrap', 'justifyContent'].includes(prop), +})` display: flex; align-items: center; padding: 0 ${Spacings.l}; @@ -68,7 +70,7 @@ export const StyledNavItem = styled(({ isMobile: _isMobile, ...rest }: NavItemPr ${(props) => !props.isMobile && ` - &:hover { + &:hover, &.active { border-bottom: ${Spacings.xxs} solid ${props.theme.iconWhite}; } `} @@ -76,10 +78,13 @@ export const StyledNavItem = styled(({ isMobile: _isMobile, ...rest }: NavItemPr props.isMobile && ` color: ${props.theme.darkFont}; + &:hover { + opacity: 1; + } + &.active { + border-bottom: ${Spacings.xxs} solid ${props.theme.iconDarkGrey}; + } `} - &.active { - border-bottom: ${Spacings.xxs} solid ${(props) => props.theme.iconWhite}; - } ${(props) => props.isMobile && StyledMobileNavItemStyle} ` @@ -88,14 +93,15 @@ export const StyledLogoutItem = styled.div` display: flex; align-items: center; text-decoration: none; - color: ${(props) => props.theme.headerFont}; + color: ${(props) => props.theme.darkFont}; font-size: ${FontSizes.copy}; transition: 0.1s all; - opacity: 0.8; ${StyledMobileNavItemStyle} - :hover { + &:hover { cursor: pointer; + opacity: 1; + color: ${(props) => props.theme.buttonSecondaryDangerHovered}; } ` @@ -137,7 +143,7 @@ export const StyledLink = styled.div` export const StyledMobileNavWrapper = styled.div` width: 100vw; - background-color: ${(props) => props.theme.buttonPrimaryFont}; + background: ${(props) => props.theme.background}; position: fixed; z-index: 5; display: flex; diff --git a/packages/components/src/new-input.ts b/packages/components/src/new-input.ts index 787085d..b7ec339 100644 --- a/packages/components/src/new-input.ts +++ b/packages/components/src/new-input.ts @@ -8,7 +8,9 @@ export interface InputProps { block?: boolean } -export const Input = styled.input` +export const Input = styled.input.withConfig({ + shouldForwardProp: (prop) => !['block', 'error'].includes(prop), +})` width: ${(props) => props.block && '100%'}; background: ${(props) => props.theme.inputBackground}; padding: ${Spacings.m}; diff --git a/packages/components/src/paragraph.ts b/packages/components/src/paragraph.ts index d357470..2411c6a 100644 --- a/packages/components/src/paragraph.ts +++ b/packages/components/src/paragraph.ts @@ -13,7 +13,9 @@ export interface ParagraphProps { margin?: string } -export const Paragraph = styled.p` +export const Paragraph = styled.p.withConfig({ + shouldForwardProp: (prop) => !['center', 'noMargin'].includes(prop), +})` line-height: 1.4; color: ${(props) => (props.color ? props.theme[props.color] : props.theme.lightFont)}; font-family: ${(props) => props.font || Fonts.primary}; diff --git a/packages/components/src/progess-bar.ts b/packages/components/src/progess-bar.ts index 73ab809..70f4ebc 100644 --- a/packages/components/src/progess-bar.ts +++ b/packages/components/src/progess-bar.ts @@ -15,7 +15,9 @@ const Bar = styled.div` border-radius: ${BorderRadii.xxxs}; ` -export const StyledProgressBarIndicator = styled(Bar)` +export const StyledProgressBarIndicator = styled(Bar).withConfig({ + shouldForwardProp: (prop) => !['progress'].includes(prop), +})` width: ${(props) => (props.progress * 100).toString() + '%'}; transition: width 0.5s; height: 100%; diff --git a/packages/components/src/select.tsx b/packages/components/src/select.tsx index 2fdf839..c271a6e 100644 --- a/packages/components/src/select.tsx +++ b/packages/components/src/select.tsx @@ -39,7 +39,9 @@ export const StyledSelect = styled.select` -webkit-appearance: none; -moz-appearance: none; ` -export const StyledSelectWithIconContainer = styled.div` +export const StyledSelectWithIconContainer = styled.div.withConfig({ + shouldForwardProp: (prop) => !['hasLabel'].includes(prop), +})` display: flex; align-items: center; border-radius: ${BorderRadii.xxs}; diff --git a/packages/components/src/signature-settings.ts b/packages/components/src/signature-settings.ts index 4c4f110..7a1391b 100644 --- a/packages/components/src/signature-settings.ts +++ b/packages/components/src/signature-settings.ts @@ -1,6 +1,8 @@ import styled from 'styled-components' -import { Box, Flex } from '@rebass/grid' +import { Box } from './box' + +import { Flex } from './flex' import { BorderRadii } from './border-radius' import { FontSizes } from './font-size' @@ -50,7 +52,9 @@ export const StyledSignatureLine = styled.div` width: 100%; border-top: 3px dashed ${(props) => props.theme.buttonPrimaryFont}; ` -export const StyledNoCameraAccessWarning = styled.span<{ visible: boolean }>` +export const StyledNoCameraAccessWarning = styled.span.withConfig({ + shouldForwardProp: (prop) => !['visible'].includes(prop), +})<{ visible: boolean }>` font-size: ${FontSizes.copy}; margin-top: ${Spacings.xs}; margin-bottom: ${Spacings.m}; diff --git a/packages/components/src/spacer.ts b/packages/components/src/spacer.ts index 9ea29f8..738f8e2 100644 --- a/packages/components/src/spacer.ts +++ b/packages/components/src/spacer.ts @@ -23,7 +23,9 @@ const findSpacing = (...args: (SpacingKey | undefined)[]) => { return Spacings[spacing] } -export const Spacer = styled.div` +export const Spacer = styled.div.withConfig({ + shouldForwardProp: (prop) => !['top', 'bottom', 'left', 'right', 'x', 'y', 'xy'].includes(prop), +})` padding-top: ${(props) => findSpacing(props.top, props.y, props.xy)}; padding-bottom: ${(props) => findSpacing(props.bottom, props.y, props.xy)}; padding-left: ${(props) => findSpacing(props.left, props.x, props.xy)}; diff --git a/packages/components/src/status-bar.ts b/packages/components/src/status-bar.ts index a153d8e..7227da4 100644 --- a/packages/components/src/status-bar.ts +++ b/packages/components/src/status-bar.ts @@ -10,6 +10,7 @@ export const StyledStatusBar = styled.div<{ open: boolean }>` font-weight: bold; .status-bar-div { + margin-top: 8px; display: ${({ open }) => (open ? 'block' : 'none')}; } ` diff --git a/packages/components/src/suggestions.ts b/packages/components/src/suggestions.ts index 08fddb0..ad4fbf7 100644 --- a/packages/components/src/suggestions.ts +++ b/packages/components/src/suggestions.ts @@ -12,7 +12,9 @@ export const StyledSuggestionWrapper = styled.div` width: 100%; ` -export const StyledSuggestionList = styled.div` +export const StyledSuggestionList = styled.div.withConfig({ + shouldForwardProp: (prop) => !['active'].includes(prop), +})` width: 100%; position: absolute; z-index: 2; diff --git a/packages/components/src/text-input.ts b/packages/components/src/text-input.ts index 20b8126..caac740 100644 --- a/packages/components/src/text-input.ts +++ b/packages/components/src/text-input.ts @@ -8,7 +8,9 @@ interface InputLabelProps { valid: boolean } -export const StyledTextInputLabel = styled.label` +export const StyledTextInputLabel = styled.label.withConfig({ + shouldForwardProp: (prop) => !['valid'].includes(prop), +})` color: ${(props) => (props.valid ? props.theme.darkFont : props.theme.errorRed)}; font-size: ${FontSizes.label}; letter-spacing: 1.2px; @@ -35,7 +37,9 @@ interface InputContainerProps { floating: boolean } -export const StyledTextInput = styled.input` +export const StyledTextInput = styled.input.withConfig({ + shouldForwardProp: (prop) => !['valid', 'floating'].includes(prop), +})` box-sizing: border-box; width: 100%; margin-top: ${Spacings.s}; diff --git a/packages/components/src/text.ts b/packages/components/src/text.ts index 9f038d7..2a47dec 100644 --- a/packages/components/src/text.ts +++ b/packages/components/src/text.ts @@ -16,7 +16,9 @@ export type TextProps = { noLineHeight?: boolean } -export const Text = styled.span` +export const Text = styled.span.withConfig({ + shouldForwardProp: (prop) => !['noLineHeight'].includes(prop), +})` font-size: ${(props) => (props.size ? FontSizes[props.size] : FontSizes.label)}; font-weight: ${(props) => props.weight}; font-family: ${(props) => props.font && Fonts[props.font]}; diff --git a/packages/components/src/toast.ts b/packages/components/src/toast.ts index d2d16dd..61522dd 100644 --- a/packages/components/src/toast.ts +++ b/packages/components/src/toast.ts @@ -37,7 +37,9 @@ export const StyledToastHeader = styled.div` justify-content: space-between; ` -export const StyledIconWrapper = styled.div` +export const StyledIconWrapper = styled.div.withConfig({ + shouldForwardProp: (prop) => !['background'].includes(prop), +})` display: flex; align-items: center; align-self: center; diff --git a/packages/components/src/total.tsx b/packages/components/src/total.tsx index 841437f..52d2995 100644 --- a/packages/components/src/total.tsx +++ b/packages/components/src/total.tsx @@ -4,14 +4,18 @@ import styled from 'styled-components' import { FontSizes } from './font-size' import { Spacings } from './spacing' -const TotalLabel = styled.span` +const TotalLabel = styled.span.withConfig({ + shouldForwardProp: (prop) => !['primary'].includes(prop), +})` color: ${(props) => props.theme.blueFont}; font-weight: ${(props) => props.primary && 500}; font-size: ${(props) => (props.primary ? FontSizes.copy : FontSizes.copy)}; margin-right: ${Spacings.m}; ` -const TotalValue = styled.span` +const TotalValue = styled.span.withConfig({ + shouldForwardProp: (prop) => !['primary'].includes(prop), +})` font-weight: bold; letter-spacing: 0.3px; font-size: ${(props) => (props.primary ? FontSizes.copy : FontSizes.copy)}; diff --git a/packages/components/src/trainee-overview-layout.tsx b/packages/components/src/trainee-overview-layout.tsx index 2a4e55a..fec0cbe 100644 --- a/packages/components/src/trainee-overview-layout.tsx +++ b/packages/components/src/trainee-overview-layout.tsx @@ -1,7 +1,7 @@ import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' -import { Flex } from '@rebass/grid' +import { Flex } from './flex' import { BorderRadii } from './border-radius' import { FontSizes } from './font-size' diff --git a/packages/components/src/trainee-row.ts b/packages/components/src/trainee-row.ts index a6f7b1b..257588f 100644 --- a/packages/components/src/trainee-row.ts +++ b/packages/components/src/trainee-row.ts @@ -25,7 +25,9 @@ export const StyledName = styled.span` margin-left: ${Spacings.m}; ` -export const StyledIndicatorIcon = styled(StyledIcon)` +export const StyledIndicatorIcon = styled(StyledIcon).withConfig({ + shouldForwardProp: (prop) => !['active'].includes(prop), +})` margin-right: ${Spacings.m}; transform: rotate(${(props) => (props.active ? '180deg' : '0deg')}); transition: all 0.2s; @@ -45,7 +47,7 @@ export const StyledWrapper = styled.div` } ` -export const StyledControls = styled.div` +export const StyledControls = styled.div` display: flex; align-items: center; background-color: ${(props) => props.theme.surface}; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8154e5c..292d1dd 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -20,7 +20,6 @@ "@lara/components": "^1.0.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@rebass/grid": "^6.1.0", - "@types/jest": "^30.0.0", "apollo-link-token-refresh": "0.7.0", "date-fns": "4.1.0", "file-saver": "^2.0.2", @@ -44,7 +43,7 @@ "@graphql-codegen/typescript-react-apollo": "^4.3.3", "@types/file-saver": "^2.0.1", "@types/graphql": "^14.5.0", - "@types/jest": "^29.1.2", + "@types/jest": "^30.0.0", "@types/node": "^22.9.0", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", diff --git a/packages/frontend/src/components/navigation.tsx b/packages/frontend/src/components/navigation.tsx index e768dde..50a223e 100644 --- a/packages/frontend/src/components/navigation.tsx +++ b/packages/frontend/src/components/navigation.tsx @@ -1,5 +1,4 @@ -import React, { useState } from 'react' - +import React, { useState, useRef, useEffect } from 'react' import { StyledAvatarMenuItem, StyledAvatarText, @@ -20,104 +19,101 @@ import strings from '../locales/localization' import Avatar from './avatar' import Dropdown from './dropdown' -const Navigation: React.FunctionComponent = () => { +const Navigation: React.FC = () => { const { data, loading } = useNavigationDataQuery() - const { logout } = useAuthentication() const [showOverlay, setShowOverlay] = useState(false) const [showDropdown, setShowDropdown] = useState(false) const [isMobile, setIsMobile] = useState(window.innerWidth <= 550) - React.useEffect(() => { - const updateDimensions = () => { - setIsMobile(window.innerWidth <= 550) - } - window.addEventListener('resize', updateDimensions) + const dropdownRef = useRef(null) + // Handle resizing + useEffect(() => { + const updateDimensions = () => setIsMobile(window.innerWidth <= 550) + window.addEventListener('resize', updateDimensions) return () => window.removeEventListener('resize', updateDimensions) - }, [setIsMobile]) + }, []) + + // Close dropdown if click outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setShowDropdown(false) + } + } - const toggleMenu = () => { - setShowOverlay(!showOverlay) - } + if (showDropdown) { + document.addEventListener('mousedown', handleClickOutside) + } else { + document.removeEventListener('mousedown', handleClickOutside) + } - const hideDropdown = React.useCallback(() => { - setShowDropdown(!showDropdown) - document.removeEventListener('click', hideDropdown) + return () => document.removeEventListener('mousedown', handleClickOutside) }, [showDropdown]) - React.useEffect(() => { - if (showDropdown) { - document.addEventListener('click', hideDropdown) - } - }, [showDropdown, hideDropdown]) - - const renderTraineeNav = () => { - return ( - <> - - {strings.navigation.dashhboard} - - - {strings.navigation.archive} - - - {strings.navigation.settings} - - - ) - } - - const renderTrainerNav = () => { - return ( - <> - - {strings.navigation.reports} - - - {strings.navigation.trainees} - - - {strings.navigation.settings} - - - ) - } - - const renderAdminNav = () => { - return ( - <> - - {strings.navigation.trainees} - - - {strings.navigation.trainer} - - - {strings.navigation.settings} - - - ) - } - - const renderGeneralMobileNav = () => { - return ( - <> - - {strings.dropdown.help} - - logout()}>{strings.dropdown.logout} - - ) - } + const toggleMenu = () => setShowOverlay((prev) => !prev) + + // Render functions + const renderTraineeNav = () => ( + <> + + {strings.navigation.dashhboard} + + + {strings.navigation.archive} + + + {strings.navigation.settings} + + + ) + + const renderTrainerNav = () => ( + <> + + {strings.navigation.reports} + + + {strings.navigation.trainees} + + + {strings.navigation.settings} + + + ) + + const renderAdminNav = () => ( + <> + + {strings.navigation.trainees} + + + {strings.navigation.trainer} + + + {strings.navigation.settings} + + + ) + + const renderGeneralMobileNav = () => ( + <> + + {strings.dropdown.help} + + {strings.dropdown.logout} + + ) return ( <> - - + + + {!loading && data && ( <> {!isMobile && ( @@ -127,19 +123,30 @@ const Navigation: React.FunctionComponent = () => { {data.currentUser?.type === UserTypeEnum.Admin && renderAdminNav()} )} + {isMobile ? ( - + ) : ( - setShowDropdown(true)}> - {data.currentUser?.firstName + ' ' + data.currentUser?.lastName} - - +
+ { + e.stopPropagation() + setShowDropdown((prev) => !prev) + }} + > + {data.currentUser?.firstName + ' ' + data.currentUser?.lastName} + + + + +
)} )}
+ {!loading && data && showOverlay && isMobile && ( {data.currentUser?.type === UserTypeEnum.Trainer && renderTrainerNav()} @@ -148,8 +155,6 @@ const Navigation: React.FunctionComponent = () => { {renderGeneralMobileNav()} )} - - ) } diff --git a/packages/frontend/src/components/report-page-footer.tsx b/packages/frontend/src/components/report-page-footer.tsx index 68e1d49..3784871 100644 --- a/packages/frontend/src/components/report-page-footer.tsx +++ b/packages/frontend/src/components/report-page-footer.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { H2, Spacer, StyledTopBorderWrapper } from '@lara/components' -import { Box, Flex } from '@rebass/grid' +import { H2, Spacer, StyledTopBorderWrapper, Flex, Box } from '@lara/components' import { Comment, Day, Entry, Report, ReportStatus, useCreateCommentOnReportMutation, UserInterface } from '../graphql' import strings from '../locales/localization' diff --git a/packages/frontend/src/components/report-page-header.tsx b/packages/frontend/src/components/report-page-header.tsx index 7828f26..d90f461 100644 --- a/packages/frontend/src/components/report-page-header.tsx +++ b/packages/frontend/src/components/report-page-header.tsx @@ -1,8 +1,7 @@ import React from 'react' import { useForm } from 'react-hook-form' -import { H1, Paragraph, Spacer, DepartmentInput, ErrorText } from '@lara/components' -import { Flex } from '@rebass/grid' +import { H1, Paragraph, Spacer, DepartmentInput, ErrorText, Flex } from '@lara/components' import { Report, ReportStatus, Trainee } from '../graphql' import DateHelper from '../helper/date-helper' diff --git a/packages/frontend/src/components/signature-settings.tsx b/packages/frontend/src/components/signature-settings.tsx index 1ccc7c5..4f2f9ad 100644 --- a/packages/frontend/src/components/signature-settings.tsx +++ b/packages/frontend/src/components/signature-settings.tsx @@ -15,8 +15,9 @@ import { StyledWebcamLabel, StyledWebcamPicture, StyledWebcamPictureContainer, + Flex, + Box, } from '@lara/components' -import { Box, Flex } from '@rebass/grid' import { useSignatureSettingsDataQuery, useSignatureSettingsUpdateSignatureMutation } from '../graphql' import { useToastContext } from '../hooks/use-toast-context' diff --git a/packages/frontend/src/components/trainee-row.tsx b/packages/frontend/src/components/trainee-row.tsx index 84b0764..c2e5044 100644 --- a/packages/frontend/src/components/trainee-row.tsx +++ b/packages/frontend/src/components/trainee-row.tsx @@ -14,10 +14,10 @@ import { StyledName, StyledUnclaimIcon, StyledWrapper, + Flex, + Box, } from '@lara/components' -import { Box, Flex } from '@rebass/grid' - import { Company, Trainee, Trainer, useClaimTraineeMutation, useUnclaimTraineeMutation } from '../graphql' import strings from '../locales/localization' import Avatar from './avatar' @@ -91,7 +91,7 @@ const TraineeRow: React.FunctionComponent = (props) => { {trainee.course} - + ) + if (!window.__root) { + window.__root = createRoot(rootElement) } + window.__root.render() } if (module.hot && ENVIRONMENT.debug) { diff --git a/packages/frontend/src/pages/admin-edit-user-page.tsx b/packages/frontend/src/pages/admin-edit-user-page.tsx index 68028c6..8ea0fb9 100644 --- a/packages/frontend/src/pages/admin-edit-user-page.tsx +++ b/packages/frontend/src/pages/admin-edit-user-page.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useParams } from 'react-router' -import { EditUserLayout, H1, Paragraph, Spacings } from '@lara/components' +import { EditUserLayout, H1, Paragraph, Spacings, Flex, Box } from '@lara/components' import { PrimaryButton, SecondaryButton } from '../components/button' import { EditTraineeContent } from '../components/edit-trainee-content' @@ -11,7 +11,6 @@ import NavigationButtonLink from '../components/navigation-button-link' import { useMarkUserForDeleteMutation, useUnmarkUserForDeleteMutation, useUserPageQuery } from '../graphql' import strings from '../locales/localization' import { Template } from '../templates/template' -import { Box, Flex } from '@rebass/grid' import Modal from '../components/modal' import { useToastContext } from '../hooks/use-toast-context' diff --git a/packages/frontend/src/pages/archive-page.tsx b/packages/frontend/src/pages/archive-page.tsx index 54bc3d1..e77653e 100644 --- a/packages/frontend/src/pages/archive-page.tsx +++ b/packages/frontend/src/pages/archive-page.tsx @@ -16,8 +16,8 @@ import { StyledSearchWrapper, Spacings, StyledArchiveLink, + Flex, } from '@lara/components' -import { Flex } from '@rebass/grid' import { PrimaryButton } from '../components/button' import { CheckBox } from '../components/checkbox' diff --git a/packages/frontend/src/pages/dashboard-page.tsx b/packages/frontend/src/pages/dashboard-page.tsx index 5b02fc9..58ff648 100644 --- a/packages/frontend/src/pages/dashboard-page.tsx +++ b/packages/frontend/src/pages/dashboard-page.tsx @@ -1,8 +1,7 @@ import { getDay, getISOWeek, getYear, isWeekend } from 'date-fns' import React from 'react' -import { Container, H1, Paragraph, StyledDashboardWeeks } from '@lara/components' -import { Box, Flex } from '@rebass/grid' +import { Container, H1, Paragraph, StyledDashboardWeeks, Flex, Box } from '@lara/components' import DayInput from '../components/day-input' import Illustrations from '../components/illustration' diff --git a/packages/frontend/src/pages/missing-page.tsx b/packages/frontend/src/pages/missing-page.tsx index a46e3da..50ae89e 100644 --- a/packages/frontend/src/pages/missing-page.tsx +++ b/packages/frontend/src/pages/missing-page.tsx @@ -1,8 +1,7 @@ import React from 'react' import { useNavigate } from 'react-router' -import { H1, H2, Spacer } from '@lara/components' -import { Flex } from '@rebass/grid' +import { H1, H2, Spacer, Flex } from '@lara/components' import { PrimaryButton } from '../components/button' import strings from '../locales/localization' diff --git a/packages/frontend/src/pages/no-user-page.tsx b/packages/frontend/src/pages/no-user-page.tsx index 14c49a9..fdfc23b 100644 --- a/packages/frontend/src/pages/no-user-page.tsx +++ b/packages/frontend/src/pages/no-user-page.tsx @@ -11,8 +11,9 @@ import { StyledNoUserHero, StyledNoUserPageContent, StyledPreviewImage, + Flex, + Box, } from '@lara/components' -import { Box, Flex } from '@rebass/grid' import previewImage from '../assets/lara-preview.png' import strings from '../locales/localization' diff --git a/packages/frontend/src/pages/report-page.tsx b/packages/frontend/src/pages/report-page.tsx index 3cafbab..9a9cfc7 100644 --- a/packages/frontend/src/pages/report-page.tsx +++ b/packages/frontend/src/pages/report-page.tsx @@ -2,8 +2,7 @@ import { GraphQLError } from 'graphql' import React from 'react' import { Navigate, useParams, useNavigate } from 'react-router' -import { Container, H2, Paragraph, Spacer, StyledTopBorderWrapper } from '@lara/components' -import { Box, Flex } from '@rebass/grid' +import { Container, H2, Paragraph, Spacer, StyledTopBorderWrapper, Flex, Box } from '@lara/components' import { PrimaryButton, SecondaryButton } from '../components/button' import DayInput from '../components/day-input' diff --git a/packages/frontend/src/pages/report-review-page.tsx b/packages/frontend/src/pages/report-review-page.tsx index 54a4263..7446b4e 100644 --- a/packages/frontend/src/pages/report-review-page.tsx +++ b/packages/frontend/src/pages/report-review-page.tsx @@ -11,8 +11,9 @@ import { StyledDepartmentHeadline, StyledTraineeName, StyledTotalContainer, + Flex, + Box, } from '@lara/components' -import { Box, Flex } from '@rebass/grid' import { PrimaryButton, SecondaryButton } from '../components/button' import CommentSection from '../components/comment-section' diff --git a/packages/frontend/src/pages/trainer-reports-page.tsx b/packages/frontend/src/pages/trainer-reports-page.tsx index c6b1cc8..36760e0 100644 --- a/packages/frontend/src/pages/trainer-reports-page.tsx +++ b/packages/frontend/src/pages/trainer-reports-page.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { H1, Paragraph } from '@lara/components' -import { Flex } from '@rebass/grid' +import { H1, Paragraph, Flex } from '@lara/components' import Illustrations from '../components/illustration' import Loader from '../components/loader' diff --git a/yarn.lock b/yarn.lock index f5c2d2f..4393cfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8898,7 +8898,7 @@ eslint-plugin-react-hooks@^5.2.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== -eslint-plugin-react@^7.33.2: +eslint-plugin-react@^7.37.5: version "7.37.5" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==