diff --git a/web/components/button.react.js b/web/components/button.react.js index 7fceed5aa..4682e400e 100644 --- a/web/components/button.react.js +++ b/web/components/button.react.js @@ -1,77 +1,78 @@ // @flow import classnames from 'classnames'; import * as React from 'react'; import css from './button.css'; export type ButtonVariant = 'plain' | 'filled' | 'outline' | 'text'; export type ButtonColor = { +backgroundColor?: string, +color?: string, + +borderColor?: string, }; export const buttonThemes: { [string]: ButtonColor } = { standard: { backgroundColor: 'var(--btn-bg-filled)', }, danger: { backgroundColor: 'var(--btn-bg-danger)', }, success: { backgroundColor: 'var(--btn-bg-success)', }, outline: { backgroundColor: 'var(--btn-bg-outline)', }, }; export type ButtonProps = { ...React.ElementConfig<'button'>, +variant?: ButtonVariant, +buttonColor?: ButtonColor, }; function Button(props: ButtonProps): React.Node { const { variant = 'plain', buttonColor, children, type = 'button', className, ...buttonProps } = props; const btnCls = classnames( { [css.plain]: true, [css.btn]: variant === 'filled' || variant === 'outline', [css[variant]]: true, }, className, ); let style = {}; if (buttonColor) { style = buttonColor; } else if (variant === 'outline') { style = buttonThemes.outline; } else if (variant === 'filled') { style = buttonThemes.standard; } const wrappedChildren = React.Children.map(children, child => { if (typeof child === 'string' || typeof child === 'number') { return {child}; } return child; }); return ( ); } export default Button; diff --git a/web/invite-links/manage-invite-links-modal.react.js b/web/invite-links/manage-invite-links-modal.react.js index b3981ecee..8e968cfb4 100644 --- a/web/invite-links/manage-invite-links-modal.react.js +++ b/web/invite-links/manage-invite-links-modal.react.js @@ -1,64 +1,80 @@ // @flow import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import type { InviteLink } from 'lib/types/link-types.js'; +import DisableLinkModal from './manage/disable-link-modal.react.js'; import EditLinkModal from './manage/edit-link-modal.react.js'; import EmptyLinkContent from './manage/empty-link-content.react.js'; import ExistingLinkContent from './manage/existing-link-content.react.js'; import css from './manage/manage-invite-links-modal.css'; import Modal from '../modals/modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = { +communityID: string, }; function ManageInviteLinksModal(props: Props): React.Node { const { communityID } = props; const inviteLink: ?InviteLink = useSelector(primaryInviteLinksSelector)[ communityID ]; const community = useSelector( state => threadInfoSelector(state)[communityID], ); const { popModal } = useModalContext(); const [modalStage, setModalStage] = React.useState('view'); const enterEditMode = React.useCallback(() => setModalStage('edit'), []); const enterViewMode = React.useCallback(() => setModalStage('view'), []); + const enterDisableMode = React.useCallback( + () => setModalStage('disable'), + [], + ); if (modalStage === 'edit') { return ( ); } + if (modalStage === 'disable' && inviteLink) { + return ( + + ); + } + let content; if (inviteLink) { content = ( ); } else { content = ; } return (
{content}
); } export default ManageInviteLinksModal; diff --git a/web/invite-links/manage/disable-link-modal.react.js b/web/invite-links/manage/disable-link-modal.react.js new file mode 100644 index 000000000..af67051f0 --- /dev/null +++ b/web/invite-links/manage/disable-link-modal.react.js @@ -0,0 +1,57 @@ +// @flow + +import classnames from 'classnames'; +import * as React from 'react'; + +import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { useInviteLinksActions } from 'lib/hooks/invite-links.js'; +import type { InviteLink } from 'lib/types/link-types.js'; + +import css from './manage-invite-links-modal.css'; +import Button, { buttonThemes } from '../../components/button.react.js'; +import Modal from '../../modals/modal.react.js'; + +type Props = { + +inviteLink: InviteLink, + +enterEditMode: () => mixed, + +enterViewMode: () => mixed, +}; + +function DisableLinkModal(props: Props): React.Node { + const { inviteLink, enterEditMode, enterViewMode } = props; + const { popModal } = useModalContext(); + const { isLoading, disableInviteLink } = useInviteLinksActions( + inviteLink.communityID, + inviteLink, + ); + const disableLink = React.useCallback(async () => { + await disableInviteLink(); + enterViewMode(); + }, [disableInviteLink, enterViewMode]); + + return ( + +
+
+

Are you sure you want to disable your public link?

+

Other communities will be able to claim the same URL.

+
+
+ + +
+
+
+ ); +} + +export default DisableLinkModal; diff --git a/web/invite-links/manage/edit-link-modal.react.js b/web/invite-links/manage/edit-link-modal.react.js index ad76ec341..cdd94e76f 100644 --- a/web/invite-links/manage/edit-link-modal.react.js +++ b/web/invite-links/manage/edit-link-modal.react.js @@ -1,85 +1,110 @@ // @flow import classnames from 'classnames'; import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { inviteLinkUrl } from 'lib/facts/links.js'; import { useInviteLinksActions } from 'lib/hooks/invite-links.js'; import type { InviteLink } from 'lib/types/link-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import css from './manage-invite-links-modal.css'; import Button from '../../components/button.react.js'; import Input from '../../modals/input.react.js'; import Modal from '../../modals/modal.react.js'; type Props = { +inviteLink: ?InviteLink, +enterViewMode: () => mixed, + +enterDisableMode: () => mixed, +community: ThreadInfo, }; +const disableButtonColor = { + color: 'var(--error-primary)', + borderColor: 'var(--error-primary)', +}; function EditLinkModal(props: Props): React.Node { - const { inviteLink, enterViewMode, community } = props; + const { inviteLink, enterViewMode, enterDisableMode, community } = props; const { popModal } = useModalContext(); const { error, isLoading, name, setName, createOrUpdateInviteLink } = useInviteLinksActions(community.id, inviteLink); const onChangeName = React.useCallback( (event: SyntheticEvent) => { setName(event.currentTarget.value); }, [setName], ); let errorComponent = null; if (error) { errorComponent =
{error}
; } + let disableLinkComponent = null; + if (inviteLink) { + disableLinkComponent = ( + <> +
+
+
You may also disable the community public link
+ +
+ + ); + } + return (

Invite links make it easy for your friends to join your community. Anybody who knows your community’s invite link will be able to join it.

Note that if you change your public link’s URL, other communities will be able to claim the old URL.


Invite URL
{inviteLinkUrl('')}
{errorComponent}
+ {disableLinkComponent}
); } export default EditLinkModal; diff --git a/web/invite-links/manage/manage-invite-links-modal.css b/web/invite-links/manage/manage-invite-links-modal.css index e6707f145..d06a8c92f 100644 --- a/web/invite-links/manage/manage-invite-links-modal.css +++ b/web/invite-links/manage/manage-invite-links-modal.css @@ -1,83 +1,97 @@ .container { font-size: var(--m-font-16); padding: 24px 32px; } .sectionHeaderRow { display: flex; flex-direction: row; justify-content: space-between; align-items: center; } .sectionHeaderText { color: var(--fg); margin-bottom: 16px; } .description { font-size: var(--s-font-14); color: var(--fg); margin-top: 8px; } .inlineButton { display: inline; font-size: var(--s-font-14); } .editLinkContainer { display: flex; flex-direction: column; gap: 32px; color: var(--modal-fg); font-size: var(--s-font-14); } .editLinkDescription { font-weight: var(--semi-bold); color: var(--modal-fg); display: flex; flex-direction: column; gap: 12px; } .editLinkDescription p { font-size: var(--s-font-14); } .separator { border-color: var(--border-color); margin: 0; } .linkSection { display: flex; flex-direction: column; gap: 16px; } .linkRow { display: flex; font-size: var(--m-font-16); align-items: center; gap: 8px; } .buttonRow { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; } .buttonRowWithError { margin-top: 8px; } .errorContainer { display: flex; justify-content: center; font-size: var(--s-font-14); color: var(--error-primary); } + +.disableLinkRow { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + font-size: var(--s-font-14); +} + +.disableLinkButtons { + display: flex; + flex-direction: column; + gap: 16px; +}