[Chat] Tweak styles in invite link dialog (#10638)

This commit is contained in:
Samuel Newman
2026-06-02 13:23:51 +03:00
committed by GitHub
parent 26e48e3876
commit 771fd5ddf6
3 changed files with 106 additions and 78 deletions
+7 -3
View File
@@ -212,9 +212,12 @@ export function Item({
)
const highlightStyle = highlightRow
? selected
? [a.rounded_full, a.p_md, {backgroundColor: t.palette.primary_50}]
: [a.rounded_full, a.p_md]
? [
a.rounded_full,
a.p_md,
hovered && t.atoms.bg_contrast_25,
selected && {backgroundColor: t.palette.primary_50},
]
: null
return (
@@ -588,6 +591,7 @@ export function RadioWithLabel({
style={[
a.font_medium,
a.flex_1,
a.text_md,
a.leading_tight,
selected ? t.atoms.text : t.atoms.text_contrast_high,
]}>
@@ -35,13 +35,14 @@ export function EditTextButton({
a.flex_row,
a.align_center,
a.justify_between,
a.px_md,
a.pl_lg,
a.pr_sm,
a.py_sm,
a.gap_sm,
]}>
{typeof children === 'function' ? children(context) : children}
<View
style={[
a.ml_sm,
a.rounded_full,
t.atoms.bg_contrast_50,
{paddingHorizontal: 10, paddingVertical: 8},
@@ -1,6 +1,11 @@
import {useEffect, useState} from 'react'
import {useState} from 'react'
import {View} from 'react-native'
import {moderateProfile, type ModerationOpts} from '@atproto/api'
import {
type ChatBskyGroupDefs,
moderateProfile,
type ModerationOpts,
} from '@atproto/api'
import {plural} from '@lingui/core/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
@@ -64,14 +69,9 @@ export function InviteLinkDialog({
)
const {joinLink} = convo.details
const enabledStatus = joinLink?.enabledStatus
const defaultStep = joinLink ? Step.MANAGE : Step.INFO
const defaultWhoCanJoin = joinLink
? [
`${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}`,
]
: ['anyone']
const defaultWhoCanJoin = joinLink ? joinLinkToKey(joinLink) : 'anyone'
const [step, setStep] = useState(defaultStep)
const [whoCanJoin, setWhoCanJoin] = useState(defaultWhoCanJoin)
@@ -79,13 +79,13 @@ export function InviteLinkDialog({
// Resync local state when the server-side join link rules change (mutation
// success, refetch, or change from another client). Keyed on the rule string
// so identity-only refetches don't bump a user mid-edit.
const joinLinkRuleKey = joinLink
? `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}`
: null
useEffect(() => {
const joinLinkRuleKey = joinLink ? joinLinkToKey(joinLink) : null
const [prevKey, setPrevKey] = useState(joinLinkRuleKey)
if (joinLinkRuleKey !== prevKey) {
setStep(joinLinkRuleKey ? Step.MANAGE : Step.INFO)
setWhoCanJoin([joinLinkRuleKey ?? 'anyone'])
}, [joinLinkRuleKey])
setWhoCanJoin(joinLinkRuleKey ?? 'anyone')
setPrevKey(joinLinkRuleKey)
}
const {openComposer} = useOpenComposer()
@@ -163,22 +163,32 @@ export function InviteLinkDialog({
let content: React.ReactNode = null
let header: string | null = null
switch (step) {
case Step.INFO:
case Step.INFO: {
header = l`Invite link`
content = (
<>
<View>
<Text style={[a.text_md, t.atoms.text]}>
<View style={[a.gap_lg]}>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
An invite link lets people join this group chat without being
added directly. You control who can use the link and whether
they need your approval. You can disable the link at any time.
added directly. You control who can join the chat. You can
disable the link at any time.
</Trans>
</Text>
<Text style={[a.mt_lg, a.text_md, t.atoms.text]}>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
Your name, avatar, and the name of the group chat will be
visible to everyone.
Group chats can only have a maximum of{' '}
{plural(convo.details.memberLimit, {
one: '# person',
other: '# people',
})}
.
</Trans>
</Text>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
Your name, avatar, the name of the group chat, and the number of
members will be visible to everyone.
</Trans>
</Text>
</View>
@@ -199,15 +209,16 @@ export function InviteLinkDialog({
</>
)
break
case Step.GENERATE:
header =
joinLink && enabledStatus === 'enabled'
? l`Update invite link`
: l`Generate invite link`
}
case Step.GENERATE: {
const linkEnabled = joinLink?.enabledStatus === 'enabled'
const linkHasChanged = linkEnabled && joinLinkRuleKey !== whoCanJoin
header = linkEnabled ? l`Update invite link` : l`Generate invite link`
content = (
<>
<View>
<Text style={[a.text_md, t.atoms.text]}>
<Text style={[a.text_md]}>
<Trans>Choose who can join this group chat and how.</Trans>
</Text>
</View>
@@ -215,9 +226,9 @@ export function InviteLinkDialog({
<Toggle.Group
label={l`Who can join this group chat and how`}
type="radio"
values={whoCanJoin}
onChange={setWhoCanJoin}>
<View style={[a.gap_sm]}>
values={[whoCanJoin]}
onChange={([value]) => setWhoCanJoin(value)}>
<View style={[a.gap_xs]}>
{whoCanJoinOptions.map(option => (
<Toggle.Item
key={option.name}
@@ -239,18 +250,22 @@ export function InviteLinkDialog({
<View style={[a.mt_4xl]}>
<Button
label={
joinLink && enabledStatus === 'enabled'
? l`Update invite link`
linkEnabled
? linkHasChanged
? l`Update invite link`
: l`Back`
: l`Generate invite link`
}
color="primary"
color={linkEnabled && !linkHasChanged ? 'secondary' : 'primary'}
size="large"
disabled={isSaving}
onPress={() => {
const parts = whoCanJoin[0].split(':')
const joinRule = parts[0]
const requireApproval = parts[1] === 'requireApproval'
if (joinLink && enabledStatus === 'enabled') {
const {joinRule, requireApproval} = keyToJoinLink(whoCanJoin)
if (linkEnabled) {
if (!linkHasChanged) {
setStep(Step.MANAGE)
return
}
editJoinLink({
joinRule,
requireApproval,
@@ -263,36 +278,40 @@ export function InviteLinkDialog({
}
}}>
<ButtonText>
{joinLink && enabledStatus === 'enabled'
? l`Update invite link`
{linkEnabled
? linkHasChanged
? l`Update invite link`
: l`Back`
: l`Generate invite link`}
</ButtonText>
<ButtonIcon icon={isSaving ? Loader : ArrowRightIcon} />
{linkHasChanged && (
<ButtonIcon icon={isSaving ? Loader : ArrowRightIcon} />
)}
</Button>
</View>
</>
)
break
}
case Step.MANAGE: {
const linkEnabled = joinLink?.enabledStatus === 'enabled'
const linkDisabled = joinLink?.enabledStatus === 'disabled'
const joinLinkURI = joinLink?.code
? `https://bsky.app/chat/${joinLink.code}`
: 'https://bsky.app/chat'
const createdAt = joinLink ? new Date(joinLink.createdAt) : null
const currentOptionName = joinLink
? `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}`
: whoCanJoinOptions[0].name
const currentOption = whoCanJoinOptions.find(
o => o.name === currentOptionName,
)
const currentOption =
whoCanJoinOptions.find(
o => o.name === (joinLink ? joinLinkToKey(joinLink) : null),
) ?? whoCanJoinOptions[0]
const ownerValue = currentOption?.owner ?? whoCanJoinOptions[0].owner
const memberValue = currentOption?.member ?? whoCanJoinOptions[0].member
header =
enabledStatus === 'enabled' ? l`Invite link` : l`Invite link disabled`
header = linkEnabled ? l`Invite link` : l`Invite link disabled`
content = (
<>
<View style={[a.mt_lg]}>
<CopyTextButton
disabled={enabledStatus === 'disabled' || !joinLink?.code}
disabled={linkDisabled || !joinLink?.code}
label={l`Invite link`}
value={joinLinkURI}>
<Text
@@ -300,9 +319,7 @@ export function InviteLinkDialog({
style={[
a.mr_xs,
a.text_md,
enabledStatus === 'disabled'
? t.atoms.text_contrast_low
: t.atoms.text,
linkDisabled ? t.atoms.text_contrast_low : t.atoms.text,
]}>
{joinLinkURI}
</Text>
@@ -319,25 +336,25 @@ export function InviteLinkDialog({
</Text>
) : null}
</View>
{enabledStatus === 'enabled' ? (
{linkEnabled ? (
<View style={[a.mt_lg]}>
{isOwner ? (
<EditTextButton
label={l`Edit link settings`}
value={ownerValue}
onPress={() => setStep(Step.GENERATE)}>
<View style={[a.flex_1, a.mr_xs]}>
<Text numberOfLines={1} style={[a.text_md, t.atoms.text]}>
<View style={[a.flex_1]}>
<Text numberOfLines={1} style={[a.text_sm]}>
{ownerValue}
</Text>
</View>
</EditTextButton>
) : (
<Text style={[a.text_sm, t.atoms.text]}>{memberValue}</Text>
<Text style={[a.text_sm]}>{memberValue}</Text>
)}
</View>
) : null}
{enabledStatus === 'enabled' ? (
{linkEnabled ? (
<View style={[a.flex_row, a.justify_between, a.gap_sm, a.mt_lg]}>
{isOwner ? (
<StackedButton
@@ -350,7 +367,7 @@ export function InviteLinkDialog({
</StackedButton>
) : null}
<StackedButton
disabled={enabledStatus === 'disabled'}
disabled={linkDisabled}
label={l`Post link`}
icon={EditIcon}
color="primary_subtle"
@@ -366,7 +383,7 @@ export function InviteLinkDialog({
<Trans>Post link</Trans>
</StackedButton>
<StackedButton
disabled={enabledStatus === 'disabled'}
disabled={linkDisabled}
label={l`Share`}
icon={ArrowShareRightIcon}
color="primary_subtle"
@@ -408,7 +425,7 @@ export function InviteLinkDialog({
)
break
}
case Step.CONFIRM_DISABLE:
case Step.CONFIRM_DISABLE: {
content = (
<>
<View style={[a.align_center, a.justify_center, a.mb_lg]}>
@@ -425,14 +442,7 @@ export function InviteLinkDialog({
]}>
<Trans>Disable this invite link?</Trans>
</Text>
<Text
style={[
a.pb_2xl,
a.text_center,
a.text_sm,
a.leading_snug,
t.atoms.text,
]}>
<Text style={[a.pb_2xl, a.text_center, a.text_sm, a.leading_snug]}>
<Trans>
Anyone who has it will no longer be able to join or request to
join. You can always create a new one.
@@ -465,6 +475,7 @@ export function InviteLinkDialog({
</>
)
break
}
}
if (!isOwner && (!joinLink || joinLink.enabledStatus === 'disabled')) {
@@ -472,7 +483,7 @@ export function InviteLinkDialog({
content = (
<>
<View style={[a.mt_lg]}>
<Text style={[a.text_sm, t.atoms.text]}>
<Text style={[a.text_sm]}>
<Trans>There is no invite link for this group chat.</Trans>
</Text>
</View>
@@ -503,9 +514,7 @@ export function InviteLinkDialog({
header={
<View>
<View style={[IS_WEB ? [a.px_2xl, a.pt_xl] : {paddingTop: 10}]}>
<Text style={[a.font_bold, a.text_2xl, a.mb_sm, t.atoms.text]}>
{header}
</Text>
<Text style={[a.font_bold, a.text_2xl, a.mb_sm]}>{header}</Text>
</View>
<Dialog.Close />
</View>
@@ -517,3 +526,17 @@ export function InviteLinkDialog({
</Dialog.Outer>
)
}
function joinLinkToKey(joinLink: ChatBskyGroupDefs.JoinLinkView): string {
return `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}`
}
function keyToJoinLink(
key: string,
): Pick<ChatBskyGroupDefs.JoinLinkView, 'joinRule' | 'requireApproval'> {
const [joinRule, requireApproval] = key.split(':')
return {
joinRule,
requireApproval: requireApproval === 'requireApproval',
}
}