diff --git a/.env.example b/.env.example index 3b0981c1..15aaf242 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,6 @@ DEBUG = true COMPANY_ABBREVIATION = exp OLD_COMPANY_NAME = oldExampleCompany NEW_COMPANY_NAME = newExampleCompany -AVATAR_URL = url SES_EMAIL = secret SES_REGION = eu-west-1 diff --git a/.github/workflows/reusable-build-job.yml b/.github/workflows/reusable-build-job.yml index 39551d25..44b67012 100644 --- a/.github/workflows/reusable-build-job.yml +++ b/.github/workflows/reusable-build-job.yml @@ -32,7 +32,6 @@ jobs: COMPANY_ABBREVIATION: ${{ secrets.COMPANY_ABBREVIATION }} OLD_COMPANY_NAME: ${{ secrets.OLD_COMPANY_NAME }} NEW_COMPANY_NAME: ${{ secrets.NEW_COMPANY_NAME }} - AVATAR_URL: ${{ secrets.AVATAR_URL }} LARA_VERSION: ${{ github.ref_name }} FRONTEND_URL: ${{ secrets.FRONTEND_URL }} BACKEND_URL: ${{ secrets.BACKEND_URL }} diff --git a/.github/workflows/reusable-deploy-job.yml b/.github/workflows/reusable-deploy-job.yml index e5d55e05..c54e5719 100644 --- a/.github/workflows/reusable-deploy-job.yml +++ b/.github/workflows/reusable-deploy-job.yml @@ -42,7 +42,6 @@ jobs: COMPANY_ABBREVIATION: ${{ secrets.COMPANY_ABBREVIATION }} OLD_COMPANY_NAME: ${{ secrets.OLD_COMPANY_NAME }} NEW_COMPANY_NAME: ${{ secrets.NEW_COMPANY_NAME }} - AVATAR_URL: ${{ secrets.AVATAR_URL }} LARA_VERSION: ${{ github.ref_name }} SES_REGION: ${{ secrets.SES_REGION }} FRONTEND_URL: ${{ secrets.FRONTEND_URL }} diff --git a/packages/api/schema.gql b/packages/api/schema.gql index d7ed2694..b3cba742 100644 --- a/packages/api/schema.gql +++ b/packages/api/schema.gql @@ -215,6 +215,11 @@ type Mutation { Create OAuth Code """ createOAuthCode: String! + + """ + Get Avatar Bucket Upload URL + """ + getAvatarSignedUrl(id: String!): String } type LaraConfig { @@ -343,10 +348,6 @@ enum ReportStatus { } type Trainee implements UserInterface { - """ - The url for the users avatar image. - """ - avatar: String! company: Company! course: String createdAt: String! @@ -408,7 +409,6 @@ input UpdateTrainerInput { } type Trainer implements UserInterface { - avatar: String! createdAt: String! firstName: String! id: ID! @@ -426,7 +426,6 @@ type Trainer implements UserInterface { } type Admin implements UserInterface { - avatar: String! createdAt: String! firstName: String! id: ID! @@ -454,7 +453,6 @@ input UserInput { } interface UserInterface { - avatar: String! createdAt: String! firstName: String! id: ID! diff --git a/packages/api/src/graphql.ts b/packages/api/src/graphql.ts index 6af97553..01032748 100644 --- a/packages/api/src/graphql.ts +++ b/packages/api/src/graphql.ts @@ -22,7 +22,6 @@ export type Scalars = { export type GqlAdmin = GqlUserInterface & { __typename?: 'Admin'; alexaSkillLinked?: Maybe; - avatar: Scalars['String']['output']; createdAt: Scalars['String']['output']; email: Scalars['String']['output']; firstName: Scalars['String']['output']; @@ -165,6 +164,8 @@ export type GqlMutation = { createTrainer?: Maybe; /** Deletes an entry by the given ID. Only considers entries made by the current user. Returns the ID of the deleted entry. */ deleteEntry: GqlMutateEntryPayload; + /** Get Avatar Bucket Upload URL */ + getAvatarSignedUrl?: Maybe; /** Link Alexa account */ linkAlexa?: Maybe; /** Login via microsoft */ @@ -251,6 +252,11 @@ export type GqlMutationDeleteEntryArgs = { }; +export type GqlMutationGetAvatarSignedUrlArgs = { + id: Scalars['String']['input']; +}; + + export type GqlMutationLinkAlexaArgs = { code: Scalars['String']['input']; state: Scalars['String']['input']; @@ -429,8 +435,6 @@ export type GqlSuggestion = { export type GqlTrainee = GqlUserInterface & { __typename?: 'Trainee'; alexaSkillLinked?: Maybe; - /** The url for the users avatar image. */ - avatar: Scalars['String']['output']; company: GqlCompany; course?: Maybe; createdAt: Scalars['String']['output']; @@ -457,7 +461,6 @@ export type GqlTrainee = GqlUserInterface & { export type GqlTrainer = GqlUserInterface & { __typename?: 'Trainer'; alexaSkillLinked?: Maybe; - avatar: Scalars['String']['output']; createdAt: Scalars['String']['output']; deleteAt?: Maybe; email: Scalars['String']['output']; @@ -514,7 +517,6 @@ export type GqlUserInput = { export type GqlUserInterface = { alexaSkillLinked?: Maybe; - avatar: Scalars['String']['output']; createdAt: Scalars['String']['output']; email: Scalars['String']['output']; firstName: Scalars['String']['output']; @@ -683,7 +685,6 @@ export type GqlResolversParentTypes = ResolversObject<{ export type GqlAdminResolvers = ResolversObject<{ alexaSkillLinked?: Resolver, ParentType, ContextType>; - avatar?: Resolver; createdAt?: Resolver; email?: Resolver; firstName?: Resolver; @@ -781,6 +782,7 @@ export type GqlMutationResolvers, ParentType, ContextType, RequireFields>; createTrainer?: Resolver, ParentType, ContextType, RequireFields>; deleteEntry?: Resolver>; + getAvatarSignedUrl?: Resolver, ParentType, ContextType, RequireFields>; linkAlexa?: Resolver, ParentType, ContextType, RequireFields>; login?: Resolver, ParentType, ContextType, RequireFields>; markUserForDeletion?: Resolver, ParentType, ContextType, RequireFields>; @@ -849,7 +851,6 @@ export type GqlSuggestionResolvers = ResolversObject<{ alexaSkillLinked?: Resolver, ParentType, ContextType>; - avatar?: Resolver; company?: Resolver; course?: Resolver, ParentType, ContextType>; createdAt?: Resolver; @@ -876,7 +877,6 @@ export type GqlTraineeResolvers = ResolversObject<{ alexaSkillLinked?: Resolver, ParentType, ContextType>; - avatar?: Resolver; createdAt?: Resolver; deleteAt?: Resolver, ParentType, ContextType>; email?: Resolver; @@ -908,7 +908,6 @@ export type GqlUpdateReportPayloadResolvers = ResolversObject<{ __resolveType: TypeResolveFn<'Admin' | 'Trainee' | 'Trainer', ParentType, ContextType>; alexaSkillLinked?: Resolver, ParentType, ContextType>; - avatar?: Resolver; createdAt?: Resolver; email?: Resolver; firstName?: Resolver; diff --git a/packages/api/src/models.ts b/packages/api/src/models.ts index f5807b10..b7c95cb5 100644 --- a/packages/api/src/models.ts +++ b/packages/api/src/models.ts @@ -18,7 +18,7 @@ export type User = Trainee | Trainer | Admin * during the runtime in the backend. Only after beeing * transformed by GraphQL these fields are available */ -type ResolvedUserFields = 'avatar' | 'username' | 'alexaSkillLinked' +type ResolvedUserFields = 'username' | 'alexaSkillLinked' export type UserInterface = Omit & { email: string diff --git a/packages/backend/package.json b/packages/backend/package.json index f784726f..3b60301b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -15,6 +15,7 @@ "@aws-sdk/client-dynamodb": "3.883.0", "@aws-sdk/client-lambda": "3.883.0", "@aws-sdk/lib-dynamodb": "3.883.0", + "@aws-sdk/s3-request-presigner": "3.883.0", "@aws-sdk/util-dynamodb": "3.883.0", "@graphql-tools/schema": "^10.0.25", "@lara/api": "^1.0.0", diff --git a/packages/backend/src/handler.ts b/packages/backend/src/handler.ts index 0e567e88..0fdfacf5 100644 --- a/packages/backend/src/handler.ts +++ b/packages/backend/src/handler.ts @@ -15,6 +15,7 @@ import { resolvers } from './resolvers' import { handleAuthorizeRequest } from './routes/authorize' import { validateJWT } from './services/oauth.service' import { parseBearerAuth } from './utils/security' +import { handleAvatarDeletion, handleAvatarUpload } from './routes/avatar' const { STAGE, AUTH_HEADER } = process.env @@ -91,6 +92,8 @@ export const server: APIGatewayProxyHandler = apolloServer.createHandler({ app.use(cors(corsOptions)) app.post('/oauth/token', handleAuthorizeRequest) + app.post('/avatar', handleAvatarUpload) + app.delete('/avatar', handleAvatarDeletion) app.use(middleware) diff --git a/packages/backend/src/resolvers/admin.resolver.ts b/packages/backend/src/resolvers/admin.resolver.ts index efbc26f4..7974fa5a 100644 --- a/packages/backend/src/resolvers/admin.resolver.ts +++ b/packages/backend/src/resolvers/admin.resolver.ts @@ -10,13 +10,12 @@ import { allUsers, saveUser, updateUser, userByEmail, userById } from '../reposi import { sendDeletionMail } from '../services/email.service' import { deleteTrainee, generateReports, generateTrainee, validateTrainee } from '../services/trainee.service' import { deleteTrainer, generateTrainer, validateTrainer } from '../services/trainer.service' -import { avatar, username } from '../services/user.service' +import { username } from '../services/user.service' import { parseISODateString } from '../utils/date' import { t } from '../i18n' export const adminResolver: GqlResolvers = { Admin: { - avatar, username, }, Query: { diff --git a/packages/backend/src/resolvers/avatar.resolver.ts b/packages/backend/src/resolvers/avatar.resolver.ts new file mode 100644 index 00000000..96c633b8 --- /dev/null +++ b/packages/backend/src/resolvers/avatar.resolver.ts @@ -0,0 +1,46 @@ +import { S3Client, GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { AuthenticatedContext, GqlResolvers } from '@lara/api' + +const { IS_OFFLINE, AVATAR_BUCKET } = process.env + +const s3Client = new S3Client( + IS_OFFLINE + ? { + forcePathStyle: true, + credentials: { + accessKeyId: 'S3RVER', // This specific key is required when working offline + secretAccessKey: 'S3RVER', + }, + endpoint: 'http://localhost:8181', + } + : { region: 'eu-central-1' } +) + +export const avatarResolver: GqlResolvers = { + Mutation: { + getAvatarSignedUrl: async (_parent, { id }, _context) => { + const key = `${id}` + + try { + await s3Client.send( + new HeadObjectCommand({ + Bucket: AVATAR_BUCKET, + Key: key, + }) + ) + } catch (_) { + return undefined + } + + const command = new GetObjectCommand({ + Bucket: AVATAR_BUCKET, + Key: key, + }) + + const url = await getSignedUrl(s3Client, command, { expiresIn: 60 }) + if (!url) throw new Error('Could not generate signed get URL') + return url + }, + }, +} diff --git a/packages/backend/src/resolvers/index.ts b/packages/backend/src/resolvers/index.ts index 1a0dba3c..79e12322 100644 --- a/packages/backend/src/resolvers/index.ts +++ b/packages/backend/src/resolvers/index.ts @@ -9,6 +9,7 @@ import { reportResolver, reportTraineeResolver } from './report.resolver' import { traineeResolver, traineeTraineeResolver } from './trainee.resolver' import { trainerResolver } from './trainer.resolver' import { userResolver } from './user.resolver' +import { avatarResolver } from './avatar.resolver' export const resolvers = [ configResolver, @@ -36,4 +37,6 @@ export const resolvers = [ trainerAdminResolver, alexaResolver, + + avatarResolver, ] diff --git a/packages/backend/src/resolvers/trainee.resolver.ts b/packages/backend/src/resolvers/trainee.resolver.ts index b0a3b96c..c8c954a6 100644 --- a/packages/backend/src/resolvers/trainee.resolver.ts +++ b/packages/backend/src/resolvers/trainee.resolver.ts @@ -18,7 +18,7 @@ import { company } from '../services/company.service' import { createPrintReportData, createPrintUserData, invokePrintLambda, savePrintData } from '../services/print.service' import { reportsWithinApprenticeship } from '../services/report.service' import { endOfToolUsage, startOfToolUsage, validateTrainee } from '../services/trainee.service' -import { avatar, username } from '../services/user.service' +import { username } from '../services/user.service' import { filterNullish } from '../utils/array' export const traineeResolver: GqlResolvers = { @@ -51,7 +51,6 @@ export const traineeTraineeResolver: GqlResolvers = { }, startOfToolUsage: (model) => startOfToolUsage(model).toISOString(), endOfToolUsage: (model) => endOfToolUsage(model).toISOString(), - avatar, alexaSkillLinked, }, Query: { diff --git a/packages/backend/src/resolvers/trainer.resolver.ts b/packages/backend/src/resolvers/trainer.resolver.ts index 4ff84da9..96b26a98 100644 --- a/packages/backend/src/resolvers/trainer.resolver.ts +++ b/packages/backend/src/resolvers/trainer.resolver.ts @@ -6,7 +6,7 @@ import { reportByYearAndWeek } from '../repositories/report.repo' import { allTrainees, traineeById, traineesByTrainerId } from '../repositories/trainee.repo' import { updateUser } from '../repositories/user.repo' import { alexaSkillLinked } from '../services/alexa.service' -import { avatar, username } from '../services/user.service' +import { username } from '../services/user.service' import { createT } from '../i18n' export const trainerResolver: GqlResolvers = { @@ -14,7 +14,6 @@ export const trainerResolver: GqlResolvers = { trainees: async (model) => { return traineesByTrainerId(model.id) }, - avatar, username, alexaSkillLinked, }, diff --git a/packages/backend/src/resolvers/user.resolver.ts b/packages/backend/src/resolvers/user.resolver.ts index 7004cb1e..58e9a539 100644 --- a/packages/backend/src/resolvers/user.resolver.ts +++ b/packages/backend/src/resolvers/user.resolver.ts @@ -7,14 +7,13 @@ import { generateAdmin } from '../services/admin.service' import { alexaSkillLinked } from '../services/alexa.service' import { generateTrainee } from '../services/trainee.service' import { generateTrainer } from '../services/trainer.service' -import { avatar, username } from '../services/user.service' +import { username } from '../services/user.service' export const userResolver: GqlResolvers = { UserInterface: { __resolveType: (model) => { return model.type }, - avatar, username, alexaSkillLinked, }, diff --git a/packages/backend/src/routes/avatar.ts b/packages/backend/src/routes/avatar.ts new file mode 100644 index 00000000..ed34de30 --- /dev/null +++ b/packages/backend/src/routes/avatar.ts @@ -0,0 +1,63 @@ +import { DeleteObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { Request, Response } from 'express' + +const { IS_OFFLINE, AVATAR_BUCKET } = process.env + +const s3Client = new S3Client( + IS_OFFLINE + ? { + forcePathStyle: true, + credentials: { + accessKeyId: 'S3RVER', // This specific key is required when working offline + secretAccessKey: 'S3RVER', + }, + endpoint: 'http://localhost:8181', + } + : { region: 'eu-central-1' } +) + +export const handleAvatarUpload = async (req: Request, res: Response) => { + const key = req.headers['x-user-id'] as string + + let base64String: string + try { + const parsed = JSON.parse(req.body.toString()) + base64String = parsed.data + } catch { + base64String = req.body.data + } + + if (!base64String) return res.status(400).send('No file provided in request') + + const buffer = Buffer.from(base64String, 'base64') + + const maxSize = 250 * 1024 + if (buffer.length > maxSize) { + return res.status(413).send(`File too large. Max size is 250 KB.`) + } + + s3Client.send( + new PutObjectCommand({ + Bucket: AVATAR_BUCKET, + Key: key, + Body: buffer, + ContentType: req.headers['content-type'], + ContentLength: buffer.length, + }) + ) + + return res.status(200).send() +} + +export const handleAvatarDeletion = async (req: Request, res: Response) => { + const key = req.headers['x-user-id'] as string + + s3Client.send( + new DeleteObjectCommand({ + Bucket: AVATAR_BUCKET, + Key: key, + }) + ) + + return res.status(200).send() +} diff --git a/packages/backend/src/services/user.service.ts b/packages/backend/src/services/user.service.ts index 19b8a628..64bd2708 100644 --- a/packages/backend/src/services/user.service.ts +++ b/packages/backend/src/services/user.service.ts @@ -1,25 +1,5 @@ -import crypto from 'crypto' - import { User } from '@lara/api' -import { isDebug } from '../permissions' - -const { OLD_COMPANY_NAME, NEW_COMPANY_NAME, AVATAR_URL } = process.env - -// TODO REMOVE THIS WITH AN OWN AVATAR UPLOAD -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 @@ -27,23 +7,3 @@ async function hashEmail(email: string) { */ export const username = (user: Pick): string => `${user.firstName.slice(0, 3)}${user.lastName.slice(0, 3)}`.toLowerCase() - -/** - * Creates the company avatar url - * @param user User to get avatar from - * @returns URL of the avatar - */ -export const avatar = async (user: Pick): Promise => { - if (isDebug() || !AVATAR_URL) { - 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 = await hashEmail(email) - - return getAvatarURL(emailHash, 300) -} diff --git a/packages/components/src/avatar-layout.tsx b/packages/components/src/avatar-layout.tsx index 365063fd..93d89336 100644 --- a/packages/components/src/avatar-layout.tsx +++ b/packages/components/src/avatar-layout.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components' import { BorderRadii } from './border-radius' const AvatarContainer = styled.div<{ size: number }>` + position: relative; height: ${(props) => props.size}px; width: ${(props) => props.size}px; background: ${(props) => props.theme.background}; @@ -13,10 +14,11 @@ const AvatarContainer = styled.div<{ size: number }>` align-items: center; background-size: cover; overflow: hidden; - position: relative; img { - max-width: 100%; + width: 100%; + height: 100%; + object-fit: cover; } ` diff --git a/packages/email/package.json b/packages/email/package.json index 2ef99ad3..07b1372c 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -10,8 +10,8 @@ "debug": "node lib/mock.js" }, "dependencies": { - "@aws-sdk/client-s3": "3.883.0", - "@aws-sdk/client-sesv2": "3.883.0", + "@aws-sdk/client-s3": "^3.883.0", + "@aws-sdk/client-sesv2": "^3.883.0", "@lara/api": "^1.0.0", "handlebars": "^4.7.7", "mjml": "^4.11.0", diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 292d1dde..3a7610da 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -14,13 +14,14 @@ "test": "jest" }, "dependencies": { - "@apollo/client": "^3.2.2", + "@apollo/client": "^3.14.0", "@azure/msal-browser": "^4.19.0", "@azure/msal-react": "^3.0.17", "@lara/components": "^1.0.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@rebass/grid": "^6.1.0", "apollo-link-token-refresh": "0.7.0", + "browser-image-compression": "^2.0.2", "date-fns": "4.1.0", "file-saver": "^2.0.2", "framer-motion": "^12.23.12", diff --git a/packages/frontend/src/components/avatar-settings.tsx b/packages/frontend/src/components/avatar-settings.tsx new file mode 100644 index 00000000..f377230c --- /dev/null +++ b/packages/frontend/src/components/avatar-settings.tsx @@ -0,0 +1,214 @@ +import { Box, Flex, H2, Paragraph, Spacings } from '@lara/components' +import { PrimaryButton, SecondaryButton } from './button' +import strings from '../locales/localization' +import { useAvatarSettingsDataQuery, useAvatarSettingsGetSignedUrlMutation } from '../graphql' +import Loader from './loader' +import { styled } from 'styled-components' +import { useEffect, useRef, useState } from 'react' +import { BackendUrl } from '../apollo-provider' +import { useToastContext } from '../hooks/use-toast-context' +import eventBus from '../helper/event-bus-helper' +import { getAvatarURL } from './avatar' +import imageCompression from 'browser-image-compression' + +async function compressToLimit(file: File, limitKB = 250): Promise { + let compressedFile = file + let quality = 0.9 + + while (compressedFile.size > limitKB * 1024 && quality > 0.1) { + compressedFile = await imageCompression(file, { + maxSizeMB: limitKB / 1024, + maxWidthOrHeight: 512, + initialQuality: quality, + useWebWorker: true, + }) + + quality -= 0.1 + } + + if (compressedFile.size > limitKB * 1024) { + throw new Error(`Could not compress under ${limitKB} KB`) + } + + return compressedFile +} + +const AvatarSettings: React.FunctionComponent = () => { + const { loading, data } = useAvatarSettingsDataQuery() + const [mutate] = useAvatarSettingsGetSignedUrlMutation() + const { addToast } = useToastContext() + const fileInputRef = useRef(null) + const [avatar, setAvatar] = useState(null) + const [hasCustomAvatar, setHasCustomAvatar] = useState(false) + + const { currentUser } = data || {} + + useEffect(() => { + if (!currentUser) return + + mutate({ + variables: { + id: currentUser.id, + }, + }).then(({ data }) => { + if (data?.getAvatarSignedUrl) { + setAvatar(data.getAvatarSignedUrl) + setHasCustomAvatar(true) + } else { + getAvatarURL(currentUser.id).then((hash) => setAvatar(hash)) + setHasCustomAvatar(false) + } + }) + }, [currentUser, mutate]) + + if (loading || !data) { + return + } + + if (!currentUser) { + return null + } + + eventBus.on('avatarUpdated', () => { + if (!currentUser) return + mutate({ + variables: { + id: currentUser.id, + }, + }).then(({ data }) => { + if (data?.getAvatarSignedUrl) { + setAvatar(data.getAvatarSignedUrl) + setHasCustomAvatar(true) + } else { + getAvatarURL(currentUser.id).then((hash) => setAvatar(hash)) + setHasCustomAvatar(false) + } + }) + }) + + async function handleAvatarUpload(file: File) { + if (!currentUser) return + + const maxSize = 250 * 1024 + if (file.size > maxSize) { + const prevSize = file.size + file = await compressToLimit(file) + console.log( + `Successfully compressed image from ${(prevSize / 1024).toFixed(1)} KB to ${(file.size / 1024).toFixed(1)} KB (${((file.size / prevSize) * 100).toFixed(1)}% of original)` + ) + + if (file.size > maxSize) { + addToast({ + icon: 'Error', + title: strings.settings.avatar.toasts.errorCompression.title, + text: strings.settings.avatar.toasts.errorCompression.description, + type: 'error', + }) + return + } + } + + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = async () => { + const base64String = (reader.result as string).split(',')[1] + const response = await fetch(`${BackendUrl}/avatar`, { + method: 'POST', + headers: { + authorization: 'allow', + 'Content-Type': file.type, + 'X-User-Id': currentUser.id, + }, + body: JSON.stringify({ data: base64String }), + credentials: 'include', + }) + + if (response.ok) { + eventBus.emit('avatarUpdated', undefined) + addToast({ + icon: 'Success', + title: strings.settings.avatar.toasts.success.title, + text: strings.settings.avatar.toasts.success.description, + type: 'success', + }) + } else { + console.error('Upload failed:', await response.text()) + if (response.status === 413) { + addToast({ + icon: 'Error', + title: strings.settings.avatar.toasts.errorFileSize.title, + text: strings.settings.avatar.toasts.errorFileSize.description, + type: 'error', + }) + } + } + } + } + + function handleFileChange(e: React.ChangeEvent) { + const file = e.target.files?.[0] + if (file) { + handleAvatarUpload(file) + } + } + + async function removeAvatar() { + if (!currentUser) return + + await fetch(`${BackendUrl}/avatar`, { + method: 'DELETE', + headers: { + authorization: 'allow', + 'X-User-Id': currentUser.id, + }, + credentials: 'include', + }) + + eventBus.emit('avatarUpdated', undefined) + } + + return ( + + {avatar && } + + +

{strings.settings.avatar.title}

+
+ + {strings.settings.avatar.description} + + + fileInputRef.current?.click()}> + {hasCustomAvatar ? strings.settings.avatar.updateAvatar : strings.settings.avatar.addAvatar} + +
+ {hasCustomAvatar && ( + removeAvatar()}> + {strings.settings.avatar.removeAvatar} + + )} + + + + + ) +} + +const Avatar = ({ src }: { src: string }) => { + return +} + +const StyledImg = styled.img` + width: 100%; + border-radius: 100%; + aspect-ratio: 1; + object-fit: cover; +` + +export default AvatarSettings diff --git a/packages/frontend/src/components/avatar.tsx b/packages/frontend/src/components/avatar.tsx index 8c77b02a..01acdd01 100644 --- a/packages/frontend/src/components/avatar.tsx +++ b/packages/frontend/src/components/avatar.tsx @@ -1,15 +1,60 @@ -import React from 'react' +import React, { useEffect } from 'react' import { AvatarLayout, StyledIcon } from '@lara/components' +import { useAvatarSettingsGetSignedUrlMutation } from '../graphql' +import eventBus from '../helper/event-bus-helper' interface AvatarProps { size: number - image: string + id: string } -const Avatar: React.FunctionComponent = ({ size, image }) => { +export async function hashSeed(seed: string) { + const encoder = new TextEncoder() + const data = encoder.encode(seed.toLowerCase().trim()) + 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 +} + +export const getAvatarURL = async (seed: string): Promise => { + return `https://api.dicebear.com/9.x/identicon/svg?seed=${await hashSeed(seed)}.` +} + +const Avatar: React.FunctionComponent = ({ size, id }) => { + const [avatar, setAvatar] = React.useState(null) const [loading, setLoading] = React.useState(true) const [noImage, setNoImage] = React.useState(false) + const [mutate] = useAvatarSettingsGetSignedUrlMutation() + + useEffect(() => { + mutate({ + variables: { + id, + }, + }).then(({ data }) => { + if (data?.getAvatarSignedUrl) { + setAvatar(data.getAvatarSignedUrl) + } else { + getAvatarURL(id).then((hash) => setAvatar(hash)) + } + }) + }, [mutate, id]) + + eventBus.on('avatarUpdated', () => { + mutate({ + variables: { + id, + }, + }).then(({ data }) => { + if (data?.getAvatarSignedUrl) { + setAvatar(data.getAvatarSignedUrl) + } else { + getAvatarURL(id).then((hash) => setAvatar(hash)) + } + }) + }) const handleError = React.useCallback(() => { setLoading(false) @@ -22,7 +67,7 @@ const Avatar: React.FunctionComponent = ({ size, image }) => { loadingIcon={<>{loading && }} noImageIcon={<>{noImage && }} > - {image && !noImage && setLoading(false)} onError={handleError} src={image} />} + {avatar && !noImage && setLoading(false)} onError={handleError} src={avatar} />} ) } diff --git a/packages/frontend/src/components/comment-box.tsx b/packages/frontend/src/components/comment-box.tsx index 0196e98d..88fc0cb0 100644 --- a/packages/frontend/src/components/comment-box.tsx +++ b/packages/frontend/src/components/comment-box.tsx @@ -6,7 +6,7 @@ import Loader from './loader' interface CommentBoxProps { comments?: (Pick & { - user: Pick + user: Pick })[] } @@ -27,7 +27,7 @@ const CommentBox: React.FunctionComponent = ({ comments }) => { key={index} author={`${comment.user.firstName} ${comment.user.lastName}`} message={comment.text} - avatar={comment.user.avatar} + id={comment.user.id} right={comment.user.id !== currentUser?.id} /> )) diff --git a/packages/frontend/src/components/comment-bubble.tsx b/packages/frontend/src/components/comment-bubble.tsx index 37e4fd1c..ed95b2da 100644 --- a/packages/frontend/src/components/comment-bubble.tsx +++ b/packages/frontend/src/components/comment-bubble.tsx @@ -5,16 +5,16 @@ import { CommentBubbleLayout } from '@lara/components' import Avatar from './avatar' interface CommentBubbleProps { - avatar?: string + id?: string author?: string message?: string right?: boolean } -const CommentBubble: React.FunctionComponent = ({ avatar, author, message, right }) => { +const CommentBubble: React.FunctionComponent = ({ id, author, message, right }) => { return ( {avatar && }} + avatar={<>{id && }} author={author} message={message} right={right} diff --git a/packages/frontend/src/components/comment-section.tsx b/packages/frontend/src/components/comment-section.tsx index 1a3447d6..5eaf49c9 100644 --- a/packages/frontend/src/components/comment-section.tsx +++ b/packages/frontend/src/components/comment-section.tsx @@ -9,7 +9,7 @@ import CommentBox from './comment-box' interface CommentSectionProps { comments: (Pick & { - user: Pick + user: Pick })[] onSubmit: onSubmitType displayTextInput: boolean diff --git a/packages/frontend/src/components/day-input.tsx b/packages/frontend/src/components/day-input.tsx index 3b8beaa2..848b6b63 100644 --- a/packages/frontend/src/components/day-input.tsx +++ b/packages/frontend/src/components/day-input.tsx @@ -52,12 +52,12 @@ export const StatusTypes = { interface DayInputProps { day?: Pick & { comments: (Pick & { - user: Pick + user: Pick })[] } & { entries: (Pick & { comments: (Pick & { - user: Pick + user: Pick })[] })[] } diff --git a/packages/frontend/src/components/edit-trainee-content.tsx b/packages/frontend/src/components/edit-trainee-content.tsx index a7116ec6..3854da06 100644 --- a/packages/frontend/src/components/edit-trainee-content.tsx +++ b/packages/frontend/src/components/edit-trainee-content.tsx @@ -12,9 +12,9 @@ import { UserInfo } from './user-info' interface EditTraineeProps { trainee: Pick< Trainee, - 'id' | 'firstName' | 'lastName' | 'startDate' | 'endDate' | 'startOfToolUsage' | 'avatar' | 'email' | 'deleteAt' + 'id' | 'firstName' | 'lastName' | 'startDate' | 'endDate' | 'startOfToolUsage' | 'email' | 'deleteAt' > & { - trainer?: Pick + trainer?: Pick company: Pick } companies: Pick[] @@ -52,7 +52,7 @@ export const EditTraineeContent: React.FC = ({ trainee, compan = ({ trainee, compan diff --git a/packages/frontend/src/components/edit-trainer-content.tsx b/packages/frontend/src/components/edit-trainer-content.tsx index 9e52b25d..c470f68f 100644 --- a/packages/frontend/src/components/edit-trainer-content.tsx +++ b/packages/frontend/src/components/edit-trainer-content.tsx @@ -10,8 +10,8 @@ import { TrainerForm, EditTrainerFormData } from './trainer-form' import { UserInfo } from './user-info' interface EditTrainerProps { - trainer: Pick & { - trainees: Pick[] + trainer: Pick & { + trainees: Pick[] } } @@ -47,7 +47,7 @@ export const EditTrainer: React.FC = ({ trainer }) => { = ({ trainer }) => { <> {trainer.trainees.map((trainee) => ( - + ))} diff --git a/packages/frontend/src/components/edit-user-row.tsx b/packages/frontend/src/components/edit-user-row.tsx index df72fa51..0bb595fd 100644 --- a/packages/frontend/src/components/edit-user-row.tsx +++ b/packages/frontend/src/components/edit-user-row.tsx @@ -8,7 +8,7 @@ import strings from '../locales/localization' import { SecondaryButton } from './button' import { UserInfo } from './user-info' -type EditUser = Pick +type EditUser = Pick interface EditUserRowProps { user: EditUser @@ -26,9 +26,7 @@ export const EditUserRow: React.FunctionComponent = ({ user, b return ( - } + content={} button={ {strings.edit} diff --git a/packages/frontend/src/components/entries-input.tsx b/packages/frontend/src/components/entries-input.tsx index 2e84fbd8..2ef4c863 100644 --- a/packages/frontend/src/components/entries-input.tsx +++ b/packages/frontend/src/components/entries-input.tsx @@ -23,7 +23,7 @@ interface EntriesInputProps { day?: Pick & { entries: (Pick & { comments: (Pick & { - user: Pick + user: Pick })[] })[] } diff --git a/packages/frontend/src/components/entry-input.tsx b/packages/frontend/src/components/entry-input.tsx index 38ba291b..e904dffb 100644 --- a/packages/frontend/src/components/entry-input.tsx +++ b/packages/frontend/src/components/entry-input.tsx @@ -43,7 +43,7 @@ interface EntryDisplayFieldProps { } entry: Pick & { comments: (Pick & { - user: Pick + user: Pick })[] } trainee: Pick diff --git a/packages/frontend/src/components/ms-sign-in-button.tsx b/packages/frontend/src/components/ms-sign-in-button.tsx index bcfbcfa6..2784e8f6 100644 --- a/packages/frontend/src/components/ms-sign-in-button.tsx +++ b/packages/frontend/src/components/ms-sign-in-button.tsx @@ -39,6 +39,7 @@ export const SignInButton: React.FunctionComponent = () => { try { const loginResponse = await instance.loginPopup(loginRequest) + const email = loginResponse.account?.username if (email) { mutate({ variables: { email } }) diff --git a/packages/frontend/src/components/navigation.tsx b/packages/frontend/src/components/navigation.tsx index b104a057..9779e28b 100644 --- a/packages/frontend/src/components/navigation.tsx +++ b/packages/frontend/src/components/navigation.tsx @@ -29,6 +29,8 @@ const Navigation: React.FC = () => { const dropdownRef = useRef(null) + const { currentUser } = data || {} + // Handle resizing useEffect(() => { const updateDimensions = () => setIsMobile(window.innerWidth <= 550) @@ -140,7 +142,7 @@ const Navigation: React.FC = () => { }} > {data.currentUser?.firstName + ' ' + data.currentUser?.lastName} - + diff --git a/packages/frontend/src/components/report-page-footer.tsx b/packages/frontend/src/components/report-page-footer.tsx index 37848710..2064fc3b 100644 --- a/packages/frontend/src/components/report-page-footer.tsx +++ b/packages/frontend/src/components/report-page-footer.tsx @@ -12,13 +12,13 @@ import { useReportHelper } from '../helper/report-helper' interface ReportPageFooterProps { report: Pick & { comments: (Pick & { - user: Pick + user: Pick })[] days: (Pick & { entries: ({ __typename?: 'Entry' } & Pick)[] })[] } - user: Pick + user: Pick disabled: boolean updateReport: (values: Partial) => Promise } diff --git a/packages/frontend/src/components/trainee-reports-overview.tsx b/packages/frontend/src/components/trainee-reports-overview.tsx index 7864a442..251f6554 100644 --- a/packages/frontend/src/components/trainee-reports-overview.tsx +++ b/packages/frontend/src/components/trainee-reports-overview.tsx @@ -8,7 +8,7 @@ import Avatar from './avatar' import WeekOverview from './week-overview' interface TraineeReportsOverviweProps { - trainee: Pick & { + trainee: Pick & { reports: Pick[] } } @@ -21,7 +21,7 @@ const TraineeReportsOverview: React.FunctionComponent 1 ? todos : todo return ( } + avatar={} name={`${trainee.firstName} ${trainee.lastName}`} icon={ openReportsCount > 0 ? ( diff --git a/packages/frontend/src/components/trainee-row.tsx b/packages/frontend/src/components/trainee-row.tsx index c2e50444..20deacf3 100644 --- a/packages/frontend/src/components/trainee-row.tsx +++ b/packages/frontend/src/components/trainee-row.tsx @@ -26,7 +26,7 @@ import Loader from './loader' interface TraineeRowProps { active?: boolean - trainee: Pick & { + trainee: Pick & { trainer?: Pick company?: Pick } @@ -85,7 +85,7 @@ const TraineeRow: React.FunctionComponent = (props) => { - + {trainee.firstName} {trainee.lastName} diff --git a/packages/frontend/src/components/trainee-settings.tsx b/packages/frontend/src/components/trainee-settings.tsx index c7b57787..9675f8cc 100644 --- a/packages/frontend/src/components/trainee-settings.tsx +++ b/packages/frontend/src/components/trainee-settings.tsx @@ -121,7 +121,7 @@ const TraineeSettings: React.FunctionComponent = ({ disabl {currentUser.trainer ? ( /* Display actual avatar of assigned trainer */ - + ) : ( /* Display a placeholder */ diff --git a/packages/frontend/src/components/user-info.tsx b/packages/frontend/src/components/user-info.tsx index ef90a768..458d0bb7 100644 --- a/packages/frontend/src/components/user-info.tsx +++ b/packages/frontend/src/components/user-info.tsx @@ -8,15 +8,15 @@ import Avatar from './avatar' type UserInfoProps = { firstName: string lastName: string - avatar: string + id: string secondary?: boolean deleteAt?: string } -export const UserInfo: React.FC = ({ firstName, lastName, avatar, secondary, deleteAt }) => { +export const UserInfo: React.FC = ({ firstName, lastName, id, secondary, deleteAt }) => { return ( } + avatar={} name={firstName + ' ' + lastName} secondary={Boolean(secondary)} forDeletion={Boolean(deleteAt)} diff --git a/packages/frontend/src/graphql/index.tsx b/packages/frontend/src/graphql/index.tsx index 00fc0eb0..e2245117 100644 --- a/packages/frontend/src/graphql/index.tsx +++ b/packages/frontend/src/graphql/index.tsx @@ -20,7 +20,6 @@ export type Scalars = { export type Admin = UserInterface & { __typename?: 'Admin'; alexaSkillLinked?: Maybe; - avatar: Scalars['String']['output']; createdAt: Scalars['String']['output']; email: Scalars['String']['output']; firstName: Scalars['String']['output']; @@ -164,6 +163,8 @@ export type Mutation = { createTrainer?: Maybe; /** Deletes an entry by the given ID. Only considers entries made by the current user. Returns the ID of the deleted entry. */ deleteEntry: MutateEntryPayload; + /** Get Avatar Bucket Upload URL */ + getAvatarSignedUrl?: Maybe; /** Link Alexa account */ linkAlexa?: Maybe; /** Login via microsoft */ @@ -250,6 +251,11 @@ export type MutationDeleteEntryArgs = { }; +export type MutationGetAvatarSignedUrlArgs = { + id: Scalars['String']['input']; +}; + + export type MutationLinkAlexaArgs = { code: Scalars['String']['input']; state: Scalars['String']['input']; @@ -429,8 +435,6 @@ export type Suggestion = { export type Trainee = UserInterface & { __typename?: 'Trainee'; alexaSkillLinked?: Maybe; - /** The url for the users avatar image. */ - avatar: Scalars['String']['output']; company: Company; course?: Maybe; createdAt: Scalars['String']['output']; @@ -457,7 +461,6 @@ export type Trainee = UserInterface & { export type Trainer = UserInterface & { __typename?: 'Trainer'; alexaSkillLinked?: Maybe; - avatar: Scalars['String']['output']; createdAt: Scalars['String']['output']; deleteAt?: Maybe; email: Scalars['String']['output']; @@ -514,7 +517,6 @@ export type UserInput = { export type UserInterface = { alexaSkillLinked?: Maybe; - avatar: Scalars['String']['output']; createdAt: Scalars['String']['output']; email: Scalars['String']['output']; firstName: Scalars['String']['output']; @@ -546,6 +548,13 @@ export type ApplicationSettingsUpdateUserMutationVariables = Exact<{ export type ApplicationSettingsUpdateUserMutation = { __typename?: 'Mutation', updateCurrentUser?: { __typename: 'Admin', language?: string | undefined, theme?: string | undefined, notification?: boolean | undefined, id: string } | { __typename: 'Trainee', language?: string | undefined, theme?: string | undefined, notification?: boolean | undefined, id: string } | { __typename: 'Trainer', language?: string | undefined, theme?: string | undefined, notification?: boolean | undefined, id: string } | undefined }; +export type AvatarSettingsGetSignedUrlMutationVariables = Exact<{ + id: Scalars['String']['input']; +}>; + + +export type AvatarSettingsGetSignedUrlMutation = { __typename?: 'Mutation', getAvatarSignedUrl?: string | undefined }; + export type ClaimTraineeMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; @@ -560,7 +569,7 @@ export type CreateCommentOnDayMutationVariables = Exact<{ }>; -export type CreateCommentOnDayMutation = { __typename?: 'Mutation', createCommentOnDay: { __typename?: 'CreateCommentPayload', commentable: { __typename?: 'Day', id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> } | { __typename?: 'Entry' } | { __typename?: 'Report' } } }; +export type CreateCommentOnDayMutation = { __typename?: 'Mutation', createCommentOnDay: { __typename?: 'CreateCommentPayload', commentable: { __typename?: 'Day', id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> } | { __typename?: 'Entry' } | { __typename?: 'Report' } } }; export type CreateCommentOnEntryMutationVariables = Exact<{ id: Scalars['ID']['input']; @@ -569,7 +578,7 @@ export type CreateCommentOnEntryMutationVariables = Exact<{ }>; -export type CreateCommentOnEntryMutation = { __typename?: 'Mutation', createCommentOnEntry: { __typename?: 'CreateCommentPayload', commentable: { __typename?: 'Day' } | { __typename?: 'Entry', id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> } | { __typename?: 'Report' } } }; +export type CreateCommentOnEntryMutation = { __typename?: 'Mutation', createCommentOnEntry: { __typename?: 'CreateCommentPayload', commentable: { __typename?: 'Day' } | { __typename?: 'Entry', id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> } | { __typename?: 'Report' } } }; export type CreateCommentOnReportMutationVariables = Exact<{ id: Scalars['ID']['input']; @@ -578,7 +587,7 @@ export type CreateCommentOnReportMutationVariables = Exact<{ }>; -export type CreateCommentOnReportMutation = { __typename?: 'Mutation', createCommentOnReport: { __typename?: 'CreateCommentPayload', commentable: { __typename?: 'Day' } | { __typename?: 'Entry' } | { __typename?: 'Report', id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> } } }; +export type CreateCommentOnReportMutation = { __typename?: 'Mutation', createCommentOnReport: { __typename?: 'CreateCommentPayload', commentable: { __typename?: 'Day' } | { __typename?: 'Entry' } | { __typename?: 'Report', id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> } } }; export type CreateEntryMutationVariables = Exact<{ dayId: Scalars['String']['input']; @@ -598,14 +607,14 @@ export type CreateTraineeMutationVariables = Exact<{ }>; -export type CreateTraineeMutation = { __typename?: 'Mutation', createTrainee?: { __typename?: 'Trainee', id: string, startDate?: string | undefined, startOfToolUsage?: string | undefined, endDate?: string | undefined, course?: string | undefined, avatar: string, firstName: string, lastName: string, email: string, type: UserTypeEnum, deleteAt?: string | undefined, company: { __typename?: 'Company', id: string } } | undefined }; +export type CreateTraineeMutation = { __typename?: 'Mutation', createTrainee?: { __typename?: 'Trainee', id: string, startDate?: string | undefined, startOfToolUsage?: string | undefined, endDate?: string | undefined, course?: string | undefined, firstName: string, lastName: string, email: string, type: UserTypeEnum, deleteAt?: string | undefined, company: { __typename?: 'Company', id: string } } | undefined }; export type CreateTrainerMutationVariables = Exact<{ input: CreateTrainerInput; }>; -export type CreateTrainerMutation = { __typename?: 'Mutation', createTrainer?: { __typename?: 'Trainer', id: string, avatar: string, firstName: string, lastName: string, email: string, type: UserTypeEnum } | undefined }; +export type CreateTrainerMutation = { __typename?: 'Mutation', createTrainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string, email: string, type: UserTypeEnum } | undefined }; export type DayStatusSelectUpdateDayMutationVariables = Exact<{ id: Scalars['ID']['input']; @@ -734,7 +743,7 @@ export type UpdateTraineeMutationVariables = Exact<{ }>; -export type UpdateTraineeMutation = { __typename?: 'Mutation', updateTrainee?: { __typename?: 'Trainee', id: string, startDate?: string | undefined, startOfToolUsage?: string | undefined, endDate?: string | undefined, firstName: string, lastName: string, email: string, avatar: string, company: { __typename?: 'Company', id: string } } | undefined }; +export type UpdateTraineeMutation = { __typename?: 'Mutation', updateTrainee?: { __typename?: 'Trainee', id: string, startDate?: string | undefined, startOfToolUsage?: string | undefined, endDate?: string | undefined, firstName: string, lastName: string, email: string, company: { __typename?: 'Company', id: string } } | undefined }; export type UpdateTrainerMutationVariables = Exact<{ input: UpdateTrainerInput; @@ -742,17 +751,17 @@ export type UpdateTrainerMutationVariables = Exact<{ }>; -export type UpdateTrainerMutation = { __typename?: 'Mutation', updateTrainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string, email: string, avatar: string, type: UserTypeEnum } | undefined }; +export type UpdateTrainerMutation = { __typename?: 'Mutation', updateTrainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string, email: string, type: UserTypeEnum } | undefined }; export type AdminTraineesPageQueryVariables = Exact<{ [key: string]: never; }>; -export type AdminTraineesPageQuery = { __typename?: 'Query', trainees: Array<{ __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string, deleteAt?: string | undefined }>, companies?: Array<{ __typename?: 'Company', id: string, name: string }> | undefined }; +export type AdminTraineesPageQuery = { __typename?: 'Query', trainees: Array<{ __typename?: 'Trainee', id: string, firstName: string, lastName: string, deleteAt?: string | undefined }>, companies?: Array<{ __typename?: 'Company', id: string, name: string }> | undefined }; export type AdminTrainersPageQueryVariables = Exact<{ [key: string]: never; }>; -export type AdminTrainersPageQuery = { __typename?: 'Query', trainers: Array<{ __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string, deleteAt?: string | undefined }> }; +export type AdminTrainersPageQuery = { __typename?: 'Query', trainers: Array<{ __typename?: 'Trainer', id: string, firstName: string, lastName: string, deleteAt?: string | undefined }> }; export type AlexaLinkingUrlQueryVariables = Exact<{ [key: string]: never; }>; @@ -764,6 +773,11 @@ export type ArchivePageDataQueryVariables = Exact<{ [key: string]: never; }>; export type ArchivePageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, theme?: string | undefined, firstName: string, lastName: string, language?: string | undefined } | { __typename?: 'Trainee', id: string, theme?: string | undefined, firstName: string, lastName: string, language?: string | undefined } | { __typename?: 'Trainer', id: string, theme?: string | undefined, firstName: string, lastName: string, language?: string | undefined } | undefined, reports: Array<{ __typename: 'Report', id: string, week: number, year: number, status: ReportStatus, department?: string | undefined, summary?: string | undefined, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, entries: Array<{ __typename?: 'Entry', id: string, time: number, text: string }> }> } | undefined> }; +export type AvatarSettingsDataQueryVariables = Exact<{ [key: string]: never; }>; + + +export type AvatarSettingsDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string } | { __typename?: 'Trainee', id: string } | { __typename?: 'Trainer', id: string } | undefined }; + export type CommentBoxDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -785,29 +799,29 @@ export type DashboardPageDataQueryVariables = Exact<{ }>; -export type DashboardPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, theme?: string | undefined } | { __typename?: 'Trainee', id: string, theme?: string | undefined } | { __typename?: 'Trainer', id: string, theme?: string | undefined } | undefined, reports: Array<{ __typename: 'Report', id: string, week: number, year: number, status: ReportStatus, department?: string | undefined, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, entries: Array<{ __typename?: 'Entry', id: string, time: number }> }> } | undefined>, reportForYearAndWeek?: { __typename?: 'Report', id: string, status: ReportStatus, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, date: string, id: string, entries: Array<{ __typename?: 'Entry', id: string, text: string, time: number, orderId: number, comments: Array<{ __typename?: 'Comment', id: string, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> }>, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> }> } | undefined }; +export type DashboardPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, theme?: string | undefined } | { __typename?: 'Trainee', id: string, theme?: string | undefined } | { __typename?: 'Trainer', id: string, theme?: string | undefined } | undefined, reports: Array<{ __typename: 'Report', id: string, week: number, year: number, status: ReportStatus, department?: string | undefined, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, entries: Array<{ __typename?: 'Entry', id: string, time: number }> }> } | undefined>, reportForYearAndWeek?: { __typename?: 'Report', id: string, status: ReportStatus, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, date: string, id: string, entries: Array<{ __typename?: 'Entry', id: string, text: string, time: number, orderId: number, comments: Array<{ __typename?: 'Comment', id: string, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> }>, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> }> } | undefined }; export type DayInputDataQueryVariables = Exact<{ [key: string]: never; }>; -export type DayInputDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string, type: UserTypeEnum } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string, type: UserTypeEnum } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string, type: UserTypeEnum } | undefined }; +export type DayInputDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, type: UserTypeEnum } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, type: UserTypeEnum } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, type: UserTypeEnum } | undefined }; export type EntryInputDataQueryVariables = Exact<{ [key: string]: never; }>; -export type EntryInputDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, type: UserTypeEnum, firstName: string, lastName: string, avatar: string, username: string } | { __typename?: 'Trainee', id: string, type: UserTypeEnum, firstName: string, lastName: string, avatar: string, username: string } | { __typename?: 'Trainer', id: string, type: UserTypeEnum, firstName: string, lastName: string, avatar: string, username: string } | undefined }; +export type EntryInputDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, type: UserTypeEnum, firstName: string, lastName: string, username: string } | { __typename?: 'Trainee', id: string, type: UserTypeEnum, firstName: string, lastName: string, username: string } | { __typename?: 'Trainer', id: string, type: UserTypeEnum, firstName: string, lastName: string, username: string } | undefined }; export type UserPageQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type UserPageQuery = { __typename?: 'Query', getUser?: { __typename?: 'Admin', id: string, avatar: string, firstName: string, lastName: string, email: string, type: UserTypeEnum } | { __typename?: 'Trainee', startDate?: string | undefined, startOfToolUsage?: string | undefined, endDate?: string | undefined, deleteAt?: string | undefined, course?: string | undefined, id: string, avatar: string, firstName: string, lastName: string, email: string, type: UserTypeEnum, company: { __typename?: 'Company', id: string }, trainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } | undefined } | { __typename?: 'Trainer', deleteAt?: string | undefined, id: string, avatar: string, firstName: string, lastName: string, email: string, type: UserTypeEnum, trainees: Array<{ __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string }> } | undefined, companies?: Array<{ __typename?: 'Company', id: string, name: string }> | undefined }; +export type UserPageQuery = { __typename?: 'Query', getUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, email: string, type: UserTypeEnum } | { __typename?: 'Trainee', startDate?: string | undefined, startOfToolUsage?: string | undefined, endDate?: string | undefined, deleteAt?: string | undefined, course?: string | undefined, id: string, firstName: string, lastName: string, email: string, type: UserTypeEnum, company: { __typename?: 'Company', id: string }, trainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string } | undefined } | { __typename?: 'Trainer', deleteAt?: string | undefined, id: string, firstName: string, lastName: string, email: string, type: UserTypeEnum, trainees: Array<{ __typename?: 'Trainee', id: string, firstName: string, lastName: string }> } | undefined, companies?: Array<{ __typename?: 'Company', id: string, name: string }> | undefined }; export type NavigationDataQueryVariables = Exact<{ [key: string]: never; }>; -export type NavigationDataQuery = { __typename?: 'Query', currentUser?: { __typename: 'Admin', type: UserTypeEnum, id: string, firstName: string, lastName: string, avatar: string } | { __typename: 'Trainee', type: UserTypeEnum, id: string, firstName: string, lastName: string, avatar: string } | { __typename: 'Trainer', type: UserTypeEnum, id: string, firstName: string, lastName: string, avatar: string } | undefined }; +export type NavigationDataQuery = { __typename?: 'Query', currentUser?: { __typename: 'Admin', type: UserTypeEnum, id: string, firstName: string, lastName: string } | { __typename: 'Trainee', type: UserTypeEnum, id: string, firstName: string, lastName: string } | { __typename: 'Trainer', type: UserTypeEnum, id: string, firstName: string, lastName: string } | undefined }; export type OnboardingPageDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -827,7 +841,7 @@ export type ReportPageDataQueryVariables = Exact<{ }>; -export type ReportPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string, username: string } | { __typename?: 'Trainee', startOfToolUsage?: string | undefined, endOfToolUsage?: string | undefined, id: string, firstName: string, lastName: string, avatar: string, username: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string, username: string } | undefined, reportForYearAndWeek?: { __typename?: 'Report', id: string, week: number, year: number, summary?: string | undefined, department?: string | undefined, status: ReportStatus, previousReportLink?: string | undefined, nextReportLink?: string | undefined, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }>, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, date: string, id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }>, entries: Array<{ __typename?: 'Entry', id: string, text: string, time: number, orderId: number, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> }> }> } | undefined }; +export type ReportPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, username: string } | { __typename?: 'Trainee', startOfToolUsage?: string | undefined, endOfToolUsage?: string | undefined, id: string, firstName: string, lastName: string, username: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, username: string } | undefined, reportForYearAndWeek?: { __typename?: 'Report', id: string, week: number, year: number, summary?: string | undefined, department?: string | undefined, status: ReportStatus, previousReportLink?: string | undefined, nextReportLink?: string | undefined, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }>, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, date: string, id: string, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }>, entries: Array<{ __typename?: 'Entry', id: string, text: string, time: number, orderId: number, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> }> }> } | undefined }; export type ReportReviewPageDataQueryVariables = Exact<{ year: Scalars['Int']['input']; @@ -836,7 +850,7 @@ export type ReportReviewPageDataQueryVariables = Exact<{ }>; -export type ReportReviewPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string, username: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string, username: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string, username: string } | undefined, reportForTrainee?: { __typename?: 'Report', id: string, week: number, year: number, summary?: string | undefined, department?: string | undefined, status: ReportStatus, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }>, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, date: string, id: string, entries: Array<{ __typename?: 'Entry', id: string, text: string, time: number, orderId: number, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> }>, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, avatar: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string } }> }> } | undefined }; +export type ReportReviewPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, firstName: string, lastName: string, username: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string, username: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string, username: string } | undefined, reportForTrainee?: { __typename?: 'Report', id: string, week: number, year: number, summary?: string | undefined, department?: string | undefined, status: ReportStatus, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }>, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined, date: string, id: string, entries: Array<{ __typename?: 'Entry', id: string, text: string, time: number, orderId: number, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> }>, comments: Array<{ __typename?: 'Comment', id: string, text?: string | undefined, user: { __typename?: 'Admin', id: string, firstName: string, lastName: string } | { __typename?: 'Trainee', id: string, firstName: string, lastName: string } | { __typename?: 'Trainer', id: string, firstName: string, lastName: string } }> }> } | undefined }; export type SettingsPageDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -856,22 +870,22 @@ export type SuggestionsDataQuery = { __typename?: 'Query', suggestions: Array<{ export type TraineePageDataQueryVariables = Exact<{ [key: string]: never; }>; -export type TraineePageDataQuery = { __typename?: 'Query', trainees: Array<{ __typename?: 'Trainee', id: string, username: string, firstName: string, lastName: string, course?: string | undefined, avatar: string, startDate?: string | undefined, trainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string } | undefined, company: { __typename?: 'Company', id: string, name: string } }>, currentUser?: { __typename?: 'Admin', id: string } | { __typename?: 'Trainee', id: string } | { __typename?: 'Trainer', id: string } | undefined }; +export type TraineePageDataQuery = { __typename?: 'Query', trainees: Array<{ __typename?: 'Trainee', id: string, username: string, firstName: string, lastName: string, course?: string | undefined, startDate?: string | undefined, trainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string } | undefined, company: { __typename?: 'Company', id: string, name: string } }>, currentUser?: { __typename?: 'Admin', id: string } | { __typename?: 'Trainee', id: string } | { __typename?: 'Trainer', id: string } | undefined }; export type TraineeSettingsDataQueryVariables = Exact<{ [key: string]: never; }>; -export type TraineeSettingsDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin' } | { __typename: 'Trainee', id: string, startDate?: string | undefined, endDate?: string | undefined, course?: string | undefined, company: { __typename?: 'Company', id: string, name: string }, trainer?: { __typename?: 'Trainer', firstName: string, lastName: string, avatar: string } | undefined } | { __typename?: 'Trainer' } | undefined }; +export type TraineeSettingsDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin' } | { __typename: 'Trainee', id: string, startDate?: string | undefined, endDate?: string | undefined, course?: string | undefined, company: { __typename?: 'Company', id: string, name: string }, trainer?: { __typename?: 'Trainer', id: string, firstName: string, lastName: string } | undefined } | { __typename?: 'Trainer' } | undefined }; export type TrainerReportsPageDataQueryVariables = Exact<{ [key: string]: never; }>; -export type TrainerReportsPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, theme?: string | undefined } | { __typename?: 'Trainee', id: string, theme?: string | undefined } | { __typename?: 'Trainer', id: string, theme?: string | undefined, trainees: Array<{ __typename?: 'Trainee', username: string, firstName: string, lastName: string, id: string, avatar: string, openReportsCount: number, reports: Array<{ __typename?: 'Report', id: string, status: ReportStatus, week: number, year: number, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined }> }> }> } | undefined }; +export type TrainerReportsPageDataQuery = { __typename?: 'Query', currentUser?: { __typename?: 'Admin', id: string, theme?: string | undefined } | { __typename?: 'Trainee', id: string, theme?: string | undefined } | { __typename?: 'Trainer', id: string, theme?: string | undefined, trainees: Array<{ __typename?: 'Trainee', username: string, firstName: string, lastName: string, id: string, openReportsCount: number, reports: Array<{ __typename?: 'Report', id: string, status: ReportStatus, week: number, year: number, days: Array<{ __typename?: 'Day', status?: DayStatusEnum | undefined }> }> }> } | undefined }; export type TrainersPageQueryVariables = Exact<{ [key: string]: never; }>; -export type TrainersPageQuery = { __typename?: 'Query', trainers: Array<{ __typename?: 'Trainer', id: string, firstName: string, lastName: string, avatar: string }> }; +export type TrainersPageQuery = { __typename?: 'Query', trainers: Array<{ __typename?: 'Trainer', id: string, firstName: string, lastName: string }> }; export const ApplicationSettingsUpdateUserDocument = gql` @@ -892,6 +906,16 @@ export function useApplicationSettingsUpdateUserMutation(baseOptions?: Apollo.Mu return Apollo.useMutation(ApplicationSettingsUpdateUserDocument, options); } export type ApplicationSettingsUpdateUserMutationHookResult = ReturnType; +export const AvatarSettingsGetSignedUrlDocument = gql` + mutation AvatarSettingsGetSignedUrl($id: String!) { + getAvatarSignedUrl(id: $id) +} + `; +export function useAvatarSettingsGetSignedUrlMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(AvatarSettingsGetSignedUrlDocument, options); + } +export type AvatarSettingsGetSignedUrlMutationHookResult = ReturnType; export const ClaimTraineeDocument = gql` mutation claimTrainee($id: ID!) { claimTrainee(id: $id) { @@ -928,7 +952,6 @@ export const CreateCommentOnDayDocument = gql` id firstName lastName - avatar } } } @@ -954,7 +977,6 @@ export const CreateCommentOnEntryDocument = gql` id firstName lastName - avatar } } } @@ -980,7 +1002,6 @@ export const CreateCommentOnReportDocument = gql` id firstName lastName - avatar } } } @@ -1038,7 +1059,6 @@ export const CreateTraineeDocument = gql` startOfToolUsage endDate course - avatar firstName lastName email @@ -1056,7 +1076,6 @@ export const CreateTrainerDocument = gql` mutation CreateTrainer($input: CreateTrainerInput!) { createTrainer(input: $input) { id - avatar firstName lastName email @@ -1361,7 +1380,6 @@ export const UpdateTraineeDocument = gql` firstName lastName email - avatar company { id } @@ -1380,7 +1398,6 @@ export const UpdateTrainerDocument = gql` firstName lastName email - avatar type } } @@ -1396,7 +1413,6 @@ export const AdminTraineesPageDocument = gql` id firstName lastName - avatar deleteAt } companies { @@ -1426,7 +1442,6 @@ export const AdminTrainersPageDocument = gql` id firstName lastName - avatar deleteAt } } @@ -1509,6 +1524,28 @@ export function useArchivePageDataSuspenseQuery(baseOptions?: Apollo.SkipToken | export type ArchivePageDataQueryHookResult = ReturnType; export type ArchivePageDataLazyQueryHookResult = ReturnType; export type ArchivePageDataSuspenseQueryHookResult = ReturnType; +export const AvatarSettingsDataDocument = gql` + query AvatarSettingsData { + currentUser { + id + } +} + `; +export function useAvatarSettingsDataQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(AvatarSettingsDataDocument, options); + } +export function useAvatarSettingsDataLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(AvatarSettingsDataDocument, options); + } +export function useAvatarSettingsDataSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(AvatarSettingsDataDocument, options); + } +export type AvatarSettingsDataQueryHookResult = ReturnType; +export type AvatarSettingsDataLazyQueryHookResult = ReturnType; +export type AvatarSettingsDataSuspenseQueryHookResult = ReturnType; export const CommentBoxDataDocument = gql` query CommentBoxData { currentUser { @@ -1638,7 +1675,6 @@ export const DashboardPageDataDocument = gql` id firstName lastName - avatar } } } @@ -1649,7 +1685,6 @@ export const DashboardPageDataDocument = gql` id firstName lastName - avatar } } } @@ -1677,7 +1712,6 @@ export const DayInputDataDocument = gql` id firstName lastName - avatar type } } @@ -1704,7 +1738,6 @@ export const EntryInputDataDocument = gql` type firstName lastName - avatar username } } @@ -1728,7 +1761,6 @@ export const UserPageDocument = gql` query UserPage($id: ID!) { getUser(id: $id) { id - avatar firstName lastName email @@ -1746,7 +1778,6 @@ export const UserPageDocument = gql` id firstName lastName - avatar } } ... on Trainer { @@ -1755,7 +1786,6 @@ export const UserPageDocument = gql` id firstName lastName - avatar } } } @@ -1788,7 +1818,6 @@ export const NavigationDataDocument = gql` id firstName lastName - avatar } } `; @@ -1862,7 +1891,6 @@ export const ReportPageDataDocument = gql` id firstName lastName - avatar username } reportForYearAndWeek(year: $year, week: $week) { @@ -1881,7 +1909,6 @@ export const ReportPageDataDocument = gql` id firstName lastName - avatar } } days { @@ -1895,7 +1922,6 @@ export const ReportPageDataDocument = gql` id firstName lastName - avatar } } entries { @@ -1910,7 +1936,6 @@ export const ReportPageDataDocument = gql` id firstName lastName - avatar } } } @@ -1939,7 +1964,6 @@ export const ReportReviewPageDataDocument = gql` id firstName lastName - avatar username } reportForTrainee(year: $year, week: $week, id: $trainee) { @@ -1956,7 +1980,6 @@ export const ReportReviewPageDataDocument = gql` id firstName lastName - avatar } } days { @@ -1975,7 +1998,6 @@ export const ReportReviewPageDataDocument = gql` id firstName lastName - avatar } } } @@ -1986,7 +2008,6 @@ export const ReportReviewPageDataDocument = gql` id firstName lastName - avatar } } } @@ -2102,7 +2123,6 @@ export const TraineePageDataDocument = gql` firstName lastName course - avatar startDate trainer { id @@ -2149,9 +2169,9 @@ export const TraineeSettingsDataDocument = gql` __typename id trainer { + id firstName lastName - avatar } } } @@ -2183,7 +2203,6 @@ export const TrainerReportsPageDataDocument = gql` firstName lastName id - avatar openReportsCount reports { days { @@ -2220,7 +2239,6 @@ export const TrainersPageDocument = gql` id firstName lastName - avatar } } `; diff --git a/packages/frontend/src/graphql/mutations/avatar-settings-get-avatar.gql b/packages/frontend/src/graphql/mutations/avatar-settings-get-avatar.gql new file mode 100644 index 00000000..2003ec6e --- /dev/null +++ b/packages/frontend/src/graphql/mutations/avatar-settings-get-avatar.gql @@ -0,0 +1,3 @@ +mutation AvatarSettingsGetSignedUrl($id: String!) { + getAvatarSignedUrl(id: $id) +} diff --git a/packages/frontend/src/graphql/mutations/create-comment-on-day.gql b/packages/frontend/src/graphql/mutations/create-comment-on-day.gql index de4f0ead..14fcecc1 100644 --- a/packages/frontend/src/graphql/mutations/create-comment-on-day.gql +++ b/packages/frontend/src/graphql/mutations/create-comment-on-day.gql @@ -10,7 +10,6 @@ mutation createCommentOnDay($id: ID!, $text: String!, $traineeId: ID!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/mutations/create-comment-on-entry.gql b/packages/frontend/src/graphql/mutations/create-comment-on-entry.gql index 62bd3f5e..9cb92ce9 100644 --- a/packages/frontend/src/graphql/mutations/create-comment-on-entry.gql +++ b/packages/frontend/src/graphql/mutations/create-comment-on-entry.gql @@ -10,7 +10,6 @@ mutation createCommentOnEntry($id: ID!, $text: String!, $traineeId: ID!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/mutations/create-comment-on-report.gql b/packages/frontend/src/graphql/mutations/create-comment-on-report.gql index d521cd05..02796c76 100644 --- a/packages/frontend/src/graphql/mutations/create-comment-on-report.gql +++ b/packages/frontend/src/graphql/mutations/create-comment-on-report.gql @@ -10,7 +10,6 @@ mutation createCommentOnReport($id: ID!, $text: String!, $traineeId: ID!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/mutations/create-trainee.gql b/packages/frontend/src/graphql/mutations/create-trainee.gql index 1817ac67..895fd90e 100644 --- a/packages/frontend/src/graphql/mutations/create-trainee.gql +++ b/packages/frontend/src/graphql/mutations/create-trainee.gql @@ -8,7 +8,6 @@ mutation CreateTrainee($input: CreateTraineeInput!) { startOfToolUsage endDate course - avatar firstName lastName email diff --git a/packages/frontend/src/graphql/mutations/create-trainer.gql b/packages/frontend/src/graphql/mutations/create-trainer.gql index cdf1851b..04da1a50 100644 --- a/packages/frontend/src/graphql/mutations/create-trainer.gql +++ b/packages/frontend/src/graphql/mutations/create-trainer.gql @@ -1,7 +1,6 @@ mutation CreateTrainer($input: CreateTrainerInput!) { createTrainer(input: $input) { id - avatar firstName lastName email diff --git a/packages/frontend/src/graphql/mutations/update-trainee.gql b/packages/frontend/src/graphql/mutations/update-trainee.gql index af58e2f0..d23d8836 100644 --- a/packages/frontend/src/graphql/mutations/update-trainee.gql +++ b/packages/frontend/src/graphql/mutations/update-trainee.gql @@ -7,7 +7,6 @@ mutation UpdateTrainee($input: UpdateTraineeInput!, $id: ID!) { firstName lastName email - avatar company { id } diff --git a/packages/frontend/src/graphql/mutations/update-trainer.gql b/packages/frontend/src/graphql/mutations/update-trainer.gql index d2c74710..c1b2112c 100644 --- a/packages/frontend/src/graphql/mutations/update-trainer.gql +++ b/packages/frontend/src/graphql/mutations/update-trainer.gql @@ -4,7 +4,6 @@ mutation UpdateTrainer($input: UpdateTrainerInput!, $id: ID!) { firstName lastName email - avatar type } } diff --git a/packages/frontend/src/graphql/queries/admin-trainees-page.gql b/packages/frontend/src/graphql/queries/admin-trainees-page.gql index b3107f6a..f8d62282 100644 --- a/packages/frontend/src/graphql/queries/admin-trainees-page.gql +++ b/packages/frontend/src/graphql/queries/admin-trainees-page.gql @@ -3,7 +3,6 @@ query AdminTraineesPage { id firstName lastName - avatar deleteAt } companies { diff --git a/packages/frontend/src/graphql/queries/admin-trainers-page.gql b/packages/frontend/src/graphql/queries/admin-trainers-page.gql index c4e0fd64..4fdb4c8a 100644 --- a/packages/frontend/src/graphql/queries/admin-trainers-page.gql +++ b/packages/frontend/src/graphql/queries/admin-trainers-page.gql @@ -3,7 +3,6 @@ query AdminTrainersPage { id firstName lastName - avatar deleteAt } } diff --git a/packages/frontend/src/graphql/queries/avatar-settings-data.gql b/packages/frontend/src/graphql/queries/avatar-settings-data.gql new file mode 100644 index 00000000..4157c47e --- /dev/null +++ b/packages/frontend/src/graphql/queries/avatar-settings-data.gql @@ -0,0 +1,5 @@ +query AvatarSettingsData { + currentUser { + id + } +} diff --git a/packages/frontend/src/graphql/queries/dashboard-page-data.gql b/packages/frontend/src/graphql/queries/dashboard-page-data.gql index eb020c59..0e6cb124 100644 --- a/packages/frontend/src/graphql/queries/dashboard-page-data.gql +++ b/packages/frontend/src/graphql/queries/dashboard-page-data.gql @@ -36,7 +36,6 @@ query DashboardPageData($currentYear: Int!, $currentWeek: Int!) { id firstName lastName - avatar } } } @@ -47,7 +46,6 @@ query DashboardPageData($currentYear: Int!, $currentWeek: Int!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/queries/day-input-data.gql b/packages/frontend/src/graphql/queries/day-input-data.gql index 221c86b0..1599d666 100644 --- a/packages/frontend/src/graphql/queries/day-input-data.gql +++ b/packages/frontend/src/graphql/queries/day-input-data.gql @@ -3,7 +3,6 @@ query DayInputData { id firstName lastName - avatar type } } diff --git a/packages/frontend/src/graphql/queries/entry-input-data.gql b/packages/frontend/src/graphql/queries/entry-input-data.gql index 34d94e6d..ba46ad43 100644 --- a/packages/frontend/src/graphql/queries/entry-input-data.gql +++ b/packages/frontend/src/graphql/queries/entry-input-data.gql @@ -4,7 +4,6 @@ query EntryInputData { type firstName lastName - avatar username } } diff --git a/packages/frontend/src/graphql/queries/get-user.gql b/packages/frontend/src/graphql/queries/get-user.gql index 9e87e849..507e473f 100644 --- a/packages/frontend/src/graphql/queries/get-user.gql +++ b/packages/frontend/src/graphql/queries/get-user.gql @@ -1,7 +1,6 @@ query UserPage($id: ID!) { getUser(id: $id) { id - avatar firstName lastName email @@ -20,7 +19,6 @@ query UserPage($id: ID!) { id firstName lastName - avatar } } @@ -30,7 +28,6 @@ query UserPage($id: ID!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/queries/navigation-data.gql b/packages/frontend/src/graphql/queries/navigation-data.gql index 3b3e0f4d..6e36bbc8 100644 --- a/packages/frontend/src/graphql/queries/navigation-data.gql +++ b/packages/frontend/src/graphql/queries/navigation-data.gql @@ -5,6 +5,5 @@ query NavigationData { id firstName lastName - avatar } } diff --git a/packages/frontend/src/graphql/queries/report-page-data.gql b/packages/frontend/src/graphql/queries/report-page-data.gql index 11653b1a..187eee29 100644 --- a/packages/frontend/src/graphql/queries/report-page-data.gql +++ b/packages/frontend/src/graphql/queries/report-page-data.gql @@ -7,7 +7,6 @@ query ReportPageData($year: Int!, $week: Int!) { id firstName lastName - avatar username } reportForYearAndWeek(year: $year, week: $week) { @@ -26,7 +25,6 @@ query ReportPageData($year: Int!, $week: Int!) { id firstName lastName - avatar } } days { @@ -40,7 +38,6 @@ query ReportPageData($year: Int!, $week: Int!) { id firstName lastName - avatar } } entries { @@ -55,7 +52,6 @@ query ReportPageData($year: Int!, $week: Int!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/queries/report-review-page-data.gql b/packages/frontend/src/graphql/queries/report-review-page-data.gql index 2cace51a..4eec9429 100644 --- a/packages/frontend/src/graphql/queries/report-review-page-data.gql +++ b/packages/frontend/src/graphql/queries/report-review-page-data.gql @@ -3,7 +3,6 @@ query reportReviewPageData($year: Int!, $week: Int!, $trainee: ID!) { id firstName lastName - avatar username } reportForTrainee(year: $year, week: $week, id: $trainee) { @@ -20,7 +19,6 @@ query reportReviewPageData($year: Int!, $week: Int!, $trainee: ID!) { id firstName lastName - avatar } } days { @@ -39,7 +37,6 @@ query reportReviewPageData($year: Int!, $week: Int!, $trainee: ID!) { id firstName lastName - avatar } } } @@ -50,7 +47,6 @@ query reportReviewPageData($year: Int!, $week: Int!, $trainee: ID!) { id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/queries/trainee-page-data.gql b/packages/frontend/src/graphql/queries/trainee-page-data.gql index 835dc1f7..925850d9 100644 --- a/packages/frontend/src/graphql/queries/trainee-page-data.gql +++ b/packages/frontend/src/graphql/queries/trainee-page-data.gql @@ -5,7 +5,6 @@ query TraineePageData { firstName lastName course - avatar startDate trainer { id diff --git a/packages/frontend/src/graphql/queries/trainee-settings-data.gql b/packages/frontend/src/graphql/queries/trainee-settings-data.gql index 8917c869..1b884fa4 100644 --- a/packages/frontend/src/graphql/queries/trainee-settings-data.gql +++ b/packages/frontend/src/graphql/queries/trainee-settings-data.gql @@ -12,9 +12,9 @@ query TraineeSettingsData { __typename id trainer { + id firstName lastName - avatar } } } diff --git a/packages/frontend/src/graphql/queries/trainer-reports-page-data.gql b/packages/frontend/src/graphql/queries/trainer-reports-page-data.gql index 0d35d862..e0b46fd9 100644 --- a/packages/frontend/src/graphql/queries/trainer-reports-page-data.gql +++ b/packages/frontend/src/graphql/queries/trainer-reports-page-data.gql @@ -8,7 +8,6 @@ query TrainerReportsPageData { firstName lastName id - avatar openReportsCount reports { days { diff --git a/packages/frontend/src/graphql/queries/trainers-page.gql b/packages/frontend/src/graphql/queries/trainers-page.gql index ccd78e65..df8a4bde 100644 --- a/packages/frontend/src/graphql/queries/trainers-page.gql +++ b/packages/frontend/src/graphql/queries/trainers-page.gql @@ -3,6 +3,5 @@ query TrainersPage { id firstName lastName - avatar } } diff --git a/packages/frontend/src/helper/event-bus-helper.ts b/packages/frontend/src/helper/event-bus-helper.ts new file mode 100644 index 00000000..b20420fa --- /dev/null +++ b/packages/frontend/src/helper/event-bus-helper.ts @@ -0,0 +1,30 @@ +type EventMap = { + avatarUpdated: undefined +} + +type EventKey = keyof EventMap +type EventCallback = (data: EventMap[K]) => void + +class EventBus { + private listeners: { [key: string]: unknown[] } = {} + + on(event: K, callback: EventCallback): void { + if (!this.listeners[event]) { + this.listeners[event] = [] + } + ;(this.listeners[event] as EventCallback[]).push(callback) + } + + off(event: K, callback: EventCallback): void { + if (this.listeners[event]) { + this.listeners[event] = (this.listeners[event] as EventCallback[]).filter((listener) => listener !== callback) + } + } + + emit(event: K, data: EventMap[K]): void { + ;(this.listeners[event] as EventCallback[] | undefined)?.forEach((listener) => listener(data)) + } +} + +const eventBus = new EventBus() +export default eventBus diff --git a/packages/frontend/src/locales/de.ts b/packages/frontend/src/locales/de.ts index a6a73259..382085bc 100644 --- a/packages/frontend/src/locales/de.ts +++ b/packages/frontend/src/locales/de.ts @@ -169,6 +169,28 @@ const germanTranslation: Translation = { }, other: 'Sonstiges', notification: 'E-Mail Benachrichtigungen erhalten', + avatar: { + label: 'Profilbild', + title: 'Füge ein Profilbild hinzu', + description: 'Lade ein eigenens Profilbild hoch.', + addAvatar: 'Bild hochladen', + removeAvatar: 'Bild entfernen', + updateAvatar: 'Bild ändern', + toasts: { + success: { + title: 'Bild hochgeladen', + description: 'Ihr Bild konnte erfolgreich hochgeladen werden.', + }, + errorCompression: { + title: 'Komprimieren fehlgeschlagen', + description: 'Datei konnte nicht unter 250 KB komprimiert werden.', + }, + errorFileSize: { + title: 'Hochladen fehlgeschlagen', + description: 'Datei war zu groß. Maximale Größe ist 250 KB.', + }, + }, + }, signature: { title: 'Unterschrift', deleteSignature: 'Unterschrift löschen', diff --git a/packages/frontend/src/locales/en.ts b/packages/frontend/src/locales/en.ts index 1ae5ed5c..c55cb0cb 100644 --- a/packages/frontend/src/locales/en.ts +++ b/packages/frontend/src/locales/en.ts @@ -167,6 +167,28 @@ const englishTranslation: Translation = { }, other: 'other', notification: 'Get mail notifications', + avatar: { + label: 'profile picture', + title: 'Add a profile picture', + description: 'Upload your own profile picture.', + addAvatar: 'Add image', + removeAvatar: 'Remove image', + updateAvatar: 'Update image', + toasts: { + success: { + title: 'Image uploaded', + description: 'Your image was uploaded successfully.', + }, + errorCompression: { + title: 'Compression failed', + description: 'Could not compress file to below 250 KB.', + }, + errorFileSize: { + title: 'Upload failed', + description: 'File was too large. Maximum is 250 KB.', + }, + }, + }, signature: { title: 'signature', deleteSignature: 'Delete signature', diff --git a/packages/frontend/src/locales/translation.ts b/packages/frontend/src/locales/translation.ts index feb90421..d818f265 100644 --- a/packages/frontend/src/locales/translation.ts +++ b/packages/frontend/src/locales/translation.ts @@ -160,6 +160,28 @@ export default interface Translation { } other: string notification: string + avatar: { + label: string + title: string + description: string + addAvatar: string + removeAvatar: string + updateAvatar: string + toasts: { + success: { + title: string + description: string + } + errorCompression: { + title: string + description: string + } + errorFileSize: { + title: string + description: string + } + } + } signature: { title: string addSignature: string diff --git a/packages/frontend/src/pages/report-review-page.tsx b/packages/frontend/src/pages/report-review-page.tsx index 7446b4ef..a763e4b3 100644 --- a/packages/frontend/src/pages/report-review-page.tsx +++ b/packages/frontend/src/pages/report-review-page.tsx @@ -166,7 +166,7 @@ const ReportReviewPage: React.FunctionComponent = () => { ( day: Pick & { comments: (Pick & { - user: Pick + user: Pick })[] } ) => diff --git a/packages/frontend/src/pages/settings-page.tsx b/packages/frontend/src/pages/settings-page.tsx index 923ef2be..e9c7293e 100644 --- a/packages/frontend/src/pages/settings-page.tsx +++ b/packages/frontend/src/pages/settings-page.tsx @@ -10,6 +10,7 @@ import TraineeSettings from '../components/trainee-settings' import { UserTypeEnum, useSettingsPageDataQuery } from '../graphql' import strings from '../locales/localization' import { Template } from '../templates/template' +import AvatarSettings from '../components/avatar-settings' const SettingsPage: React.FunctionComponent = () => { const { loading, data } = useSettingsPageDataQuery() @@ -34,19 +35,30 @@ const SettingsPage: React.FunctionComponent = () => { )} {/* Admin Settings */} - {!loading && data?.currentUser && data.currentUser.type !== UserTypeEnum.Admin && ( - + {!loading && data?.currentUser && ( +

{strings.settings.user}

- {strings.settings.signature.title} + + {strings.settings.avatar.label} - + + {data.currentUser.type !== UserTypeEnum.Admin && ( + + {strings.settings.signature.title} + + + + + + )} + {data.currentUser.__typename === 'Trainee' && ( {strings.settings.alexa.headline} diff --git a/readme.md b/readme.md index dc37d976..a4d4807f 100644 --- a/readme.md +++ b/readme.md @@ -120,8 +120,6 @@ PDF printing is developed inside `packages/print`. Run `yarn debug` inside this - OLD_COMPANY_NAME - NEW_COMPANY_NAME - Add these two, if you have mails mapped from an old name to a new name. If not provided, no changes will be applied to usage of email addresses. -- AVATAR_URL - - String containing the URL to avatars. In the end a mailhash from the user is added. If not provided random avatars from dicebear are used. - SES_EMAIL - String containing the email address of the bot sending notifications e.g.: lara-bot@exampleCompany.com - SES_REGION @@ -162,8 +160,6 @@ The following variables can be added to your cloned version of Lara. - OLD_COMPANY_NAME - NEW_COMPANY_NAME - Add these two, if you have mails mapped from an old name to a new name. If not provided, no changes will be applied to usage of email addresses. -- AVATAR_URL - - String containing the URL to avatars. In the end a mailhash from the user is added. If not provided random avatars from dicebear are used. - SES_EMAIL - String containing the email address of the bot sending notifications e.g.: lara-bot@exampleCompany.com - SES_REGION diff --git a/serverless.yml b/serverless.yml index 53391503..713c6f7c 100644 --- a/serverless.yml +++ b/serverless.yml @@ -77,6 +77,7 @@ custom: s3: export: ${self:provider.environment.COMPANY_ABBREVIATION}-${self:custom.stage}-lara-export-bucket frontend: ${self:provider.environment.COMPANY_ABBREVIATION}-lara-frontend-${self:custom.stage} + avatar: ${self:provider.environment.COMPANY_ABBREVIATION}-${self:custom.stage}-lara-avatar-bucket ses: region: ${self:provider.environment.SES_REGION, self:provider.region} @@ -145,6 +146,7 @@ functions: - 's3:GetObject' Resource: - 'arn:aws:s3:::${self:custom.config.s3.export}/*' + - 'arn:aws:s3:::${self:custom.config.s3.avatar}/*' environment: STAGE: ${self:custom.stage} USER_TABLE: ${self:custom.config.table.user} @@ -159,6 +161,7 @@ functions: USER_TRAINER_ID_INDEX: ${self:custom.config.gsi.userTrainerId} USER_OAUTH_CODE_INDEX: ${self:custom.config.gsi.userOAuthCode} EXPORT_BUCKET: ${self:custom.config.s3.export} + AVATAR_BUCKET: ${self:custom.config.s3.avatar} authorizer: handler: packages/authorizer/lib/handler.handler @@ -375,6 +378,11 @@ resources: IgnorePublicAcls: true RestrictPublicBuckets: true + AvatarBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: ${self:custom.config.s3.avatar} + FrontendDistribution: Type: AWS::CloudFront::Distribution Properties: @@ -540,6 +548,24 @@ resources: Bool: 'aws:SecureTransport': 'false' + AvatarBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref AvatarBucket + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: 'AllowSSLRequestsOnly' + Effect: Deny + Principal: '*' + Action: 's3:*' + Resource: + - !Sub 'arn:aws:s3:::${AvatarBucket}' + - !Sub 'arn:aws:s3:::${AvatarBucket}/*' + Condition: + Bool: + 'aws:SecureTransport': 'false' + CustomCorsAndSecurityHeadersPolicy: Type: AWS::CloudFront::ResponseHeadersPolicy Properties: diff --git a/yarn.lock b/yarn.lock index b8cc5c32..ec2d6767 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ d "1" es5-ext "^0.10.47" -"@apollo/client@^3.2.2": +"@apollo/client@^3.14.0": version "3.14.0" resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.14.0.tgz#eda34b85ee03c6c0828ba21782419c8699feaf0b" integrity sha512-0YQKKRIxiMlIou+SekQqdCo0ZTHxOcES+K8vKB53cIDpwABNR0P0yRzPgsbgcj3zRJniD93S/ontsnZsCLZrxQ== @@ -260,17 +260,17 @@ tslib "^2.6.2" "@aws-sdk/client-api-gateway@^3.588.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-api-gateway/-/client-api-gateway-3.883.0.tgz#47efa1ea941e0a7bbf8bfa6b5259690c802960e5" - integrity sha512-blVrG8SrtDuWFGtiJIXsCPpxMRt599GBv3Yt4yHbc/nooHZzGVFX33pR/dRn+qmCWdwUgClW6C6lt0eqnK8nuQ== + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-api-gateway/-/client-api-gateway-3.886.0.tgz#3e7a0cd5802c3d3bcf55d2af69e4ba558aaa82ab" + integrity sha512-+hn8qxENnf8rO8k92hKztbXTEJtZpa84p4ISDprCT+J3aZTZu8Q8KueiVqG+G9vc8ceR69e6jcwaL7RiJ9toIA== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-sdk-api-gateway" "3.873.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" @@ -307,17 +307,17 @@ tslib "^2.6.2" "@aws-sdk/client-cloudformation@^3.410.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cloudformation/-/client-cloudformation-3.883.0.tgz#1b3134d626719df8b3c2e0259840b3776f8ac27d" - integrity sha512-5Zl79ZpzsjhopoCy/JzEzUXvky7Tp3a4Cfe3dnmlWY6xuTqW5Ob0Qvi0JOSD7tAi1MW91xcrwED1VGVlzGzz+w== + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-cloudformation/-/client-cloudformation-3.886.0.tgz#5abf772cb7488f2a3aa8f9fef5d8acf5f8892ffa" + integrity sha512-KvVEP8XfDfBSZFqTumNi+FZHBv+m0MSlAsVw/AIgnThtr/SWVKomYP3SGLreg18QpE+grCfNJ+Laf1WeYe+YmA== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" "@aws-sdk/types" "3.862.0" @@ -355,17 +355,17 @@ uuid "^9.0.1" "@aws-sdk/client-cognito-identity-provider@^3.588.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.883.0.tgz#41ca15f923d434ca56a5010f73ea742f519126a6" - integrity sha512-yj3RSIkgR3yGtHajvljF+Pc4LKjqCCF3Ud0McheKxJECm4cXgBRNq7uU18niJ2MKReQ7Atu/8Dl3y5GURQrU/g== + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.886.0.tgz#626e39c624ace15c41f89b3b54c8b9c9e59972e4" + integrity sha512-cfCTnGlWv1scGOAOSa5hEOdg4F3zHdlUu5oSRWgb7xcHTKeHf/BdshMQv3lBWG7Qbh1s8RvWbV3+yhtaF+B0Fw== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" "@aws-sdk/types" "3.862.0" @@ -399,7 +399,7 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" -"@aws-sdk/client-dynamodb@3.883.0", "@aws-sdk/client-dynamodb@^3.428.0", "@aws-sdk/client-dynamodb@^3.693.0": +"@aws-sdk/client-dynamodb@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-dynamodb/-/client-dynamodb-3.883.0.tgz#709df04216d43d173b3e381ca4ee8ef035482709" integrity sha512-7Mrlix4PW+zoXH3fGYPMRgkxpFmHlD5qgfsZgkZ2glddtTxZbuTgKGwPSPu22Ax55Jiz3HRZgV5wZ2T0bHgZgA== @@ -448,18 +448,67 @@ tslib "^2.6.2" uuid "^9.0.1" +"@aws-sdk/client-dynamodb@^3.428.0", "@aws-sdk/client-dynamodb@^3.693.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-dynamodb/-/client-dynamodb-3.886.0.tgz#7f4c2e0d3abd10c0320f7591cbdf50294260dea3" + integrity sha512-go90uKE7vvZ6HCaS66hG9GFESzArc2LwVydYCcC8cE+R9iOEzGhm/0lD7wDItcybNZa6F+Cbao0ZkyvtNFOt3g== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" + "@aws-sdk/middleware-endpoint-discovery" "3.873.0" + "@aws-sdk/middleware-host-header" "3.873.0" + "@aws-sdk/middleware-logger" "3.876.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" + "@aws-sdk/middleware-user-agent" "3.883.0" + "@aws-sdk/region-config-resolver" "3.873.0" + "@aws-sdk/types" "3.862.0" + "@aws-sdk/util-endpoints" "3.879.0" + "@aws-sdk/util-user-agent-browser" "3.873.0" + "@aws-sdk/util-user-agent-node" "3.883.0" + "@smithy/config-resolver" "^4.1.5" + "@smithy/core" "^3.9.2" + "@smithy/fetch-http-handler" "^5.1.1" + "@smithy/hash-node" "^4.0.5" + "@smithy/invalid-dependency" "^4.0.5" + "@smithy/middleware-content-length" "^4.0.5" + "@smithy/middleware-endpoint" "^4.1.21" + "@smithy/middleware-retry" "^4.1.22" + "@smithy/middleware-serde" "^4.0.9" + "@smithy/middleware-stack" "^4.0.5" + "@smithy/node-config-provider" "^4.1.4" + "@smithy/node-http-handler" "^4.1.1" + "@smithy/protocol-http" "^5.1.3" + "@smithy/smithy-client" "^4.5.2" + "@smithy/types" "^4.3.2" + "@smithy/url-parser" "^4.0.5" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.29" + "@smithy/util-defaults-mode-node" "^4.0.29" + "@smithy/util-endpoints" "^3.0.7" + "@smithy/util-middleware" "^4.0.5" + "@smithy/util-retry" "^4.0.7" + "@smithy/util-utf8" "^4.0.0" + "@smithy/util-waiter" "^4.0.7" + "@types/uuid" "^9.0.1" + tslib "^2.6.2" + uuid "^9.0.1" + "@aws-sdk/client-eventbridge@^3.588.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-eventbridge/-/client-eventbridge-3.883.0.tgz#f2d5a6cf343dee2a8e94f845ebf6156fcf407ef4" - integrity sha512-npDVVLVhZ6OGmqIQoM56yijI9glV0z3/1eEGH9TZPfEY7jXO2lofZtueNuijPD6bMKdxLZE0LJ1CooHnbTwCqg== + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-eventbridge/-/client-eventbridge-3.886.0.tgz#b32e971aee76e01d969207c876f0f345e897cb41" + integrity sha512-mw0WYJuHcj+3k6UeXJoOowk+ELqtuhR6jQHyjQPEnKzoX3tY9JtYwlkwDYkeWfUe3dMA4zWRrjD6pQEJyyamMA== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" "@aws-sdk/signature-v4-multi-region" "3.883.0" @@ -495,17 +544,17 @@ tslib "^2.6.2" "@aws-sdk/client-iam@^3.588.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-iam/-/client-iam-3.883.0.tgz#fec3dbf196ce530613ccbf9887ebc2c5653be73f" - integrity sha512-UCU6qC2iNO1wmgRFjp4xbvn23wQSH4ATxooc8tgR3TH12lQzYV34ksseFx3mpP8JRE8QUgJ6EVX8yuf9XjNWTA== + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-iam/-/client-iam-3.886.0.tgz#ab7c458ce6a304a482630e93295decbf6f885092" + integrity sha512-w/DJIoG2nOlSyuGKTl7d4AWs7hJ3+J612jLYJujqne0H6LlkPUZTW9IBNmzMXWuF3JYM8rVOMTfRpocKkcWS6A== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" "@aws-sdk/types" "3.862.0" @@ -540,7 +589,7 @@ "@smithy/util-waiter" "^4.0.7" tslib "^2.6.2" -"@aws-sdk/client-lambda@3.883.0", "@aws-sdk/client-lambda@^3.588.0", "@aws-sdk/client-lambda@^3.636.0": +"@aws-sdk/client-lambda@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-lambda/-/client-lambda-3.883.0.tgz#e6db6ced133016a3f2d42105fba3bc7de21321d2" integrity sha512-kLqPovA/6N1Qfw10t2hyYEMf3bIM9zxXxtB5ADgUURYRKqjOnC7RuOhr7ezNVxNsZu5tjJJxrYWf3HljOuYYhQ== @@ -590,6 +639,56 @@ "@smithy/util-waiter" "^4.0.7" tslib "^2.6.2" +"@aws-sdk/client-lambda@^3.588.0", "@aws-sdk/client-lambda@^3.636.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-lambda/-/client-lambda-3.886.0.tgz#782bcf14239349967ed6e6fdf663a8808fde2431" + integrity sha512-wSwwDRHx2nRiHuQhl7Z1GMOay0woxPmwM9xEszR5tfd3Aw6KULwlAd0wJW0yN13K64s52iBMEMMmNrlFsAIaZw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" + "@aws-sdk/middleware-host-header" "3.873.0" + "@aws-sdk/middleware-logger" "3.876.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" + "@aws-sdk/middleware-user-agent" "3.883.0" + "@aws-sdk/region-config-resolver" "3.873.0" + "@aws-sdk/types" "3.862.0" + "@aws-sdk/util-endpoints" "3.879.0" + "@aws-sdk/util-user-agent-browser" "3.873.0" + "@aws-sdk/util-user-agent-node" "3.883.0" + "@smithy/config-resolver" "^4.1.5" + "@smithy/core" "^3.9.2" + "@smithy/eventstream-serde-browser" "^4.0.5" + "@smithy/eventstream-serde-config-resolver" "^4.1.3" + "@smithy/eventstream-serde-node" "^4.0.5" + "@smithy/fetch-http-handler" "^5.1.1" + "@smithy/hash-node" "^4.0.5" + "@smithy/invalid-dependency" "^4.0.5" + "@smithy/middleware-content-length" "^4.0.5" + "@smithy/middleware-endpoint" "^4.1.21" + "@smithy/middleware-retry" "^4.1.22" + "@smithy/middleware-serde" "^4.0.9" + "@smithy/middleware-stack" "^4.0.5" + "@smithy/node-config-provider" "^4.1.4" + "@smithy/node-http-handler" "^4.1.1" + "@smithy/protocol-http" "^5.1.3" + "@smithy/smithy-client" "^4.5.2" + "@smithy/types" "^4.3.2" + "@smithy/url-parser" "^4.0.5" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.29" + "@smithy/util-defaults-mode-node" "^4.0.29" + "@smithy/util-endpoints" "^3.0.7" + "@smithy/util-middleware" "^4.0.5" + "@smithy/util-retry" "^4.0.7" + "@smithy/util-stream" "^4.2.4" + "@smithy/util-utf8" "^4.0.0" + "@smithy/util-waiter" "^4.0.7" + tslib "^2.6.2" + "@aws-sdk/client-s3@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.883.0.tgz#5a3ca0737534b7d96ea6b41c12f4b14da1cc63fe" @@ -654,23 +753,23 @@ tslib "^2.6.2" uuid "^9.0.1" -"@aws-sdk/client-s3@^3.53.1", "@aws-sdk/client-s3@^3.588.0": - version "3.884.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.884.0.tgz#0d53c3a7d118dd0216e20796a16818c4e62a20e8" - integrity sha512-Okwg52/iho5wMCzd7A70g50k+bEYS+VUbSjD3NOzwrvZ3c7DwYjDUt13hVnUwMGygTVL+NGSpXSziTZe9P3/Cg== +"@aws-sdk/client-s3@^3.53.1", "@aws-sdk/client-s3@^3.588.0", "@aws-sdk/client-s3@^3.883.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.886.0.tgz#5ee89042c0b7cc492549233af2c1802611369cf3" + integrity sha512-IuZ2EHiSMkEqjOJk/QMQG55zGpj/iDkKNS1V3oKV6ClstmErySBfW0Ri8GuW7WJ1k1pkFO+hvgL6yn1yO+Y6XQ== dependencies: "@aws-crypto/sha1-browser" "5.2.0" "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-bucket-endpoint" "3.873.0" "@aws-sdk/middleware-expect-continue" "3.873.0" "@aws-sdk/middleware-flexible-checksums" "3.883.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-location-constraint" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-sdk-s3" "3.883.0" "@aws-sdk/middleware-ssec" "3.873.0" "@aws-sdk/middleware-user-agent" "3.883.0" @@ -718,18 +817,18 @@ tslib "^2.6.2" uuid "^9.0.1" -"@aws-sdk/client-sesv2@3.883.0", "@aws-sdk/client-sesv2@^3.839.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sesv2/-/client-sesv2-3.883.0.tgz#995792131d4b0387bbd179a6c44bccfdf0e7882d" - integrity sha512-3HVaYgOTk0WCGUKN26yb1OGMl7VIP5w+fjm5SKDupbP179KoFaG7UfzDspFrulYadIN+rOjAPIgNY9p128yAKQ== +"@aws-sdk/client-sesv2@^3.839.0", "@aws-sdk/client-sesv2@^3.883.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sesv2/-/client-sesv2-3.886.0.tgz#dd1d82fd99a5160863dced8a7ad2d1b4822ae6e0" + integrity sha512-jqQ083dedxG0dJmFFtgBzAf9RHnY/eVOz8NFYDXvGRF2+vr1vIKWZNo94LcD7FrmR4R2utQdp7aMXp+Pp4X8gA== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" "@aws-sdk/signature-v4-multi-region" "3.883.0" @@ -808,18 +907,62 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" +"@aws-sdk/client-sso@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.886.0.tgz#127dbe0651f90ac39e71b08001c673507dac80be" + integrity sha512-CwpPZBlONsUO6OvMzNNP9PETZ3dPCQum3nUisk5VuzLTvNd80w2aWeSN/TpcAAbNvcRYbM+FsC4gBm4Q4VWn0g== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.883.0" + "@aws-sdk/middleware-host-header" "3.873.0" + "@aws-sdk/middleware-logger" "3.876.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" + "@aws-sdk/middleware-user-agent" "3.883.0" + "@aws-sdk/region-config-resolver" "3.873.0" + "@aws-sdk/types" "3.862.0" + "@aws-sdk/util-endpoints" "3.879.0" + "@aws-sdk/util-user-agent-browser" "3.873.0" + "@aws-sdk/util-user-agent-node" "3.883.0" + "@smithy/config-resolver" "^4.1.5" + "@smithy/core" "^3.9.2" + "@smithy/fetch-http-handler" "^5.1.1" + "@smithy/hash-node" "^4.0.5" + "@smithy/invalid-dependency" "^4.0.5" + "@smithy/middleware-content-length" "^4.0.5" + "@smithy/middleware-endpoint" "^4.1.21" + "@smithy/middleware-retry" "^4.1.22" + "@smithy/middleware-serde" "^4.0.9" + "@smithy/middleware-stack" "^4.0.5" + "@smithy/node-config-provider" "^4.1.4" + "@smithy/node-http-handler" "^4.1.1" + "@smithy/protocol-http" "^5.1.3" + "@smithy/smithy-client" "^4.5.2" + "@smithy/types" "^4.3.2" + "@smithy/url-parser" "^4.0.5" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.29" + "@smithy/util-defaults-mode-node" "^4.0.29" + "@smithy/util-endpoints" "^3.0.7" + "@smithy/util-middleware" "^4.0.5" + "@smithy/util-retry" "^4.0.7" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + "@aws-sdk/client-sts@^3.410.0": - version "3.883.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.883.0.tgz#3408adb565e1003e26bd675540910802d497eba8" - integrity sha512-S3j+lkUPmgGq8fOHMFXXQI/P51Pf7Q6eA6vbJW5hztkgw+OMNcAuOiUJ2p9sTWA1XaAIkcqOPfx80xgDP939lg== + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.886.0.tgz#4b1e5664f3ca26d8136a7b77933448483ab408b9" + integrity sha512-7ucGZylYyxtaTSJSOWLzsWdQ9bQoH8Yt+IPznTRCauX3oXPHKxBUCeikWwkcVzYCrjLHvGpej9fxWCxH67jqrQ== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" "@aws-sdk/core" "3.883.0" - "@aws-sdk/credential-provider-node" "3.883.0" + "@aws-sdk/credential-provider-node" "3.886.0" "@aws-sdk/middleware-host-header" "3.873.0" "@aws-sdk/middleware-logger" "3.876.0" - "@aws-sdk/middleware-recursion-detection" "3.873.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" "@aws-sdk/middleware-user-agent" "3.883.0" "@aws-sdk/region-config-resolver" "3.873.0" "@aws-sdk/types" "3.862.0" @@ -920,6 +1063,25 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/credential-provider-ini@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.886.0.tgz#6f5bb13252b065477daebd7bb618b5eb3e6b3527" + integrity sha512-86ZuuUGLzzYqxkglFBUMCsvb7vSr+IeIPkXD/ERuX9wX0xPxBK961UG7pygO7yaAVzcHSWbWArAXOcEWVlk+7Q== + dependencies: + "@aws-sdk/core" "3.883.0" + "@aws-sdk/credential-provider-env" "3.883.0" + "@aws-sdk/credential-provider-http" "3.883.0" + "@aws-sdk/credential-provider-process" "3.883.0" + "@aws-sdk/credential-provider-sso" "3.886.0" + "@aws-sdk/credential-provider-web-identity" "3.886.0" + "@aws-sdk/nested-clients" "3.886.0" + "@aws-sdk/types" "3.862.0" + "@smithy/credential-provider-imds" "^4.0.7" + "@smithy/property-provider" "^4.0.5" + "@smithy/shared-ini-file-loader" "^4.0.5" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/credential-provider-node@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.883.0.tgz#70cea5890fd040ac18cf045f693f2bb604755d58" @@ -938,6 +1100,24 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/credential-provider-node@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.886.0.tgz#89073d8a27689c56174fcf8215e248818b70916c" + integrity sha512-hyXQrUW6bXkSWOZlNWnNcbXsjM0CBIOfutDFd3tS7Ilhqkx8P3eptT0fVR8GFxNg/ruq5PvnybGK83brUmD7tw== + dependencies: + "@aws-sdk/credential-provider-env" "3.883.0" + "@aws-sdk/credential-provider-http" "3.883.0" + "@aws-sdk/credential-provider-ini" "3.886.0" + "@aws-sdk/credential-provider-process" "3.883.0" + "@aws-sdk/credential-provider-sso" "3.886.0" + "@aws-sdk/credential-provider-web-identity" "3.886.0" + "@aws-sdk/types" "3.862.0" + "@smithy/credential-provider-imds" "^4.0.7" + "@smithy/property-provider" "^4.0.5" + "@smithy/shared-ini-file-loader" "^4.0.5" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/credential-provider-process@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.883.0.tgz#00938bd9257a7affb041386d92b708dbdee8dc58" @@ -964,6 +1144,20 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/credential-provider-sso@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.886.0.tgz#87899424255801ce85b0d711708b61d21e69b839" + integrity sha512-KxNgGcT/2ec7XBhiYGBYlk+UyiMqosi5LzLjq2qR4nYf8Deo/lCtbqXSQplwSQ0JIV2kNDcnMQiSafSS9TrL/A== + dependencies: + "@aws-sdk/client-sso" "3.886.0" + "@aws-sdk/core" "3.883.0" + "@aws-sdk/token-providers" "3.886.0" + "@aws-sdk/types" "3.862.0" + "@smithy/property-provider" "^4.0.5" + "@smithy/shared-ini-file-loader" "^4.0.5" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/credential-provider-web-identity@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.883.0.tgz#9b71ca87c748fff3999eabf966b09f0e280bc67c" @@ -976,6 +1170,18 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/credential-provider-web-identity@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.886.0.tgz#9f234d8624447e498a901acb1840cb0ebe19a985" + integrity sha512-pilcy1GUOr4lIWApTcgJLGL+t79SOoe66pzmranQhbn+HGAp2VgiZizeID9P3HLmZObStVal4yTaJur0hWb5ZQ== + dependencies: + "@aws-sdk/core" "3.883.0" + "@aws-sdk/nested-clients" "3.886.0" + "@aws-sdk/types" "3.862.0" + "@smithy/property-provider" "^4.0.5" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/endpoint-cache@3.873.0": version "3.873.0" resolved "https://registry.yarnpkg.com/@aws-sdk/endpoint-cache/-/endpoint-cache-3.873.0.tgz#f0b271d43210924731b15feebc13d9252a567f96" @@ -984,7 +1190,7 @@ mnemonist "0.38.3" tslib "^2.6.2" -"@aws-sdk/lib-dynamodb@3.883.0", "@aws-sdk/lib-dynamodb@^3.428.0", "@aws-sdk/lib-dynamodb@^3.693.0": +"@aws-sdk/lib-dynamodb@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.883.0.tgz#c57050a486769d73736484bdb37059e7a0202f7e" integrity sha512-eLlgfePMRXBDBkHrLYcH0W+DjRg1gVbX8jUQExZ2R+0lotv8Gma53ZQaVYgLwR+sX/QIm29pdvMNeBUNrm2ksg== @@ -996,6 +1202,18 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/lib-dynamodb@^3.428.0", "@aws-sdk/lib-dynamodb@^3.693.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.886.0.tgz#cb922ff392d11f7b88ee72307dacde2b8fdbc616" + integrity sha512-L5cabeZjStLjbIRILn3PoMurO5D+M648KFzw/wX6Gp+PktEqMeCxPVjXt5JVCpH5zMjx+kBingCvLEfwIToQQA== + dependencies: + "@aws-sdk/core" "3.883.0" + "@aws-sdk/util-dynamodb" "3.886.0" + "@smithy/core" "^3.9.2" + "@smithy/smithy-client" "^4.5.2" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/middleware-bucket-endpoint@3.873.0": version "3.873.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.873.0.tgz#cfc2d87328e3d9fecd165e4f5caa4cf1a22b220d" @@ -1088,6 +1306,17 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/middleware-recursion-detection@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.886.0.tgz#2ba280503609b63fbe28bc92ca37ad51a048b06f" + integrity sha512-yMMlPqiX1SXFwQ0L1a/U19rdXx7eYseHsJEC9F9M5LUUPBI7k117nA0vXxvsvODVQ6JKtY7nTiPrc98GcVKgnw== + dependencies: + "@aws-sdk/types" "3.862.0" + "@aws/lambda-invoke-store" "^0.0.1" + "@smithy/protocol-http" "^5.1.3" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/middleware-sdk-api-gateway@3.873.0": version "3.873.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-api-gateway/-/middleware-sdk-api-gateway-3.873.0.tgz#08c48abde12197b139159f9ece5baa637fbc4824" @@ -1184,6 +1413,50 @@ "@smithy/util-utf8" "^4.0.0" tslib "^2.6.2" +"@aws-sdk/nested-clients@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.886.0.tgz#bc9a8227c63fc7917d7e9f7a69e33b4dda5c42ee" + integrity sha512-CqeRdkNyJ7LlKLQtMTzK11WIiryEK8JbSL5LCia0B1Lp22OByDUiUSFZZ3FZq9poD5qHQI63pHkzAr5WkLGS5A== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.883.0" + "@aws-sdk/middleware-host-header" "3.873.0" + "@aws-sdk/middleware-logger" "3.876.0" + "@aws-sdk/middleware-recursion-detection" "3.886.0" + "@aws-sdk/middleware-user-agent" "3.883.0" + "@aws-sdk/region-config-resolver" "3.873.0" + "@aws-sdk/types" "3.862.0" + "@aws-sdk/util-endpoints" "3.879.0" + "@aws-sdk/util-user-agent-browser" "3.873.0" + "@aws-sdk/util-user-agent-node" "3.883.0" + "@smithy/config-resolver" "^4.1.5" + "@smithy/core" "^3.9.2" + "@smithy/fetch-http-handler" "^5.1.1" + "@smithy/hash-node" "^4.0.5" + "@smithy/invalid-dependency" "^4.0.5" + "@smithy/middleware-content-length" "^4.0.5" + "@smithy/middleware-endpoint" "^4.1.21" + "@smithy/middleware-retry" "^4.1.22" + "@smithy/middleware-serde" "^4.0.9" + "@smithy/middleware-stack" "^4.0.5" + "@smithy/node-config-provider" "^4.1.4" + "@smithy/node-http-handler" "^4.1.1" + "@smithy/protocol-http" "^5.1.3" + "@smithy/smithy-client" "^4.5.2" + "@smithy/types" "^4.3.2" + "@smithy/url-parser" "^4.0.5" + "@smithy/util-base64" "^4.0.0" + "@smithy/util-body-length-browser" "^4.0.0" + "@smithy/util-body-length-node" "^4.0.0" + "@smithy/util-defaults-mode-browser" "^4.0.29" + "@smithy/util-defaults-mode-node" "^4.0.29" + "@smithy/util-endpoints" "^3.0.7" + "@smithy/util-middleware" "^4.0.5" + "@smithy/util-retry" "^4.0.7" + "@smithy/util-utf8" "^4.0.0" + tslib "^2.6.2" + "@aws-sdk/region-config-resolver@3.873.0": version "3.873.0" resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.873.0.tgz#9a5ddf8aa5a068d1c728dda3ef7e5b31561f7419" @@ -1196,6 +1469,20 @@ "@smithy/util-middleware" "^4.0.5" tslib "^2.6.2" +"@aws-sdk/s3-request-presigner@3.883.0": + version "3.883.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.883.0.tgz#556fd0031351356d66fafd067aa49a3625b8c9a6" + integrity sha512-NW20A3PD+vqVcr/5F3cbqibKogUNdDAE9K9MUE7X1JSz3Gi/2nMV2hoRdC3+T3c6XE44t6LW1Swt3JOvaQOJLw== + dependencies: + "@aws-sdk/signature-v4-multi-region" "3.883.0" + "@aws-sdk/types" "3.862.0" + "@aws-sdk/util-format-url" "3.873.0" + "@smithy/middleware-endpoint" "^4.1.21" + "@smithy/protocol-http" "^5.1.3" + "@smithy/smithy-client" "^4.5.2" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/signature-v4-multi-region@3.883.0": version "3.883.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.883.0.tgz#127440b9a2a0242e3e0d25338c3d0f1f7244283c" @@ -1221,6 +1508,19 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws-sdk/token-providers@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.886.0.tgz#9829095ea3b510cddf3e3c3c8cabab0584e10e1c" + integrity sha512-dYS3apmGcldFglpAiAajcdDKtKjBw/NkG6nRYIC2q7+OZsxeyzunT1EUSxV4xphLoqiuhuCg/fTnBI3WVtb3IQ== + dependencies: + "@aws-sdk/core" "3.883.0" + "@aws-sdk/nested-clients" "3.886.0" + "@aws-sdk/types" "3.862.0" + "@smithy/property-provider" "^4.0.5" + "@smithy/shared-ini-file-loader" "^4.0.5" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/types@3.862.0", "@aws-sdk/types@^3.222.0": version "3.862.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.862.0.tgz#2f5622e1aa3a5281d4f419f5d2c90f87dd5ff0cf" @@ -1243,6 +1543,13 @@ dependencies: tslib "^2.6.2" +"@aws-sdk/util-dynamodb@3.886.0": + version "3.886.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-dynamodb/-/util-dynamodb-3.886.0.tgz#7bd5d6cdb73f322c8fd2b0d6e325068fb716566c" + integrity sha512-zt7PfQCSf2bIcQfyjB+ayodBlZNekAKhuobx5/qf4keI0Dm4tNp/v/ZW6+AqGO8hpf7g7ClNYvSUhi0KPCIkww== + dependencies: + tslib "^2.6.2" + "@aws-sdk/util-endpoints@3.879.0": version "3.879.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.879.0.tgz#e30c15beede883d327dbd290c47512d6d700a2e9" @@ -1254,6 +1561,16 @@ "@smithy/util-endpoints" "^3.0.7" tslib "^2.6.2" +"@aws-sdk/util-format-url@3.873.0": + version "3.873.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.873.0.tgz#b376610ee5fb06386501bf360556d3690854c06f" + integrity sha512-v//b9jFnhzTKKV3HFTw2MakdM22uBAs2lBov51BWmFXuFtSTdBLrR7zgfetQPE3PVkFai0cmtJQPdc3MX+T/cQ== + dependencies: + "@aws-sdk/types" "3.862.0" + "@smithy/querystring-builder" "^4.0.5" + "@smithy/types" "^4.3.2" + tslib "^2.6.2" + "@aws-sdk/util-locate-window@^3.0.0": version "3.873.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz#cc10edef3b7aecf365943ec657116d6eb470d9cb" @@ -1290,6 +1607,11 @@ "@smithy/types" "^4.3.2" tslib "^2.6.2" +"@aws/lambda-invoke-store@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz#92d792a7dda250dfcb902e13228f37a81be57c8f" + integrity sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw== + "@azure/msal-browser@^4.19.0": version "4.22.1" resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-4.22.1.tgz#e4c41cc0bdae7b21b7aee8c013f99beea60c9410" @@ -3485,9 +3807,9 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.30" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" - integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -4501,7 +4823,7 @@ "@smithy/types" "^4.5.0" tslib "^2.6.2" -"@smithy/querystring-builder@^4.1.1": +"@smithy/querystring-builder@^4.0.5", "@smithy/querystring-builder@^4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz#4d35c1735de8214055424045a117fa5d1d5cdec1" integrity sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA== @@ -6521,9 +6843,9 @@ axios@^1.0.0, axios@^1.6.2: proxy-from-env "^1.1.0" b4a@^1.6.4: - version "1.7.0" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.7.0.tgz#d3c1f72f68be612020c15702d0a6f0e8bc0b65e5" - integrity sha512-KtsH1alSKomfNi/yDAFaD8PPFfi0LxJCEbPuzogcXrMF+yH40Z1ykTDo2vyxuQfN1FLjv0LFM7CadLHEPrVifw== + version "1.7.1" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.7.1.tgz#6fd4ec2fb33ba7a4ff341a2869bbfc88a6e57850" + integrity sha512-ZovbrBV0g6JxK5cGUF1Suby1vLfKjv4RWi8IxoaO/Mon8BDD9I21RxjHFtgQ+kskJqLAVyQZly3uMBui+vhc8Q== babel-jest@30.1.2: version "30.1.2" @@ -6804,6 +7126,13 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" +browser-image-compression@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/browser-image-compression/-/browser-image-compression-2.0.2.tgz#4d5ef8882e9e471d6d923715ceb9034499d14eaa" + integrity sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw== + dependencies: + uzip "0.20201231.0" + browserslist@^4.24.0: version "4.25.4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.4.tgz#ebdd0e1d1cf3911834bab3a6cd7b917d9babf5af" @@ -8634,9 +8963,9 @@ ejs@^3.1.10, ejs@^3.1.7: jake "^10.8.5" electron-to-chromium@^1.5.211: - version "1.5.215" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.215.tgz#200c8d69b1270af6126837b6b1f95077c3a347b1" - integrity sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ== + version "1.5.217" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.217.tgz#71285850356ef48bc08275b26f0f355721e0f17d" + integrity sha512-Pludfu5iBxp9XzNl0qq2G87hdD17ZV7h5T4n6rQXDi3nCyloBV3jreE9+8GC6g4X/5yxqVgXEURpcLtM0WS4jA== emittery@^0.13.1: version "0.13.1" @@ -17757,6 +18086,11 @@ uuid@^9.0.0, uuid@^9.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +uzip@0.20201231.0: + version "0.20201231.0" + resolved "https://registry.yarnpkg.com/uzip/-/uzip-0.20201231.0.tgz#9e64b065b9a8ebf26eb7583fe8e77e1d9a15ed14" + integrity sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"