From 07e89ad13511a9fc7bb676e89292940898f10b07 Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:52:44 +0800 Subject: [PATCH 01/27] Innovation Flow Template creation dialog actions fixed (#5534) --- src/core/help/dialog/HelpDialog.tsx | 10 +++----- .../default/components/MuiDialogActions.ts | 2 +- .../CreateInnovationTemplateDialog.tsx | 25 +++++++++++++------ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/core/help/dialog/HelpDialog.tsx b/src/core/help/dialog/HelpDialog.tsx index ddcdfd434c..d5f8629009 100644 --- a/src/core/help/dialog/HelpDialog.tsx +++ b/src/core/help/dialog/HelpDialog.tsx @@ -1,13 +1,14 @@ import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box, Dialog, Grid, Link, styled, Typography } from '@mui/material'; +import { Box, Dialog, Grid, Link, styled } from '@mui/material'; import QuizOutlinedIzon from '@mui/icons-material/QuizOutlined'; import ForumOutlinedIcon from '@mui/icons-material/ForumOutlined'; import FiberNewTwoToneIcon from '@mui/icons-material/FiberNewTwoTone'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { DialogActions, DialogContent, DialogTitle } from '../../ui/dialog/deprecated'; +import { DialogContent } from '../../ui/dialog/deprecated'; import WrapperMarkdown from '../../ui/markdown/WrapperMarkdown'; import { useConfig } from '../../../domain/platform/config/useConfig'; +import DialogHeader from '../../ui/dialog/DialogHeader'; interface HelpDialogProps { open: boolean; @@ -61,9 +62,7 @@ const HelpDialog: FC = ({ open, onClose }) => { return ( - - {t('pages.help-dialog.title')} - + {t('pages.help-dialog.text')} @@ -91,7 +90,6 @@ const HelpDialog: FC = ({ open, onClose }) => { {t('pages.help-dialog.tips-and-tricks')} - ); }; diff --git a/src/core/ui/themes/default/components/MuiDialogActions.ts b/src/core/ui/themes/default/components/MuiDialogActions.ts index 2b72abc086..b93b0fd963 100644 --- a/src/core/ui/themes/default/components/MuiDialogActions.ts +++ b/src/core/ui/themes/default/components/MuiDialogActions.ts @@ -4,7 +4,7 @@ import { gutters } from '../../../grid/utils'; const MuiDialogActions: Components['MuiDialogActions'] = { styleOverrides: { root: ({ theme }) => ({ - padding: gutters(0.5)(theme), + padding: gutters()(theme), }), }, }; diff --git a/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx b/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx index 4692d58fad..09be36acca 100644 --- a/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx +++ b/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx @@ -3,11 +3,12 @@ import InnovationTemplateForm, { InnovationTemplateFormValues, } from './InnovationTemplateForm'; import { useTranslation } from 'react-i18next'; -import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid'; +import DialogWithGrid, { DialogFooter } from '../../../../../core/ui/dialog/DialogWithGrid'; import DialogHeader, { DialogHeaderProps } from '../../../../../core/ui/dialog/DialogHeader'; import React from 'react'; -import FormikSubmitButton from '../../../../shared/components/forms/FormikSubmitButton'; +import { FormikSubmitButtonPure } from '../../../../shared/components/forms/FormikSubmitButton'; import { InnovationFlowType } from '../../../../../core/apollo/generated/graphql-schema'; +import { DialogActions, DialogContent } from '@mui/material'; interface CreatePostTemplateDialogProps { open: boolean; @@ -32,11 +33,21 @@ const CreateInnovationTemplateDialog = ({ open, onClose, onSubmit }: CreatePostT {t('common.create-new-entity', { entity: t('templateLibrary.innovationFlowTemplates.name') })} - {t('common.create')}} - /> + + ( + + + formik.handleSubmit()}> + {t('common.create')} + + + + )} + /> + ); }; From 181575b06d5aeeb1d65cec1bb65cd20890cddf48 Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:00:27 +0800 Subject: [PATCH 02/27] MyDashboard start own space link points to welcome site (#5535) Co-authored-by: Svetoslav Petkov --- src/core/i18n/bg/translation.bg.json | 3 +-- src/core/i18n/de/translation.de.json | 3 +-- src/core/i18n/en/translation.en.json | 2 +- src/core/i18n/es/translation.es.json | 3 +-- src/core/i18n/fr/translation.fr.json | 3 +-- src/core/i18n/nl/translation.nl.json | 3 +-- src/core/i18n/pt/translation.pt.json | 3 +-- src/core/i18n/ua/translation.ua.json | 3 +-- 8 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/core/i18n/bg/translation.bg.json b/src/core/i18n/bg/translation.bg.json index 254738e5ef..04575d934f 100644 --- a/src/core/i18n/bg/translation.bg.json +++ b/src/core/i18n/bg/translation.bg.json @@ -2046,8 +2046,7 @@ "body1": "For Users, Spaces are free to join. Explore the membership options, including for public and private Spaces." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" diff --git a/src/core/i18n/de/translation.de.json b/src/core/i18n/de/translation.de.json index 69105fd3d7..744b68e01c 100644 --- a/src/core/i18n/de/translation.de.json +++ b/src/core/i18n/de/translation.de.json @@ -2046,8 +2046,7 @@ "body1": "Für Benutzer stehen Hubs kostenlos zur Verfügung. Erkunden Sie die Mitgliedschaftsoptionen, einschließlich für öffentliche und private Hubs." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" }, "my-spaces": { "header": "Meine Räume ({{mySpacesCount}})" diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index a1e72b1cb0..d20acf240b 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -2049,7 +2049,7 @@ }, "startingSpace": { "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" diff --git a/src/core/i18n/es/translation.es.json b/src/core/i18n/es/translation.es.json index 562c7fa1dc..981e8a93b4 100644 --- a/src/core/i18n/es/translation.es.json +++ b/src/core/i18n/es/translation.es.json @@ -2046,8 +2046,7 @@ "body1": "For Users, Spaces are free to join. Explore the membership options, including for public and private Spaces." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" diff --git a/src/core/i18n/fr/translation.fr.json b/src/core/i18n/fr/translation.fr.json index 2a87a31ae7..423cff636e 100644 --- a/src/core/i18n/fr/translation.fr.json +++ b/src/core/i18n/fr/translation.fr.json @@ -2046,8 +2046,7 @@ "body1": "Pour les utilisateurs, les hubs sont libres de se joindre. Explorez les options d'adhésion, y compris pour les hubs publics et privés." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" }, "my-spaces": { "header": "Mes espaces ({{mySpacesCount}})" diff --git a/src/core/i18n/nl/translation.nl.json b/src/core/i18n/nl/translation.nl.json index e644b0cd16..65c101b497 100644 --- a/src/core/i18n/nl/translation.nl.json +++ b/src/core/i18n/nl/translation.nl.json @@ -2046,8 +2046,7 @@ "body1": "Individuele gebruikers kunnen zich aansluiten bij één of meerdere Spaces. Deze Spaces kunnen zowel openbaar als privé zijn." }, "startingSpace": { - "title": "🎉 Start jouw Space Klaar om een community te bouwen op Alkemio? Klik hier om te starten!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Start jouw Space Klaar om een community te bouwen op Alkemio? Klik hier om te starten!" }, "my-spaces": { "header": "Mijn Spaces ({{mySpacesCount}})" diff --git a/src/core/i18n/pt/translation.pt.json b/src/core/i18n/pt/translation.pt.json index d9ef788b24..83ef446ff2 100644 --- a/src/core/i18n/pt/translation.pt.json +++ b/src/core/i18n/pt/translation.pt.json @@ -2046,8 +2046,7 @@ "body1": "Para usuários, os Espaços são livres para entrar. Explore as opções, inclusive para espaços públicos e privados." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" }, "my-spaces": { "header": "Meus Espaços ({{mySpacesCount}})" diff --git a/src/core/i18n/ua/translation.ua.json b/src/core/i18n/ua/translation.ua.json index d38e42347f..2a27175681 100644 --- a/src/core/i18n/ua/translation.ua.json +++ b/src/core/i18n/ua/translation.ua.json @@ -2046,8 +2046,7 @@ "body1": "For Users, Spaces are free to join. Explore the membership options, including for public and private Spaces." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", - "url": "https://alkemio.foundation/contact/" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" From e34bfc4314a0cfafc8ab8672234275c42c092ca9 Mon Sep 17 00:00:00 2001 From: Svetoslav Petkov Date: Mon, 12 Feb 2024 11:25:52 +0200 Subject: [PATCH 03/27] upload whiteboard rt preview on save on close (#5537) --- .../WhiteboardDialog/WhiteboardRtDialog.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx b/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx index 0b13da27f1..728bec4d38 100644 --- a/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx +++ b/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx @@ -153,7 +153,7 @@ const WhiteboardRtDialog = ({ const prepareWhiteboardForUpdate = async ( whiteboard: WhiteboardRtWithContent, state: RelevantExcalidrawState | undefined, - shouldUploadPreviewImages = false + shouldUploadPreviewImages = true ): Promise<{ whiteboard: Whiteboard; previewImages?: WhiteboardPreviewImage[]; @@ -213,15 +213,14 @@ const WhiteboardRtDialog = ({ }; }; - const handleSave = async () => { + const handleManualSave = async () => { if (!whiteboard) { throw new Error('Whiteboard not defined'); } const whiteboardState = await getWhiteboardState(); const { whiteboard: updatedWhiteboard, previewImages } = await prepareWhiteboardForUpdate( whiteboard, - whiteboardState, - true + whiteboardState ); return submitUpdate(updatedWhiteboard, previewImages); }; @@ -229,8 +228,11 @@ const WhiteboardRtDialog = ({ const onClose = async () => { if (editModeEnabled && collaborationEnabled && whiteboard) { const whiteboardState = await getWhiteboardState(); - const { whiteboard: updatedWhiteboard } = await prepareWhiteboardForUpdate(whiteboard, whiteboardState); - submitUpdate(updatedWhiteboard); + const { whiteboard: updatedWhiteboard, previewImages } = await prepareWhiteboardForUpdate( + whiteboard, + whiteboardState + ); + submitUpdate(updatedWhiteboard, previewImages); } actions.onCancel(whiteboard!); }; @@ -276,7 +278,7 @@ const WhiteboardRtDialog = ({ { }} + onSubmit={() => {}} validationSchema={whiteboardSchema} > {() => ( @@ -320,8 +322,12 @@ const WhiteboardRtDialog = ({ actions={{ onInitApi: setExcalidrawAPI, onUpdate: async state => { - const { whiteboard: updatedWhiteboard } = await prepareWhiteboardForUpdate(whiteboard, state); - return submitUpdate(updatedWhiteboard); + const { whiteboard: updatedWhiteboard, previewImages } = await prepareWhiteboardForUpdate( + whiteboard, + state, + false + ); + return submitUpdate(updatedWhiteboard, previewImages); }, onSavedToDatabase: () => { refetchLastSaved({ @@ -341,7 +347,7 @@ const WhiteboardRtDialog = ({ Date: Mon, 12 Feb 2024 17:38:18 +0800 Subject: [PATCH 04/27] Removed WhiteboardRtProvider (#5539) * Removed WhiteboardRtProvider from SingleWhiteboardRtCallout - data already fetched * cleanup --------- Co-authored-by: Svetoslav Petkov --- src/core/apollo/generated/apollo-hooks.ts | 81 ------- src/core/apollo/generated/graphql-schema.ts | 210 +----------------- .../SingleWhiteboardRtCallout.tsx | 39 ++-- .../WhiteboardRtManagementView.tsx | 6 +- .../WhiteboardsRtManagementViewWrapper.tsx | 4 +- .../containers/WhiteboardRtProvider.tsx | 51 ----- .../containers/whiteboardQueries.graphql | 24 -- 7 files changed, 26 insertions(+), 389 deletions(-) delete mode 100644 src/domain/collaboration/whiteboard/containers/WhiteboardRtProvider.tsx diff --git a/src/core/apollo/generated/apollo-hooks.ts b/src/core/apollo/generated/apollo-hooks.ts index fae33b602c..45e081f6a6 100644 --- a/src/core/apollo/generated/apollo-hooks.ts +++ b/src/core/apollo/generated/apollo-hooks.ts @@ -1027,24 +1027,6 @@ export const CalloutWithWhiteboardFragmentDoc = gql` } ${WhiteboardDetailsFragmentDoc} `; -export const CalloutWithWhiteboardRtFragmentDoc = gql` - fragment CalloutWithWhiteboardRt on Callout { - id - nameID - type - authorization { - id - anonymousReadAccess - myPrivileges - } - framing { - whiteboardRt { - ...WhiteboardRtDetails - } - } - } - ${WhiteboardRtDetailsFragmentDoc} -`; export const CollaborationWithWhiteboardDetailsFragmentDoc = gql` fragment CollaborationWithWhiteboardDetails on Collaboration { id @@ -8773,69 +8755,6 @@ export function refetchWhiteboardFromCalloutQuery(variables: SchemaTypes.Whitebo return { query: WhiteboardFromCalloutDocument, variables: variables }; } -export const WhiteboardRtFromCalloutDocument = gql` - query WhiteboardRtFromCallout($calloutId: UUID!) { - lookup { - callout(ID: $calloutId) { - ...CalloutWithWhiteboardRt - } - } - } - ${CalloutWithWhiteboardRtFragmentDoc} -`; - -/** - * __useWhiteboardRtFromCalloutQuery__ - * - * To run a query within a React component, call `useWhiteboardRtFromCalloutQuery` and pass it any options that fit your needs. - * When your component renders, `useWhiteboardRtFromCalloutQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useWhiteboardRtFromCalloutQuery({ - * variables: { - * calloutId: // value for 'calloutId' - * }, - * }); - */ -export function useWhiteboardRtFromCalloutQuery( - baseOptions: Apollo.QueryHookOptions< - SchemaTypes.WhiteboardRtFromCalloutQuery, - SchemaTypes.WhiteboardRtFromCalloutQueryVariables - > -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useQuery( - WhiteboardRtFromCalloutDocument, - options - ); -} - -export function useWhiteboardRtFromCalloutLazyQuery( - baseOptions?: Apollo.LazyQueryHookOptions< - SchemaTypes.WhiteboardRtFromCalloutQuery, - SchemaTypes.WhiteboardRtFromCalloutQueryVariables - > -) { - const options = { ...defaultOptions, ...baseOptions }; - return Apollo.useLazyQuery< - SchemaTypes.WhiteboardRtFromCalloutQuery, - SchemaTypes.WhiteboardRtFromCalloutQueryVariables - >(WhiteboardRtFromCalloutDocument, options); -} - -export type WhiteboardRtFromCalloutQueryHookResult = ReturnType; -export type WhiteboardRtFromCalloutLazyQueryHookResult = ReturnType; -export type WhiteboardRtFromCalloutQueryResult = Apollo.QueryResult< - SchemaTypes.WhiteboardRtFromCalloutQuery, - SchemaTypes.WhiteboardRtFromCalloutQueryVariables ->; -export function refetchWhiteboardRtFromCalloutQuery(variables: SchemaTypes.WhiteboardRtFromCalloutQueryVariables) { - return { query: WhiteboardRtFromCalloutDocument, variables: variables }; -} - export const WhiteboardWithContentDocument = gql` query WhiteboardWithContent($whiteboardId: UUID!) { lookup { diff --git a/src/core/apollo/generated/graphql-schema.ts b/src/core/apollo/generated/graphql-schema.ts index 45f0d085ce..e39abe2ec5 100644 --- a/src/core/apollo/generated/graphql-schema.ts +++ b/src/core/apollo/generated/graphql-schema.ts @@ -1436,6 +1436,8 @@ export type CreateCalloutTemplateOnTemplatesSetInput = { export type CreateChallengeOnChallengeInput = { challengeID: Scalars['UUID']; + /** The ID of the Challenge to use for setting up the collaboration the Challenge. */ + collaborationTemplateChallengeID?: InputMaybe; context?: InputMaybe; /** The Innovation Flow template to use for the Challenge. */ innovationFlowTemplateID?: InputMaybe; @@ -1448,6 +1450,8 @@ export type CreateChallengeOnChallengeInput = { }; export type CreateChallengeOnSpaceInput = { + /** The ID of the Challenge to use for setting up the collaboration the Challenge. */ + collaborationTemplateChallengeID?: InputMaybe; context?: InputMaybe; /** The Innovation Flow template to use for the Challenge. */ innovationFlowTemplateID?: InputMaybe; @@ -1549,6 +1553,8 @@ export type CreateNvpInput = { export type CreateOpportunityInput = { challengeID: Scalars['UUID']; + /** The ID of the Opportunity to use for setting up the collaboration of the Opportunity. */ + collaborationTemplateOpportunityID?: InputMaybe; context?: InputMaybe; /** The Innovation Flow template to use for the Opportunity. */ innovationFlowTemplateID?: InputMaybe; @@ -15625,102 +15631,6 @@ export type CalloutWithWhiteboardFragment = { | undefined; }; -export type CalloutWithWhiteboardRtFragment = { - __typename?: 'Callout'; - id: string; - nameID: string; - type: CalloutType; - authorization?: - | { - __typename?: 'Authorization'; - id: string; - anonymousReadAccess: boolean; - myPrivileges?: Array | undefined; - } - | undefined; - framing: { - __typename?: 'CalloutFraming'; - whiteboardRt?: - | { - __typename?: 'WhiteboardRt'; - id: string; - nameID: string; - createdDate: Date; - contentUpdatePolicy: ContentUpdatePolicy; - profile: { - __typename?: 'Profile'; - id: string; - displayName: string; - description?: string | undefined; - visual?: - | { - __typename?: 'Visual'; - id: string; - uri: string; - name: string; - allowedTypes: Array; - aspectRatio: number; - maxHeight: number; - maxWidth: number; - minHeight: number; - minWidth: number; - alternativeText?: string | undefined; - } - | undefined; - preview?: - | { - __typename?: 'Visual'; - id: string; - uri: string; - name: string; - allowedTypes: Array; - aspectRatio: number; - maxHeight: number; - maxWidth: number; - minHeight: number; - minWidth: number; - alternativeText?: string | undefined; - } - | undefined; - tagset?: - | { - __typename?: 'Tagset'; - id: string; - name: string; - tags: Array; - allowedValues: Array; - type: TagsetType; - } - | undefined; - storageBucket: { __typename?: 'StorageBucket'; id: string }; - }; - authorization?: - | { - __typename?: 'Authorization'; - id: string; - myPrivileges?: Array | undefined; - anonymousReadAccess: boolean; - } - | undefined; - createdBy?: - | { - __typename?: 'User'; - id: string; - profile: { - __typename?: 'Profile'; - id: string; - displayName: string; - url: string; - location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; - avatar?: { __typename?: 'Visual'; id: string; uri: string } | undefined; - }; - } - | undefined; - } - | undefined; - }; -}; - export type CollaborationWithWhiteboardDetailsFragment = { __typename?: 'Collaboration'; id: string; @@ -16137,114 +16047,6 @@ export type WhiteboardFromCalloutQuery = { }; }; -export type WhiteboardRtFromCalloutQueryVariables = Exact<{ - calloutId: Scalars['UUID']; -}>; - -export type WhiteboardRtFromCalloutQuery = { - __typename?: 'Query'; - lookup: { - __typename?: 'LookupQueryResults'; - callout?: - | { - __typename?: 'Callout'; - id: string; - nameID: string; - type: CalloutType; - authorization?: - | { - __typename?: 'Authorization'; - id: string; - anonymousReadAccess: boolean; - myPrivileges?: Array | undefined; - } - | undefined; - framing: { - __typename?: 'CalloutFraming'; - whiteboardRt?: - | { - __typename?: 'WhiteboardRt'; - id: string; - nameID: string; - createdDate: Date; - contentUpdatePolicy: ContentUpdatePolicy; - profile: { - __typename?: 'Profile'; - id: string; - displayName: string; - description?: string | undefined; - visual?: - | { - __typename?: 'Visual'; - id: string; - uri: string; - name: string; - allowedTypes: Array; - aspectRatio: number; - maxHeight: number; - maxWidth: number; - minHeight: number; - minWidth: number; - alternativeText?: string | undefined; - } - | undefined; - preview?: - | { - __typename?: 'Visual'; - id: string; - uri: string; - name: string; - allowedTypes: Array; - aspectRatio: number; - maxHeight: number; - maxWidth: number; - minHeight: number; - minWidth: number; - alternativeText?: string | undefined; - } - | undefined; - tagset?: - | { - __typename?: 'Tagset'; - id: string; - name: string; - tags: Array; - allowedValues: Array; - type: TagsetType; - } - | undefined; - storageBucket: { __typename?: 'StorageBucket'; id: string }; - }; - authorization?: - | { - __typename?: 'Authorization'; - id: string; - myPrivileges?: Array | undefined; - anonymousReadAccess: boolean; - } - | undefined; - createdBy?: - | { - __typename?: 'User'; - id: string; - profile: { - __typename?: 'Profile'; - id: string; - displayName: string; - url: string; - location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; - avatar?: { __typename?: 'Visual'; id: string; uri: string } | undefined; - }; - } - | undefined; - } - | undefined; - }; - } - | undefined; - }; -}; - export type WhiteboardWithContentQueryVariables = Exact<{ whiteboardId: Scalars['UUID']; }>; diff --git a/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx b/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx index 60d260d9b8..b7facf7e4d 100644 --- a/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx +++ b/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx @@ -8,7 +8,6 @@ import { useState } from 'react'; import CalloutLayout, { CalloutLayoutProps } from '../../CalloutBlock/CalloutLayout'; import { BaseCalloutViewProps } from '../CalloutViewTypes'; -import { WhiteboardRtProvider } from '../../whiteboard/containers/WhiteboardRtProvider'; import WhiteboardsRtManagementViewWrapper from '../../whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper'; import { buildCalloutUrl } from '../../../../main/routing/urlBuilders'; import WhiteboardPreview from '../../whiteboard/whiteboardPreview/WhiteboardPreview'; @@ -56,29 +55,21 @@ const SingleWhiteboardRtCallout = ({ onClick={() => setIsWhiteboardDialogOpen(true)} /> {isWhiteboardDialogOpen && ( - - {(entities, state) => ( - - )} - + )} ); diff --git a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtManagementView.tsx b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtManagementView.tsx index fbcdf907aa..989de78a0c 100644 --- a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtManagementView.tsx +++ b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtManagementView.tsx @@ -17,7 +17,7 @@ import WhiteboardShareSettings from '../share/WhiteboardShareSettings'; import useWhiteboardRtContentUpdatePolicy from '../whiteboardRt/contentUpdatePolicy/WhiteboardRtContentUpdatePolicy'; export interface ActiveWhiteboardIdHolder { - whiteboardNameId?: string; + whiteboardId?: string; } export interface WhiteboardManagementViewEntities extends ActiveWhiteboardIdHolder { @@ -67,7 +67,7 @@ const WhiteboardRtManagementView: FC = ({ options, backToWhiteboards, }) => { - const { whiteboardNameId, whiteboard, contextSource } = entities; + const { whiteboardId, whiteboard, contextSource } = entities; const { fullscreen, setFullscreen } = useFullscreen(); const handleCancel = (/*whiteboard: WhiteboardRtDetailsFragment*/) => { @@ -97,7 +97,7 @@ const WhiteboardRtManagementView: FC = ({ }} options={{ canEdit: options.canUpdateContent, - show: Boolean(whiteboardNameId), + show: Boolean(whiteboardId), fixedDialogTitle: options.canUpdateDisplayName ? undefined : ( {whiteboard?.profile.displayName} diff --git a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx index 3496bd34bc..6269479f72 100644 --- a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx +++ b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx @@ -18,7 +18,7 @@ export interface WhiteboardsRtManagementViewWrapperProps extends ActiveWhiteboar } const WhiteboardsRtManagementViewWrapper: FC = ({ - whiteboardNameId, + whiteboardId, calloutId, whiteboard, authorization, @@ -47,7 +47,7 @@ const WhiteboardsRtManagementViewWrapper: FC React.ReactNode; -} - -export interface IProvidedEntities { - whiteboard: WhiteboardRtDetailsFragment | undefined; - calloutId: string | undefined; - authorization: WhiteboardRtDetailsFragment['authorization']; -} - -export interface IProvidedEntitiesState { - loadingWhiteboards: boolean; -} - -const WhiteboardRtProvider: FC = ({ - calloutId, - whiteboardNameId: whiteboardId, - children, -}) => { - const { data, loading } = useWhiteboardRtFromCalloutQuery({ - variables: { calloutId: calloutId! }, - skip: !calloutId || !whiteboardId, - errorPolicy: 'all', - fetchPolicy: 'cache-and-network', // TODO: Check if this is still needed - }); - - const callout = data?.lookup.callout; - - const authorization = callout?.framing.whiteboardRt?.authorization; - - return ( - <> - {children( - { whiteboard: callout?.framing.whiteboardRt, calloutId, authorization }, - { loadingWhiteboards: loading } - )} - - ); -}; - -export { WhiteboardRtProvider }; diff --git a/src/domain/collaboration/whiteboard/containers/whiteboardQueries.graphql b/src/domain/collaboration/whiteboard/containers/whiteboardQueries.graphql index dac2e78d4e..19df727cb4 100644 --- a/src/domain/collaboration/whiteboard/containers/whiteboardQueries.graphql +++ b/src/domain/collaboration/whiteboard/containers/whiteboardQueries.graphql @@ -153,22 +153,6 @@ fragment CalloutWithWhiteboard on Callout { } } -fragment CalloutWithWhiteboardRt on Callout { - id - nameID - type - authorization { - id - anonymousReadAccess - myPrivileges - } - framing { - whiteboardRt { - ...WhiteboardRtDetails - } - } -} - fragment CollaborationWithWhiteboardDetails on Collaboration { id callouts { @@ -201,14 +185,6 @@ query WhiteboardFromCallout($calloutId: UUID!, $whiteboardId: UUID_NAMEID!) { } } -query WhiteboardRtFromCallout($calloutId: UUID!) { - lookup { - callout(ID: $calloutId) { - ...CalloutWithWhiteboardRt - } - } -} - query WhiteboardWithContent($whiteboardId: UUID!) { lookup { whiteboard(ID: $whiteboardId) { From 00b66285583ff9dd26b7eb7f28be2043dbf68e5c Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:47:24 +0800 Subject: [PATCH 05/27] NewMemberships translations updated (#5541) Co-authored-by: Svetoslav Petkov --- src/core/i18n/bg/translation.bg.json | 14 +------------- src/core/i18n/de/translation.de.json | 14 +------------- src/core/i18n/es/translation.es.json | 14 +------------- src/core/i18n/fr/translation.fr.json | 14 +------------- src/core/i18n/nl/translation.nl.json | 6 ------ src/core/i18n/pt/translation.pt.json | 14 +------------- src/core/i18n/ua/translation.ua.json | 14 +------------- 7 files changed, 6 insertions(+), 84 deletions(-) diff --git a/src/core/i18n/bg/translation.bg.json b/src/core/i18n/bg/translation.bg.json index 04575d934f..107feb65f2 100644 --- a/src/core/i18n/bg/translation.bg.json +++ b/src/core/i18n/bg/translation.bg.json @@ -2000,19 +2000,7 @@ "title": "New Memberships", "openMemberships": "Open applications & invitations", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships", - "invitation": { - "message": "You are invited to join {{journey}}", - "caption": "Click here to see the invitation" - }, - "application": { - "message": "You applied to join {{journey}}", - "caption": "Your application still has to be approved" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" - } + "seeMore": "Show all my memberships" }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", diff --git a/src/core/i18n/de/translation.de.json b/src/core/i18n/de/translation.de.json index 744b68e01c..3167a1c15f 100644 --- a/src/core/i18n/de/translation.de.json +++ b/src/core/i18n/de/translation.de.json @@ -2000,19 +2000,7 @@ "title": "New Memberships", "openMemberships": "Open applications & invitations", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships", - "invitation": { - "message": "You are invited to join {{journey}}", - "caption": "Click here to see the invitation" - }, - "application": { - "message": "You applied to join {{journey}}", - "caption": "Your application still has to be approved" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" - } + "seeMore": "Show all my memberships" }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", diff --git a/src/core/i18n/es/translation.es.json b/src/core/i18n/es/translation.es.json index 981e8a93b4..ff39b250f2 100644 --- a/src/core/i18n/es/translation.es.json +++ b/src/core/i18n/es/translation.es.json @@ -2000,19 +2000,7 @@ "title": "New Memberships", "openMemberships": "Open applications & invitations", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships", - "invitation": { - "message": "You are invited to join {{journey}}", - "caption": "Click here to see the invitation" - }, - "application": { - "message": "You applied to join {{journey}}", - "caption": "Your application still has to be approved" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" - } + "seeMore": "Show all my memberships" }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", diff --git a/src/core/i18n/fr/translation.fr.json b/src/core/i18n/fr/translation.fr.json index 423cff636e..8f242fc66f 100644 --- a/src/core/i18n/fr/translation.fr.json +++ b/src/core/i18n/fr/translation.fr.json @@ -2000,19 +2000,7 @@ "title": "New Memberships", "openMemberships": "Open applications & invitations", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships", - "invitation": { - "message": "You are invited to join {{journey}}", - "caption": "Click here to see the invitation" - }, - "application": { - "message": "You applied to join {{journey}}", - "caption": "Your application still has to be approved" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" - } + "seeMore": "Show all my memberships" }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", diff --git a/src/core/i18n/nl/translation.nl.json b/src/core/i18n/nl/translation.nl.json index 65c101b497..d5248ea979 100644 --- a/src/core/i18n/nl/translation.nl.json +++ b/src/core/i18n/nl/translation.nl.json @@ -2002,16 +2002,10 @@ "recentlyJoined": "Recent toegetreden", "seeMore": "Toon al mijn lidmaatschappen", "invitation": { - "message": "Je bent uitgenodigd om je aan te sluiten bij {{journey}}", "caption": "Klik hier om de uitnodiging te zien" }, "application": { - "message": "Je hebt een aanmelding ingediend om lid te worden van {{journey}}", "caption": "Je aanvraag moet nog goedgekeurd worden" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" } }, "welcome": { diff --git a/src/core/i18n/pt/translation.pt.json b/src/core/i18n/pt/translation.pt.json index 83ef446ff2..7867af0afd 100644 --- a/src/core/i18n/pt/translation.pt.json +++ b/src/core/i18n/pt/translation.pt.json @@ -2000,19 +2000,7 @@ "title": "New Memberships", "openMemberships": "Open applications & invitations", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships", - "invitation": { - "message": "You are invited to join {{journey}}", - "caption": "Click here to see the invitation" - }, - "application": { - "message": "You applied to join {{journey}}", - "caption": "Your application still has to be approved" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" - } + "seeMore": "Show all my memberships" }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", diff --git a/src/core/i18n/ua/translation.ua.json b/src/core/i18n/ua/translation.ua.json index 2a27175681..76ad75edfa 100644 --- a/src/core/i18n/ua/translation.ua.json +++ b/src/core/i18n/ua/translation.ua.json @@ -2000,19 +2000,7 @@ "title": "New Memberships", "openMemberships": "Open applications & invitations", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships", - "invitation": { - "message": "You are invited to join {{journey}}", - "caption": "Click here to see the invitation" - }, - "application": { - "message": "You applied to join {{journey}}", - "caption": "Your application still has to be approved" - }, - "membership": { - "message": " {{journey}}", - "caption": "{{tagline}}" - } + "seeMore": "Show all my memberships" }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", From ad7e9f46c3decaffa53693e8426194228d02031b Mon Sep 17 00:00:00 2001 From: Valentin Yanakiev Date: Mon, 12 Feb 2024 12:03:06 +0200 Subject: [PATCH 06/27] Proper routing on deletion of a post in call for posts (#5538) * Proper routing on deletion of a post in call for posts * cleanup --------- Co-authored-by: Andrew Pazniak Co-authored-by: Svetoslav Petkov --- src/domain/collaboration/post/pages/PostSettingsPage.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/domain/collaboration/post/pages/PostSettingsPage.tsx b/src/domain/collaboration/post/pages/PostSettingsPage.tsx index b69ae82d34..9c52a05d7f 100644 --- a/src/domain/collaboration/post/pages/PostSettingsPage.tsx +++ b/src/domain/collaboration/post/pages/PostSettingsPage.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect, useState } from 'react'; -import { useNavigate, useResolvedPath } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Autocomplete, Button, DialogActions, TextField } from '@mui/material'; import { useTranslation } from 'react-i18next'; import Box from '@mui/material/Box'; @@ -37,7 +37,6 @@ export interface PostSettingsPageProps { const PostSettingsPage: FC = ({ journeyTypeName, onClose }) => { const { t } = useTranslation(); const { spaceNameId = '', challengeNameId, opportunityNameId, postNameId = '', calloutNameId = '' } = useUrlParams(); - const resolved = useResolvedPath('.'); const navigate = useNavigate(); const [post, setPost] = useState(); @@ -55,9 +54,6 @@ const PostSettingsPage: FC = ({ journeyTypeName, onClose references: post?.profile.references, }; - const postIndex = resolved.pathname.indexOf('/posts'); - const contributeUrl = resolved.pathname.substring(0, postIndex); - const postSettings = usePostSettings({ postNameId, spaceNameId, @@ -101,7 +97,7 @@ const PostSettingsPage: FC = ({ journeyTypeName, onClose } await postSettings.handleDelete(postSettings.post.id); - navigate(contributeUrl); + onClose(); }; const [handleUpdate, loading] = useLoadingState(async (shouldUpdate: boolean) => { From 35f6605ac39fea2a01cd512f8d239b0885003b8c Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Mon, 12 Feb 2024 14:36:48 +0200 Subject: [PATCH 07/27] Moved delete confirm dialog to CalloutLayout and added the delete option to the context menu (#5531) Co-authored-by: Aleksandar Stojanovic --- .../CalloutBlock/CalloutLayout.tsx | 35 +++++++++++++++++- .../edit/editDialog/CalloutEditDialog.tsx | 36 ++----------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/domain/collaboration/CalloutBlock/CalloutLayout.tsx b/src/domain/collaboration/CalloutBlock/CalloutLayout.tsx index 4d9de747b8..16c5b1aef3 100644 --- a/src/domain/collaboration/CalloutBlock/CalloutLayout.tsx +++ b/src/domain/collaboration/CalloutBlock/CalloutLayout.tsx @@ -33,6 +33,7 @@ import { ArrowUpwardOutlined, CheckCircleOutlined, Close, + DeleteOutline, EditOutlined, UnpublishedOutlined, VerticalAlignBottomOutlined, @@ -50,6 +51,8 @@ import { useCreateCalloutTemplate } from '../../platform/admin/templates/Callout import SkipLink from '../../../core/ui/keyboardNavigation/SkipLink'; import { useNextBlockAnchor } from '../../../core/ui/keyboardNavigation/NextBlockAnchor'; import { LinkDetails } from '../callout/links/LinkCollectionCallout'; +import ConfirmationDialog from '../../../core/ui/dialogs/ConfirmationDialog'; +import useLoadingState from '../../shared/utils/useLoadingState'; export interface CalloutLayoutProps extends CalloutLayoutEvents, Partial { callout: { @@ -156,6 +159,16 @@ const CalloutLayout = ({ await onVisibilityChange?.(callout.id, visibility, sendNotification); setVisibilityDialogOpen(false); }; + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const handleDeleteDialogOpen = () => { + setDeleteDialogOpen(true); + setSettingsAnchorEl(null); + }; + + const [handleDelete, loadingDelete] = useLoadingState(async () => { + await onCalloutDelete?.(callout); + setDeleteDialogOpen(false); + }); const [saveAsTemplateDialogOpen, setSaveAsTemplateDialogOpen] = useState(false); const handleSaveAsTemplateDialogOpen = () => { @@ -296,6 +309,9 @@ const CalloutLayout = ({ > {t(`buttons.${callout.draft ? '' : 'un'}publish` as const)} + + {t('buttons.delete')} + {callout.canSaveAsTemplate && ( setDeleteDialogOpen(true)} canChangeCalloutLocation calloutNames={calloutNames} journeyTypeName={journeyTypeName} /> )} + setDeleteDialogOpen(false), + }} + state={{ + isLoading: loadingDelete, + }} + /> ); }; diff --git a/src/domain/collaboration/callout/edit/editDialog/CalloutEditDialog.tsx b/src/domain/collaboration/callout/edit/editDialog/CalloutEditDialog.tsx index 31a91cf533..62887dd9f6 100644 --- a/src/domain/collaboration/callout/edit/editDialog/CalloutEditDialog.tsx +++ b/src/domain/collaboration/callout/edit/editDialog/CalloutEditDialog.tsx @@ -1,10 +1,9 @@ -import React, { FC, useCallback, useMemo, useState } from 'react'; +import React, { FC, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { LoadingButton } from '@mui/lab'; import calloutIcons from '../../utils/calloutIcons'; import { DialogActions, DialogContent } from '../../../../../core/ui/dialog/deprecated'; import DialogHeader from '../../../../../core/ui/dialog/DialogHeader'; -import ConfirmationDialog, { ConfirmationDialogProps } from '../../../../../core/ui/dialogs/ConfirmationDialog'; import { CalloutDeleteType, CalloutEditType } from '../CalloutEditType'; import CalloutForm, { CalloutFormInput, CalloutFormOutput } from '../../CalloutForm'; import { CalloutType, TagsetType } from '../../../../../core/apollo/generated/graphql-schema'; @@ -23,7 +22,7 @@ export interface CalloutEditDialogProps { calloutType: CalloutType; callout: CalloutLayoutProps['callout']; onClose: () => void; - onDelete: (callout: CalloutDeleteType) => Promise; + onDelete: (callout: CalloutDeleteType) => void; onCalloutEdit: (callout: CalloutEditType) => Promise; canChangeCalloutLocation?: boolean; calloutNames: string[]; @@ -100,34 +99,6 @@ const CalloutEditDialog: FC = ({ setLoading(false); }, [callout, fetchWhiteboardTemplateContent, newCallout, spaceNameId, onCalloutEdit]); - const handleDelete = useCallback(async () => { - setLoading(true); - await onDelete(callout); - setLoading(false); - }, [onDelete, callout]); - - const handleDialogDelete = () => setConfirmDialogOpened(true); - - const [confirmDialogOpened, setConfirmDialogOpened] = useState(false); - - const confirmationDialogProps = useMemo( - () => ({ - entities: { - titleId: 'callout.delete-confirm-title', - contentId: 'callout.delete-confirm-text', - confirmButtonTextId: 'buttons.delete', - }, - options: { - show: confirmDialogOpened, - }, - actions: { - onConfirm: handleDelete, - onCancel: () => setConfirmDialogOpened(false), - }, - }), - [confirmDialogOpened, handleDelete, setConfirmDialogOpened] - ); - const CalloutIcon = calloutType ? calloutIcons[calloutType] : undefined; return ( @@ -164,7 +135,7 @@ const CalloutEditDialog: FC = ({ loading={loading} disabled={loading} variant="outlined" - onClick={handleDialogDelete} + onClick={() => onDelete(callout)} aria-label={t('buttons.delete')} > {t('buttons.delete')} @@ -180,7 +151,6 @@ const CalloutEditDialog: FC = ({ - ); }; From 6964795ead27c02419488ea343f8d0ebc4307b80 Mon Sep 17 00:00:00 2001 From: Svetoslav Petkov Date: Mon, 12 Feb 2024 14:37:20 +0200 Subject: [PATCH 08/27] latest translations (#5542) Co-authored-by: Valentin Yanakiev --- src/core/i18n/ach/translation.ach.json | 64 ++++++++++++++---------- src/core/i18n/bg/translation.bg.json | 67 ++++++++++++++++--------- src/core/i18n/de/translation.de.json | 67 ++++++++++++++++--------- src/core/i18n/es/translation.es.json | 69 +++++++++++++++++--------- src/core/i18n/fr/translation.fr.json | 67 ++++++++++++++++--------- src/core/i18n/nl/translation.nl.json | 69 ++++++++++++++++---------- src/core/i18n/pt/translation.pt.json | 67 ++++++++++++++++--------- src/core/i18n/ua/translation.ua.json | 67 ++++++++++++++++--------- 8 files changed, 345 insertions(+), 192 deletions(-) diff --git a/src/core/i18n/ach/translation.ach.json b/src/core/i18n/ach/translation.ach.json index dfaf89b81e..1a046edc63 100644 --- a/src/core/i18n/ach/translation.ach.json +++ b/src/core/i18n/ach/translation.ach.json @@ -36,6 +36,10 @@ "dont-show-notes": "crwdns10972:0crwdne10972:0", "icon": "crwdns10964:0crwdne10964:0", "releaseUpdates": { + "3": { + "title": "crwdns11280:0crwdne11280:0", + "content": "crwdns11282:0crwdne11282:0" + }, "2": { "title": "crwdns10958:0crwdne10958:0", "content": "crwdns10960:0crwdne10960:0" @@ -624,7 +628,6 @@ "add-another": "crwdns8982:0crwdne8982:0", "delete-link": "crwdns8984:0crwdne8984:0", "save-to-add": "crwdns8986:0crwdne8986:0", - "click-to-see": "crwdns8988:0{{count}}crwdne8988:0", "no-links-yet": "crwdns8990:0crwdne8990:0", "delete-confirm-title": "crwdns8992:0crwdne8992:0", "delete-confirm": "crwdns8994:0{{title}}crwdne8994:0", @@ -911,7 +914,7 @@ "title": "crwdns10970:0{{space}}crwdne10970:0", "help": "crwdns9276:0crwdne9276:0", "privateChallenge": "crwdns9278:0crwdne9278:0", - "showAll": "crwdns9280:0crwdne9280:0" + "showAll": "crwdns11214:0crwdne11214:0" }, "dashboardRecentContributions": { "title": "crwdns9282:0crwdne9282:0", @@ -1115,7 +1118,7 @@ "POST": "crwdns9544:0crwdne9544:0", "POST_COLLECTION": "crwdns9546:0crwdne9546:0", "WHITEBOARD_COLLECTION": "crwdns9548:0crwdne9548:0", - "LINK_COLLECTION": "crwdns9550:0crwdne9550:0", + "LINK_COLLECTION": "crwdns11218:0crwdne11218:0", "WHITEBOARD": "crwdns9552:0crwdne9552:0", "WHITEBOARD_RT": "crwdns9554:0crwdne9554:0" }, @@ -1971,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "crwdns11146:0crwdne11146:0", + "full": "crwdns11216:0crwdne11216:0", "short": "crwdns11148:0crwdne11148:0" } }, @@ -2003,19 +2006,22 @@ }, "newMemberships": { "title": "crwdns11050:0crwdne11050:0", - "openMemberships": "crwdns11052:0crwdne11052:0", + "openInvitations": "crwdns11260:0crwdne11260:0", + "noOpenInvitations": "crwdns11272:0crwdne11272:0", + "openApplications": "crwdns11262:0crwdne11262:0", "recentlyJoined": "crwdns11054:0crwdne11054:0", - "seeMore": "crwdns11056:0crwdne11056:0", + "seeMore": "crwdns11264:0crwdne11264:0", + "seeAll": "crwdns11270:0crwdne11270:0", "invitation": { - "message": "crwdns11058:0{{journey}}crwdne11058:0", + "message": "crwdns11274:0{{journey}}crwdne11274:0", "caption": "crwdns11060:0crwdne11060:0" }, "application": { - "message": "crwdns11062:0{{journey}}crwdne11062:0", + "message": "crwdns11276:0{{journey}}crwdne11276:0", "caption": "crwdns11064:0crwdne11064:0" }, "membership": { - "message": "crwdns11066:0{{journey}}crwdne11066:0", + "message": "crwdns11278:0{{journey}}crwdne11278:0", "caption": "crwdns11068:0{{tagline}}crwdne11068:0" } }, @@ -2052,7 +2058,7 @@ }, "startingSpace": { "title": "crwdns11078:0crwdne11078:0", - "url": "crwdns11080:0crwdne11080:0" + "url": "crwdns11284:0crwdne11284:0" }, "my-spaces": { "header": "crwdns10390:0{{mySpacesCount}}crwdne10390:0" @@ -2074,28 +2080,34 @@ "findMoreUrl": "crwdns11090:0crwdne11090:0", "items": [ { - "title": "crwdns11092:0crwdne11092:0", - "description": "crwdns11094:0crwdne11094:0", - "imageUrl": "crwdns11096:0crwdne11096:0", - "url": "crwdns11098:0crwdne11098:0" + "title": "crwdns11220:0crwdne11220:0", + "description": "crwdns11222:0crwdne11222:0", + "imageUrl": "crwdns11224:0crwdne11224:0", + "url": "crwdns11226:0crwdne11226:0" + }, + { + "title": "crwdns11228:0crwdne11228:0", + "description": "crwdns11230:0crwdne11230:0", + "imageUrl": "crwdns11232:0crwdne11232:0", + "url": "crwdns11234:0crwdne11234:0" }, { - "title": "crwdns11100:0crwdne11100:0", - "description": "crwdns11102:0crwdne11102:0", - "imageUrl": "crwdns11104:0crwdne11104:0", - "url": "crwdns11106:0crwdne11106:0" + "title": "crwdns11236:0crwdne11236:0", + "description": "crwdns11238:0crwdne11238:0", + "imageUrl": "crwdns11240:0crwdne11240:0", + "url": "crwdns11242:0crwdne11242:0" }, { - "title": "crwdns11108:0crwdne11108:0", - "description": "crwdns11110:0crwdne11110:0", - "imageUrl": "crwdns11112:0crwdne11112:0", - "url": "crwdns11114:0crwdne11114:0" + "title": "crwdns11244:0crwdne11244:0", + "description": "crwdns11246:0crwdne11246:0", + "imageUrl": "crwdns11248:0crwdne11248:0", + "url": "crwdns11250:0crwdne11250:0" }, { - "title": "crwdns11116:0crwdne11116:0", - "description": "crwdns11118:0crwdne11118:0", - "imageUrl": "crwdns11120:0crwdne11120:0", - "url": "crwdns11122:0crwdne11122:0" + "title": "crwdns11252:0crwdne11252:0", + "description": "crwdns11254:0crwdne11254:0", + "imageUrl": "crwdns11256:0crwdne11256:0", + "url": "crwdns11258:0crwdne11258:0" } ] }, diff --git a/src/core/i18n/bg/translation.bg.json b/src/core/i18n/bg/translation.bg.json index 107feb65f2..6475f5a00c 100644 --- a/src/core/i18n/bg/translation.bg.json +++ b/src/core/i18n/bg/translation.bg.json @@ -628,7 +628,6 @@ "add-another": "Add another link", "delete-link": "Click here to delete this link", "save-to-add": "Click ‘Save as draft’ or ‘Publish’ to start adding links.", - "click-to-see": "{{count}} links, click here to show all", "no-links-yet": "There are no links yet, be the first to add one!", "delete-confirm-title": "Delete this link?", "delete-confirm": "Are you sure that you want to remove this link from {{title}}?", @@ -915,7 +914,7 @@ "title": "Challenges and Opportunities in {{space}}", "help": "This Space holds the following Challenges. When these Challenges have Opportunities, these are visible as well.", "privateChallenge": "This is a private Challenge. To view it’s content and potential Opportunities, visit the Challenge and apply to become a member.", - "showAll": "Show all Challenges and Opportunities in this Space" + "showAll": "Show all" }, "dashboardRecentContributions": { "title": "Contributions", @@ -1119,7 +1118,7 @@ "POST": "Post", "POST_COLLECTION": "Call for Posts", "WHITEBOARD_COLLECTION": "Call for Whiteboards", - "LINK_COLLECTION": "Collection of Links", + "LINK_COLLECTION": "Collection of Links and Documents", "WHITEBOARD": "Whiteboard", "WHITEBOARD_RT": "Whiteboard RealTime" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Explore all your Spaces, Challenges, and Opportunities", + "full": "Explore all your Spaces", "short": "Explore all your Spaces" } }, @@ -1998,9 +1997,24 @@ }, "newMemberships": { "title": "New Memberships", - "openMemberships": "Open applications & invitations", + "openInvitations": "Open invitations", + "noOpenInvitations": "No open invitations", + "openApplications": "Open applications", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships" + "seeMore": "All pending memberships", + "seeAll": "See all my memberships", + "invitation": { + "message": "{{journey}}", + "caption": "Click here to see the invitation" + }, + "application": { + "message": "{{journey}}", + "caption": "Your application still has to be approved" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" + } }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", @@ -2034,7 +2048,8 @@ "body1": "For Users, Spaces are free to join. Explore the membership options, including for public and private Spaces." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" @@ -2056,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "New here? 👋 Welcome at the platform!", - "description": "Click here for some quick steps to get started", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Join the Welcome@Alkemio Space", + "description": "To find examples, best practices, tips and tricks", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Have a look at the FAQ", + "description": "To find answers to your most urgent questions", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Guiding your Challenge step by step", - "description": "How to use the innovation flow to structure your process", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Start a new Space", + "description": "To gather contributors for your envisioned change", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Using whiteboards like a pro", - "description": "The keyboard-shortcuts that will change your life", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Read more about the Key concepts", + "description": "To help you get started smoothly on the platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Starting a new Space", - "description": "👍 Have a look at the possibilities here! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "Introducing Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, diff --git a/src/core/i18n/de/translation.de.json b/src/core/i18n/de/translation.de.json index 3167a1c15f..17247e3515 100644 --- a/src/core/i18n/de/translation.de.json +++ b/src/core/i18n/de/translation.de.json @@ -628,7 +628,6 @@ "add-another": "Einen weiteren Link hinzufügen", "delete-link": "Klicken Sie hier, um diesen Benutzer löschen", "save-to-add": "Klicken Sie auf \"Als Entwurf speichern\" oder \"Veröffentlichen\" um Links hinzuzufügen.", - "click-to-see": "{{count}} Links, hier klicken um alle anzuzeigen", "no-links-yet": "Es gibt noch keine Links, seien Sie die ersten, die einen hinzufügen!", "delete-confirm-title": "Diesen Link löschen", "delete-confirm": "Bist du dir sicher, dass du diese Allianz aus {0} entfernen willst?", @@ -915,7 +914,7 @@ "title": "Challenges and Opportunities in {{space}}", "help": "Dieser Raum enthält die folgenden Herausforderungen. Wenn diese Herausforderungen Chancen haben, sind diese auch sichtbar.", "privateChallenge": "Dies ist eine private Herausforderung. Um Inhalte und potenzielle Chancen zu sehen, besuchen Sie die Challenge und bewerben Sie sich für ein Mitglied.", - "showAll": "Alle Herausforderungen und Verkaufschancen in diesem Raum anzeigen" + "showAll": "Show all" }, "dashboardRecentContributions": { "title": "Contributions", @@ -1119,7 +1118,7 @@ "POST": "Posten", "POST_COLLECTION": "Tags für Einträge", "WHITEBOARD_COLLECTION": "Whiteboard leeren", - "LINK_COLLECTION": "Diamantsammlung", + "LINK_COLLECTION": "Collection of Links and Documents", "WHITEBOARD": "Whiteboard", "WHITEBOARD_RT": "Whiteboard RealTime" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Explore all your Spaces, Challenges, and Opportunities", + "full": "Explore all your Spaces", "short": "Explore all your Spaces" } }, @@ -1998,9 +1997,24 @@ }, "newMemberships": { "title": "New Memberships", - "openMemberships": "Open applications & invitations", + "openInvitations": "Open invitations", + "noOpenInvitations": "No open invitations", + "openApplications": "Open applications", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships" + "seeMore": "All pending memberships", + "seeAll": "See all my memberships", + "invitation": { + "message": "{{journey}}", + "caption": "Click here to see the invitation" + }, + "application": { + "message": "{{journey}}", + "caption": "Your application still has to be approved" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" + } }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", @@ -2034,7 +2048,8 @@ "body1": "Für Benutzer stehen Hubs kostenlos zur Verfügung. Erkunden Sie die Mitgliedschaftsoptionen, einschließlich für öffentliche und private Hubs." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "Meine Räume ({{mySpacesCount}})" @@ -2056,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "New here? 👋 Welcome at the platform!", - "description": "Click here for some quick steps to get started", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Join the Welcome@Alkemio Space", + "description": "To find examples, best practices, tips and tricks", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Have a look at the FAQ", + "description": "To find answers to your most urgent questions", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Guiding your Challenge step by step", - "description": "How to use the innovation flow to structure your process", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Start a new Space", + "description": "To gather contributors for your envisioned change", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Using whiteboards like a pro", - "description": "The keyboard-shortcuts that will change your life", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Read more about the Key concepts", + "description": "To help you get started smoothly on the platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Starting a new Space", - "description": "👍 Have a look at the possibilities here! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "Introducing Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, diff --git a/src/core/i18n/es/translation.es.json b/src/core/i18n/es/translation.es.json index ff39b250f2..224fa858bd 100644 --- a/src/core/i18n/es/translation.es.json +++ b/src/core/i18n/es/translation.es.json @@ -37,7 +37,7 @@ "icon": "🎉", "releaseUpdates": { "3": { - "title": "Te presentamos... My Dashboard!", + "title": "Introducing... My Dashboard!", "content": "We're very excited to welcome you at your new Alkemio \"Homepage\": My Dashboard. Here you can easily resume your work and stay updated on your communities. Have a look at these new features:

🚀 Quick access to your favorite communities You’ll see the last 5 places that you’ve contributed to, whether they are Spaces, Challenges, or Opportunities. To see more, just click on the button on the right.

🔔 All the latest activities in one place The Recent Contributions panel shows you everything that’s going on in your communities. You can filter by Spaces and by your role in them.

...and there is more:
  • Don’t miss any invitations: they are now shown in the New Memberships block
  • Keep track of your own latest activities with My Latest Contributions
  • Discover new templates from the Innovation Library
  • Make the most out of Alkemio with the ‘Tips and Tricks’
  • Good to know: The exact layout of your Dashboard depends on whether you're signed in or not, and whether you have any memberships.
Scroll down to explore what else is on your new Dashboard, and tell us what you think! We’ve created a discussion on the [forum](/forum) to hear your feedback." }, "2": { @@ -628,7 +628,6 @@ "add-another": "Add another link", "delete-link": "Click here to delete this link", "save-to-add": "Click ‘Save as draft’ or ‘Publish’ to start adding links.", - "click-to-see": "{{count}} links, click here to show all", "no-links-yet": "There are no links yet, be the first to add one!", "delete-confirm-title": "Delete this link?", "delete-confirm": "Are you sure that you want to remove this link from {{title}}?", @@ -915,7 +914,7 @@ "title": "Challenges and Opportunities in {{space}}", "help": "This Space holds the following Challenges. When these Challenges have Opportunities, these are visible as well.", "privateChallenge": "This is a private Challenge. To view it’s content and potential Opportunities, visit the Challenge and apply to become a member.", - "showAll": "Show all Challenges and Opportunities in this Space" + "showAll": "Show all" }, "dashboardRecentContributions": { "title": "Contributions", @@ -1119,7 +1118,7 @@ "POST": "Post", "POST_COLLECTION": "Call for Posts", "WHITEBOARD_COLLECTION": "Call for Whiteboards", - "LINK_COLLECTION": "Collection of Links", + "LINK_COLLECTION": "Collection of Links and Documents", "WHITEBOARD": "Whiteboard", "WHITEBOARD_RT": "Whiteboard RealTime" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Explore all your Spaces, Challenges, and Opportunities", + "full": "Explore all your Spaces", "short": "Explore all your Spaces" } }, @@ -1998,9 +1997,24 @@ }, "newMemberships": { "title": "New Memberships", - "openMemberships": "Open applications & invitations", + "openInvitations": "Open invitations", + "noOpenInvitations": "No open invitations", + "openApplications": "Open applications", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships" + "seeMore": "All pending memberships", + "seeAll": "See all my memberships", + "invitation": { + "message": "{{journey}}", + "caption": "Click here to see the invitation" + }, + "application": { + "message": "{{journey}}", + "caption": "Your application still has to be approved" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" + } }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", @@ -2034,7 +2048,8 @@ "body1": "For Users, Spaces are free to join. Explore the membership options, including for public and private Spaces." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" @@ -2056,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "New here? 👋 Welcome at the platform!", - "description": "Click here for some quick steps to get started", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Join the Welcome@Alkemio Space", + "description": "To find examples, best practices, tips and tricks", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Have a look at the FAQ", + "description": "To find answers to your most urgent questions", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Guiding your Challenge step by step", - "description": "How to use the innovation flow to structure your process", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Start a new Space", + "description": "To gather contributors for your envisioned change", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Using whiteboards like a pro", - "description": "The keyboard-shortcuts that will change your life", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Read more about the Key concepts", + "description": "To help you get started smoothly on the platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Starting a new Space", - "description": "👍 Have a look at the possibilities here! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "Introducing Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, diff --git a/src/core/i18n/fr/translation.fr.json b/src/core/i18n/fr/translation.fr.json index 8f242fc66f..f516fab37a 100644 --- a/src/core/i18n/fr/translation.fr.json +++ b/src/core/i18n/fr/translation.fr.json @@ -628,7 +628,6 @@ "add-another": "Ajouter un autre lien", "delete-link": "Cliquez ici pour supprimer cet utilisateur", "save-to-add": "Cliquez sur « Enregistrer en tant que brouillon » ou « Publier » pour commencer à ajouter des liens.", - "click-to-see": "{{count}} liens, cliquez ici pour tout afficher", "no-links-yet": "Il n'y a aucun post dans ce forum, soyez le premier à en ajouter un.", "delete-confirm-title": "Supprimer le lien de ce site ?", "delete-confirm": "Êtes-vous sûr de vouloir supprimer cette alliance de la {0} ?", @@ -915,7 +914,7 @@ "title": "Challenges and Opportunities in {{space}}", "help": "Cet espace contient les défis suivants. Quand ces défis ont des opportunités, ils sont également visibles.", "privateChallenge": "Il s'agit d'un défi privé. Pour voir son contenu et ses opportunités potentielles, visitez le Défi et postulez pour devenir membre.", - "showAll": "Afficher tous les défis et opportunités dans cet espace" + "showAll": "Show all" }, "dashboardRecentContributions": { "title": "Contributions", @@ -1119,7 +1118,7 @@ "POST": "Publication", "POST_COLLECTION": "Appel aux publications", "WHITEBOARD_COLLECTION": "Appel pour tableaux blancs", - "LINK_COLLECTION": "Collection de Liens", + "LINK_COLLECTION": "Collection of Links and Documents", "WHITEBOARD": "Tableau blanc", "WHITEBOARD_RT": "Whiteboard RealTime" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Explore all your Spaces, Challenges, and Opportunities", + "full": "Explore all your Spaces", "short": "Explore all your Spaces" } }, @@ -1998,9 +1997,24 @@ }, "newMemberships": { "title": "New Memberships", - "openMemberships": "Open applications & invitations", + "openInvitations": "Open invitations", + "noOpenInvitations": "No open invitations", + "openApplications": "Open applications", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships" + "seeMore": "All pending memberships", + "seeAll": "See all my memberships", + "invitation": { + "message": "{{journey}}", + "caption": "Click here to see the invitation" + }, + "application": { + "message": "{{journey}}", + "caption": "Your application still has to be approved" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" + } }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", @@ -2034,7 +2048,8 @@ "body1": "Pour les utilisateurs, les hubs sont libres de se joindre. Explorez les options d'adhésion, y compris pour les hubs publics et privés." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "Mes espaces ({{mySpacesCount}})" @@ -2056,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "New here? 👋 Welcome at the platform!", - "description": "Click here for some quick steps to get started", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Join the Welcome@Alkemio Space", + "description": "To find examples, best practices, tips and tricks", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Have a look at the FAQ", + "description": "To find answers to your most urgent questions", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Guiding your Challenge step by step", - "description": "How to use the innovation flow to structure your process", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Start a new Space", + "description": "To gather contributors for your envisioned change", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Using whiteboards like a pro", - "description": "The keyboard-shortcuts that will change your life", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Read more about the Key concepts", + "description": "To help you get started smoothly on the platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Starting a new Space", - "description": "👍 Have a look at the possibilities here! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "Introducing Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, diff --git a/src/core/i18n/nl/translation.nl.json b/src/core/i18n/nl/translation.nl.json index d5248ea979..fa28412fd5 100644 --- a/src/core/i18n/nl/translation.nl.json +++ b/src/core/i18n/nl/translation.nl.json @@ -37,8 +37,8 @@ "icon": "🎉", "releaseUpdates": { "3": { - "title": "Introducing... My Dashboard!", - "content": "We're very excited to welcome you at your new Alkemio \"Homepage\": My Dashboard. Here you can easily resume your work and stay updated on your communities. Have a look at these new features:

🚀 Quick access to your favorite communities You’ll see the last 5 places that you’ve contributed to, whether they are Spaces, Challenges, or Opportunities. To see more, just click on the button on the right.

🔔 All the latest activities in one place The Recent Contributions panel shows you everything that’s going on in your communities. You can filter by Spaces and by your role in them.

...and there is more:
  • Don’t miss any invitations: they are now shown in the New Memberships block
  • Keep track of your own latest activities with My Latest Contributions
  • Discover new templates from the Innovation Library
  • Make the most out of Alkemio with the ‘Tips and Tricks’
  • Good to know: The exact layout of your Dashboard depends on whether you're signed in or not, and whether you have any memberships.
Scroll down to explore what else is on your new Dashboard, and tell us what you think! We’ve created a discussion on the [forum](/forum) to hear your feedback." + "title": "We introduceren... Mijn Dashboard!", + "content": "Welkom op je nieuwe Alkemio “Homepage”: Mijn Dashboard! Hier kun je gemakkelijk je werk hervatten en op de hoogte blijven van je communities. Bekijk deze nieuwe functies:

🚀 Snel toegang tot je favoriete communities Je ziet de laatste 5 plaatsen waar je aan hebt bijgedragen, of het nu Spaces, Challenges of Opportunities zijn. Om meer te zien, klik je op de knop rechts.

🔔 Alle laatste activiteiten op één plek Het Recent Contributions panel laat je alles zien wat er gebeurt in je communities. Je kunt filteren op Spaces en op je rol in hen.

…en er is meer:
  • Mis geen enkele uitnodiging: ze worden nu getoond in het New Memberships blok
  • Houd je eigen laatste activiteiten bij met My Latest Contributions
  • Ontdek nieuwe templates uit de Innovation Library
  • Haal het meeste uit Alkemio met de ‘Tips and Tricks’
  • Goed om te weten: De exacte indeling van je Dashboard hangt af van of je ingelogd bent of niet, en of je wel of geen lid bent van een Space.
Scroll naar beneden om te ontdekken wat er nog meer op je nieuwe Dashboard staat, en vertel ons wat je ervan vindt! We hebben een discussie op het [forum](/forum) gemaakt om alle feedback te verzamelen." }, "2": { "title": "We hebben spannend nieuws, er is een nieuwe release vol interessante functionaliteiten uit!", @@ -604,9 +604,9 @@ "maxLeadsWarning": "Let op dat er een maximum aantal leads is voor de communities. Als het maximale aantal wordt bereikt, wordt deze optie voor anderen uitgeschakeld.", "removeMember": { "sectionTitle": "Lid verwijderen", - "remove": "Klik hier om deze gebruiker uit deze Space te verwijderen. Wees voorzichtig, deze actie kan niet ongedaan worden gemaakt.", + "remove": "Klik hier om deze gebruiker uit de Community te verwijderen. Wees voorzichtig, deze actie kan niet ongedaan worden gemaakt.", "dialogTitle": "Weet je zeker dat je dit lid wilt verwijderen?", - "dialogContent": "Door op 'bevestigen' te klikken zal {{member}} worden verwijderd uit deze Space en alle onderliggende Challenges en Opportunities waar {{memberFirstName}} lid van is. De bijdragen van {{memberFirstName}} blijven wel zichtbaar." + "dialogContent": "Door op 'bevestigen' te klikken zal {{member}} worden verwijderd uit deze Community en alle onderliggende Challenges en Opportunities waar {{memberFirstName}} lid van is. De bijdragen van {{memberFirstName}} blijven wel zichtbaar." } } }, @@ -628,7 +628,6 @@ "add-another": "Voeg nog een link toe", "delete-link": "Klik hier om deze link te verwijderen", "save-to-add": "Klik op 'Opslaan als concept' of 'Publiceren' om links toe te voegen.", - "click-to-see": "{{count}} links, klik hier voor alle links", "no-links-yet": "Er zijn nog geen links, voeg nu de eerste toe!", "delete-confirm-title": "Deze link verwijderen?", "delete-confirm": "Weet je zeker dat je deze link uit {{title}} wilt verwijderen?", @@ -915,7 +914,7 @@ "title": "Challenges en Opportunities in {{space}}", "help": "Deze Space bevat de volgende Challenges. Ook zijn de eventuele onderliggende Opportunities zichtbaar.", "privateChallenge": "Dit is een gesloten Challenge. Om de inhoud en eventuele onderliggende Opportunities te bekijken, kun je je aanmelden voor de Challenge.", - "showAll": "Toon alle Challenges en Opportunities in deze Space" + "showAll": "Toon alles" }, "dashboardRecentContributions": { "title": "Bijdragen", @@ -1119,7 +1118,7 @@ "POST": "Post", "POST_COLLECTION": "Oproep voor Posts", "WHITEBOARD_COLLECTION": "Oproep voor Whiteboards", - "LINK_COLLECTION": "Collectie van links", + "LINK_COLLECTION": "Collectie van links en documenten", "WHITEBOARD": "Whiteboard", "WHITEBOARD_RT": "Whiteboard RealTime" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Bekijk al je Spaces, Challenges en Opportunities", + "full": "Bekijk al je Spaces", "short": "Bekijk al je Spaces" } }, @@ -1998,14 +1997,23 @@ }, "newMemberships": { "title": "Nieuwe Lidmaatschappen", - "openMemberships": "Openstaande aanmeldingen & uitnodigingen", + "openInvitations": "Openstaande uitnodigingen", + "noOpenInvitations": "Geen openstaande uitnodigingen", + "openApplications": "Openstaande aanmeldingen", "recentlyJoined": "Recent toegetreden", - "seeMore": "Toon al mijn lidmaatschappen", + "seeMore": "Alle openstaande lidmaatschappen", + "seeAll": "Al mijn lidmaatschappen", "invitation": { + "message": "{{journey}}", "caption": "Klik hier om de uitnodiging te zien" }, "application": { + "message": "{{journey}}", "caption": "Je aanvraag moet nog goedgekeurd worden" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" } }, "welcome": { @@ -2040,7 +2048,8 @@ "body1": "Individuele gebruikers kunnen zich aansluiten bij één of meerdere Spaces. Deze Spaces kunnen zowel openbaar als privé zijn." }, "startingSpace": { - "title": "🎉 Start jouw Space Klaar om een community te bouwen op Alkemio? Klik hier om te starten!" + "title": "🎉 Start jouw Space Klaar om een community te bouwen op Alkemio? Klik hier om te starten!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "Mijn Spaces ({{mySpacesCount}})" @@ -2062,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "Nieuw hier? 👋 Welkom op het platform!", - "description": "Begin meteen met deze snelle stappen", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Welkom bij de Welcome@Alkemio Space", + "description": "Bekijk de voorbeelden, best practices, tips en trucs", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Neem een kijkje in de FAQ", + "description": "Vind een antwoord op de veelgestelde vragen", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Begeleid je Challenge in eenvoudige stappen", - "description": "Hoe je de innovation flow kan gebruiken om je proces te structureren", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Een nieuwe Space starten", + "description": "Een plek om met elkaar aan de slag te gaan", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Whiteboards gebruiken als een pro", - "description": "De sneltoetsen die je leven veranderen", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Lees meer over de kernconcepten", + "description": "Voor een soepele start op het platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Een nieuwe Space starten", - "description": "👍 Bekijk de mogelijkheden hier! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "De introductie van Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, diff --git a/src/core/i18n/pt/translation.pt.json b/src/core/i18n/pt/translation.pt.json index 7867af0afd..57cb428b8b 100644 --- a/src/core/i18n/pt/translation.pt.json +++ b/src/core/i18n/pt/translation.pt.json @@ -628,7 +628,6 @@ "add-another": "Adicionar outro link", "delete-link": "Clique aqui para excluir este link", "save-to-add": "Clique em ‘Salvar como rascunho’ ou ‘Publicar’ para começar a adicionar links.", - "click-to-see": "Links {{count}}, clique aqui para mostrar todos", "no-links-yet": "Ainda não há links, seja o primeiro a adicionar um!", "delete-confirm-title": "Excluir este link?", "delete-confirm": "Tem certeza que deseja remover este link do {{title}}?", @@ -915,7 +914,7 @@ "title": "Desafios e Oportunidades em {{space}}", "help": "Este espaço possui os seguintes Desafios. Quando estes Desafios tiverem Oportunidades, estes também serão visíveis.", "privateChallenge": "Este é um Desafio privado. Para visualizar seu conteúdo e oportunidades potenciais, visite o Desafio e inscreva-se para se tornar um membro.", - "showAll": "Mostrar todos os Desafios e Oportunidades neste Espaço" + "showAll": "Show all" }, "dashboardRecentContributions": { "title": "Contribuições", @@ -1119,7 +1118,7 @@ "POST": "Post", "POST_COLLECTION": "Chamada para Posts", "WHITEBOARD_COLLECTION": "Chamada para Whiteboards", - "LINK_COLLECTION": "Coleção de Links", + "LINK_COLLECTION": "Collection of Links and Documents", "WHITEBOARD": "Whiteboard", "WHITEBOARD_RT": "Whiteboard em tempo real" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Explore all your Spaces, Challenges, and Opportunities", + "full": "Explore all your Spaces", "short": "Explore all your Spaces" } }, @@ -1998,9 +1997,24 @@ }, "newMemberships": { "title": "New Memberships", - "openMemberships": "Open applications & invitations", + "openInvitations": "Open invitations", + "noOpenInvitations": "No open invitations", + "openApplications": "Open applications", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships" + "seeMore": "All pending memberships", + "seeAll": "See all my memberships", + "invitation": { + "message": "{{journey}}", + "caption": "Click here to see the invitation" + }, + "application": { + "message": "{{journey}}", + "caption": "Your application still has to be approved" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" + } }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", @@ -2034,7 +2048,8 @@ "body1": "Para usuários, os Espaços são livres para entrar. Explore as opções, inclusive para espaços públicos e privados." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "Meus Espaços ({{mySpacesCount}})" @@ -2056,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "New here? 👋 Welcome at the platform!", - "description": "Click here for some quick steps to get started", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Join the Welcome@Alkemio Space", + "description": "To find examples, best practices, tips and tricks", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Have a look at the FAQ", + "description": "To find answers to your most urgent questions", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Guiding your Challenge step by step", - "description": "How to use the innovation flow to structure your process", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Start a new Space", + "description": "To gather contributors for your envisioned change", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Using whiteboards like a pro", - "description": "The keyboard-shortcuts that will change your life", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Read more about the Key concepts", + "description": "To help you get started smoothly on the platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Starting a new Space", - "description": "👍 Have a look at the possibilities here! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "Introducing Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, diff --git a/src/core/i18n/ua/translation.ua.json b/src/core/i18n/ua/translation.ua.json index 76ad75edfa..5f68d85474 100644 --- a/src/core/i18n/ua/translation.ua.json +++ b/src/core/i18n/ua/translation.ua.json @@ -628,7 +628,6 @@ "add-another": "Add another link", "delete-link": "Click here to delete this link", "save-to-add": "Click ‘Save as draft’ or ‘Publish’ to start adding links.", - "click-to-see": "{{count}} links, click here to show all", "no-links-yet": "There are no links yet, be the first to add one!", "delete-confirm-title": "Delete this link?", "delete-confirm": "Are you sure that you want to remove this link from {{title}}?", @@ -915,7 +914,7 @@ "title": "Challenges and Opportunities in {{space}}", "help": "This Space holds the following Challenges. When these Challenges have Opportunities, these are visible as well.", "privateChallenge": "This is a private Challenge. To view it’s content and potential Opportunities, visit the Challenge and apply to become a member.", - "showAll": "Show all Challenges and Opportunities in this Space" + "showAll": "Show all" }, "dashboardRecentContributions": { "title": "Contributions", @@ -1119,7 +1118,7 @@ "POST": "Post", "POST_COLLECTION": "Call for Posts", "WHITEBOARD_COLLECTION": "Call for Whiteboards", - "LINK_COLLECTION": "Collection of Links", + "LINK_COLLECTION": "Collection of Links and Documents", "WHITEBOARD": "Whiteboard", "WHITEBOARD_RT": "Whiteboard RealTime" }, @@ -1975,7 +1974,7 @@ }, "recentJourneys": { "seeMore": { - "full": "Explore all your Spaces, Challenges, and Opportunities", + "full": "Explore all your Spaces", "short": "Explore all your Spaces" } }, @@ -1998,9 +1997,24 @@ }, "newMemberships": { "title": "New Memberships", - "openMemberships": "Open applications & invitations", + "openInvitations": "Open invitations", + "noOpenInvitations": "No open invitations", + "openApplications": "Open applications", "recentlyJoined": "Recently joined", - "seeMore": "Show all my memberships" + "seeMore": "All pending memberships", + "seeAll": "See all my memberships", + "invitation": { + "message": "{{journey}}", + "caption": "Click here to see the invitation" + }, + "application": { + "message": "{{journey}}", + "caption": "Your application still has to be approved" + }, + "membership": { + "message": "{{journey}}", + "caption": "{{tagline}}" + } }, "welcome": { "welcomeUnauthenticated": "Welcome at Alkemio!", @@ -2034,7 +2048,8 @@ "body1": "For Users, Spaces are free to join. Explore the membership options, including for public and private Spaces." }, "startingSpace": { - "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!" + "title": "🎉 Starting a Space Ready to build a community on Alkemio? Click here to get started!", + "url": "https://welcome.alkem.io/want-to-own-space/" }, "my-spaces": { "header": "My Spaces ({{mySpacesCount}})" @@ -2056,28 +2071,34 @@ "findMoreUrl": "https://welcome.alkem.io/blog/", "items": [ { - "title": "New here? 👋 Welcome at the platform!", - "description": "Click here for some quick steps to get started", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/cd53aebe-cb43-4d04-98c6-04888bad1c9f", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "👋 Join the Welcome@Alkemio Space", + "description": "To find examples, best practices, tips and tricks", + "imageUrl": "/my-dashboard/tipsntricks-welcome.webp", + "url": "https://alkem.io/welcome-space/dashboard" + }, + { + "title": "💭 Have a look at the FAQ", + "description": "To find answers to your most urgent questions", + "imageUrl": "/my-dashboard/tipsntricks-faq.webp", + "url": "https://welcome.alkem.io/faq/" }, { - "title": "Guiding your Challenge step by step", - "description": "How to use the innovation flow to structure your process", - "imageUrl": "https://welcome.alkem.io/blog/2023-07-nadine-intro/featured_hu3d03a01dcc18bc5be0e67db3d8d209a6_2619486_808x455_fill_q75_h2_lanczos_smart1_3.webp", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "⚒️ Start a new Space", + "description": "To gather contributors for your envisioned change", + "imageUrl": "/my-dashboard/tipsntricks-space.webp", + "url": "https://welcome.alkem.io/want-to-own-space/" }, { - "title": "🧑‍🎨 Using whiteboards like a pro", - "description": "The keyboard-shortcuts that will change your life", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/6ec5cc25-ea56-4eff-8470-3adc576ed7b5", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "📑 Read more about the Key concepts", + "description": "To help you get started smoothly on the platform", + "imageUrl": "/my-dashboard/tipsntricks-concepts.webp", + "url": "https://welcome.alkem.io/help/key-concepts/" }, { - "title": "Starting a new Space", - "description": "👍 Have a look at the possibilities here! ", - "imageUrl": "https://alkem.io/api/private/rest/storage/document/51f4e9f8-758d-412a-9026-48e348384c04", - "url": "https://alkem.io/building-alkemio-org/challenges/welcomealkemio-6154/dashboard" + "title": "💬 Powered by Generative AI", + "description": "Introducing Alkemio's Automated Guidance Chat", + "imageUrl": "/my-dashboard/tipsntricks-genai.webp", + "url": "https://welcome.alkem.io/blog/2024-01-19-chatbot/" } ] }, From 453b3791aff79b8af7e600161edf31b56e7b0808 Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:32:20 +0800 Subject: [PATCH 09/27] TemplateDialogs structure fixed (#5545) --- .../CreateInnovationTemplateDialog.tsx | 14 ++---- .../EditInnovationTemplateDialog.tsx | 47 ++++++++++--------- .../CreatePostTemplateDialog.tsx | 37 +++++++++------ .../PostTemplates/EditPostTemplateDialog.tsx | 45 +++++++++--------- .../CreateWhiteboardTemplateDialog.tsx | 38 ++++++++------- .../EditWhiteboardTemplateDialog.tsx | 45 +++++++++--------- 6 files changed, 122 insertions(+), 104 deletions(-) diff --git a/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx b/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx index 09be36acca..d41ddc6699 100644 --- a/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx +++ b/src/domain/platform/admin/templates/InnovationTemplates/CreateInnovationTemplateDialog.tsx @@ -24,15 +24,11 @@ const CreateInnovationTemplateDialog = ({ open, onClose, onSubmit }: CreatePostT }; return ( - theme.spacing(128) } }} - maxWidth={false} - > - - {t('common.create-new-entity', { entity: t('templateLibrary.innovationFlowTemplates.name') })} - + + theme.spacing(128) } }} - maxWidth={false} - > - - {t('common.edit-entity', { entity: t('templateLibrary.innovationFlowTemplates.name') })} - - - - {t('common.update')} - - } + + + + ( + + + + formik.handleSubmit()}> + {t('common.update')} + + + + )} + /> + ); }; diff --git a/src/domain/platform/admin/templates/PostTemplates/CreatePostTemplateDialog.tsx b/src/domain/platform/admin/templates/PostTemplates/CreatePostTemplateDialog.tsx index f318cc1be0..70460d33ed 100644 --- a/src/domain/platform/admin/templates/PostTemplates/CreatePostTemplateDialog.tsx +++ b/src/domain/platform/admin/templates/PostTemplates/CreatePostTemplateDialog.tsx @@ -1,9 +1,10 @@ import PostTemplateForm, { PostTemplateFormSubmittedValues, PostTemplateFormValues } from './PostTemplateForm'; import { useTranslation } from 'react-i18next'; -import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid'; +import DialogWithGrid, { DialogFooter } from '../../../../../core/ui/dialog/DialogWithGrid'; import DialogHeader, { DialogHeaderProps } from '../../../../../core/ui/dialog/DialogHeader'; import React from 'react'; -import FormikSubmitButton from '../../../../shared/components/forms/FormikSubmitButton'; +import { FormikSubmitButtonPure } from '../../../../shared/components/forms/FormikSubmitButton'; +import { DialogActions, DialogContent } from '@mui/material'; interface CreatePostTemplateDialogProps { open: boolean; @@ -17,20 +18,26 @@ const CreatePostTemplateDialog = ({ open, onClose, onSubmit }: CreatePostTemplat const values: Partial = {}; return ( - theme.spacing(128) } }} - maxWidth={false} - > - - {t('common.create-new-entity', { entity: t('templateLibrary.postTemplates.name') })} - - {t('common.create')}} + + + + ( + + + formik.handleSubmit()}> + {t('common.create')} + + + + )} + /> + ); }; diff --git a/src/domain/platform/admin/templates/PostTemplates/EditPostTemplateDialog.tsx b/src/domain/platform/admin/templates/PostTemplates/EditPostTemplateDialog.tsx index a7a9ecb7a6..77e54a591b 100644 --- a/src/domain/platform/admin/templates/PostTemplates/EditPostTemplateDialog.tsx +++ b/src/domain/platform/admin/templates/PostTemplates/EditPostTemplateDialog.tsx @@ -1,11 +1,12 @@ import { AdminPostTemplateFragment } from '../../../../../core/apollo/generated/graphql-schema'; import { useTranslation } from 'react-i18next'; import PostTemplateForm, { PostTemplateFormSubmittedValues, PostTemplateFormValues } from './PostTemplateForm'; -import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid'; +import DialogWithGrid, { DialogFooter } from '../../../../../core/ui/dialog/DialogWithGrid'; import DialogHeader, { DialogHeaderProps } from '../../../../../core/ui/dialog/DialogHeader'; import React from 'react'; import DeleteButton from '../../../../shared/components/DeleteButton'; -import FormikSubmitButton from '../../../../shared/components/forms/FormikSubmitButton'; +import { FormikSubmitButtonPure } from '../../../../shared/components/forms/FormikSubmitButton'; +import { DialogActions, DialogContent } from '@mui/material'; interface EditPostTemplateDialogProps { open: boolean; @@ -38,26 +39,28 @@ const EditPostTemplateDialog = ({ template, open, onClose, onSubmit, onDelete }: }; return ( - theme.spacing(128) } }} - maxWidth={false} - > - - {t('common.edit-entity', { entity: t('templateLibrary.postTemplates.name') })} - - - - {t('common.update')} - - } + + + + ( + + + + formik.handleSubmit()}> + {t('common.update')} + + + + )} + /> + ); }; diff --git a/src/domain/platform/admin/templates/WhiteboardTemplates/CreateWhiteboardTemplateDialog.tsx b/src/domain/platform/admin/templates/WhiteboardTemplates/CreateWhiteboardTemplateDialog.tsx index e90e68e99e..6ceb9819db 100644 --- a/src/domain/platform/admin/templates/WhiteboardTemplates/CreateWhiteboardTemplateDialog.tsx +++ b/src/domain/platform/admin/templates/WhiteboardTemplates/CreateWhiteboardTemplateDialog.tsx @@ -3,11 +3,12 @@ import WhiteboardTemplateForm, { WhiteboardTemplateFormSubmittedValuesWithPreviewImages, WhiteboardTemplateFormValues, } from './WhiteboardTemplateForm'; -import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid'; +import DialogWithGrid, { DialogFooter } from '../../../../../core/ui/dialog/DialogWithGrid'; import DialogHeader, { DialogHeaderProps } from '../../../../../core/ui/dialog/DialogHeader'; import React from 'react'; -import FormikSubmitButton from '../../../../shared/components/forms/FormikSubmitButton'; +import { FormikSubmitButtonPure } from '../../../../shared/components/forms/FormikSubmitButton'; import EmptyWhiteboard from '../../../../common/whiteboard/EmptyWhiteboard'; +import { DialogActions, DialogContent } from '@mui/material'; export interface CreateWhiteboardTemplateDialogProps { open: boolean; @@ -26,21 +27,26 @@ const CreateWhiteboardTemplateDialog = ({ open, onClose, onSubmit }: CreateWhite }; return ( - theme.spacing(128) } }} - maxWidth={false} - > - - {t('common.create-new-entity', { entity: t('templateLibrary.whiteboardTemplates.name') })} - - - {t('common.create')}} + + + + ( + + + formik.handleSubmit()}> + {t('common.create')} + + + + )} + /> + ); }; diff --git a/src/domain/platform/admin/templates/WhiteboardTemplates/EditWhiteboardTemplateDialog.tsx b/src/domain/platform/admin/templates/WhiteboardTemplates/EditWhiteboardTemplateDialog.tsx index 8b70c5df0d..bb386d595e 100644 --- a/src/domain/platform/admin/templates/WhiteboardTemplates/EditWhiteboardTemplateDialog.tsx +++ b/src/domain/platform/admin/templates/WhiteboardTemplates/EditWhiteboardTemplateDialog.tsx @@ -5,10 +5,11 @@ import WhiteboardTemplateForm, { WhiteboardTemplateFormSubmittedValuesWithPreviewImages, WhiteboardTemplateFormValues, } from './WhiteboardTemplateForm'; -import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid'; +import DialogWithGrid, { DialogFooter } from '../../../../../core/ui/dialog/DialogWithGrid'; import DialogHeader, { DialogHeaderProps } from '../../../../../core/ui/dialog/DialogHeader'; import DeleteButton from '../../../../shared/components/DeleteButton'; -import FormikSubmitButton from '../../../../shared/components/forms/FormikSubmitButton'; +import { FormikSubmitButtonPure } from '../../../../shared/components/forms/FormikSubmitButton'; +import { DialogActions, DialogContent } from '@mui/material'; export interface EditWhiteboardTemplateDialogProps { open: boolean; @@ -58,26 +59,28 @@ const EditWhiteboardTemplateDialog = ({ }; return ( - theme.spacing(128) } }} - maxWidth={false} - > - - {t('common.edit-entity', { entity: t('templateLibrary.whiteboardTemplates.name') })} - - - - {t('common.update')} - - } + + + + ( + + + + formik.handleSubmit()}> + {t('common.update')} + + + + )} + /> + ); }; From d2287a66ac998e323d5497b06fdea67da7bad640 Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:10:57 +0800 Subject: [PATCH 10/27] Content Not Available dialog when no access to Callout (#5548) * Content Not Available dialog when no access to Callout * cleanup * fix --------- Co-authored-by: Svetoslav Petkov --- .../apollo/hooks/useApolloErrorHandler.ts | 12 ++++-- src/core/i18n/en/translation.en.json | 4 ++ .../collaboration/CalloutPage/CalloutPage.tsx | 39 ++++++++++++++----- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/core/apollo/hooks/useApolloErrorHandler.ts b/src/core/apollo/hooks/useApolloErrorHandler.ts index 5c9724c96e..c40d1b4126 100644 --- a/src/core/apollo/hooks/useApolloErrorHandler.ts +++ b/src/core/apollo/hooks/useApolloErrorHandler.ts @@ -65,9 +65,15 @@ export const useApolloErrorHandler = (severity: Severity = 'error') => { export const isApolloNotFoundError = (error: ApolloError | undefined) => { if (error && error.graphQLErrors) { const extensions = error.graphQLErrors.map(graphQLError => graphQLError.extensions); - if (extensions.find(extension => extension.code === 'ENTITY_NOT_FOUND')) { - return true; - } + return extensions.some(extension => extension.code === 'ENTITY_NOT_FOUND'); + } + return false; +}; + +export const isApolloForbiddenError = (error: ApolloError | undefined) => { + if (error && error.graphQLErrors) { + const extensions = error.graphQLErrors.map(graphQLError => graphQLError.extensions); + return extensions.some(extension => extension.code === 'FORBIDDEN'); } return false; }; diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index d20acf240b..3d7a94764e 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -611,6 +611,10 @@ } }, "callout": { + "accessForbidden": { + "title": "Content not available", + "description": "We apologize, but the content you requested is not available. It may have been deleted, or you may not have the permission to access it, such as if it was put in draft mode." + }, "createFull": "Add collaboration tool", "contributions": "{{count}} Contribution", "contributions_plural": "{{count}} Contributions", diff --git a/src/domain/collaboration/CalloutPage/CalloutPage.tsx b/src/domain/collaboration/CalloutPage/CalloutPage.tsx index c1c1112b42..4b4f4c3610 100644 --- a/src/domain/collaboration/CalloutPage/CalloutPage.tsx +++ b/src/domain/collaboration/CalloutPage/CalloutPage.tsx @@ -9,15 +9,18 @@ import { TypedCallout } from '../callout/useCallouts/useCallouts'; import DialogWithGrid from '../../../core/ui/dialog/DialogWithGrid'; import { useLocation } from 'react-router-dom'; import { buildCalloutUrl } from '../../../main/routing/urlBuilders'; -import { Theme, useMediaQuery } from '@mui/material'; +import { DialogContent, Theme, useMediaQuery } from '@mui/material'; import { getCalloutDisplayLocationValue } from '../callout/utils/getCalloutDisplayLocationValue'; import Loading from '../../../core/ui/loading/Loading'; -import { isApolloNotFoundError } from '../../../core/apollo/hooks/useApolloErrorHandler'; +import { isApolloForbiddenError, isApolloNotFoundError } from '../../../core/apollo/hooks/useApolloErrorHandler'; import { NotFoundPageLayout } from '../../journey/common/EntityPageLayout'; import { Error404 } from '../../../core/pages/Errors/Error404'; import useBackToPath from '../../../core/routing/useBackToPath'; import usePageLayoutByEntity from '../../shared/utils/usePageLayoutByEntity'; import { EntityPageSection } from '../../shared/layout/EntityPageSection'; +import DialogHeader from '../../../core/ui/dialog/DialogHeader'; +import { Text } from '../../../core/ui/typography'; +import { useTranslation } from 'react-i18next'; interface CalloutLocation { journeyTypeName: JourneyTypeName; @@ -51,6 +54,8 @@ const CalloutPage = ({ journeyTypeName, parentRoute, renderPage, children }: Cal const locationState = (useLocation().state ?? {}) as LocationStateCachedCallout; + const { t } = useTranslation(); + if (!spaceNameId) { throw new Error('Must be within a Space'); } @@ -74,7 +79,8 @@ const CalloutPage = ({ journeyTypeName, parentRoute, renderPage, children }: Cal includeChallenge: journeyTypeName === 'challenge', includeOpportunity: journeyTypeName === 'opportunity', }, - fetchPolicy: 'cache-first', + fetchPolicy: 'cache-and-network', + errorPolicy: 'all', }); const [callout] = @@ -124,13 +130,8 @@ const CalloutPage = ({ journeyTypeName, parentRoute, renderPage, children }: Cal ); } - if (!typedCallout) { - return renderPage(); - } - - const calloutDisplayLocation = getCalloutDisplayLocationValue( - typedCallout.framing.profile.displayLocationTagset?.tags - ); + const calloutDisplayLocation = + typedCallout && getCalloutDisplayLocationValue(typedCallout.framing.profile.displayLocationTagset?.tags); const parentPagePath = typeof parentRoute === 'function' ? parentRoute(calloutDisplayLocation) : parentRoute; @@ -138,6 +139,24 @@ const CalloutPage = ({ journeyTypeName, parentRoute, renderPage, children }: Cal backOrElse(parentPagePath); }; + if (isApolloForbiddenError(error)) { + return ( + <> + {renderPage(calloutDisplayLocation)} + + + + {t('callout.accessForbidden.description')} + + + + ); + } + + if (!typedCallout) { + return renderPage(); + } + const calloutUri = buildCalloutUrl(typedCallout.nameID, { spaceNameId, challengeNameId, From 62d2effb5b5cddd3e6fec3c9e7c12b8b3e39cfed Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Wed, 14 Feb 2024 11:37:04 +0200 Subject: [PATCH 11/27] Removed WhiteboardRtManagementViewWrapper and refactored a bit (#5530) * Removed and integrated it into ViewWrapper * Cleanup * Remove calloutId * rename Management --------- Co-authored-by: Andrew Pazniak <594548+me-andre@users.noreply.github.com> --- .../SingleWhiteboardRtCallout.tsx | 12 +- .../WhiteboardRtManagementView.tsx | 131 ------------------ .../WhiteboardRtView.tsx | 118 ++++++++++++++++ .../WhiteboardsRtManagementViewWrapper.tsx | 71 ---------- 4 files changed, 120 insertions(+), 212 deletions(-) delete mode 100644 src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtManagementView.tsx create mode 100644 src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtView.tsx delete mode 100644 src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx diff --git a/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx b/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx index b7facf7e4d..fb401d7362 100644 --- a/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx +++ b/src/domain/collaboration/callout/SingleWhiteboard/SingleWhiteboardRtCallout.tsx @@ -1,14 +1,7 @@ -/** - * Just a copy from SingleWhiteboardCallout with: - * - Added Rt suffix - * - Changed whiteboards from WhiteboardCardWhiteboard[] to WhiteboardCardWhiteboard - * - Use WhiteboardRtProvider instead of WhiteboardProvider - * - WhiteboardsRtManagementViewWrapper - */ import { useState } from 'react'; import CalloutLayout, { CalloutLayoutProps } from '../../CalloutBlock/CalloutLayout'; import { BaseCalloutViewProps } from '../CalloutViewTypes'; -import WhiteboardsRtManagementViewWrapper from '../../whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper'; +import WhiteboardRtView from '../../whiteboard/WhiteboardsManagement/WhiteboardRtView'; import { buildCalloutUrl } from '../../../../main/routing/urlBuilders'; import WhiteboardPreview from '../../whiteboard/whiteboardPreview/WhiteboardPreview'; @@ -55,7 +48,7 @@ const SingleWhiteboardRtCallout = ({ onClick={() => setIsWhiteboardDialogOpen(true)} /> {isWhiteboardDialogOpen && ( - void; -} - -export interface WhiteboardManagementViewProps - extends ViewProps< - WhiteboardManagementViewEntities, - WhiteboardManagementViewActions, - ContextViewState, - WhiteboardManagementViewOptions - >, - WhiteboardNavigationMethods {} - -const WhiteboardRtManagementView: FC = ({ - entities, - actions, - state, - options, - backToWhiteboards, -}) => { - const { whiteboardId, whiteboard, contextSource } = entities; - const { fullscreen, setFullscreen } = useFullscreen(); - - const handleCancel = (/*whiteboard: WhiteboardRtDetailsFragment*/) => { - backToWhiteboards(); - if (fullscreen) { - setFullscreen(false); - } - }; - - const contentUpdatePolicyProvided = useWhiteboardRtContentUpdatePolicy({ - whiteboardId: whiteboard?.id, - skip: !options.canUpdate, - }); - - return ( - <> - - {entities => { - return ( - - {whiteboard?.profile.displayName} -
- ), - fullscreen, - headerActions: ( - <> - - {options.canUpdate && ( - - )} - - - - ), - }} - state={state} - /> - ); - }} - - - ); -}; - -export default WhiteboardRtManagementView; diff --git a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtView.tsx b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtView.tsx new file mode 100644 index 0000000000..585f363649 --- /dev/null +++ b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardRtView.tsx @@ -0,0 +1,118 @@ +import React, { FC } from 'react'; +import WhiteboardRtActionsContainer from '../containers/WhiteboardRtActionsContainer'; +import { + AuthorizationPrivilege, + WhiteboardRtContentFragment, + WhiteboardRtDetailsFragment, +} from '../../../../core/apollo/generated/graphql-schema'; +import { JourneyTypeName } from '../../../journey/JourneyTypeName'; +import WhiteboardRtContentContainer from '../containers/WhiteboardRtContentContainer'; +import WhiteboardRtDialog from '../WhiteboardDialog/WhiteboardRtDialog'; +import { useFullscreen } from '../../../../core/ui/fullscreen/useFullscreen'; +import FullscreenButton from '../../../../core/ui/button/FullscreenButton'; +import ShareButton from '../../../shared/components/ShareDialog/ShareButton'; +import { BlockTitle } from '../../../../core/ui/typography'; +import useWhiteboardRtContentUpdatePolicy from '../whiteboardRt/contentUpdatePolicy/WhiteboardRtContentUpdatePolicy'; +import WhiteboardShareSettings from '../share/WhiteboardShareSettings'; + +export interface ActiveWhiteboardIdHolder { + whiteboardId?: string; +} +export interface WhiteboardNavigationMethods { + backToWhiteboards: () => void; +} + +export interface WhiteboardRtViewProps extends ActiveWhiteboardIdHolder, WhiteboardNavigationMethods { + journeyTypeName: JourneyTypeName; + whiteboard: WhiteboardRtDetailsFragment | undefined; + authorization: WhiteboardRtDetailsFragment['authorization']; + whiteboardShareUrl: string; + readOnlyDisplayName?: boolean; + loadingWhiteboards: boolean; +} + +const WhiteboardRtView: FC = ({ + whiteboardId, + whiteboard, + authorization, + journeyTypeName, + backToWhiteboards, + loadingWhiteboards, + whiteboardShareUrl, + readOnlyDisplayName, + ...whiteboardsState +}) => { + const { fullscreen, setFullscreen } = useFullscreen(); + + const handleCancel = () => { + backToWhiteboards(); + if (fullscreen) { + setFullscreen(false); + } + }; + + // Todo: need to decide who can edit what whiteboards, for now tie to UpdateContent. May need to extend the information on a Whiteboard + // to include who created it etc. + // Also to have in mind: In SingleWhiteboard Callout whiteboards, users may not have CreateWhiteboard privilege to add another whiteboard but may have privilege + // to update an existing whiteboard + const hasUpdatePrivileges = authorization?.myPrivileges?.includes(AuthorizationPrivilege.Update); + const hasUpdateContentPrivileges = authorization?.myPrivileges?.includes(AuthorizationPrivilege.UpdateContent); + + const contentUpdatePolicyProvided = useWhiteboardRtContentUpdatePolicy({ + whiteboardId: whiteboard?.id, + skip: !hasUpdatePrivileges, + }); + + return ( + + {(_, actionsState, actions) => ( + + {entities => { + return ( + + {whiteboard?.profile.displayName} + + ), + fullscreen, + headerActions: ( + <> + + {hasUpdatePrivileges && ( + + )} + + + + ), + }} + state={{ + ...whiteboardsState, + ...actionsState, + }} + /> + ); + }} + + )} + + ); +}; + +export default WhiteboardRtView; diff --git a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx b/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx deleted file mode 100644 index 6269479f72..0000000000 --- a/src/domain/collaboration/whiteboard/WhiteboardsManagement/WhiteboardsRtManagementViewWrapper.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { FC } from 'react'; -import WhiteboardRtActionsContainer from '../containers/WhiteboardRtActionsContainer'; -import { AuthorizationPrivilege, WhiteboardRtDetailsFragment } from '../../../../core/apollo/generated/graphql-schema'; -import WhiteboardRtManagementView, { - ActiveWhiteboardIdHolder, - WhiteboardNavigationMethods, -} from './WhiteboardRtManagementView'; -import { JourneyTypeName } from '../../../journey/JourneyTypeName'; - -export interface WhiteboardsRtManagementViewWrapperProps extends ActiveWhiteboardIdHolder, WhiteboardNavigationMethods { - journeyTypeName: JourneyTypeName; - whiteboard: WhiteboardRtDetailsFragment | undefined; - calloutId: string | undefined; - authorization: WhiteboardRtDetailsFragment['authorization']; - whiteboardShareUrl: string; - readOnlyDisplayName?: boolean; - loadingWhiteboards: boolean; -} - -const WhiteboardsRtManagementViewWrapper: FC = ({ - whiteboardId, - calloutId, - whiteboard, - authorization, - journeyTypeName, - backToWhiteboards, - loadingWhiteboards, - whiteboardShareUrl, - readOnlyDisplayName, - ...whiteboardsState -}) => { - if (!calloutId) { - return null; - } - - // Todo: need to decide who can edit what whiteboards, for now tie to UpdateContent. May need to extend the information on a Whiteboard - // to include who created it etc. - // Also to have in mind: In SingleWhiteboard Callout whiteboards, users may not have CreateWhiteboard privilege to add another whiteboard but may have privilege - // to update an existing whiteboard - const hasUpdatePrivileges = authorization?.myPrivileges?.includes(AuthorizationPrivilege.Update); - const hasUpdateContentPrivileges = authorization?.myPrivileges?.includes(AuthorizationPrivilege.UpdateContent); - - return ( - - {(_, actionsState, actions) => ( - - )} - - ); -}; - -export default WhiteboardsRtManagementViewWrapper; From b1103e9ba97dd91187a1a52fe989b76a4a647625 Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:42:27 +0800 Subject: [PATCH 12/27] Registration Success page texts/styling updated (#5563) Co-authored-by: Carlos Cano --- .../pages/RegistrationSuccessPage.tsx | 39 +++++++++++++------ src/core/i18n/en/translation.en.json | 6 ++- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/core/auth/authentication/pages/RegistrationSuccessPage.tsx b/src/core/auth/authentication/pages/RegistrationSuccessPage.tsx index 779d3fcbdf..f7803036d9 100644 --- a/src/core/auth/authentication/pages/RegistrationSuccessPage.tsx +++ b/src/core/auth/authentication/pages/RegistrationSuccessPage.tsx @@ -1,26 +1,43 @@ import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; -import EmailVerificationNotice from '../../verification/components/EmailVerificationNotice/EmailVerificationNotice'; -import AuthPageContentContainer from '../../../../domain/shared/layout/AuthPageContentContainer'; +import { Trans, useTranslation } from 'react-i18next'; import FixedHeightLogo from '../components/FixedHeightLogo'; -import { useUserContext } from '../../../../domain/community/user'; -import { PageTitle } from '../../../ui/typography'; +import { BlockTitle, PageTitle } from '../../../ui/typography'; import { useReturnUrl } from '../utils/SignUpReturnUrl'; +import RouterLink from '../../../ui/link/RouterLink'; +import { AUTH_VERIFY_PATH } from '../constants/authentication.constants'; +import PageContent from '../../../ui/content/PageContent'; +import PageContentColumn from '../../../ui/content/PageContentColumn'; +import PageContentBlockSeamless from '../../../ui/content/PageContentBlockSeamless'; interface RegistrationSuccessPageProps {} export const RegistrationSuccessPage: FC = () => { const { t } = useTranslation(); - const { user } = useUserContext(); const returnUrl = useReturnUrl(); return ( - - - {t('pages.registration-success.header', { firstName: user?.user?.firstName })} - {returnUrl && } - + + + + + {t('pages.registrationSuccess.header')} + + , + }} + /> + + + + {t('pages.registrationSuccess.continue')} + + + + + ); }; diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index 3d7a94764e..7d783322c3 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -2323,8 +2323,10 @@ "continue": "Continue to the platform", "tooltip": "Please read and accept the Terms of Use and Privacy Policy before you continue" }, - "registration-success": { - "header": "Hi, thank you for signing up!" + "registrationSuccess": { + "header": "Nearly there…", + "message": "The last step is to verify your email address. Please check your inbox for an email with instructions.
If you have not received an email, click here to send it again.", + "continue": "…or continue to the platform without signing in" }, "verification-success": { "header": "Thank you for verifying your email address!", From fe6bd117c69228ae57e43fcb745d57c3b7f3c03c Mon Sep 17 00:00:00 2001 From: Aleksandar Stojanovic Date: Wed, 14 Feb 2024 17:06:11 +0100 Subject: [PATCH 13/27] Add opportunity application button (#5562) * Add opportunity applciation button. * Fix challenge application button. * Cleanup and add todo. * Add separate query for opportunity application button container. * Cleanup opportunity application button components. * Fix translations. * Change translation keys. --- src/core/apollo/generated/apollo-hooks.ts | 147 ++++++++++++++++++ src/core/apollo/generated/graphql-schema.ts | 87 +++++++++++ src/core/i18n/en/translation.en.json | 10 +- .../applicationButton/ApplicationButton.tsx | 2 +- .../OpportunityApplicationButton.tsx | 114 ++++++++++++++ .../OpportunityApplicationButtonContainer.tsx | 80 ++++++++++ ...yUserPrivilegesWithParentCommunity.graphql | 80 ++++++++++ .../common/tabs/About/AboutSection.tsx | 50 +++--- 8 files changed, 544 insertions(+), 26 deletions(-) create mode 100644 src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx create mode 100644 src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx create mode 100644 src/domain/community/application/containers/communityUserPrivilegesWithParentCommunity.graphql diff --git a/src/core/apollo/generated/apollo-hooks.ts b/src/core/apollo/generated/apollo-hooks.ts index 45e081f6a6..f890334a09 100644 --- a/src/core/apollo/generated/apollo-hooks.ts +++ b/src/core/apollo/generated/apollo-hooks.ts @@ -11077,6 +11077,153 @@ export function refetchPlatformUpdatesRoomQuery(variables?: SchemaTypes.Platform return { query: PlatformUpdatesRoomDocument, variables: variables }; } +export const CommunityUserPrivilegesWithParentCommunityDocument = gql` + query communityUserPrivilegesWithParentCommunity( + $spaceNameId: UUID_NAMEID! + $challengeNameId: UUID_NAMEID = "mockid" + $opportunityNameId: UUID_NAMEID = "mockid" + $includeSpaceCommunity: Boolean = false + $includeChallenge: Boolean = false + $includeOpportunity: Boolean = false + ) { + space(ID: $spaceNameId) { + id + community @include(if: $includeSpaceCommunity) { + id + myMembershipStatus + authorization { + id + myPrivileges + } + leadUsers: usersInRole(role: LEAD) { + id + profile { + id + displayName + avatar: visual(type: AVATAR) { + ...VisualUri + } + location { + id + country + city + } + } + } + } + challenge(ID: $challengeNameId) @include(if: $includeChallenge) { + id + authorization { + id + myPrivileges + } + community { + id + myMembershipStatus + authorization { + id + myPrivileges + } + leadUsers: usersInRole(role: LEAD) { + id + profile { + id + displayName + avatar: visual(type: AVATAR) { + ...VisualUri + } + location { + id + country + city + } + } + } + } + } + opportunity(ID: $opportunityNameId) @include(if: $includeOpportunity) { + id + authorization { + id + myPrivileges + } + community { + id + myMembershipStatus + authorization { + id + myPrivileges + } + } + } + } + } + ${VisualUriFragmentDoc} +`; + +/** + * __useCommunityUserPrivilegesWithParentCommunityQuery__ + * + * To run a query within a React component, call `useCommunityUserPrivilegesWithParentCommunityQuery` and pass it any options that fit your needs. + * When your component renders, `useCommunityUserPrivilegesWithParentCommunityQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useCommunityUserPrivilegesWithParentCommunityQuery({ + * variables: { + * spaceNameId: // value for 'spaceNameId' + * challengeNameId: // value for 'challengeNameId' + * opportunityNameId: // value for 'opportunityNameId' + * includeSpaceCommunity: // value for 'includeSpaceCommunity' + * includeChallenge: // value for 'includeChallenge' + * includeOpportunity: // value for 'includeOpportunity' + * }, + * }); + */ +export function useCommunityUserPrivilegesWithParentCommunityQuery( + baseOptions: Apollo.QueryHookOptions< + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQuery, + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQueryVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery< + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQuery, + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQueryVariables + >(CommunityUserPrivilegesWithParentCommunityDocument, options); +} + +export function useCommunityUserPrivilegesWithParentCommunityLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQuery, + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQueryVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery< + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQuery, + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQueryVariables + >(CommunityUserPrivilegesWithParentCommunityDocument, options); +} + +export type CommunityUserPrivilegesWithParentCommunityQueryHookResult = ReturnType< + typeof useCommunityUserPrivilegesWithParentCommunityQuery +>; +export type CommunityUserPrivilegesWithParentCommunityLazyQueryHookResult = ReturnType< + typeof useCommunityUserPrivilegesWithParentCommunityLazyQuery +>; +export type CommunityUserPrivilegesWithParentCommunityQueryResult = Apollo.QueryResult< + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQuery, + SchemaTypes.CommunityUserPrivilegesWithParentCommunityQueryVariables +>; +export function refetchCommunityUserPrivilegesWithParentCommunityQuery( + variables: SchemaTypes.CommunityUserPrivilegesWithParentCommunityQueryVariables +) { + return { query: CommunityUserPrivilegesWithParentCommunityDocument, variables: variables }; +} + export const CommunityUserPrivilegesDocument = gql` query communityUserPrivileges($spaceNameId: UUID_NAMEID!, $communityId: UUID!) { space(ID: $spaceNameId) { diff --git a/src/core/apollo/generated/graphql-schema.ts b/src/core/apollo/generated/graphql-schema.ts index e39abe2ec5..ff4d8d9a17 100644 --- a/src/core/apollo/generated/graphql-schema.ts +++ b/src/core/apollo/generated/graphql-schema.ts @@ -17511,6 +17511,93 @@ export type PlatformUpdatesRoomQuery = { }; }; +export type CommunityUserPrivilegesWithParentCommunityQueryVariables = Exact<{ + spaceNameId: Scalars['UUID_NAMEID']; + challengeNameId?: InputMaybe; + opportunityNameId?: InputMaybe; + includeSpaceCommunity?: InputMaybe; + includeChallenge?: InputMaybe; + includeOpportunity?: InputMaybe; +}>; + +export type CommunityUserPrivilegesWithParentCommunityQuery = { + __typename?: 'Query'; + space: { + __typename?: 'Space'; + id: string; + community?: + | { + __typename?: 'Community'; + id: string; + myMembershipStatus?: CommunityMembershipStatus | undefined; + authorization?: + | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } + | undefined; + leadUsers?: + | Array<{ + __typename?: 'User'; + id: string; + profile: { + __typename?: 'Profile'; + id: string; + displayName: string; + avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined; + location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; + }; + }> + | undefined; + } + | undefined; + challenge?: { + __typename?: 'Challenge'; + id: string; + authorization?: + | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } + | undefined; + community?: + | { + __typename?: 'Community'; + id: string; + myMembershipStatus?: CommunityMembershipStatus | undefined; + authorization?: + | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } + | undefined; + leadUsers?: + | Array<{ + __typename?: 'User'; + id: string; + profile: { + __typename?: 'Profile'; + id: string; + displayName: string; + avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined; + location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; + }; + }> + | undefined; + } + | undefined; + }; + opportunity?: { + __typename?: 'Opportunity'; + id: string; + authorization?: + | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } + | undefined; + community?: + | { + __typename?: 'Community'; + id: string; + myMembershipStatus?: CommunityMembershipStatus | undefined; + authorization?: + | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } + | undefined; + } + | undefined; + }; + }; +}; + export type CommunityUserPrivilegesQueryVariables = Exact<{ spaceNameId: Scalars['UUID_NAMEID']; communityId: Scalars['UUID']; diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index 7d783322c3..923693c08b 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -1204,10 +1204,18 @@ "full": "You are invited to become a member of this Space, please click here to accept", "short": "Accept Invitation" }, - "joinParentFirst": { + "joinSpaceFirst": { "full": "To become a member of this Challenge, first become a member of the parent Space", "short": "Please join parent Space first" }, + "joinChallengeFirst": { + "full": "To become a member of this Opportunity, first become a member of the parent Challenge", + "short": "Please join parent Challenge first" + }, + "contactChallengeLeads": { + "full": "Send a message to Challenge leads to become a member", + "short": "Send a message to Challenge leads to become a member" + }, "goToSpace": "Go to $t(common.space)", "goToChallenge": "Go to $t(common.challenge)", "parent-pending": "Parent application pending", diff --git a/src/domain/community/application/applicationButton/ApplicationButton.tsx b/src/domain/community/application/applicationButton/ApplicationButton.tsx index d383192e2f..5da2d05d64 100644 --- a/src/domain/community/application/applicationButton/ApplicationButton.tsx +++ b/src/domain/community/application/applicationButton/ApplicationButton.tsx @@ -194,7 +194,7 @@ export const ApplicationButton = forwardRef - {t(`components.application-button.joinParentFirst.${extended ? 'full' : 'short'}` as const)} + {t(`components.application-button.joinSpaceFirst.${extended ? 'full' : 'short'}` as const)} ) ); diff --git a/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx b/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx new file mode 100644 index 0000000000..d4af3f567d --- /dev/null +++ b/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx @@ -0,0 +1,114 @@ +import { Button as MuiButton, CircularProgress } from '@mui/material'; +import React, { forwardRef, Ref } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link as RouterLink } from 'react-router-dom'; +import { buildLoginUrl } from '../../../../main/routing/urlBuilders'; +import { AddOutlined } from '@mui/icons-material'; +import RootThemeProvider from '../../../../core/ui/themes/RootThemeProvider'; +import useDirectMessageDialog from '../../../communication/messaging/DirectMessaging/useDirectMessageDialog'; + +export interface OpportunityApplicationButtonProps { + isAuthenticated?: boolean; + isMember: boolean; + isParentMember?: boolean; + parentUrl?: string; + parentLeadUsers: { + id: string; + displayName?: string; + city?: string; + country?: string; + avatarUri?: string; + }[]; + loading: boolean; + component?: typeof MuiButton; + extended?: boolean; +} + +export const OpportunityApplicationButton = forwardRef< + HTMLButtonElement | HTMLAnchorElement, + OpportunityApplicationButtonProps +>( + ( + { + isAuthenticated, + isMember = false, + isParentMember = false, + parentUrl, + parentLeadUsers, + loading = false, + component: Button = MuiButton, + extended = false, + }, + ref + ) => { + const { t } = useTranslation(); + const { sendMessage, directMessageDialog } = useDirectMessageDialog({ + dialogTitle: t('send-message-dialog.direct-message-title'), + }); + + const handleSendMessageToParentLeads = () => { + sendMessage('user', ...parentLeadUsers); + }; + + const renderApplicationButton = () => { + if (loading) { + return + ); + } + + if (isMember) { + return null; + } + + if (!isParentMember) { + return ( + parentUrl && ( + + ) + ); + } + + return ( + + ); + }; + + return ( + <> + {renderApplicationButton()} + {directMessageDialog} + + ); + } +); + +export default OpportunityApplicationButton; diff --git a/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx b/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx new file mode 100644 index 0000000000..c030051f18 --- /dev/null +++ b/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx @@ -0,0 +1,80 @@ +import React, { FC } from 'react'; +import { OpportunityApplicationButtonProps } from '../applicationButton/OpportunityApplicationButton'; + +import { useSpace } from '../../../journey/space/SpaceContext/useSpace'; +import { useCommunityUserPrivilegesWithParentCommunityQuery } from '../../../../core/apollo/generated/apollo-hooks'; +import { buildChallengeUrl, buildSpaceUrl } from '../../../../main/routing/urlBuilders'; +import { CommunityMembershipStatus } from '../../../../core/apollo/generated/graphql-schema'; +import { useCommunityContext } from '../../community/CommunityContext'; +import { useAuthenticationContext } from '../../../../core/auth/authentication/hooks/useAuthenticationContext'; +import { SimpleContainerProps } from '../../../../core/container/SimpleContainer'; + +interface ApplicationContainerState { + loading: boolean; +} + +export interface OpportunityApplicationButtonContainerProps + extends SimpleContainerProps<{ + applicationButtonProps: OpportunityApplicationButtonProps; + state: ApplicationContainerState; + }> { + challengeNameId?: string; + opportunityNameId?: string; +} + +export const OpportunityApplicationButtonContainer: FC = ({ + challengeNameId, + opportunityNameId, + children, +}) => { + const { isAuthenticated } = useAuthenticationContext(); + const { myMembershipStatus } = useCommunityContext(); + const { spaceNameId } = useSpace(); + + const { data: _communityPrivileges, loading: communityPrivilegesLoading } = + useCommunityUserPrivilegesWithParentCommunityQuery({ + variables: { + spaceNameId, + challengeNameId, + opportunityNameId, + includeChallenge: true, + includeOpportunity: true, + }, + }); + + const isMember = myMembershipStatus === CommunityMembershipStatus.Member; + const isParentMember = + _communityPrivileges?.space?.challenge?.community?.myMembershipStatus === CommunityMembershipStatus.Member; + + const parentUrl = challengeNameId ? buildChallengeUrl(spaceNameId, challengeNameId) : buildSpaceUrl(spaceNameId); + const parentCommunityLeadUsers = _communityPrivileges?.space?.challenge?.community?.leadUsers ?? []; + const parentLeadUsers = parentCommunityLeadUsers?.map(user => ({ + id: user.id, + displayName: user.profile.displayName, + country: user.profile.location?.country, + city: user.profile.location?.city, + avatarUri: user.profile.avatar?.uri, + })); + + const loading = communityPrivilegesLoading; + + const applicationButtonProps: OpportunityApplicationButtonProps = { + isAuthenticated, + isMember, + isParentMember, + parentLeadUsers, + parentUrl, + loading, + }; + + return ( + <> + {children({ + applicationButtonProps, + state: { loading }, + })} + + ); +}; + +export default OpportunityApplicationButtonContainer; diff --git a/src/domain/community/application/containers/communityUserPrivilegesWithParentCommunity.graphql b/src/domain/community/application/containers/communityUserPrivilegesWithParentCommunity.graphql new file mode 100644 index 0000000000..c224cd0e01 --- /dev/null +++ b/src/domain/community/application/containers/communityUserPrivilegesWithParentCommunity.graphql @@ -0,0 +1,80 @@ +query communityUserPrivilegesWithParentCommunity( + $spaceNameId: UUID_NAMEID! + $challengeNameId: UUID_NAMEID = "mockid" + $opportunityNameId: UUID_NAMEID = "mockid" + $includeSpaceCommunity: Boolean = false + $includeChallenge: Boolean = false + $includeOpportunity: Boolean = false +) { + space(ID: $spaceNameId) { + id + community @include(if: $includeSpaceCommunity) { + id + myMembershipStatus + authorization { + id + myPrivileges + } + leadUsers: usersInRole(role: LEAD) { + id + profile { + id + displayName + avatar: visual(type: AVATAR) { + ...VisualUri + } + location { + id + country + city + } + } + } + } + challenge(ID: $challengeNameId) @include(if: $includeChallenge) { + id + authorization { + id + myPrivileges + } + community { + id + myMembershipStatus + authorization { + id + myPrivileges + } + leadUsers: usersInRole(role: LEAD) { + id + profile { + id + displayName + avatar: visual(type: AVATAR) { + ...VisualUri + } + location { + id + country + city + } + } + } + } + } + opportunity(ID: $opportunityNameId) @include(if: $includeOpportunity) { + id + authorization { + id + myPrivileges + } + community { + id + myMembershipStatus + authorization { + id + myPrivileges + } + } + } + } +} diff --git a/src/domain/journey/common/tabs/About/AboutSection.tsx b/src/domain/journey/common/tabs/About/AboutSection.tsx index 21f0e07d43..12eff271a6 100644 --- a/src/domain/journey/common/tabs/About/AboutSection.tsx +++ b/src/domain/journey/common/tabs/About/AboutSection.tsx @@ -1,7 +1,7 @@ import React, { FC, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ApolloError } from '@apollo/client'; -import { Box, DialogContent } from '@mui/material'; +import { Box, DialogContent, Theme, useMediaQuery } from '@mui/material'; import { LifecycleContextTabFragment, ReferenceDetailsFragment, @@ -17,8 +17,8 @@ import { BlockTitle, Tagline } from '../../../../../core/ui/typography'; import PageContentBlock, { PageContentBlockProps } from '../../../../../core/ui/content/PageContentBlock'; import TagsComponent from '../../../../shared/components/TagsComponent/TagsComponent'; import InnovationFlow from '../../../../platform/admin/templates/InnovationTemplates/InnovationFlow/InnovationFlow'; -import { ApplicationButton } from '../../../../community/application/applicationButton/ApplicationButton'; -import ApplicationButtonContainer from '../../../../community/application/containers/ApplicationButtonContainer'; +import { OpportunityApplicationButton } from '../../../../community/application/applicationButton/OpportunityApplicationButton'; +import OpportunityApplicationButtonContainer from '../../../../community/application/containers/OpportunityApplicationButtonContainer'; import EntityDashboardContributorsSection from '../../../../community/community/EntityDashboardContributorsSection/EntityDashboardContributorsSection'; import WrapperMarkdown, { MarkdownProps } from '../../../../../core/ui/markdown/WrapperMarkdown'; import ActivityView from '../../../../platform/metrics/views/MetricsView'; @@ -39,6 +39,7 @@ import { buildUpdatesUrl } from '../../../../../main/routing/urlBuilders'; import { useUrlParams } from '../../../../../core/routing/useUrlParams'; import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid'; import DialogHeader from '../../../../../core/ui/dialog/DialogHeader'; +import FullWidthButton from '../../../../../core/ui/button/FullWidthButton'; export interface AboutSectionProps extends EntityDashboardContributors, EntityDashboardLeads { journeyTypeName: JourneyTypeName; @@ -116,11 +117,7 @@ export const AboutSection: FC = ({ : 'community.leading-organizations'; const usersHeader = isSpace ? 'community.host' : 'community.leads'; - const { - challengeId, - challengeNameId, - profile: { displayName: challengeName }, - } = useChallenge(); + const { challengeNameId } = useChallenge(); const { opportunityNameId } = useUrlParams(); @@ -143,6 +140,8 @@ export const AboutSection: FC = ({ who, } as const; + const hasExtendedApplicationButton = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm')); + if (!spaceNameId) { throw new Error('Must be within a Space route.'); } @@ -151,27 +150,30 @@ export const AboutSection: FC = ({ return ( <> + + {({ applicationButtonProps, state: { loading } }) => { + if (loading || applicationButtonProps.isMember) { + return null; + } + + return ( + + + + ); + }} + {tagline} - - {lifecycle && } - - {(e, s) => ( - - )} - - + {lifecycle && } {communityReadAccess && ( Date: Thu, 15 Feb 2024 22:05:41 +0800 Subject: [PATCH 14/27] Always fetching visuals for Pending Memberships (#5565) Co-authored-by: Valentin Yanakiev Co-authored-by: Carlos Cano --- src/core/apollo/generated/apollo-hooks.ts | 8 ++++---- .../InvitationCardHorizontal/InvitationCardHorizontal.tsx | 2 +- .../pendingMembership/PendingMemberships.graphql | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/apollo/generated/apollo-hooks.ts b/src/core/apollo/generated/apollo-hooks.ts index f890334a09..1d6ad90ccf 100644 --- a/src/core/apollo/generated/apollo-hooks.ts +++ b/src/core/apollo/generated/apollo-hooks.ts @@ -1670,16 +1670,16 @@ export const PendingMembershipsJourneyProfileFragmentDoc = gql` fragment PendingMembershipsJourneyProfile on Profile { id displayName + visual(type: $visualType) { + id + uri + } ... on Profile @include(if: $fetchDetails) { tagline tagset { id tags } - visual(type: $visualType) { - id - uri - } } } `; diff --git a/src/domain/community/invitations/InvitationCardHorizontal/InvitationCardHorizontal.tsx b/src/domain/community/invitations/InvitationCardHorizontal/InvitationCardHorizontal.tsx index b9546812c7..df785566a6 100644 --- a/src/domain/community/invitations/InvitationCardHorizontal/InvitationCardHorizontal.tsx +++ b/src/domain/community/invitations/InvitationCardHorizontal/InvitationCardHorizontal.tsx @@ -21,7 +21,7 @@ const InvitationCardHorizontal = ({ invitation, onClick }: InvitationCardHorizon return ( } + visual={} onClick={onClick} outlined > diff --git a/src/domain/community/pendingMembership/PendingMemberships.graphql b/src/domain/community/pendingMembership/PendingMemberships.graphql index 40d4dd4b54..4d294a9b2c 100644 --- a/src/domain/community/pendingMembership/PendingMemberships.graphql +++ b/src/domain/community/pendingMembership/PendingMemberships.graphql @@ -60,16 +60,16 @@ query PendingMembershipsUser($userId: UUID_NAMEID_EMAIL!) { fragment PendingMembershipsJourneyProfile on Profile { id displayName + visual(type: $visualType) { + id + uri + } ... on Profile @include(if: $fetchDetails) { tagline tagset { id tags } - visual(type: $visualType) { - id - uri - } } } From 853093cb611ee045643e72cdc0c486f424653a23 Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Thu, 15 Feb 2024 18:48:40 +0200 Subject: [PATCH 15/27] Move SignUp link higher (#5571) * Move SignUp link higher * Simone's style adjustments --- src/core/auth/authentication/components/KratosUI.tsx | 6 +++--- src/core/auth/authentication/pages/LoginPage.tsx | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/auth/authentication/components/KratosUI.tsx b/src/core/auth/authentication/components/KratosUI.tsx index fc73e8fd5d..3f860bc241 100644 --- a/src/core/auth/authentication/components/KratosUI.tsx +++ b/src/core/auth/authentication/components/KratosUI.tsx @@ -12,7 +12,7 @@ import { KratosInputExtraProps } from './Kratos/KratosProps'; import { useKratosT } from './Kratos/messages'; import isAcceptTermsCheckbox from '../utils/isAcceptTermsCheckbox'; import KratosAcceptTermsCheckbox from './Kratos/KratosAcceptTermsCheckbox'; -import Paragraph from '../../../../domain/shared/components/Text/Paragraph'; +import { Text } from '../../../ui/typography'; import AuthActionButton, { AuthActionButtonProps } from './Button'; import { UiNodeInput } from './Kratos/UiNodeTypes'; import { KratosAcceptTermsProps } from '../pages/AcceptTerms'; @@ -193,9 +193,9 @@ export const KratosUI: FC = ({ {nodesByGroup.submit.map(toUiControl)} {nodesByGroup.submit.length > 0 && nodesByGroup.oidc.length > 0 && ( - + {t('common.or')} - + )} {nodesByGroup.oidc.map(toUiControl)} {children} diff --git a/src/core/auth/authentication/pages/LoginPage.tsx b/src/core/auth/authentication/pages/LoginPage.tsx index c5f1fea0f0..b35e0cbdcf 100644 --- a/src/core/auth/authentication/pages/LoginPage.tsx +++ b/src/core/auth/authentication/pages/LoginPage.tsx @@ -2,7 +2,7 @@ import { Box } from '@mui/material'; import produce from 'immer'; import AuthPageContentContainer from '../../../../domain/shared/layout/AuthPageContentContainer'; import SubHeading from '../../../../domain/shared/components/Text/SubHeading'; -import Paragraph from '../../../../domain/shared/components/Text/Paragraph'; +import { Text } from '../../../ui/typography'; import FixedHeightLogo from '../components/FixedHeightLogo'; import useKratosFlow, { FlowTypeName } from '../hooks/useKratosFlow'; import KratosUI from '../components/KratosUI'; @@ -90,19 +90,19 @@ const LoginPage = ({ flow }: LoginPageProps) => { ); - const tLink = translateWithElements(); + const tLink = translateWithElements(); return ( {t('pages.login.title')} - - + {tLink('pages.login.register', { signup: { to: AUTH_SIGN_UP_PATH }, })} - + + ); From f0753c1a07e0b5df1fd4b7d5b18323234053930f Mon Sep 17 00:00:00 2001 From: Simone <38861315+SimoneZaza@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:12:32 +0100 Subject: [PATCH 16/27] Tiny text update My Dashboard Update translation.en.json (#5576) --- src/core/i18n/en/translation.en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index 923693c08b..1583df2331 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -2046,7 +2046,7 @@ } }, "exploreOtherChallenges": { - "header": "Explore the other Challenge Spaces!" + "header": "Explore the other Spaces!" }, "innovationLibraryBlock": { "title": "Innovation library: Have you seen this one yet...?", From 1b44b4f7b243f3e4841d5e8d5696d5dfd00da6e7 Mon Sep 17 00:00:00 2001 From: Aleksandar Stojanovic Date: Fri, 16 Feb 2024 14:23:37 +0100 Subject: [PATCH 17/27] Change post description markdown text limit. (#5575) Co-authored-by: Carlos Cano --- src/core/ui/forms/MarkdownInput/MarkdownValidator.ts | 5 +++-- src/core/ui/forms/field-length.constants.ts | 3 ++- src/domain/collaboration/post/PostForm/PostForm.tsx | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/ui/forms/MarkdownInput/MarkdownValidator.ts b/src/core/ui/forms/MarkdownInput/MarkdownValidator.ts index 571a7b86ff..c97af8cd94 100644 --- a/src/core/ui/forms/MarkdownInput/MarkdownValidator.ts +++ b/src/core/ui/forms/MarkdownInput/MarkdownValidator.ts @@ -2,11 +2,12 @@ import { string } from 'yup'; import TranslationKey from '../../../i18n/utils/TranslationKey'; import { ValidationMessageWithPayload } from '../../../../domain/shared/i18n/ValidationMessageTranslation/ValidationMessageWithPayload'; import { MarkdownTextMaxLength } from '../field-length.constants'; +import { MessageWithPayload } from '../../../../domain/shared/i18n/ValidationMessageTranslation'; const translationKey: TranslationKey = 'components.wysiwyg-editor.validation.maxLength'; -const MarkdownValidator = (_maxLength: MarkdownTextMaxLength) => { - return string(); +const MarkdownValidator = (maxLength: MarkdownTextMaxLength) => { + return string().max(maxLength, MessageWithPayload(translationKey)); }; export const isMarkdownMaxLengthError = ( diff --git a/src/core/ui/forms/field-length.constants.ts b/src/core/ui/forms/field-length.constants.ts index 3715c6cde8..a9065402d9 100644 --- a/src/core/ui/forms/field-length.constants.ts +++ b/src/core/ui/forms/field-length.constants.ts @@ -4,6 +4,7 @@ export const SMALL_TEXT_LENGTH = 128; export const MID_TEXT_LENGTH = 512; export const LONG_TEXT_LENGTH = 2048; export const MARKDOWN_TEXT_LENGTH = 8000; +export const LONG_MARKDOWN_TEXT_LENGTH = 16000; export const COMMENTS_TEXT_LENGTH = 8000; -export type MarkdownTextMaxLength = typeof MARKDOWN_TEXT_LENGTH; +export type MarkdownTextMaxLength = typeof MARKDOWN_TEXT_LENGTH | typeof LONG_MARKDOWN_TEXT_LENGTH; diff --git a/src/domain/collaboration/post/PostForm/PostForm.tsx b/src/domain/collaboration/post/PostForm/PostForm.tsx index ef3651d10a..be29062ec2 100644 --- a/src/domain/collaboration/post/PostForm/PostForm.tsx +++ b/src/domain/collaboration/post/PostForm/PostForm.tsx @@ -11,7 +11,7 @@ import ReferenceSegment, { referenceSegmentSchema } from '../../../platform/admi import { PushFunc, RemoveFunc } from '../../../common/reference/useEditReference'; import { Reference } from '../../../common/profile/Profile'; import { displayNameValidator } from '../../../../core/ui/forms/validator'; -import { MARKDOWN_TEXT_LENGTH } from '../../../../core/ui/forms/field-length.constants'; +import { LONG_MARKDOWN_TEXT_LENGTH } from '../../../../core/ui/forms/field-length.constants'; import MarkdownValidator from '../../../../core/ui/forms/MarkdownInput/MarkdownValidator'; import Gutters from '../../../../core/ui/grid/Gutters'; import FormikMarkdownField from '../../../../core/ui/forms/MarkdownInput/FormikMarkdownField'; @@ -109,7 +109,7 @@ const PostForm: FC = ({ const validationSchema = yup.object().shape({ name: displayNameValidator.concat(uniqueNameValidator), - description: MarkdownValidator(MARKDOWN_TEXT_LENGTH).required(), + description: MarkdownValidator(LONG_MARKDOWN_TEXT_LENGTH).required(), tagsets: tagsetSegmentSchema, references: referenceSegmentSchema, }); @@ -150,7 +150,7 @@ const PostForm: FC = ({ placeholder={t('components.post-creation.info-step.description-placeholder')} rows={7} required - maxLength={MARKDOWN_TEXT_LENGTH} + maxLength={LONG_MARKDOWN_TEXT_LENGTH} loading={loading} /> Date: Fri, 16 Feb 2024 22:39:52 +0800 Subject: [PATCH 18/27] Callout dialog keeps state of the page in background (#5564) Co-authored-by: Valentin Yanakiev Co-authored-by: Carlos Cano --- src/core/routing/ScrollToTop.tsx | 10 ++++------ src/domain/collaboration/CalloutPage/CalloutPage.tsx | 5 +++-- .../callout/JourneyCalloutsTabView/CalloutsView.tsx | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/routing/ScrollToTop.tsx b/src/core/routing/ScrollToTop.tsx index c8086a70d5..0961bf0193 100644 --- a/src/core/routing/ScrollToTop.tsx +++ b/src/core/routing/ScrollToTop.tsx @@ -5,17 +5,15 @@ import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; -type NavigationState = - | undefined - | { - keepScroll?: boolean; - }; +export interface NavigationState { + keepScroll?: boolean; +} export default function ScrollToTop() { const { state, pathname } = useLocation(); useEffect(() => { - if (!(state as NavigationState)?.keepScroll) { + if (!(state as NavigationState | undefined)?.keepScroll) { window.scrollTo(0, 0); } }, [state, pathname]); diff --git a/src/domain/collaboration/CalloutPage/CalloutPage.tsx b/src/domain/collaboration/CalloutPage/CalloutPage.tsx index 4b4f4c3610..e1f7d079cc 100644 --- a/src/domain/collaboration/CalloutPage/CalloutPage.tsx +++ b/src/domain/collaboration/CalloutPage/CalloutPage.tsx @@ -21,6 +21,7 @@ import { EntityPageSection } from '../../shared/layout/EntityPageSection'; import DialogHeader from '../../../core/ui/dialog/DialogHeader'; import { Text } from '../../../core/ui/typography'; import { useTranslation } from 'react-i18next'; +import { NavigationState } from '../../../core/routing/ScrollToTop'; interface CalloutLocation { journeyTypeName: JourneyTypeName; @@ -36,7 +37,7 @@ export interface CalloutPageProps { export const LocationStateKeyCachedCallout = 'LocationStateKeyCachedCallout'; -export interface LocationStateCachedCallout { +export interface LocationStateCachedCallout extends NavigationState { [LocationStateKeyCachedCallout]?: TypedCallout; } @@ -114,7 +115,7 @@ const CalloutPage = ({ journeyTypeName, parentRoute, renderPage, children }: Cal const PageLayout = usePageLayoutByEntity(journeyTypeName); - if (isCalloutLoading) { + if (isCalloutLoading && !typedCallout) { return ( diff --git a/src/domain/collaboration/callout/JourneyCalloutsTabView/CalloutsView.tsx b/src/domain/collaboration/callout/JourneyCalloutsTabView/CalloutsView.tsx index b0b4697a1e..10bbd908c9 100644 --- a/src/domain/collaboration/callout/JourneyCalloutsTabView/CalloutsView.tsx +++ b/src/domain/collaboration/callout/JourneyCalloutsTabView/CalloutsView.tsx @@ -110,6 +110,7 @@ const CalloutsView = ({ }); const state: LocationStateCachedCallout = { [LocationStateKeyCachedCallout]: callout, + keepScroll: true, }; return navigate(uri, { state }); }; From d2d5c510f70a50de463d69ebcaf46deb66da964c Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Fri, 16 Feb 2024 16:40:30 +0200 Subject: [PATCH 19/27] Show all my activities no matter which callout (#5577) * Show all my activities no matter which callout * increase page size on each request * Restore 2nd/3rd/... page size --------- Co-authored-by: Valentin Yanakiev --- .../myLatestContributions/MyLatestContributions.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx b/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx index 1ef3ee733d..6811b19228 100644 --- a/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx +++ b/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx @@ -1,5 +1,4 @@ import { useEffect, useMemo } from 'react'; -import { uniqBy } from 'lodash'; import PageContentBlock from '../../../../../core/ui/content/PageContentBlock'; import { useTranslation } from 'react-i18next'; import PageContentBlockHeader from '../../../../../core/ui/content/PageContentBlockHeader'; @@ -10,7 +9,6 @@ import { LatestContributionsQuery, LatestContributionsQueryVariables, } from '../../../../../core/apollo/generated/graphql-schema'; -import { Identifiable } from '../../../../../core/utils/Identifiable'; import usePaginatedQuery from '../../../../../domain/shared/pagination/usePaginatedQuery'; import { Box } from '@mui/material'; import { @@ -51,9 +49,7 @@ const MyLatestContributions = () => { }); const activities = useMemo(() => { - return uniqBy(data?.activityFeed.activityFeed, activityItem => { - return (activityItem as { callout?: Identifiable }).callout?.id; - }).slice(0, MY_LATEST_CONTRIBUTIONS_COUNT); + return data?.activityFeed.activityFeed.slice(0, MY_LATEST_CONTRIBUTIONS_COUNT); }, [data?.activityFeed.activityFeed]); useEffect(() => { From b9be801b1989423dbb600e58851e35c41afd196d Mon Sep 17 00:00:00 2001 From: Aleksandar Stojanovic Date: Fri, 16 Feb 2024 15:48:09 +0100 Subject: [PATCH 20/27] Use corect mutations when sending messages. (#5568) Co-authored-by: Carlos Cano Co-authored-by: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Co-authored-by: Valentin Yanakiev --- .../community/utils/useCommunityMembersAsCardProps.ts | 4 ++++ .../CommunityContributorsBlockWideContent.tsx | 4 ++-- .../ContributorCardSquare/ContributorCardSquare.tsx | 3 ++- src/domain/community/user/ContributorsView.tsx | 3 +++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/domain/community/community/utils/useCommunityMembersAsCardProps.ts b/src/domain/community/community/utils/useCommunityMembersAsCardProps.ts index 99a40ea817..a6283901d5 100644 --- a/src/domain/community/community/utils/useCommunityMembersAsCardProps.ts +++ b/src/domain/community/community/utils/useCommunityMembersAsCardProps.ts @@ -10,6 +10,7 @@ import { DashboardContributingUserFragment, } from '../../../../core/apollo/generated/graphql-schema'; import { EntityDashboardContributors } from '../EntityDashboardContributorsSection/Types'; +import { ContributorType } from '../../contributor/CommunityContributorsBlockWide/CommunityContributorsBlockWideContent'; interface CommunityMembers { memberUsers?: DashboardContributingUserFragment[]; @@ -39,6 +40,7 @@ const mapUserToContributorCardProps = ( country: user.profile.location?.country ? COUNTRIES_BY_CODE[user.profile.location.country] : undefined, }, isContactable: user.isContactable, + contributorType: ContributorType.People, }); export const mapUserCardPropsToContributorCardProps = (user: UserCardProps): WithId => ({ @@ -52,6 +54,7 @@ export const mapUserCardPropsToContributorCardProps = (user: UserCardProps): Wit country: user.country, }, isContactable: user.isContactable ?? true, + contributorType: ContributorType.People, }); const mapOrganizationToContributorCardProps = ( @@ -65,6 +68,7 @@ const mapOrganizationToContributorCardProps = ( tags: org.profile.tagsets?.flatMap(x => x.tags.map(t => t)) ?? [], }, isContactable: true, + contributorType: ContributorType.Organizations, }); const applyLimit = (items: Item[] | undefined, limit?: number): Item[] | undefined => diff --git a/src/domain/community/contributor/CommunityContributorsBlockWide/CommunityContributorsBlockWideContent.tsx b/src/domain/community/contributor/CommunityContributorsBlockWide/CommunityContributorsBlockWideContent.tsx index 566615ac00..38b579c5ce 100644 --- a/src/domain/community/contributor/CommunityContributorsBlockWide/CommunityContributorsBlockWideContent.tsx +++ b/src/domain/community/contributor/CommunityContributorsBlockWide/CommunityContributorsBlockWideContent.tsx @@ -56,7 +56,7 @@ const CommunityContributorsBlockWideContent = ({ .map(user => ( - + ))} @@ -67,7 +67,7 @@ const CommunityContributorsBlockWideContent = ({ .map(organization => ( - + ))} diff --git a/src/domain/community/contributor/ContributorCardSquare/ContributorCardSquare.tsx b/src/domain/community/contributor/ContributorCardSquare/ContributorCardSquare.tsx index 0ff7b5871c..5e78e5c17a 100644 --- a/src/domain/community/contributor/ContributorCardSquare/ContributorCardSquare.tsx +++ b/src/domain/community/contributor/ContributorCardSquare/ContributorCardSquare.tsx @@ -87,7 +87,8 @@ export const ContributorCardSquare: FC = props => { }, }, }); - } else { + } + if (contributorType === ContributorType.Organizations) { await sendMessageToOrganization({ variables: { messageData: { diff --git a/src/domain/community/user/ContributorsView.tsx b/src/domain/community/user/ContributorsView.tsx index 7a7d7e8c5b..49e76a02ea 100644 --- a/src/domain/community/user/ContributorsView.tsx +++ b/src/domain/community/user/ContributorsView.tsx @@ -21,6 +21,7 @@ import GridItem from '../../../core/ui/grid/GridItem'; import { useColumns } from '../../../core/ui/grid/GridContext'; import GridProvider from '../../../core/ui/grid/GridProvider'; import { Identifiable } from '../../../core/utils/Identifiable'; +import { ContributorType } from '../contributor/CommunityContributorsBlockWide/CommunityContributorsBlockWideContent'; const USERS_GRAYED_OUT_IMAGE = '/contributors/users-grayed.png'; export const ITEMS_PER_PAGE = 16; @@ -37,6 +38,7 @@ const userToContributorCard = (user: UserContributorFragment): ContributorCardSq country: user.userProfile?.location?.country || '', }, isContactable: user.isContactable, + contributorType: ContributorType.People, }; }; @@ -47,6 +49,7 @@ const organizationToContributorCard = (org: OrganizationContributorFragment): Co avatar: org.orgProfile.visual?.uri ?? '', url: buildOrganizationUrl(org.nameID), isContactable: true, + contributorType: ContributorType.Organizations, }; }; From c9b0c3b3ae12819f4829a6a9ad6054e331b9847a Mon Sep 17 00:00:00 2001 From: Andrew Pazniak <594548+me-andre@users.noreply.github.com> Date: Fri, 16 Feb 2024 23:11:34 +0800 Subject: [PATCH 21/27] Clearer purpose and lifecycle of excalidraw/collab classes (#5578) * collab/Portal is transport-only * Collab is used as a hook --- .../WhiteboardDialog/WhiteboardRtDialog.tsx | 1 + .../CollaborativeExcalidrawWrapper.tsx | 98 ++++++-------- .../collab/{Collab.tsx => Collab.ts} | 125 ++++++------------ .../collab/{Portal.tsx => Portal.ts} | 93 ++++++------- .../excalidraw/collab/data/index.ts | 15 +-- .../whiteboard/excalidraw/collab/useCollab.ts | 38 ++++++ .../excalidraw/useWhiteboardFilesManager.ts | 7 +- 7 files changed, 172 insertions(+), 205 deletions(-) rename src/domain/common/whiteboard/excalidraw/collab/{Collab.tsx => Collab.ts} (86%) rename src/domain/common/whiteboard/excalidraw/collab/{Portal.tsx => Portal.ts} (73%) create mode 100644 src/domain/common/whiteboard/excalidraw/collab/useCollab.ts diff --git a/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx b/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx index 728bec4d38..fd6a70c1fe 100644 --- a/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx +++ b/src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx @@ -310,6 +310,7 @@ const WhiteboardRtDialog = ({ entities={{ whiteboard, filesManager }} collabApiRef={collabApiRef} options={{ + collaborationEnabled, viewModeEnabled: !editModeEnabled, UIOptions: { canvasActions: { diff --git a/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx b/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx index 872340397f..bda55b8ac2 100644 --- a/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx +++ b/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx @@ -7,9 +7,10 @@ import React, { Ref, useCallback, useEffect, useMemo, useRef, useState } from 'r import { useCombinedRefs } from '../../../shared/utils/useCombinedRefs'; import EmptyWhiteboard from '../EmptyWhiteboard'; import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types'; -import Collab, { CollabAPI } from './collab/Collab'; +import { CollabAPI } from './collab/Collab'; import { useUserContext } from '../../../community/user'; import { WhiteboardFilesManager } from './useWhiteboardFilesManager'; +import useCollab from './collab/useCollab'; const useActorWhiteboardStyles = makeStyles(theme => ({ container: { @@ -41,11 +42,13 @@ export interface WhiteboardWhiteboardEvents { onCollaborationEnabledChange?: (collaborationEnabled: boolean) => void; } -export interface WhiteboardWhiteboardOptions extends ExcalidrawProps { } +export interface WhiteboardWhiteboardOptions extends ExcalidrawProps { + collaborationEnabled: boolean; +} export interface WhiteboardWhiteboardProps { entities: WhiteboardWhiteboardEntities; - options?: WhiteboardWhiteboardOptions; + options: WhiteboardWhiteboardOptions; actions: WhiteboardWhiteboardActions; events: WhiteboardWhiteboardEvents; collabApiRef?: Ref; @@ -62,13 +65,10 @@ const CollaborativeExcalidrawWrapper = ({ }: WhiteboardWhiteboardProps) => { const { whiteboard, filesManager } = entities; - const [collabAPI, setCollabAPI] = useState(null); const combinedCollabApiRef = useCombinedRefs(null, collabApiRef); const styles = useActorWhiteboardStyles(); - const [collaborationEnabled, setCollaborationEnabled] = useState(true); - const { user } = useUserContext(); const username = user?.user.profile.displayName ?? 'User'; @@ -92,7 +92,7 @@ const CollaborativeExcalidrawWrapper = ({ const handleScroll = useRef( debounce(async () => { - excalidrawApiRef.current?.refresh(); + excalidrawApi?.refresh(); }, WINDOW_SCROLL_HANDLER_DEBOUNCE_INTERVAL) ).current; @@ -116,39 +116,55 @@ const CollaborativeExcalidrawWrapper = ({ [] ); - const { UIOptions: externalUIOptions, ...restOptions } = options || {}; + const { UIOptions: externalUIOptions, collaborationEnabled, ...restOptions } = options; const mergedUIOptions = useMemo(() => merge(UIOptions, externalUIOptions), [UIOptions, externalUIOptions]); + const [collabApi, initializeCollab] = useCollab({ + username, + onSavedToDatabase: actions.onSavedToDatabase, + filesManager, + onSaveRequest: async () => { + if (excalidrawApi) { + const state = { + ...(data as ExportedDataState), + elements: excalidrawApi.getSceneElements(), + files: excalidrawApi.getFiles(), + appState: excalidrawApi.getAppState(), + }; + const result = await actions.onUpdate?.(state); + return result ?? { success: false, errors: ['Update handler not defined'] }; + } + return { success: false, errors: ['ExcalidrawAPI not yet ready'] }; + }, + onCloseConnection: () => { + events.onCollaborationEnabledChange?.(false); + }, + onInitialize: collabApi => { + combinedCollabApiRef.current = collabApi; + events.onCollaborationEnabledChange?.(true); + }, + }); + const onChange = (elements: readonly ExcalidrawElement[], _appState: AppState, files: BinaryFiles) => { - collabAPI?.syncElements(elements); - collabAPI?.syncFiles(files); + collabApi?.syncElements(elements); + collabApi?.syncFiles(files); }; + const [excalidrawApi, setExcalidrawApi] = useState(null); + useEffect(() => { - if (collabAPI && whiteboard?.id) { - collabAPI.startCollaboration({ + if (excalidrawApi && whiteboard?.id) { + return initializeCollab({ + excalidrawApi, roomId: whiteboard.id, }); - setCollaborationEnabled(true); - events.onCollaborationEnabledChange?.(true); } - return () => { - setCollaborationEnabled(false); - events.onCollaborationEnabledChange?.(false); - collabAPI?.stopCollaboration(); - }; - }, [collabAPI, whiteboard?.id]); - - const collabRef = useCallback((collabApi: CollabAPI | null) => { - combinedCollabApiRef.current = collabApi; - setCollabAPI(collabApi); - }, []); + }, [excalidrawApi, whiteboard?.id]); - const excalidrawApiRef = useRef(null); const handleInitializeApi = useCallback( (excalidrawApi: ExcalidrawImperativeAPI) => { - excalidrawApiRef.current = excalidrawApi; + setExcalidrawApi(excalidrawApi); actions.onInitApi?.(excalidrawApi); }, [actions.onInitApi] @@ -166,7 +182,7 @@ const CollaborativeExcalidrawWrapper = ({ viewModeEnabled={!collaborationEnabled} gridModeEnabled onChange={onChange} - onPointerUpdate={collabAPI?.onPointerUpdate} + onPointerUpdate={collabApi?.onPointerUpdate} detectScroll={false} autoFocus generateIdForFile={addNewFile} @@ -176,32 +192,6 @@ const CollaborativeExcalidrawWrapper = ({ {...restOptions} /> )} - {excalidrawApiRef.current && ( - { - if (excalidrawApiRef.current) { - const state = { - ...(data as ExportedDataState), - elements: excalidrawApiRef.current.getSceneElements(), - files: excalidrawApiRef.current.getFiles(), - appState: excalidrawApiRef.current.getAppState(), - }; - const result = await actions.onUpdate?.(state); - return result ?? { success: false, errors: ['Update handler not defined'] }; - } - return { success: false, errors: ['ExcalidrawAPI not yet ready'] }; - }} - onCloseConnection={() => { - setCollaborationEnabled(false); - events.onCollaborationEnabledChange?.(false); - }} - /> - )} ); }; diff --git a/src/domain/common/whiteboard/excalidraw/collab/Collab.tsx b/src/domain/common/whiteboard/excalidraw/collab/Collab.ts similarity index 86% rename from src/domain/common/whiteboard/excalidraw/collab/Collab.tsx rename to src/domain/common/whiteboard/excalidraw/collab/Collab.ts index 3ac3516666..c22035ecc8 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/Collab.tsx +++ b/src/domain/common/whiteboard/excalidraw/collab/Collab.ts @@ -1,5 +1,4 @@ import { throttle } from 'lodash'; -import { MutableRefObject, PureComponent, RefCallback } from 'react'; import { BinaryFiles, Collaborator, ExcalidrawImperativeAPI, Gesture } from '@alkemio/excalidraw/types/types'; import { ACTIVE_THRESHOLD, @@ -14,7 +13,7 @@ import { ImportedDataState } from '@alkemio/excalidraw/types/data/types'; import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types'; import { getSceneVersion, newElementWith, restoreElements } from '@alkemio/excalidraw'; import { isImageElement, UserIdleState } from './utils'; -import { generateCollaborationLinkData, getCollabServer, SocketUpdateDataSource } from './data'; +import { getCollabServer, SocketUpdateDataSource } from './data'; import Portal from './Portal'; import { ReconciledElements, reconcileElements as _reconcileElements } from './reconciliation'; import { WhiteboardFilesManager } from '../useWhiteboardFilesManager'; @@ -37,22 +36,20 @@ export interface CollabAPI { notifySavedToDatabase: () => void; // Notify rest of the members in the room that I have saved the whiteboard } -interface PublicProps { - excalidrawAPI: ExcalidrawImperativeAPI; +export interface CollabProps { + excalidrawApi: ExcalidrawImperativeAPI; username: string; - collabAPIRef?: MutableRefObject | RefCallback; onSavedToDatabase?: () => void; // Someone in your room saved the whiteboard to the database filesManager: WhiteboardFilesManager; onSaveRequest: () => Promise<{ success: boolean; errors?: string[] }>; onCloseConnection: () => void; } -type Props = PublicProps; - -class Collab extends PureComponent { +class Collab { portal: Portal; - excalidrawAPI: Props['excalidrawAPI']; - filesManager: Props['filesManager']; + state: CollabState; + excalidrawAPI: ExcalidrawImperativeAPI; + filesManager: WhiteboardFilesManager; activeIntervalId: number | null = null; idleTimeoutId: number | null = null; @@ -63,30 +60,29 @@ class Collab extends PureComponent { private onCloseConnection: () => void; private alreadySharedFiles: string[] = []; - constructor(props: Props) { - super(props); + constructor(props: CollabProps) { this.state = { errorMessage: '', username: props.username, activeRoomLink: '', }; this.portal = new Portal({ - collab: this, - filesManager: props.filesManager, onSaveRequest: props.onSaveRequest, + onRoomUserChange: this.setCollaborators, + getSceneElements: this.getSceneElementsIncludingDeleted, onCloseConnection: this.handleCloseConnection, }); - this.excalidrawAPI = props.excalidrawAPI; + this.onCloseConnection = props.onCloseConnection; + this.excalidrawAPI = props.excalidrawApi; this.filesManager = props.filesManager; this.onSavedToDatabase = props.onSavedToDatabase; - this.onCloseConnection = props.onCloseConnection; this.alreadySharedFiles.push(...Object.keys(this.excalidrawAPI.getFiles())); } - componentDidMount() { + init(): CollabAPI { window.addEventListener(EVENT.UNLOAD, this.onUnload); - const collabAPI: CollabAPI = { + return { onPointerUpdate: this.onPointerUpdate, startCollaboration: this.startCollaboration, syncElements: this.syncElements, @@ -94,25 +90,9 @@ class Collab extends PureComponent { stopCollaboration: this.stopCollaboration, notifySavedToDatabase: this.notifySavedToDatabase, }; - - if (typeof this.props.collabAPIRef === 'function') { - this.props.collabAPIRef(collabAPI); - } else if (this.props.collabAPIRef) { - this.props.collabAPIRef.current = collabAPI; - } - - if (import.meta.env.MODE === 'development') { - window.collab = window.collab || ({} as Window['collab']); - Object.defineProperties(window, { - collab: { - configurable: true, - value: this, - }, - }); - } } - componentWillUnmount() { + destroy() { window.removeEventListener(EVENT.UNLOAD, this.onUnload); window.removeEventListener(EVENT.POINTER_MOVE, this.onPointerMove); window.removeEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange); @@ -164,9 +144,7 @@ class Collab extends PureComponent { this.portal.close(); if (!opts?.isUnload) { - this.setState({ - activeRoomLink: '', - }); + this.state.activeRoomLink = ''; this.collaborators = new Map(); this.excalidrawAPI.updateScene({ collaborators: this.collaborators, @@ -176,19 +154,13 @@ class Collab extends PureComponent { private fallbackInitializationHandler: null | (() => unknown) = null; - startCollaboration = async (existingRoomLinkData: null | { roomId: string }): Promise => + startCollaboration = async (existingRoomLinkData: { roomId: string }): Promise => new Promise(async resolve => { if (this.portal.socket) { return null; } - let roomId; - - if (existingRoomLinkData) { - ({ roomId } = existingRoomLinkData); - } else { - ({ roomId } = await generateCollaborationLinkData()); - } + const { roomId } = existingRoomLinkData; const { default: socketIOClient } = await import('socket.io-client'); @@ -216,8 +188,9 @@ class Collab extends PureComponent { this.portal.socket.once('connect_error', fallbackInitializationHandler); } catch (error) { - console.error(error); // eslint-disable no-console - this.setState({ errorMessage: (error as { message: string } | undefined)?.message ?? '' }); + // eslint-disable-next-line no-console + console.error(error); + this.state.errorMessage = (error as { message: string } | undefined)?.message ?? ''; return null; } @@ -310,11 +283,10 @@ class Collab extends PureComponent { const payload = decryptedData.payload as SocketUpdateDataSource['FILE_REQUEST']['payload']; const currentFiles = this.excalidrawAPI.getFiles(); if (payload.fileIds && payload.fileIds.length > 0) { - payload.fileIds.forEach(id => { + payload.fileIds.forEach(async id => { if (currentFiles[id]) { - this.filesManager - .convertLocalFileToRemote(currentFiles[id]) - .then(file => file && this.portal.broadcastFile(file)); + const file = await this.filesManager.convertLocalFileToRemote(currentFiles[id]); + file && this.portal.broadcastFile(file); } }); } @@ -374,9 +346,7 @@ class Collab extends PureComponent { this.initializeIdleDetector(); - this.setState({ - activeRoomLink: window.location.href, - }); + this.state.activeRoomLink = window.location.href; }); private initializeRoom = async ({ @@ -401,7 +371,8 @@ class Collab extends PureComponent { this.queueBroadcastAllElements(); } catch (error: unknown) { // log the error and move on. other peers will sync us the scene. - console.error(error); // eslint-disable no-console + // eslint-disable-next-line no-console + console.error(error); } finally { this.portal.socketInitialized = true; } @@ -493,7 +464,7 @@ class Collab extends PureComponent { document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange); }; - setCollaborators(sockets: string[]) { + private setCollaborators = (sockets: string[]) => { const collaborators: InstanceType['collaborators'] = new Map(); for (const socketId of sockets) { @@ -506,7 +477,7 @@ class Collab extends PureComponent { this.collaborators = collaborators; this.excalidrawAPI.updateScene({ collaborators }); - } + }; public setLastBroadcastedOrReceivedSceneVersion = (version: number) => { this.lastBroadcastedOrReceivedSceneVersion = version; @@ -526,18 +497,24 @@ class Collab extends PureComponent { button: SocketUpdateDataSource['MOUSE_LOCATION']['payload']['button']; pointersMap: Gesture['pointers']; }) => { - payload.pointersMap.size < 2 && this.portal.socket && this.portal.broadcastMouseLocation(payload); + payload.pointersMap.size < 2 && + this.portal.socket && + this.portal.broadcastMouseLocation({ + ...payload, + username: this.state.username, + selectedElementIds: this.excalidrawAPI.getAppState().selectedElementIds, + }); }, CURSOR_SYNC_TIMEOUT ); onIdleStateChange = (userState: UserIdleState) => { - this.portal.broadcastIdleChange(userState); + this.portal.broadcastIdleChange(userState, this.state.username); }; broadcastElements = (elements: readonly ExcalidrawElement[]) => { if (getSceneVersion(elements) > this.getLastBroadcastedOrReceivedSceneVersion()) { - this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements, false); + this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements); this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements); this.queueBroadcastAllElements(); } @@ -562,35 +539,17 @@ class Collab extends PureComponent { }; notifySavedToDatabase = () => { - this.portal.broadcastSavedEvent(); + this.portal.broadcastSavedEvent(this.state.username); }; queueBroadcastAllElements = throttle(() => { - this.portal.broadcastScene( - WS_SCENE_EVENT_TYPES.UPDATE, - this.excalidrawAPI.getSceneElementsIncludingDeleted(), - true - ); + this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, this.excalidrawAPI.getSceneElementsIncludingDeleted(), { + syncAll: true, + }); const currentVersion = this.getLastBroadcastedOrReceivedSceneVersion(); const newVersion = Math.max(currentVersion, getSceneVersion(this.getSceneElementsIncludingDeleted())); this.setLastBroadcastedOrReceivedSceneVersion(newVersion); }, SYNC_FULL_SCENE_INTERVAL_MS); - - render() { - return null; - } -} - -declare global { - interface Window { - collab: InstanceType; - } -} - -if (import.meta.env.MODE === 'development') { - window.collab = window.collab || ({} as Window['collab']); } export default Collab; - -export type TCollabClass = Collab; diff --git a/src/domain/common/whiteboard/excalidraw/collab/Portal.tsx b/src/domain/common/whiteboard/excalidraw/collab/Portal.ts similarity index 73% rename from src/domain/common/whiteboard/excalidraw/collab/Portal.tsx rename to src/domain/common/whiteboard/excalidraw/collab/Portal.ts index 36fc35fe54..efa068f0a5 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/Portal.tsx +++ b/src/domain/common/whiteboard/excalidraw/collab/Portal.ts @@ -1,33 +1,40 @@ import { isSyncableElement, SocketUpdateData, SocketUpdateDataSource } from './data'; -import { TCollabClass } from './Collab'; import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types'; -import { WS_EVENTS, WS_SCENE_EVENT_TYPES, PRECEDING_ELEMENT_KEY } from './excalidrawAppConstants'; +import { PRECEDING_ELEMENT_KEY, WS_EVENTS, WS_SCENE_EVENT_TYPES } from './excalidrawAppConstants'; import { UserIdleState } from './utils'; import { BroadcastedExcalidrawElement } from './reconciliation'; import { Socket } from 'socket.io-client'; -import { BinaryFileDataWithUrl, WhiteboardFilesManager } from '../useWhiteboardFilesManager'; +import { BinaryFileDataWithUrl } from '../useWhiteboardFilesManager'; interface PortalProps { - collab: TCollabClass; - filesManager: WhiteboardFilesManager; onSaveRequest: () => Promise<{ success: boolean; errors?: string[] }>; onCloseConnection: () => void; + onRoomUserChange: (clients: string[]) => void; + getSceneElements: () => readonly ExcalidrawElement[]; +} + +interface BroadcastOptions { + volatile?: boolean; +} + +interface BroadcastSceneOptions { + syncAll?: boolean; } class Portal { - collab: TCollabClass; - filesManager: WhiteboardFilesManager; onSaveRequest: () => Promise<{ success: boolean; errors?: string[] }>; onCloseConnection: () => void; + onRoomUserChange: (clients: string[]) => void; + getSceneElements: () => readonly ExcalidrawElement[]; socket: Socket | null = null; socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized roomId: string | null = null; broadcastedElementVersions: Map = new Map(); - constructor({ collab, filesManager, onSaveRequest, onCloseConnection }: PortalProps) { - this.collab = collab; - this.filesManager = filesManager; + constructor({ onSaveRequest, onRoomUserChange, getSceneElements, onCloseConnection }: PortalProps) { this.onSaveRequest = onSaveRequest; + this.onRoomUserChange = onRoomUserChange; + this.getSceneElements = getSceneElements; this.onCloseConnection = onCloseConnection; } @@ -43,15 +50,11 @@ class Portal { }); this.socket.on('new-user', async (_socketId: string) => { - this.broadcastScene( - WS_SCENE_EVENT_TYPES.INIT, - this.collab.getSceneElementsIncludingDeleted(), - /* syncAll */ true - ); + this.broadcastScene(WS_SCENE_EVENT_TYPES.INIT, this.getSceneElements(), { syncAll: true }); }); this.socket.on('room-user-change', (clients: string[]) => { - this.collab.setCollaborators(clients); + this.onRoomUserChange(clients); }); this.socket.on('save-request', async callback => { @@ -86,7 +89,7 @@ class Portal { return !!(this.socketInitialized && this.socket && this.roomId); } - private _broadcastSocketData(data: SocketUpdateData, volatile: boolean = false) { + private _broadcastSocketData(data: SocketUpdateData, { volatile = false }: BroadcastOptions = {}) { if (this.isOpen()) { const jsonStr = JSON.stringify(data); const encryptedBuffer = new TextEncoder().encode(jsonStr).buffer; @@ -105,7 +108,7 @@ class Portal { broadcastScene = async ( updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.UPDATE, allElements: readonly ExcalidrawElement[], - syncAll: boolean + { syncAll = false }: BroadcastSceneOptions = {} ) => { if (updateType === WS_SCENE_EVENT_TYPES.INIT && !syncAll) { throw new Error('syncAll must be true when sending SCENE.INIT'); @@ -147,19 +150,15 @@ class Portal { broadcastFile = async (file: BinaryFileDataWithUrl) => { if (this.socket?.id) { - const fileWithUrl = await this.filesManager.convertLocalFileToRemote(file); - - if (fileWithUrl) { - const data: SocketUpdateDataSource['FILE_UPLOAD'] = { - type: 'FILE_UPLOAD', - payload: { - socketId: this.socket.id, - file: fileWithUrl, - }, - }; - - this._broadcastRequestData(data as SocketUpdateData); - } + const data: SocketUpdateDataSource['FILE_UPLOAD'] = { + type: 'FILE_UPLOAD', + payload: { + socketId: this.socket.id, + file, + }, + }; + + this._broadcastRequestData(data as SocketUpdateData); } }; @@ -177,58 +176,50 @@ class Portal { } }; - broadcastIdleChange = (userState: UserIdleState) => { + broadcastIdleChange = (userState: UserIdleState, username: string) => { if (this.socket?.id) { const data: SocketUpdateDataSource['IDLE_STATUS'] = { type: 'IDLE_STATUS', payload: { socketId: this.socket.id, userState, - username: this.collab.state.username, + username, }, }; - return this._broadcastSocketData( - data as SocketUpdateData, - true // volatile - ); + return this._broadcastSocketData(data as SocketUpdateData, { volatile: true }); } }; broadcastMouseLocation = (payload: { pointer: SocketUpdateDataSource['MOUSE_LOCATION']['payload']['pointer']; button: SocketUpdateDataSource['MOUSE_LOCATION']['payload']['button']; + selectedElementIds: Readonly<{ + [id: string]: true; + }>; + username: string; }) => { if (this.socket?.id) { const data: SocketUpdateDataSource['MOUSE_LOCATION'] = { type: 'MOUSE_LOCATION', payload: { socketId: this.socket.id, - pointer: payload.pointer, - button: payload.button || 'up', - selectedElementIds: this.collab.excalidrawAPI.getAppState().selectedElementIds, - username: this.collab.state.username, + ...payload, }, }; - return this._broadcastSocketData( - data as SocketUpdateData, - true // volatile - ); + return this._broadcastSocketData(data as SocketUpdateData, { volatile: true }); } }; - broadcastSavedEvent = () => { + broadcastSavedEvent = (username: string) => { if (this.socket?.id) { const data: SocketUpdateDataSource['SAVED'] = { type: 'SAVED', payload: { socketId: this.socket.id, - username: this.collab.state.username, + username, }, }; - return this._broadcastSocketData( - data as SocketUpdateData, - false // volatile - ); + return this._broadcastSocketData(data as SocketUpdateData); } }; } diff --git a/src/domain/common/whiteboard/excalidraw/collab/data/index.ts b/src/domain/common/whiteboard/excalidraw/collab/data/index.ts index 06107b7970..c1e3cc8677 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/data/index.ts +++ b/src/domain/common/whiteboard/excalidraw/collab/data/index.ts @@ -1,8 +1,7 @@ import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types'; -import { DELETED_ELEMENT_TIMEOUT, ROOM_ID_BYTES } from '../excalidrawAppConstants'; +import { DELETED_ELEMENT_TIMEOUT } from '../excalidrawAppConstants'; import { isInvisiblySmallElement } from '@alkemio/excalidraw'; import { AppState, UserIdleState } from '@alkemio/excalidraw/types/types'; -import { bytesToHexString } from '../utils'; import { env } from '../../../../../../main/env'; import { BinaryFileDataWithUrl } from '../../useWhiteboardFilesManager'; @@ -17,12 +16,6 @@ export const isSyncableElement = (element: ExcalidrawElement): element is Syncab return !isInvisiblySmallElement(element); }; -const generateRoomId = async () => { - const buffer = new Uint8Array(ROOM_ID_BYTES); - window.crypto.getRandomValues(buffer); - return bytesToHexString(buffer); -}; - /** * Right now the reason why we resolve connection params (url, polling...) * from upstream is to allow changing the params immediately when needed without @@ -100,9 +93,3 @@ export type SocketUpdateDataSource = { export type SocketUpdateData = SocketUpdateDataSource[keyof SocketUpdateDataSource] & { _brand: 'socketUpdateData'; }; - -export const generateCollaborationLinkData = async () => { - const roomId = await generateRoomId(); - - return { roomId }; -}; diff --git a/src/domain/common/whiteboard/excalidraw/collab/useCollab.ts b/src/domain/common/whiteboard/excalidraw/collab/useCollab.ts new file mode 100644 index 0000000000..998d50666d --- /dev/null +++ b/src/domain/common/whiteboard/excalidraw/collab/useCollab.ts @@ -0,0 +1,38 @@ +import { useRef } from 'react'; +import Collab, { CollabAPI, CollabProps } from './Collab'; + +type UseCollabProvided = [CollabAPI | null, (initProps: InitProps) => void]; + +interface UseCollabProps extends Omit { + onInitialize?: (collabApi: CollabAPI) => void; +} + +interface InitProps extends Pick { + roomId: string; +} + +const useCollab = ({ onInitialize, ...collabProps }: UseCollabProps): UseCollabProvided => { + const collabRef = useRef(null); + + const collabApiRef = useRef(null); + + const initialize = ({ excalidrawApi, roomId }: InitProps) => { + collabRef.current = new Collab({ + ...collabProps, + excalidrawApi, + }); + const collabApi = collabRef.current.init(); + collabApi.startCollaboration({ roomId }); + collabApiRef.current = collabApi; + onInitialize?.(collabApi); + + return () => { + collabRef.current?.destroy(); + collabApiRef.current = null; + }; + }; + + return [collabApiRef.current, initialize]; +}; + +export default useCollab; diff --git a/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts b/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts index 891c7ad566..516fae58f9 100644 --- a/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts +++ b/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts @@ -4,8 +4,6 @@ import { BinaryFileData, DataURL, ExcalidrawImperativeAPI } from '@alkemio/excal import { excalidrawFileMimeType, generateIdFromFile } from './collab/utils'; import Semaphore from 'ts-semaphore'; -const semaphore = new Semaphore(1); - const isValidDataURL = (url: string) => url.match(/^(data:)([\w/+-]*)(;charset=[\w-]+|;base64){0,1},[A-Za-z0-9+/=]+$/gi) !== null; @@ -214,6 +212,7 @@ const useWhiteboardFilesManager = ({ newFiles[fileId] = { ...file, dataURL } as BinaryFileDataWithUrl; fileStoreAddFile(fileId, newFiles[fileId]); } else { + // eslint-disable-next-line no-console console.error('Cannot download', file); } }) @@ -278,9 +277,11 @@ const useWhiteboardFilesManager = ({ return { files: filesNext, ...rest } as W; }; + const semaphore = useRef(new Semaphore(1)).current; + /** * Finds a file in the fileStore and prepares it to be sent: - * - Ensures that it has a url + * - Ensures that it has a URL * - Removes dataURL * * A Semaphore is required in this function because it can be called multiple times in parallel by Collab.syncFiles From 7598b8286f52b43c8138ae1fc932089b0c5606d5 Mon Sep 17 00:00:00 2001 From: Simone <38861315+SimoneZaza@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:44:52 +0100 Subject: [PATCH 22/27] Clarifying text for membership settings in Challenges Update translation.en.json (#5579) Co-authored-by: Carlos Cano --- src/core/i18n/en/translation.en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index 1583df2331..5f3943f43c 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -1726,7 +1726,7 @@ "community": { "preferences": { "title": "Membership", - "subtitle": "This section allows you to modify the $t(common.challenge) membership preferences" + "subtitle": "This section allows you to modify the $t(common.challenge) membership preferences. If both switches are turned off, new members can only be added manually. If both switches are turned on, members of the Space don't have to apply but can directly join." } }, "sections": { From 877a4b8045a585d2e0950e2ba3c6b4a70916556c Mon Sep 17 00:00:00 2001 From: Valentin Yanakiev Date: Tue, 20 Feb 2024 09:24:51 +0200 Subject: [PATCH 23/27] Overfetch so we get enough myActivities (#5582) --- .../myLatestContributions/MyLatestContributions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx b/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx index 6811b19228..5e48e79379 100644 --- a/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx +++ b/src/main/topLevelPages/myDashboard/latestContributions/myLatestContributions/MyLatestContributions.tsx @@ -39,7 +39,7 @@ const MyLatestContributions = () => { useQuery: useLatestContributionsQuery, getPageInfo: data => data.activityFeed.pageInfo, pageSize: 1, - firstPageSize: MY_LATEST_CONTRIBUTIONS_COUNT, + firstPageSize: MY_LATEST_CONTRIBUTIONS_COUNT * 2, ////magic number, should not be needed. toDo Fix in https://app.zenhub.com/workspaces/alkemio-development-5ecb98b262ebd9f4aec4194c/issues/gh/alkem-io/server/3626 variables: { filter: { myActivity: true, From 75b98c120b43f71037ab22055139077ba250c1e7 Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Tue, 20 Feb 2024 14:30:04 +0200 Subject: [PATCH 24/27] Innovation flows with phases (#5586) * Innovation flows with phases * Fix build * address comments --- src/core/apollo/generated/apollo-helpers.ts | 2 + src/core/apollo/generated/apollo-hooks.ts | 1 + src/core/apollo/generated/graphql-schema.ts | 20 +++++++++ src/core/ui/card/CardDescriptionWithTags.tsx | 6 +-- .../InnovationFlowTemplateCard.tsx | 28 +++++++++--- ...InnovationFlowTemplateCardFragment.graphql | 1 + .../InnovationFlowTemplatesLibrary.tsx | 1 + .../LibraryTemplateCard.tsx | 3 +- .../InnovationImportTemplateCard.tsx | 7 ++- .../SelectInnovationFlowDialog.tsx | 3 ++ .../useInnovationFlowStatesReader.ts | 45 +++++++++++++++++++ .../TemplatePreviewDialog.tsx | 2 +- 12 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 src/domain/platform/admin/templates/InnovationTemplates/useInnovationFlowStatesReader.ts diff --git a/src/core/apollo/generated/apollo-helpers.ts b/src/core/apollo/generated/apollo-helpers.ts index 4dc9142acd..006b20ff92 100644 --- a/src/core/apollo/generated/apollo-helpers.ts +++ b/src/core/apollo/generated/apollo-helpers.ts @@ -1554,6 +1554,7 @@ export type MutationKeySpecifier = ( | 'updateTagset' | 'updateUser' | 'updateUserGroup' + | 'updateUserPlatformSettings' | 'updateVisual' | 'updateWhiteboard' | 'updateWhiteboardContentRt' @@ -1715,6 +1716,7 @@ export type MutationFieldPolicy = { updateTagset?: FieldPolicy | FieldReadFunction; updateUser?: FieldPolicy | FieldReadFunction; updateUserGroup?: FieldPolicy | FieldReadFunction; + updateUserPlatformSettings?: FieldPolicy | FieldReadFunction; updateVisual?: FieldPolicy | FieldReadFunction; updateWhiteboard?: FieldPolicy | FieldReadFunction; updateWhiteboardContentRt?: FieldPolicy | FieldReadFunction; diff --git a/src/core/apollo/generated/apollo-hooks.ts b/src/core/apollo/generated/apollo-hooks.ts index 1d6ad90ccf..b7fbaab828 100644 --- a/src/core/apollo/generated/apollo-hooks.ts +++ b/src/core/apollo/generated/apollo-hooks.ts @@ -2754,6 +2754,7 @@ export const InnovationFlowTemplateCardFragmentDoc = gql` profile { ...TemplateCardProfileInfo } + definition } ${TemplateCardProfileInfoFragmentDoc} `; diff --git a/src/core/apollo/generated/graphql-schema.ts b/src/core/apollo/generated/graphql-schema.ts index ff4d8d9a17..e908a5e80a 100644 --- a/src/core/apollo/generated/graphql-schema.ts +++ b/src/core/apollo/generated/graphql-schema.ts @@ -2795,6 +2795,8 @@ export type Mutation = { updateUser: User; /** Updates the specified User Group. */ updateUserGroup: UserGroup; + /** Update the platform settings, such as nameID, email, for the specified User. */ + updateUserPlatformSettings: User; /** Updates the image URI for the specified Visual. */ updateVisual: Visual; /** Updates the specified Whiteboard. */ @@ -3387,6 +3389,10 @@ export type MutationUpdateUserGroupArgs = { userGroupData: UpdateUserGroupInput; }; +export type MutationUpdateUserPlatformSettingsArgs = { + updateData: UpdateUserPlatformSettingsInput; +}; + export type MutationUpdateVisualArgs = { updateData: UpdateVisualInput; }; @@ -5425,6 +5431,14 @@ export type UpdateUserInput = { serviceProfile?: InputMaybe; }; +export type UpdateUserPlatformSettingsInput = { + email?: InputMaybe; + /** Upate the URL path for the User. */ + nameID?: InputMaybe; + /** The identifier for the User whose platform managed information is to be updated. */ + userID: Scalars['String']; +}; + export type UpdateUserPreferenceInput = { /** Type of the user preference */ type: UserPreferenceType; @@ -7875,6 +7889,7 @@ export type InnovationFlowTemplateCardFragment = { __typename?: 'InnovationFlowTemplate'; id: string; type: InnovationFlowType; + definition: string; profile: { __typename?: 'Profile'; id: string; @@ -7911,6 +7926,7 @@ export type SpaceInnovationFlowTemplatesLibraryQuery = { __typename?: 'InnovationFlowTemplate'; id: string; type: InnovationFlowType; + definition: string; profile: { __typename?: 'Profile'; id: string; @@ -7983,6 +7999,7 @@ export type PlatformInnovationFlowTemplatesLibraryQuery = { __typename?: 'InnovationFlowTemplate'; id: string; type: InnovationFlowType; + definition: string; profile: { __typename?: 'Profile'; id: string; @@ -8155,6 +8172,7 @@ export type InnovationPackProfilePageQuery = { __typename?: 'InnovationFlowTemplate'; id: string; type: InnovationFlowType; + definition: string; profile: { __typename?: 'Profile'; id: string; @@ -26372,6 +26390,7 @@ export type InnovationFlowTemplatesFromSpaceQuery = { __typename?: 'InnovationFlowTemplate'; id: string; type: InnovationFlowType; + definition: string; profile: { __typename?: 'Profile'; id: string; @@ -26449,6 +26468,7 @@ export type SpaceTemplatesFragment = { __typename?: 'InnovationFlowTemplate'; id: string; type: InnovationFlowType; + definition: string; profile: { __typename?: 'Profile'; id: string; diff --git a/src/core/ui/card/CardDescriptionWithTags.tsx b/src/core/ui/card/CardDescriptionWithTags.tsx index 43016692dc..5d819f1478 100644 --- a/src/core/ui/card/CardDescriptionWithTags.tsx +++ b/src/core/ui/card/CardDescriptionWithTags.tsx @@ -14,9 +14,9 @@ export const CardDescriptionWithTags = ({ ...props }: CardDescriptionWithTagsProps) => { const descriptionHeight = - heightGutters ?? tags.length > 0 - ? DEFAULT_CARDDESCRIPTION_HEIGHT_GUTTERS - : DEFAULT_CARDDESCRIPTION_HEIGHT_GUTTERS + 2; + heightGutters ?? + (tags.length > 0 ? DEFAULT_CARDDESCRIPTION_HEIGHT_GUTTERS : DEFAULT_CARDDESCRIPTION_HEIGHT_GUTTERS + 2); + return ( <> diff --git a/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCard.tsx b/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCard.tsx index c614dccf14..82cbafa135 100644 --- a/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCard.tsx +++ b/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCard.tsx @@ -9,10 +9,18 @@ import ContributeCard from '../../../../core/ui/card/ContributeCard'; import { Caption } from '../../../../core/ui/typography/components'; import InnovationPackIcon from '../../InnovationPack/InnovationPackIcon'; import { InnovationFlowIcon } from '../../../platform/admin/templates/InnovationTemplates/InnovationFlow/InnovationFlowIcon'; -import { TemplateCardBaseProps } from '../../templates/CollaborationTemplatesLibrary/TemplateBase'; -import CardDescriptionWithTags from '../../../../core/ui/card/CardDescriptionWithTags'; +import { TemplateBase, TemplateCardBaseProps } from '../../templates/CollaborationTemplatesLibrary/TemplateBase'; +import useInnovationFlowStatesReader from '../../../platform/admin/templates/InnovationTemplates/useInnovationFlowStatesReader'; +import CardDescription from '../../../../core/ui/card/CardDescription'; +import CardContent from '../../../../core/ui/card/CardContent'; +import CardTags from '../../../../core/ui/card/CardTags'; +import webkitLineClamp from '../../../../core/ui/utils/webkitLineClamp'; -interface InnovationFlowTemplateCardProps extends TemplateCardBaseProps {} +interface InnovationFlowTemplate extends TemplateBase { + definition: string; +} + +interface InnovationFlowTemplateCardProps extends TemplateCardBaseProps {} const InnovationFlowTemplateCard = ({ template, @@ -20,6 +28,8 @@ const InnovationFlowTemplateCard = ({ loading, onClick, }: InnovationFlowTemplateCardProps) => { + const { states } = useInnovationFlowStatesReader({ definition: template?.definition }); + return ( @@ -29,9 +39,15 @@ const InnovationFlowTemplateCard = ({ - - {template?.profile.description} - + {template?.profile.description} + + + + {states.join(' · ')} + + + + {innovationPack && ( }> diff --git a/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCardFragment.graphql b/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCardFragment.graphql index 6e2fda88e6..2746fcd527 100644 --- a/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCardFragment.graphql +++ b/src/domain/collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCardFragment.graphql @@ -4,4 +4,5 @@ fragment InnovationFlowTemplateCard on InnovationFlowTemplate { profile { ...TemplateCardProfileInfo } + definition } diff --git a/src/domain/collaboration/InnovationFlow/InnovationFlowTemplatesLibrary/InnovationFlowTemplatesLibrary.tsx b/src/domain/collaboration/InnovationFlow/InnovationFlowTemplatesLibrary/InnovationFlowTemplatesLibrary.tsx index a9938ac611..e5d8f9ee2d 100644 --- a/src/domain/collaboration/InnovationFlow/InnovationFlowTemplatesLibrary/InnovationFlowTemplatesLibrary.tsx +++ b/src/domain/collaboration/InnovationFlow/InnovationFlowTemplatesLibrary/InnovationFlowTemplatesLibrary.tsx @@ -17,6 +17,7 @@ import { Identifiable } from '../../../../core/utils/Identifiable'; interface InnovationFlowTemplate extends TemplateBase { type: InnovationFlowType; + definition: string; } export interface InnovationFlowTemplatesLibraryProps { diff --git a/src/domain/collaboration/InnovationPack/DashboardLibraryTemplates/LibraryTemplateCard.tsx b/src/domain/collaboration/InnovationPack/DashboardLibraryTemplates/LibraryTemplateCard.tsx index d3f6ac0507..c05ab4846f 100644 --- a/src/domain/collaboration/InnovationPack/DashboardLibraryTemplates/LibraryTemplateCard.tsx +++ b/src/domain/collaboration/InnovationPack/DashboardLibraryTemplates/LibraryTemplateCard.tsx @@ -7,12 +7,13 @@ import { TemplateType } from '../InnovationPackProfilePage/InnovationPackProfile import { PostTemplate } from '../../post/PostTemplateCard/PostTemplate'; import { TemplateBase } from '../../templates/CollaborationTemplatesLibrary/TemplateBase'; import { TemplateWithInnovationPack } from '../../../platform/admin/templates/InnovationPacks/ImportTemplatesDialogGalleryStep'; +import { InnovationFlowTemplate } from '../../../platform/admin/templates/InnovationTemplates/SelectInnovationFlowDialog'; export type LibraryTemplateCardProps = Identifiable & TemplateWithInnovationPack< | (PostTemplate & { templateType: TemplateType.PostTemplate }) | (TemplateBase & { templateType: TemplateType.WhiteboardTemplate }) - | (TemplateBase & { templateType: TemplateType.InnovationFlowTemplate }) + | (InnovationFlowTemplate & { templateType: TemplateType.InnovationFlowTemplate }) | (TemplateBase & { templateType: TemplateType.CalloutTemplate }) > & { onClick?: ContributeCardProps['onClick'] }; diff --git a/src/domain/platform/admin/templates/InnovationTemplates/InnovationImportTemplateCard.tsx b/src/domain/platform/admin/templates/InnovationTemplates/InnovationImportTemplateCard.tsx index 14ea7656ce..7c9fa6d9e1 100644 --- a/src/domain/platform/admin/templates/InnovationTemplates/InnovationImportTemplateCard.tsx +++ b/src/domain/platform/admin/templates/InnovationTemplates/InnovationImportTemplateCard.tsx @@ -1,8 +1,13 @@ import React from 'react'; import { TemplateImportCardComponentProps } from '../InnovationPacks/ImportTemplatesDialogGalleryStep'; import InnovationFlowTemplateCard from '../../../../collaboration/InnovationFlow/InnovationFlowTemplateCard/InnovationFlowTemplateCard'; +import { Template } from '../AdminTemplatesSection'; -interface InnovationImportTemplateCardProps extends TemplateImportCardComponentProps {} +interface InnovationFlowTemplate extends Template { + definition: string; +} + +interface InnovationImportTemplateCardProps extends TemplateImportCardComponentProps {} const InnovationImportTemplateCard = ({ template, ...props }: InnovationImportTemplateCardProps) => { return ; diff --git a/src/domain/platform/admin/templates/InnovationTemplates/SelectInnovationFlowDialog.tsx b/src/domain/platform/admin/templates/InnovationTemplates/SelectInnovationFlowDialog.tsx index 1a02b622d5..874ddfe6b0 100644 --- a/src/domain/platform/admin/templates/InnovationTemplates/SelectInnovationFlowDialog.tsx +++ b/src/domain/platform/admin/templates/InnovationTemplates/SelectInnovationFlowDialog.tsx @@ -13,6 +13,9 @@ import { InnovationFlowType } from '../../../../../core/apollo/generated/graphql export interface InnovationFlowTemplateProfile { id: string; displayName: string; + tagset?: { + tags: string[]; + }; } export interface InnovationFlowTemplate { definition: string; diff --git a/src/domain/platform/admin/templates/InnovationTemplates/useInnovationFlowStatesReader.ts b/src/domain/platform/admin/templates/InnovationTemplates/useInnovationFlowStatesReader.ts new file mode 100644 index 0000000000..7cae64cca2 --- /dev/null +++ b/src/domain/platform/admin/templates/InnovationTemplates/useInnovationFlowStatesReader.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; +import { LifecycleDataProvider } from '@alkemio/visualization'; +import { error as sentryError } from '../../../../../core/logging/sentry/log'; +import useLoadingState from '../../../../shared/utils/useLoadingState'; + +interface useInnovationFlowStatesReaderProps { + definition: string | undefined; +} + +interface useInnovationFlowStatesReaderProvided { + loading: boolean; + states: string[]; +} + +const useInnovationFlowStatesReader = ({ + definition, +}: useInnovationFlowStatesReaderProps): useInnovationFlowStatesReaderProvided => { + const [states, setStates] = useState([]); + + const [loadData, loading] = useLoadingState(async (definition: string) => { + try { + const lifecycleData = new LifecycleDataProvider(); + await lifecycleData.loadData(definition); + setStates(Object.keys(lifecycleData.machine.states)); + } catch (e) { + const error = new Error( + `Coulnd't load innovationFlow definition:'${definition}' ${(e as { message?: string }).message}` + ); + sentryError(error); + } + }); + + useEffect(() => { + if (definition) { + loadData(definition); + } + }, [definition]); + + return { + loading, + states, + }; +}; + +export default useInnovationFlowStatesReader; diff --git a/src/domain/template/templatePreviewDialog/TemplatePreviewDialog.tsx b/src/domain/template/templatePreviewDialog/TemplatePreviewDialog.tsx index b68ae7e57e..8347fc079a 100644 --- a/src/domain/template/templatePreviewDialog/TemplatePreviewDialog.tsx +++ b/src/domain/template/templatePreviewDialog/TemplatePreviewDialog.tsx @@ -31,7 +31,7 @@ export type TemplatePreview = templateType: TemplateType.PostTemplate; } | { - template: TemplateBase & Identifiable; + template: TemplateBase & { definition: string } & Identifiable; templateType: TemplateType.InnovationFlowTemplate; } | { From 9f9e933b032c815443053c717839074f699d8041 Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Tue, 20 Feb 2024 14:39:44 +0200 Subject: [PATCH 25/27] Simplify file broadcasting 2 (#5580) * Simplify file broadcasting 2 * change visibility of some funcs * Comments clean up --------- Co-authored-by: Valentin Yanakiev Co-authored-by: Andrew Pazniak <594548+me-andre@users.noreply.github.com> --- .../CollaborativeExcalidrawWrapper.tsx | 6 +- .../whiteboard/excalidraw/collab/Collab.ts | 138 +++++------------- .../whiteboard/excalidraw/collab/Portal.ts | 55 +++---- .../excalidraw/collab/data/index.ts | 18 +-- .../collab/excalidrawAppConstants.ts | 5 +- .../excalidraw/useWhiteboardFilesManager.ts | 39 ++++- 6 files changed, 105 insertions(+), 156 deletions(-) diff --git a/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx b/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx index bda55b8ac2..9e8fc47c69 100644 --- a/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx +++ b/src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx @@ -146,9 +146,9 @@ const CollaborativeExcalidrawWrapper = ({ }, }); - const onChange = (elements: readonly ExcalidrawElement[], _appState: AppState, files: BinaryFiles) => { - collabApi?.syncElements(elements); - collabApi?.syncFiles(files); + const onChange = async (elements: readonly ExcalidrawElement[], _appState: AppState, files: BinaryFiles) => { + const uploadedFiles = await filesManager.getUploadedFiles(files); + collabApi?.syncScene(elements, uploadedFiles); }; const [excalidrawApi, setExcalidrawApi] = useState(null); diff --git a/src/domain/common/whiteboard/excalidraw/collab/Collab.ts b/src/domain/common/whiteboard/excalidraw/collab/Collab.ts index c22035ecc8..71cc8a81b0 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/Collab.ts +++ b/src/domain/common/whiteboard/excalidraw/collab/Collab.ts @@ -1,5 +1,5 @@ import { throttle } from 'lodash'; -import { BinaryFiles, Collaborator, ExcalidrawImperativeAPI, Gesture } from '@alkemio/excalidraw/types/types'; +import { Collaborator, ExcalidrawImperativeAPI, Gesture } from '@alkemio/excalidraw/types/types'; import { ACTIVE_THRESHOLD, CURSOR_SYNC_TIMEOUT, @@ -16,7 +16,7 @@ import { isImageElement, UserIdleState } from './utils'; import { getCollabServer, SocketUpdateDataSource } from './data'; import Portal from './Portal'; import { ReconciledElements, reconcileElements as _reconcileElements } from './reconciliation'; -import { WhiteboardFilesManager } from '../useWhiteboardFilesManager'; +import { BinaryFilesWithUrl, WhiteboardFilesManager } from '../useWhiteboardFilesManager'; interface CollabState { errorMessage: string; @@ -31,8 +31,7 @@ export interface CollabAPI { onPointerUpdate: CollabInstance['onPointerUpdate']; startCollaboration: CollabInstance['startCollaboration']; stopCollaboration: CollabInstance['stopCollaboration']; - syncElements: CollabInstance['syncElements']; - syncFiles: CollabInstance['syncFiles']; + syncScene: CollabInstance['syncScene']; notifySavedToDatabase: () => void; // Notify rest of the members in the room that I have saved the whiteboard } @@ -58,7 +57,6 @@ class Collab { private collaborators = new Map(); private onSavedToDatabase: (() => void) | undefined; private onCloseConnection: () => void; - private alreadySharedFiles: string[] = []; constructor(props: CollabProps) { this.state = { @@ -70,13 +68,13 @@ class Collab { onSaveRequest: props.onSaveRequest, onRoomUserChange: this.setCollaborators, getSceneElements: this.getSceneElementsIncludingDeleted, + getFiles: this.getFiles, onCloseConnection: this.handleCloseConnection, }); this.onCloseConnection = props.onCloseConnection; this.excalidrawAPI = props.excalidrawApi; this.filesManager = props.filesManager; this.onSavedToDatabase = props.onSavedToDatabase; - this.alreadySharedFiles.push(...Object.keys(this.excalidrawAPI.getFiles())); } init(): CollabAPI { @@ -85,8 +83,7 @@ class Collab { return { onPointerUpdate: this.onPointerUpdate, startCollaboration: this.startCollaboration, - syncElements: this.syncElements, - syncFiles: this.syncFiles, + syncScene: this.syncScene, stopCollaboration: this.stopCollaboration, notifySavedToDatabase: this.notifySavedToDatabase, }; @@ -229,71 +226,22 @@ class Collab { if (!this.portal.socketInitialized) { this.initializeRoom({ fetchScene: false }); const remoteElements = decryptedData.payload.elements; - const reconciledElements = this.reconcileElements(remoteElements); - this.handleRemoteSceneUpdate(reconciledElements, { + const remoteFiles = decryptedData.payload.files; + this.handleRemoteSceneUpdate(this.reconcileElements(remoteElements, remoteFiles), { init: true, }); - // noop if already resolved via init from firebase - resolve({ - elements: reconciledElements, - scrollToContent: true, - }); - - // Download files from the storageBucket here: - // Files included in the canvas: - const requiredFilesIds = reconciledElements.reduce((files, element) => { - if (element.type === 'image' && element.fileId) { - files.push(element.fileId); - } - return files; - }, []); - // Files missing in this client: - const currentFiles = Object.keys(this.excalidrawAPI.getFiles()); - const missingFiles = requiredFilesIds.filter(fileId => !currentFiles.includes(fileId)); - - if (missingFiles.length > 0) { - this.portal.broadcastFileRequest(missingFiles); - } - - this.filesManager.loadFiles({ files: this.excalidrawAPI.getFiles() }); - } - - break; - } - - case WS_SCENE_EVENT_TYPES.UPDATE: { - this.handleRemoteSceneUpdate(this.reconcileElements(decryptedData.payload.elements)); - break; - } - - case 'FILE_UPLOAD': { - const payload = decryptedData.payload as SocketUpdateDataSource['FILE_UPLOAD']['payload']; - const currentFiles = this.excalidrawAPI.getFiles(); - - if (!currentFiles[payload.file.id]) { - this.alreadySharedFiles.push(payload.file.id); - this.excalidrawAPI.addFiles([payload.file]); - this.filesManager.loadFiles({ files: { [payload.file.id]: payload.file } }); } - break; } - case 'FILE_REQUEST': { - const payload = decryptedData.payload as SocketUpdateDataSource['FILE_REQUEST']['payload']; - const currentFiles = this.excalidrawAPI.getFiles(); - if (payload.fileIds && payload.fileIds.length > 0) { - payload.fileIds.forEach(async id => { - if (currentFiles[id]) { - const file = await this.filesManager.convertLocalFileToRemote(currentFiles[id]); - file && this.portal.broadcastFile(file); - } - }); - } + case WS_SCENE_EVENT_TYPES.SCENE_UPDATE: { + const remoteElements = decryptedData.payload.elements; + const remoteFiles = decryptedData.payload.files; + this.handleRemoteSceneUpdate(this.reconcileElements(remoteElements, remoteFiles)); break; } - case 'MOUSE_LOCATION': { + case WS_SCENE_EVENT_TYPES.MOUSE_LOCATION: { const { pointer, button, username, selectedElementIds } = decryptedData.payload; const socketId: SocketUpdateDataSource['MOUSE_LOCATION']['payload']['socketId'] = decryptedData.payload.socketId || @@ -313,7 +261,7 @@ class Collab { break; } - case 'IDLE_STATUS': { + case WS_SCENE_EVENT_TYPES.IDLE_STATUS: { const { userState, socketId, username } = decryptedData.payload; const collaborators = new Map(this.collaborators); const user = collaborators.get(socketId) || {}!; @@ -325,7 +273,7 @@ class Collab { break; } - case 'SAVED': { + case WS_SCENE_EVENT_TYPES.SAVED: { this.onSavedToDatabase?.(); break; } @@ -365,8 +313,6 @@ class Collab { } if (fetchScene && roomLinkData && this.portal.socket) { - //this.excalidrawAPI.resetScene(); - try { this.queueBroadcastAllElements(); } catch (error: unknown) { @@ -383,7 +329,10 @@ class Collab { return null; }; - private reconcileElements = (remoteElements: readonly ExcalidrawElement[]): ReconciledElements => { + private reconcileElements = ( + remoteElements: readonly ExcalidrawElement[], + remoteFiles: BinaryFilesWithUrl + ): ReconciledElements => { const localElements = this.getSceneElementsIncludingDeleted(); const appState = this.excalidrawAPI.getAppState(); @@ -391,6 +340,9 @@ class Collab { const reconciledElements = _reconcileElements(localElements, remoteElements, appState); + // Download the files that this instance is missing: + this.filesManager.loadFiles({ files: remoteFiles }); + // Avoid broadcasting to the rest of the collaborators the scene // we just received! // Note: this needs to be set before updating the scene as it @@ -406,6 +358,8 @@ class Collab { commitToHistory: !!init, }); + this.filesManager.pushFilesToExcalidraw(); + // We haven't yet implemented multiplayer undo functionality, so we clear the undo stack // when we receive any messages from another peer. This UX can be pretty rough -- if you // undo, a user makes a change, and then try to redo, your element(s) will be lost. However, @@ -479,19 +433,23 @@ class Collab { this.excalidrawAPI.updateScene({ collaborators }); }; - public setLastBroadcastedOrReceivedSceneVersion = (version: number) => { + private setLastBroadcastedOrReceivedSceneVersion = (version: number) => { this.lastBroadcastedOrReceivedSceneVersion = version; }; - public getLastBroadcastedOrReceivedSceneVersion = () => { + private getLastBroadcastedOrReceivedSceneVersion = () => { return this.lastBroadcastedOrReceivedSceneVersion; }; - public getSceneElementsIncludingDeleted = () => { + private getSceneElementsIncludingDeleted = () => { return this.excalidrawAPI.getSceneElementsIncludingDeleted(); }; - onPointerUpdate = throttle( + private getFiles = () => { + return this.filesManager.getUploadedFiles(this.excalidrawAPI.getFiles()); + }; + + private onPointerUpdate = throttle( (payload: { pointer: SocketUpdateDataSource['MOUSE_LOCATION']['payload']['pointer']; button: SocketUpdateDataSource['MOUSE_LOCATION']['payload']['button']; @@ -508,44 +466,26 @@ class Collab { CURSOR_SYNC_TIMEOUT ); - onIdleStateChange = (userState: UserIdleState) => { + private onIdleStateChange = (userState: UserIdleState) => { this.portal.broadcastIdleChange(userState, this.state.username); }; - broadcastElements = (elements: readonly ExcalidrawElement[]) => { + private syncScene = async (elements: readonly ExcalidrawElement[], files: BinaryFilesWithUrl) => { if (getSceneVersion(elements) > this.getLastBroadcastedOrReceivedSceneVersion()) { - this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements); + this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.SCENE_UPDATE, elements, files, { syncAll: false }); this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements); this.queueBroadcastAllElements(); } }; - syncElements = (elements: readonly ExcalidrawElement[]) => { - this.broadcastElements(elements); - }; - - syncFiles = async (files: BinaryFiles) => { - for (const id of Object.keys(files)) { - if (!this.alreadySharedFiles.includes(id)) { - const file = files[id]; - const fileWithUrl = await this.filesManager.convertLocalFileToRemote(file); - - if (fileWithUrl) { - this.portal.broadcastFile(fileWithUrl); - this.alreadySharedFiles.push(id); - } - } - } - }; - - notifySavedToDatabase = () => { + private notifySavedToDatabase = () => { this.portal.broadcastSavedEvent(this.state.username); }; - queueBroadcastAllElements = throttle(() => { - this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, this.excalidrawAPI.getSceneElementsIncludingDeleted(), { - syncAll: true, - }); + private queueBroadcastAllElements = throttle(async () => { + const elements = this.excalidrawAPI.getSceneElementsIncludingDeleted(); + const files = await this.filesManager.getUploadedFiles(this.excalidrawAPI.getFiles()); + this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.SCENE_UPDATE, elements, files, { syncAll: true }); const currentVersion = this.getLastBroadcastedOrReceivedSceneVersion(); const newVersion = Math.max(currentVersion, getSceneVersion(this.getSceneElementsIncludingDeleted())); this.setLastBroadcastedOrReceivedSceneVersion(newVersion); diff --git a/src/domain/common/whiteboard/excalidraw/collab/Portal.ts b/src/domain/common/whiteboard/excalidraw/collab/Portal.ts index efa068f0a5..a366663483 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/Portal.ts +++ b/src/domain/common/whiteboard/excalidraw/collab/Portal.ts @@ -4,13 +4,15 @@ import { PRECEDING_ELEMENT_KEY, WS_EVENTS, WS_SCENE_EVENT_TYPES } from './excali import { UserIdleState } from './utils'; import { BroadcastedExcalidrawElement } from './reconciliation'; import { Socket } from 'socket.io-client'; -import { BinaryFileDataWithUrl } from '../useWhiteboardFilesManager'; +import { BinaryFileDataWithUrl, BinaryFilesWithUrl } from '../useWhiteboardFilesManager'; +import { DataURL } from '@alkemio/excalidraw/types/types'; interface PortalProps { onSaveRequest: () => Promise<{ success: boolean; errors?: string[] }>; onCloseConnection: () => void; onRoomUserChange: (clients: string[]) => void; getSceneElements: () => readonly ExcalidrawElement[]; + getFiles: () => Promise; } interface BroadcastOptions { @@ -26,15 +28,18 @@ class Portal { onCloseConnection: () => void; onRoomUserChange: (clients: string[]) => void; getSceneElements: () => readonly ExcalidrawElement[]; + getFiles: () => Promise; socket: Socket | null = null; socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized roomId: string | null = null; broadcastedElementVersions: Map = new Map(); + broadcastedFiles: Set = new Set(); - constructor({ onSaveRequest, onRoomUserChange, getSceneElements, onCloseConnection }: PortalProps) { + constructor({ onSaveRequest, onRoomUserChange, getSceneElements, getFiles, onCloseConnection }: PortalProps) { this.onSaveRequest = onSaveRequest; this.onRoomUserChange = onRoomUserChange; this.getSceneElements = getSceneElements; + this.getFiles = getFiles; this.onCloseConnection = onCloseConnection; } @@ -50,7 +55,7 @@ class Portal { }); this.socket.on('new-user', async (_socketId: string) => { - this.broadcastScene(WS_SCENE_EVENT_TYPES.INIT, this.getSceneElements(), { syncAll: true }); + this.broadcastScene(WS_SCENE_EVENT_TYPES.INIT, this.getSceneElements(), await this.getFiles(), { syncAll: true }); }); this.socket.on('room-user-change', (clients: string[]) => { @@ -61,7 +66,7 @@ class Portal { try { callback(await this.onSaveRequest()); } catch (ex) { - callback({ success: false, errors: [ex?.message ?? ex] }); + callback({ success: false, errors: [(ex as { message?: string })?.message ?? ex] }); } }); @@ -106,8 +111,9 @@ class Portal { } broadcastScene = async ( - updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.UPDATE, + updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.SCENE_UPDATE, allElements: readonly ExcalidrawElement[], + allFiles: BinaryFilesWithUrl, { syncAll = false }: BroadcastSceneOptions = {} ) => { if (updateType === WS_SCENE_EVENT_TYPES.INIT && !syncAll) { @@ -134,10 +140,21 @@ class Portal { return acc; }, [] as BroadcastedExcalidrawElement[]); + const emptyDataURL = '' as DataURL; + const syncableFiles = Object.keys(allFiles).reduce>((result, fileId) => { + if (syncAll || !this.broadcastedFiles.has(fileId)) { + const file = { ...allFiles[fileId], dataURL: emptyDataURL }; + result[fileId] = file; + this.broadcastedFiles.add(fileId); + } + return result; + }, {}); + const data: SocketUpdateDataSource[typeof updateType] = { type: updateType, payload: { elements: syncableElements, + files: syncableFiles, }, }; @@ -148,34 +165,6 @@ class Portal { this._broadcastSocketData(data as SocketUpdateData); }; - broadcastFile = async (file: BinaryFileDataWithUrl) => { - if (this.socket?.id) { - const data: SocketUpdateDataSource['FILE_UPLOAD'] = { - type: 'FILE_UPLOAD', - payload: { - socketId: this.socket.id, - file, - }, - }; - - this._broadcastRequestData(data as SocketUpdateData); - } - }; - - broadcastFileRequest = (fileIds: string[]) => { - if (this.socket?.id) { - const data: SocketUpdateDataSource['FILE_REQUEST'] = { - type: 'FILE_REQUEST', - payload: { - socketId: this.socket.id, - fileIds, - }, - }; - - this._broadcastRequestData(data as SocketUpdateData); - } - }; - broadcastIdleChange = (userState: UserIdleState, username: string) => { if (this.socket?.id) { const data: SocketUpdateDataSource['IDLE_STATUS'] = { diff --git a/src/domain/common/whiteboard/excalidraw/collab/data/index.ts b/src/domain/common/whiteboard/excalidraw/collab/data/index.ts index c1e3cc8677..888d0361f1 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/data/index.ts +++ b/src/domain/common/whiteboard/excalidraw/collab/data/index.ts @@ -3,7 +3,7 @@ import { DELETED_ELEMENT_TIMEOUT } from '../excalidrawAppConstants'; import { isInvisiblySmallElement } from '@alkemio/excalidraw'; import { AppState, UserIdleState } from '@alkemio/excalidraw/types/types'; import { env } from '../../../../../../main/env'; -import { BinaryFileDataWithUrl } from '../../useWhiteboardFilesManager'; +import { BinaryFilesWithUrl } from '../../useWhiteboardFilesManager'; export type SyncableExcalidrawElement = ExcalidrawElement & { _brand: 'SyncableExcalidrawElement'; @@ -41,12 +41,14 @@ export type SocketUpdateDataSource = { type: 'SCENE_INIT'; payload: { elements: readonly ExcalidrawElement[]; + files: BinaryFilesWithUrl; }; }; SCENE_UPDATE: { type: 'SCENE_UPDATE'; payload: { elements: readonly ExcalidrawElement[]; + files: BinaryFilesWithUrl; }; }; MOUSE_LOCATION: { @@ -74,20 +76,6 @@ export type SocketUpdateDataSource = { username: string; }; }; - FILE_UPLOAD: { - type: 'FILE_UPLOAD'; - payload: { - socketId: string; - file: BinaryFileDataWithUrl; - }; - }; - FILE_REQUEST: { - type: 'FILE_REQUEST'; - payload: { - socketId: string; - fileIds: string[]; - }; - }; }; export type SocketUpdateData = SocketUpdateDataSource[keyof SocketUpdateDataSource] & { diff --git a/src/domain/common/whiteboard/excalidraw/collab/excalidrawAppConstants.ts b/src/domain/common/whiteboard/excalidraw/collab/excalidrawAppConstants.ts index fe8d2027a6..baf115a1f6 100644 --- a/src/domain/common/whiteboard/excalidraw/collab/excalidrawAppConstants.ts +++ b/src/domain/common/whiteboard/excalidraw/collab/excalidrawAppConstants.ts @@ -58,7 +58,10 @@ export const WS_EVENTS = { export enum WS_SCENE_EVENT_TYPES { INIT = 'SCENE_INIT', - UPDATE = 'SCENE_UPDATE', + SCENE_UPDATE = 'SCENE_UPDATE', + MOUSE_LOCATION = 'MOUSE_LOCATION', + IDLE_STATUS = 'IDLE_STATUS', + SAVED = 'SAVED', } export const ROOM_ID_BYTES = 10; diff --git a/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts b/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts index 516fae58f9..bde6e4b612 100644 --- a/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts +++ b/src/domain/common/whiteboard/excalidraw/useWhiteboardFilesManager.ts @@ -1,9 +1,13 @@ import { useMemo, useRef, useState } from 'react'; import { useUploadFileMutation } from '../../../../core/apollo/generated/apollo-hooks'; -import { BinaryFileData, DataURL, ExcalidrawImperativeAPI } from '@alkemio/excalidraw/types/types'; +import { BinaryFileData, BinaryFiles, DataURL, ExcalidrawImperativeAPI } from '@alkemio/excalidraw/types/types'; import { excalidrawFileMimeType, generateIdFromFile } from './collab/utils'; import Semaphore from 'ts-semaphore'; +export type BinaryFileDataWithUrl = BinaryFileData & { url: string }; +export type BinaryFileDataWithOptionalUrl = BinaryFileData & { url?: string }; +export type BinaryFilesWithUrl = Record; + const isValidDataURL = (url: string) => url.match(/^(data:)([\w/+-]*)(;charset=[\w-]+|;base64){0,1},[A-Za-z0-9+/=]+$/gi) !== null; @@ -49,9 +53,6 @@ const fetchFileToDataURL = async (url: string): Promise => { return blobToDataURL(blob); }; -export type BinaryFileDataWithUrl = BinaryFileData & { url: string }; -export type BinaryFileDataWithOptionalUrl = BinaryFileData & { url?: string }; - interface Props { storageBucketId?: string; // FilesManagers without storageBucketId will throw an exception on file upload excalidrawAPI: ExcalidrawImperativeAPI | null; @@ -66,6 +67,7 @@ interface WhiteboardWithFiles { export interface WhiteboardFilesManager { addNewFile: (file: File) => Promise; loadFiles: (data: WhiteboardWithFiles) => Promise; + getUploadedFiles: (filesInExcalidraw: BinaryFiles) => Promise; pushFilesToExcalidraw: () => Promise; convertLocalFilesToRemoteInWhiteboard: (whiteboard: W) => Promise; convertLocalFileToRemote: (file: BinaryFileData & { url?: string }) => Promise; @@ -194,7 +196,7 @@ const useWhiteboardFilesManager = ({ const pendingFileIds = Object.keys(files).filter(fileId => !files[fileId]?.dataURL); log('I need to download these files', pendingFileIds); - const newFiles: Record = {}; + const newFiles: BinaryFilesWithUrl = {}; setDownloadingFiles(true); @@ -222,6 +224,32 @@ const useWhiteboardFilesManager = ({ } }; + /** + * Returns all the files uploaded to the fileStore. + * Argument `files` should be the files that are currently in the whiteboard, can be obtained from Excalidraw's API. + * if any of the passed files is not in the fileStore, it will be uploaded to the storageBucket. + * + * @returns {BinaryFilesWithUrl} - The files with their URLs in the storage bucket. + * Property `dataURL` can contain the base64 data of the file if it was coming in the parameter `files`. + */ + const getUploadedFiles = async (files: BinaryFiles): Promise => { + const result: BinaryFilesWithUrl = {}; + if (!files) { + return result; + } + for (const id of Object.keys(files)) { + if (fileStore.current[id]) { + result[id] = fileStore.current[id]; + } else { + const file = await convertLocalFileToRemote(files[id]); + if (file) { + result[id] = file; + } + } + } + return result; + }; + /** * Injects into Excalidraw all the files in our fileStore. * Excalidraw will filter later if any of those files was deleted. @@ -315,6 +343,7 @@ const useWhiteboardFilesManager = ({ () => ({ addNewFile, loadFiles, // Load external files into Excalidraw + getUploadedFiles, pushFilesToExcalidraw, convertLocalFileToRemote, convertLocalFilesToRemoteInWhiteboard, From 89b542a40739f0e64bf941221dc6f90921d63801 Mon Sep 17 00:00:00 2001 From: Aleksandar Stojanovic Date: Tue, 20 Feb 2024 14:42:31 +0100 Subject: [PATCH 26/27] Opportunity membership updates (#5589) * Send message to opporunity leads/admins. * Add opportunity application button to dashboard. * Fix translation key. --- src/core/apollo/generated/apollo-hooks.ts | 50 +++++++++--------- src/core/apollo/generated/graphql-schema.ts | 52 +++++++++---------- src/core/i18n/en/translation.en.json | 6 +-- .../OpportunityApplicationButton.tsx | 22 ++++++-- .../OpportunityApplicationButtonContainer.tsx | 15 ++++-- ...yUserPrivilegesWithParentCommunity.graphql | 50 +++++++++--------- .../pages/OpportunityDashboardPage.tsx | 29 +++++++++++ 7 files changed, 138 insertions(+), 86 deletions(-) diff --git a/src/core/apollo/generated/apollo-hooks.ts b/src/core/apollo/generated/apollo-hooks.ts index b7fbaab828..02a01aba51 100644 --- a/src/core/apollo/generated/apollo-hooks.ts +++ b/src/core/apollo/generated/apollo-hooks.ts @@ -11096,23 +11096,23 @@ export const CommunityUserPrivilegesWithParentCommunityDocument = gql` id myPrivileges } - leadUsers: usersInRole(role: LEAD) { + } + challenge(ID: $challengeNameId) @include(if: $includeChallenge) { + id + authorization { id - profile { + myPrivileges + } + community { + id + myMembershipStatus + authorization { id - displayName - avatar: visual(type: AVATAR) { - ...VisualUri - } - location { - id - country - city - } + myPrivileges } } } - challenge(ID: $challengeNameId) @include(if: $includeChallenge) { + opportunity(ID: $opportunityNameId) @include(if: $includeOpportunity) { id authorization { id @@ -11140,20 +11140,20 @@ export const CommunityUserPrivilegesWithParentCommunityDocument = gql` } } } - } - } - opportunity(ID: $opportunityNameId) @include(if: $includeOpportunity) { - id - authorization { - id - myPrivileges - } - community { - id - myMembershipStatus - authorization { + adminUsers: usersInRole(role: ADMIN) { id - myPrivileges + profile { + id + displayName + avatar: visual(type: AVATAR) { + ...VisualUri + } + location { + id + country + city + } + } } } } diff --git a/src/core/apollo/generated/graphql-schema.ts b/src/core/apollo/generated/graphql-schema.ts index e908a5e80a..5f911b7bd0 100644 --- a/src/core/apollo/generated/graphql-schema.ts +++ b/src/core/apollo/generated/graphql-schema.ts @@ -17551,19 +17551,6 @@ export type CommunityUserPrivilegesWithParentCommunityQuery = { authorization?: | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } | undefined; - leadUsers?: - | Array<{ - __typename?: 'User'; - id: string; - profile: { - __typename?: 'Profile'; - id: string; - displayName: string; - avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined; - location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; - }; - }> - | undefined; } | undefined; challenge?: { @@ -17580,19 +17567,6 @@ export type CommunityUserPrivilegesWithParentCommunityQuery = { authorization?: | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } | undefined; - leadUsers?: - | Array<{ - __typename?: 'User'; - id: string; - profile: { - __typename?: 'Profile'; - id: string; - displayName: string; - avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined; - location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; - }; - }> - | undefined; } | undefined; }; @@ -17610,6 +17584,32 @@ export type CommunityUserPrivilegesWithParentCommunityQuery = { authorization?: | { __typename?: 'Authorization'; id: string; myPrivileges?: Array | undefined } | undefined; + leadUsers?: + | Array<{ + __typename?: 'User'; + id: string; + profile: { + __typename?: 'Profile'; + id: string; + displayName: string; + avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined; + location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; + }; + }> + | undefined; + adminUsers?: + | Array<{ + __typename?: 'User'; + id: string; + profile: { + __typename?: 'Profile'; + id: string; + displayName: string; + avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined; + location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined; + }; + }> + | undefined; } | undefined; }; diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json index 5f3943f43c..6c3f2e6428 100644 --- a/src/core/i18n/en/translation.en.json +++ b/src/core/i18n/en/translation.en.json @@ -1212,9 +1212,9 @@ "full": "To become a member of this Opportunity, first become a member of the parent Challenge", "short": "Please join parent Challenge first" }, - "contactChallengeLeads": { - "full": "Send a message to Challenge leads to become a member", - "short": "Send a message to Challenge leads to become a member" + "contactOpportunityLeads": { + "full": "Send a message to Opportunity leads to become a member", + "short": "Send a message to Opportunity leads to become a member" }, "goToSpace": "Go to $t(common.space)", "goToChallenge": "Go to $t(common.challenge)", diff --git a/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx b/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx index d4af3f567d..a009636f5c 100644 --- a/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx +++ b/src/domain/community/application/applicationButton/OpportunityApplicationButton.tsx @@ -12,7 +12,14 @@ export interface OpportunityApplicationButtonProps { isMember: boolean; isParentMember?: boolean; parentUrl?: string; - parentLeadUsers: { + leadUsers: { + id: string; + displayName?: string; + city?: string; + country?: string; + avatarUri?: string; + }[]; + adminUsers: { id: string; displayName?: string; city?: string; @@ -34,7 +41,8 @@ export const OpportunityApplicationButton = forwardRef< isMember = false, isParentMember = false, parentUrl, - parentLeadUsers, + leadUsers, + adminUsers, loading = false, component: Button = MuiButton, extended = false, @@ -46,8 +54,10 @@ export const OpportunityApplicationButton = forwardRef< dialogTitle: t('send-message-dialog.direct-message-title'), }); + const contactUsers = leadUsers.length > 0 ? leadUsers : adminUsers; + const handleSendMessageToParentLeads = () => { - sendMessage('user', ...parentLeadUsers); + sendMessage('user', ...contactUsers); }; const renderApplicationButton = () => { @@ -89,6 +99,10 @@ export const OpportunityApplicationButton = forwardRef< ); } + if (contactUsers.length === 0) { + return null; + } + return ( ); }; diff --git a/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx b/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx index c030051f18..9afcc5073f 100644 --- a/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx +++ b/src/domain/community/application/containers/OpportunityApplicationButtonContainer.tsx @@ -47,8 +47,16 @@ export const OpportunityApplicationButtonContainer: FC ({ + const communityLeadUsers = _communityPrivileges?.space?.opportunity?.community?.leadUsers ?? []; + const communityAdminUsers = _communityPrivileges?.space?.opportunity?.community?.adminUsers ?? []; + const leadUsers = communityLeadUsers.map(user => ({ + id: user.id, + displayName: user.profile.displayName, + country: user.profile.location?.country, + city: user.profile.location?.city, + avatarUri: user.profile.avatar?.uri, + })); + const adminUsers = communityAdminUsers.map(user => ({ id: user.id, displayName: user.profile.displayName, country: user.profile.location?.country, @@ -62,7 +70,8 @@ export const OpportunityApplicationButtonContainer: FC = ({ dialog }) throw new Error('Must be within a Space route.'); } const shareUpdatesUrl = buildUpdatesUrl({ spaceNameId, challengeNameId, opportunityNameId }); + const hasExtendedApplicationButton = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm')); return ( @@ -45,6 +51,29 @@ const OpportunityDashboardPage: FC = ({ dialog }) {({ callouts, ...entities }, state) => ( <> + {({ applicationButtonProps, state: { loading } }) => { + if (loading || applicationButtonProps.isMember) { + return null; + } + + return ( + + + + ); + }} + + } welcome={ Date: Tue, 20 Feb 2024 15:45:57 +0200 Subject: [PATCH 27/27] Minor version bump --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d76efb0d7..d690b0a8d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@alkemio/client-web", - "version": "0.53.4", + "version": "0.53.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@alkemio/client-web", - "version": "0.53.4", + "version": "0.53.5", "license": "EUPL-1.2", "dependencies": { "@alkemio/excalidraw": "^0.17.0-alkemio-2", diff --git a/package.json b/package.json index c12acccea7..82f783c812 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@alkemio/client-web", - "version": "0.53.4", + "version": "0.53.5", "description": "Alkemio client, enabling users to interact with Challenges hosted on the Alkemio platform.", "author": "Alkemio Foundation", "repository": {