From fc5f17129871bd959d471aa77e4ee422ae596b78 Mon Sep 17 00:00:00 2001 From: Taea Vogel Date: Fri, 1 Aug 2025 10:59:33 -0400 Subject: [PATCH 01/91] initial UI work still WIP --- .../ServerOffer/ServerOffer.stories.tsx | 30 +++ .../components/ServerOffer/ServerOffer.tsx | 0 .../ServerOffer/ServerOfferComponent.tsx | 227 ++++++++++++++++++ .../ui/assets/icons/ServerBoxIcon.tsx | 13 + packages/desktop/src/renderer/theme.ts | 2 + 5 files changed, 272 insertions(+) create mode 100644 packages/desktop/src/renderer/components/ServerOffer/ServerOffer.stories.tsx create mode 100644 packages/desktop/src/renderer/components/ServerOffer/ServerOffer.tsx create mode 100644 packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx create mode 100644 packages/desktop/src/renderer/components/ui/assets/icons/ServerBoxIcon.tsx diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.stories.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.stories.tsx new file mode 100644 index 0000000000..5546651925 --- /dev/null +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.stories.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' + +import { withTheme } from '../../storybook/decorators' + +import { ServerOfferComponent, ServerOfferComponentProps } from './ServerOfferComponent' + +const Template: ComponentStory = args => { + return +} + +export const Component = Template.bind({}) + +const args: ServerOfferComponentProps = { + open: true, + handleClose: selection => { + // eslint-disable-next-line no-console + console.info('ServerOffer closed with selection:', selection) + }, +} + +Component.args = args + +const component: ComponentMeta = { + title: 'Components/ServerOffer', + decorators: [withTheme], + component: ServerOfferComponent, +} + +export default component diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx new file mode 100644 index 0000000000..6a3a47a9d6 --- /dev/null +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx @@ -0,0 +1,227 @@ +import React, { useCallback, useState } from 'react' +import { styled } from '@mui/material/styles' +import classNames from 'classnames' + +import Typography from '@mui/material/Typography' +import Grid from '@mui/material/Grid' +import Checkbox from '@mui/material/Checkbox' +import FormControlLabel from '@mui/material/FormControlLabel' +import Divider from '@mui/material/Divider' +import Chip from '@mui/material/Chip' + +import Modal from '../ui/Modal/Modal' +import Button from '@mui/material/Button' +import { createLogger } from '../../logger' + +import ServerBoxIcon from '../ui/assets/icons/ServerBoxIcon' + +const logger = createLogger('ServerOffer:component') + +const PREFIX = 'ServerOfferComponent-' +const classes = { + content: `${PREFIX}content`, + titleBar: `${PREFIX}titleBar`, + heading: `${PREFIX}heading`, + actionWrap: `${PREFIX}actionWrap`, + primaryActionButton: `${PREFIX}primaryActionButton`, + secondaryActionButton: `${PREFIX}secondaryActionButton`, + title: `${PREFIX}title`, + info: `${PREFIX}info`, + icon: `${PREFIX}icon`, + iconContainer: `${PREFIX}iconContainer`, + pill: `${PREFIX}pill`, + mutedAction: `${PREFIX}mutedAction`, + dividerWrap: `${PREFIX}dividerWrap`, +} + +const StyledGrid = styled(Grid)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + textAlign: 'center', + padding: '0px 32px', + gap: '8px', + + [`& .${classes.content}`]: { + width: '375px', + height: '100%', + padding: '0px 0px 0px 24px', + gap: '24px', + }, + + // should hold a back button, may not be relevant yet + [`& .${classes.titleBar}`]: { + width: '100%', + height: '60px', + }, + + [`& .${classes.heading}`]: { + marginTop: '16px', + marginBottom: '16px', + }, + + [`& .${classes.actionWrap}`]: { + gap: '16px', + textAlign: 'center', + padding: '0 24px', + }, + + [`& .${classes.primaryActionButton}`]: { + width: '174px', + backgroundColor: theme.palette.primary.main, + color: theme.palette.colors.white, + '&:hover': { + backgroundColor: theme.palette.colors.quietBlue, + }, + borderRadius: '16px', + height: '50px', + fontWeight: '400', + padding: '20px 12px', + }, + + [`& .${classes.secondaryActionButton}`]: { + width: '100px', + height: '16px', + fontWeight: '400', + backgroundColor: theme.palette.background.default, + '&:hover': { + backgroundColor: theme.palette.background.default, + }, + color: theme.palette.colors.gray50, + border: 'none', + boxShadow: 'none', + }, + + [`& .${classes.iconContainer}`]: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '64px', + height: '64px', + }, + [`& .${classes.icon}`]: { + width: 48, + height: 51, + }, + + [`& .${classes.title}`]: { + fontWeight: 500, + fontSize: '28px', + lineHeight: '34px', + }, + + [`& .${classes.pill}`]: { + fontSize: '14px', + lineHeight: '20px', + marginBottom: 16, + '& .MuiChip-root': { + height: 24, + borderRadius: 8, + fontWeight: 500, + fontSize: 14, + backgroundColor: theme.palette.colors.lightPurple, + color: theme.palette.colors.primary, + borderStyle: 'solid', + borderWidth: 1, + borderColor: '#ECDCF5', + }, + }, + + [`& .${classes.info}`]: { + maxWidth: 520, + lineHeight: '20px', + fontSize: '14px', + fontWeight: 400, + color: theme.palette.colors.contrastText, + }, + + [`& .${classes.dividerWrap}`]: { + // margin: '28px auto 6px auto', + }, +})) + +export interface ServerOfferComponentProps { + open: boolean + handleClose: (selection: boolean) => void +} + +export const ServerOfferComponent: React.FC = ({ open, handleClose }) => { + const [dontShowAgain, setDontShowAgain] = useState(false) + + const persistPreference = useCallback(() => { + try { + if (dontShowAgain) { + localStorage.setItem('quiet.serverOffer.dismissed', 'true') + } + } catch (e) { + logger.warn('Unable to persist server-offer preference', e) + } + }, [dontShowAgain]) + + const onChoose = useCallback( + (useServer: boolean) => { + persistPreference() + handleClose(useServer) + }, + [persistPreference, handleClose] + ) + + return ( + +
+ + + + + + + Want a server? + +
+ +
+ + + Messages are still end-to-end encrypted, joining will be faster, and Quiet will work much better on + iPhones. + +
+
+ + + + + + + + + + + + + + setDontShowAgain(e.target.checked)} /> + } + label='Don’t show this again' + /> + +
+
+ ) +} diff --git a/packages/desktop/src/renderer/components/ui/assets/icons/ServerBoxIcon.tsx b/packages/desktop/src/renderer/components/ui/assets/icons/ServerBoxIcon.tsx new file mode 100644 index 0000000000..03382d6712 --- /dev/null +++ b/packages/desktop/src/renderer/components/ui/assets/icons/ServerBoxIcon.tsx @@ -0,0 +1,13 @@ +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon' +import React from 'react' + +export default function ServerBoxIcon(props: SvgIconProps) { + return ( + + + + ) +} diff --git a/packages/desktop/src/renderer/theme.ts b/packages/desktop/src/renderer/theme.ts index 90aa7e36f9..fe228cc090 100644 --- a/packages/desktop/src/renderer/theme.ts +++ b/packages/desktop/src/renderer/theme.ts @@ -96,6 +96,7 @@ const lightTheme = createTheme({ purple: '#521C74', // To be replaced with theme.palette.primary.main quietBlue: '#521c74', // To be replaced with theme.palette.primary.main darkPurple: '#4d1a6d', // To be replaced with theme.palette.primary.dark? + lightPurple: '#F9EFFF', lushSky: '#67BFD3', lushSky12: '#EDF7FA', linkBlue: '#1B6FEC', // Used in a variety of places - likely wants to be split / consolidated @@ -342,6 +343,7 @@ const darkTheme = createTheme({ purple: '#521C74', // To be replaced with theme.palette.primary.main quietBlue: '#521c74', // To be replaced with theme.palette.primary.main darkPurple: '#4d1a6d', // To be replaced with theme.palette.primary.dark? + lightPurple: '#F9EFFF', lushSky: '#67BFD3', lushSky12: '#EDF7FA', linkBlue: '#59c0d5', // Used in a variety of places - likely wants to be split / consolidated From 617285094060733a9d2b3938bea1853a4386732e Mon Sep 17 00:00:00 2001 From: taea Date: Tue, 19 Aug 2025 11:38:05 -0400 Subject: [PATCH 02/91] fix spacing --- .../ServerOffer/ServerOfferComponent.tsx | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx index 6a3a47a9d6..e4faa9a8e9 100644 --- a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx @@ -19,10 +19,12 @@ const logger = createLogger('ServerOffer:component') const PREFIX = 'ServerOfferComponent-' const classes = { - content: `${PREFIX}content`, titleBar: `${PREFIX}titleBar`, + contentWrap: `${PREFIX}contentWrap`, + actionsWrap: `${PREFIX}actions`, + textWrap: `${PREFIX}text`, + dividerWrap: `${PREFIX}dividerWrap`, heading: `${PREFIX}heading`, - actionWrap: `${PREFIX}actionWrap`, primaryActionButton: `${PREFIX}primaryActionButton`, secondaryActionButton: `${PREFIX}secondaryActionButton`, title: `${PREFIX}title`, @@ -31,37 +33,36 @@ const classes = { iconContainer: `${PREFIX}iconContainer`, pill: `${PREFIX}pill`, mutedAction: `${PREFIX}mutedAction`, - dividerWrap: `${PREFIX}dividerWrap`, } const StyledGrid = styled(Grid)(({ theme }) => ({ backgroundColor: theme.palette.background.default, textAlign: 'center', - padding: '0px 32px', - gap: '8px', - [`& .${classes.content}`]: { - width: '375px', + [`&.${classes.contentWrap}`]: { + width: '100%', height: '100%', - padding: '0px 0px 0px 24px', gap: '24px', + padding: '0px 32px', }, - // should hold a back button, may not be relevant yet - [`& .${classes.titleBar}`]: { - width: '100%', - height: '60px', + [`& .${classes.actionsWrap}`]: { + gap: '16px', }, - [`& .${classes.heading}`]: { - marginTop: '16px', - marginBottom: '16px', + [`& .${classes.textWrap}`]: { + padding: '0px 24px', + gap: '16px', }, - [`& .${classes.actionWrap}`]: { - gap: '16px', - textAlign: 'center', - padding: '0 24px', + [`& .${classes.dividerWrap}`]: { + // margin: '28px auto 6px auto', + }, + + // should hold a back button, may not be relevant yet + [`& .${classes.titleBar}`]: { + width: '100%', + height: '60px', }, [`& .${classes.primaryActionButton}`]: { @@ -111,7 +112,7 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ [`& .${classes.pill}`]: { fontSize: '14px', lineHeight: '20px', - marginBottom: 16, + '& .MuiChip-root': { height: 24, borderRadius: 8, @@ -132,10 +133,6 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ fontWeight: 400, color: theme.palette.colors.contrastText, }, - - [`& .${classes.dividerWrap}`]: { - // margin: '28px auto 6px auto', - }, })) export interface ServerOfferComponentProps { @@ -165,27 +162,26 @@ export const ServerOfferComponent: React.FC = ({ open ) return ( - -
- + + - - + + Want a server? - -
- -
- +
+ + + + Messages are still end-to-end encrypted, joining will be faster, and Quiet will work much better on iPhones.
- + - + setDontShowAgain(e.target.checked)} /> } From 197210d604921e649c73130e244f20e01f2e1252 Mon Sep 17 00:00:00 2001 From: taea Date: Tue, 19 Aug 2025 16:33:35 -0400 Subject: [PATCH 04/91] handle dark mode better --- .../renderer/components/ServerOffer/ServerOfferComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx index 9c008d0fbd..38d80f94d0 100644 --- a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx @@ -127,8 +127,8 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ height: 24, borderRadius: 4, backgroundColor: theme.palette.colors.lightPurple, - color: theme.palette.colors.primary, - border: '1px solid #ECDCF5', + color: theme.palette.primary.main, + border: `1px solid ${theme.palette.colors.border03}`, display: 'flex', alignItems: 'center', justifyContent: 'center', From 87009404ad812f50225a96efd15138dcd74b293b Mon Sep 17 00:00:00 2001 From: taea Date: Tue, 19 Aug 2025 17:04:19 -0400 Subject: [PATCH 05/91] rely on theme more --- .../ServerOffer/ServerOfferComponent.tsx | 78 +++++-------------- 1 file changed, 21 insertions(+), 57 deletions(-) diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx index 38d80f94d0..b20b99c5bc 100644 --- a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx @@ -18,20 +18,17 @@ const logger = createLogger('ServerOffer:component') const PREFIX = 'ServerOfferComponent-' const classes = { - titleBar: `${PREFIX}titleBar`, contentWrap: `${PREFIX}contentWrap`, actionsWrap: `${PREFIX}actions`, textWrap: `${PREFIX}text`, dividerWrap: `${PREFIX}dividerWrap`, - heading: `${PREFIX}heading`, - useServerButton: `${PREFIX}useServerButton`, - notNowButton: `${PREFIX}notNowButton`, - title: `${PREFIX}title`, info: `${PREFIX}info`, icon: `${PREFIX}icon`, iconContainer: `${PREFIX}iconContainer`, pill: `${PREFIX}pill`, mutedAction: `${PREFIX}mutedAction`, + useServerButton: `${PREFIX}useServerButton`, + notNowButton: `${PREFIX}notNowButton`, } const StyledGrid = styled(Grid)(({ theme }) => ({ @@ -42,19 +39,19 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ [`&.${classes.contentWrap}`]: { width: '100%', height: '100%', - gap: '24px', - padding: '0px 32px', + gap: theme.spacing(3), + padding: theme.spacing(0, 4), alignItems: 'center', justifyContent: 'center', }, [`& .${classes.actionsWrap}`]: { - gap: '16px', + gap: theme.spacing(2), }, [`& .${classes.textWrap}`]: { - padding: '0px 24px', - gap: '16px', + padding: theme.spacing(0, 3), + gap: theme.spacing(2), }, [`& .${classes.dividerWrap}`]: { @@ -64,40 +61,17 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ [`& .${classes.useServerButton}`]: { height: '50px', borderRadius: '16px', - padding: '12px 20px', + padding: theme.spacing(1.5, 2.5), width: 'auto', - backgroundColor: theme.palette.primary.main, - color: theme.palette.colors.white, - '&:hover': { - backgroundColor: theme.palette.colors.quietBlue, - }, - fontWeight: 400, - fontSize: '16px', - lineHeight: '26px', - textAlign: 'center', - textTransform: 'none', + ...theme.typography.body1, }, [`& .${classes.notNowButton}`]: { - height: '16px', minWidth: '62px', padding: 0, borderRadius: '4px', - - fontWeight: 400, - fontSize: '16px', - lineHeight: '16px', - letterSpacing: 0, - textTransform: 'none', - - backgroundColor: 'transparent', - color: theme.palette.colors.gray50, - border: 'none', - boxShadow: 'none', - '&:hover': { - backgroundColor: 'transparent', - boxShadow: 'none', - }, + ...theme.typography.body2, + color: theme.palette.text.secondary, }, [`& .${classes.iconContainer}`]: { @@ -112,16 +86,8 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ height: 51, }, - [`& .${classes.title}`]: { - fontWeight: 500, - fontSize: '28px', - lineHeight: '34px', - }, - [`& .${classes.pill}`]: { - fontSize: '14px', - lineHeight: '20px', - fontWeight: 500, + ...theme.typography.subtitle2, '& .MuiChip-root': { height: 24, @@ -138,20 +104,18 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ fontWeight: 500, fontSize: '14px', lineHeight: '20px', - padding: '2px 8px', + padding: theme.spacing(0.5, 1), }, }, [`& .${classes.info}`]: { maxWidth: 520, - lineHeight: '20px', - fontSize: '14px', - fontWeight: 400, - color: theme.palette.colors.contrastText, + color: theme.palette.text.secondary, + ...theme.typography.body2, }, [`& .${classes.mutedAction}`]: { '& .MuiFormControlLabel-label': { - fontSize: '14px', + ...theme.typography.body2, }, }, })) @@ -182,13 +146,13 @@ export const ServerOfferComponent: React.FC = ({ open - Want a server? + Want a server? - + Messages are still end-to-end encrypted, joining will be faster, and Quiet will work much better on iPhones. @@ -198,21 +162,21 @@ export const ServerOfferComponent: React.FC = ({ open From 2f92d14fcad818c16d4baee5661292289d70190f Mon Sep 17 00:00:00 2001 From: taea Date: Tue, 19 Aug 2025 17:06:56 -0400 Subject: [PATCH 06/91] bring back some of the specific font params --- .../ServerOffer/ServerOfferComponent.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx index b20b99c5bc..030af3bc99 100644 --- a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx @@ -63,14 +63,26 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ borderRadius: '16px', padding: theme.spacing(1.5, 2.5), width: 'auto', + fontWeight: 400, + fontSize: '16px', + lineHeight: '26px', + textAlign: 'center', + textTransform: 'none', ...theme.typography.body1, }, [`& .${classes.notNowButton}`]: { + height: '16px', minWidth: '62px', padding: 0, borderRadius: '4px', - ...theme.typography.body2, + + fontWeight: 400, + fontSize: '16px', + lineHeight: '16px', + letterSpacing: 0, + textTransform: 'none', + color: theme.palette.text.secondary, }, @@ -87,7 +99,9 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ }, [`& .${classes.pill}`]: { - ...theme.typography.subtitle2, + fontSize: '14px', + lineHeight: '20px', + fontWeight: 500, '& .MuiChip-root': { height: 24, @@ -104,7 +118,7 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ fontWeight: 500, fontSize: '14px', lineHeight: '20px', - padding: theme.spacing(0.5, 1), + padding: '2px 8px', }, }, From 5ae7fb4c438e5ccacd874e973587b59a5df81e3e Mon Sep 17 00:00:00 2001 From: taea Date: Tue, 19 Aug 2025 17:14:48 -0400 Subject: [PATCH 07/91] more theme reliance --- .../ServerOffer/ServerOfferComponent.tsx | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx index 030af3bc99..d5b4ef62a2 100644 --- a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.tsx @@ -63,26 +63,14 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ borderRadius: '16px', padding: theme.spacing(1.5, 2.5), width: 'auto', - fontWeight: 400, - fontSize: '16px', - lineHeight: '26px', - textAlign: 'center', - textTransform: 'none', ...theme.typography.body1, }, [`& .${classes.notNowButton}`]: { - height: '16px', minWidth: '62px', padding: 0, borderRadius: '4px', - - fontWeight: 400, - fontSize: '16px', - lineHeight: '16px', - letterSpacing: 0, - textTransform: 'none', - + ...theme.typography.body1, color: theme.palette.text.secondary, }, @@ -99,9 +87,7 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ }, [`& .${classes.pill}`]: { - fontSize: '14px', - lineHeight: '20px', - fontWeight: 500, + ...theme.typography.subtitle2, '& .MuiChip-root': { height: 24, @@ -118,7 +104,7 @@ const StyledGrid = styled(Grid)(({ theme }) => ({ fontWeight: 500, fontSize: '14px', lineHeight: '20px', - padding: '2px 8px', + padding: theme.spacing(0.5, 1), }, }, From bf6ce81d46955b7f549a288f95521f34a4a74366 Mon Sep 17 00:00:00 2001 From: taea Date: Wed, 20 Aug 2025 19:12:57 -0400 Subject: [PATCH 08/91] pass qss input from frontend to backend --- .../connections-manager.service.ts | 2 +- .../CreateCommunity/CreateCommunity.test.tsx | 94 +++++++++++++++++++ .../CreateCommunity/CreateCommunity.tsx | 56 ++++++++--- .../CreateCommunity.screen.tsx | 1 + .../createCommunity/createCommunity.saga.ts | 3 + packages/types/src/community.ts | 2 + 6 files changed, 142 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.ts index 8b2559f1d9..38d726a6be 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -407,7 +407,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI psk: Libp2pService.generateLibp2pPSK().psk, ownership: CommunityOwnership.Owner, teamId: sigchain.team!.id, - qssEnabled: this.qssAllowed, + qssEnabled: this.qssAllowed && payload.useServer, qssEndpoint: this.qssEndpoint, } diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx index 65c40ddd51..9792dac90d 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.test.tsx @@ -300,4 +300,98 @@ describe('Create community', () => { expect(createCommunityInput).toHaveAttribute('type', 'text') }) + + describe('ServerOfferComponent flow', () => { + const OLD_ENV = process.env + beforeEach(() => { + jest.resetModules() + process.env = { ...OLD_ENV, QSS_ALLOWED: 'true' } + }) + afterEach(() => { + process.env = OLD_ENV + }) + + it('shows ServerOfferComponent when QSS_ALLOWED is true and user submits community name', async () => { + const { store } = await prepareStore({ + [StoreKeys.Socket]: { + ...new SocketState(), + isConnected: true, + }, + [StoreKeys.Modals]: { + ...new ModalsInitialState(), + [ModalName.createCommunityModal]: { open: true }, + }, + }) + + renderComponent(, store) + const input = screen.getByPlaceholderText('Community name') + const button = screen.getByText('Continue') + await userEvent.type(input, 'rockets') + await userEvent.click(button) + + // ServerOffer modal should appear + expect(await screen.findByTestId('ServerOffer-UseQuietServer')).toBeVisible() + expect(screen.getByTestId('ServerOffer-NotNow')).toBeVisible() + }) + + it('dispatches createCommunity with useServer=true when user clicks "Use Quiet’s server"', async () => { + const { store } = await prepareStore({ + [StoreKeys.Socket]: { + ...new SocketState(), + isConnected: true, + }, + [StoreKeys.Modals]: { + ...new ModalsInitialState(), + [ModalName.createCommunityModal]: { open: true }, + }, + }) + jest.spyOn(store, 'dispatch') + + renderComponent(, store) + const input = screen.getByPlaceholderText('Community name') + const button = screen.getByText('Continue') + await userEvent.type(input, 'rockets') + await userEvent.click(button) + + const useServerBtn = await screen.findByTestId('ServerOffer-UseQuietServer') + await userEvent.click(useServerBtn) + + expect(store.dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: expect.stringContaining('createCommunity'), + payload: expect.objectContaining({ name: 'rockets', useServer: true }), + }) + ) + }) + + it('dispatches createCommunity with useServer=false when user clicks "Not now"', async () => { + const { store } = await prepareStore({ + [StoreKeys.Socket]: { + ...new SocketState(), + isConnected: true, + }, + [StoreKeys.Modals]: { + ...new ModalsInitialState(), + [ModalName.createCommunityModal]: { open: true }, + }, + }) + jest.spyOn(store, 'dispatch') + + renderComponent(, store) + const input = screen.getByPlaceholderText('Community name') + const button = screen.getByText('Continue') + await userEvent.type(input, 'rockets') + await userEvent.click(button) + + const notNowBtn = await screen.findByTestId('ServerOffer-NotNow') + await userEvent.click(notNowBtn) + + expect(store.dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: expect.stringContaining('createCommunity'), + payload: expect.objectContaining({ name: 'rockets', useServer: false }), + }) + ) + }) + }) }) diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx index d0a5826d7d..db56fcd80a 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/CreateCommunity/CreateCommunity.tsx @@ -1,9 +1,10 @@ -import React, { useEffect } from 'react' +import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { socketSelectors } from '../../../sagas/socket/socket.selectors' import { communities } from '@quiet/state-manager' import { CommunityOwnership, CreateCommunityPayload } from '@quiet/types' import PerformCommunityActionComponent from '../PerformCommunityActionComponent' +import { ServerOfferComponent } from '../../ServerOffer/ServerOfferComponent' import { ModalName } from '../../../sagas/modals/modals.types' import { useModal } from '../../../containers/hooks' import { createLogger } from '../../../logger' @@ -19,21 +20,43 @@ const CreateCommunity = () => { const createCommunityModal = useModal(ModalName.createCommunityModal) const joinCommunityModal = useModal(ModalName.joinCommunityModal) + const [pendingCommunityName, setPendingCommunityName] = useState(null) + const [showServerOffer, setShowServerOffer] = useState(false) useEffect(() => { + // Close create community modal if community is created if (currentCommunity && createCommunityModal.open) { createCommunityModal.handleClose() } + // If community is created, also close server offer + if (currentCommunity && showServerOffer) { + setShowServerOffer(false) + setPendingCommunityName(null) + } }, [currentCommunity]) const handleCommunityAction = (name: string) => { - const payload: CreateCommunityPayload = { - name: name, - } if (currentCommunity?.name === name) { return } - dispatch(communities.actions.createCommunity(payload)) + setPendingCommunityName(name) + if (process.env.QSS_ALLOWED === 'true') { + setShowServerOffer(true) + } else { + dispatch(communities.actions.createCommunity({ name, useServer: false })) + } + } + + const handleServerOfferClose = (useServer: boolean) => { + setShowServerOffer(false) + if (pendingCommunityName) { + const payload: CreateCommunityPayload = { + name: pendingCommunityName, + useServer, + } + logger.info('Creating community with payload:', payload) + dispatch(communities.actions.createCommunity(payload)) + } } // From 'You can join a community instead' link @@ -46,16 +69,19 @@ const CreateCommunity = () => { } return ( - + <> + + {showServerOffer && } + ) } diff --git a/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx b/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx index 4686e1a649..af55909cbc 100644 --- a/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx +++ b/packages/mobile/src/screens/CreateCommunity/CreateCommunity.screen.tsx @@ -21,6 +21,7 @@ export const CreateCommunityScreen: FC = () => { (name: string) => { const payload: CreateCommunityPayload = { name, + useServer: false, } dispatch(communities.actions.createCommunity(payload)) dispatch( diff --git a/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts b/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts index 4f5c43220f..1ae08c5ebe 100644 --- a/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts +++ b/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts @@ -56,6 +56,7 @@ export function* createCommunitySaga( id: communityId, name: action.payload.name, username, + useServer: action.payload.useServer, } const createCommunityResponse: ResponseCreateCommunityPayload = yield* apply( @@ -66,6 +67,8 @@ export function* createCommunitySaga( if (!createCommunityResponse || !createCommunityResponse.community || !createCommunityResponse.identity) { logger.error('Failed to create community - invalid response from backend') + yield* put(communitiesActions.setCurrentCommunity('')) + yield* put(communitiesActions.deleteCommunity(communityId)) return } diff --git a/packages/types/src/community.ts b/packages/types/src/community.ts index 0691ef491a..17f6264eb5 100644 --- a/packages/types/src/community.ts +++ b/packages/types/src/community.ts @@ -44,6 +44,7 @@ export enum CommunityOwnership { export interface CreateCommunityPayload { name: string + useServer: boolean } export interface JoinCommunityPayload { @@ -72,6 +73,7 @@ export interface InitCommunityPayload { ownerOrbitDbIdentity?: string inviteData?: InvitationData | null username: string + useServer?: boolean } export interface ResponseLaunchCommunityPayload { From 6285bb5cc7cc6e6d95ef5cc2593aa2b810824db3 Mon Sep 17 00:00:00 2001 From: taea Date: Wed, 20 Aug 2025 20:01:51 -0400 Subject: [PATCH 09/91] add test --- .../components/ServerOffer/ServerOffer.tsx | 0 .../ServerOffer/ServerOfferComponent.test.tsx | 233 ++++++++++++++++++ 2 files changed, 233 insertions(+) delete mode 100644 packages/desktop/src/renderer/components/ServerOffer/ServerOffer.tsx create mode 100644 packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.test.tsx diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOffer.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.test.tsx b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.test.tsx new file mode 100644 index 0000000000..114d4a3bd7 --- /dev/null +++ b/packages/desktop/src/renderer/components/ServerOffer/ServerOfferComponent.test.tsx @@ -0,0 +1,233 @@ +import React from 'react' +import '@testing-library/jest-dom/extend-expect' +import { screen } from '@testing-library/dom' +import userEvent from '@testing-library/user-event' +import { ServerOfferComponent } from './ServerOfferComponent' +import { renderComponent } from '../../testUtils' + +describe('ServerOfferComponent', () => { + it('renders modal and handles actions', async () => { + const handleClose = jest.fn() + const { baseElement } = renderComponent() + + // Modal content + expect(screen.getByText('Want a server?')).toBeVisible() + expect(screen.getByText('It’s free!')).toBeVisible() + expect(screen.getByText(/Messages are still end-to-end encrypted/)).toBeVisible() + + expect(baseElement).toMatchInlineSnapshot(` + +