Delete unused modals and invite code stuff (#8244)
* delete waitlist modal, which doesn't exist * delete invite code modal * rip out copied invites persisted state * delete invite query * delete unused modal implementations
This commit is contained in:
+16
-19
@@ -38,7 +38,6 @@ import {
|
|||||||
} from '#/state/geolocation'
|
} from '#/state/geolocation'
|
||||||
import {GlobalGestureEventsProvider} from '#/state/global-gesture-events'
|
import {GlobalGestureEventsProvider} from '#/state/global-gesture-events'
|
||||||
import {Provider as HomeBadgeProvider} from '#/state/home-badge'
|
import {Provider as HomeBadgeProvider} from '#/state/home-badge'
|
||||||
import {Provider as InvitesStateProvider} from '#/state/invites'
|
|
||||||
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
||||||
import {MessagesProvider} from '#/state/messages'
|
import {MessagesProvider} from '#/state/messages'
|
||||||
import {Provider as ModalStateProvider} from '#/state/modals'
|
import {Provider as ModalStateProvider} from '#/state/modals'
|
||||||
@@ -225,24 +224,22 @@ function App() {
|
|||||||
<PrefsStateProvider>
|
<PrefsStateProvider>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<ShellStateProvider>
|
<ShellStateProvider>
|
||||||
<InvitesStateProvider>
|
<ModalStateProvider>
|
||||||
<ModalStateProvider>
|
<DialogStateProvider>
|
||||||
<DialogStateProvider>
|
<LightboxStateProvider>
|
||||||
<LightboxStateProvider>
|
<PortalProvider>
|
||||||
<PortalProvider>
|
<BottomSheetProvider>
|
||||||
<BottomSheetProvider>
|
<StarterPackProvider>
|
||||||
<StarterPackProvider>
|
<SafeAreaProvider
|
||||||
<SafeAreaProvider
|
initialMetrics={initialWindowMetrics}>
|
||||||
initialMetrics={initialWindowMetrics}>
|
<InnerApp />
|
||||||
<InnerApp />
|
</SafeAreaProvider>
|
||||||
</SafeAreaProvider>
|
</StarterPackProvider>
|
||||||
</StarterPackProvider>
|
</BottomSheetProvider>
|
||||||
</BottomSheetProvider>
|
</PortalProvider>
|
||||||
</PortalProvider>
|
</LightboxStateProvider>
|
||||||
</LightboxStateProvider>
|
</DialogStateProvider>
|
||||||
</DialogStateProvider>
|
</ModalStateProvider>
|
||||||
</ModalStateProvider>
|
|
||||||
</InvitesStateProvider>
|
|
||||||
</ShellStateProvider>
|
</ShellStateProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</PrefsStateProvider>
|
</PrefsStateProvider>
|
||||||
|
|||||||
+11
-14
@@ -26,7 +26,6 @@ import {
|
|||||||
Provider as GeolocationProvider,
|
Provider as GeolocationProvider,
|
||||||
} from '#/state/geolocation'
|
} from '#/state/geolocation'
|
||||||
import {Provider as HomeBadgeProvider} from '#/state/home-badge'
|
import {Provider as HomeBadgeProvider} from '#/state/home-badge'
|
||||||
import {Provider as InvitesStateProvider} from '#/state/invites'
|
|
||||||
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
||||||
import {MessagesProvider} from '#/state/messages'
|
import {MessagesProvider} from '#/state/messages'
|
||||||
import {Provider as ModalStateProvider} from '#/state/modals'
|
import {Provider as ModalStateProvider} from '#/state/modals'
|
||||||
@@ -199,19 +198,17 @@ function App() {
|
|||||||
<PrefsStateProvider>
|
<PrefsStateProvider>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<ShellStateProvider>
|
<ShellStateProvider>
|
||||||
<InvitesStateProvider>
|
<ModalStateProvider>
|
||||||
<ModalStateProvider>
|
<DialogStateProvider>
|
||||||
<DialogStateProvider>
|
<LightboxStateProvider>
|
||||||
<LightboxStateProvider>
|
<PortalProvider>
|
||||||
<PortalProvider>
|
<StarterPackProvider>
|
||||||
<StarterPackProvider>
|
<InnerApp />
|
||||||
<InnerApp />
|
</StarterPackProvider>
|
||||||
</StarterPackProvider>
|
</PortalProvider>
|
||||||
</PortalProvider>
|
</LightboxStateProvider>
|
||||||
</LightboxStateProvider>
|
</DialogStateProvider>
|
||||||
</DialogStateProvider>
|
</ModalStateProvider>
|
||||||
</ModalStateProvider>
|
|
||||||
</InvitesStateProvider>
|
|
||||||
</ShellStateProvider>
|
</ShellStateProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</PrefsStateProvider>
|
</PrefsStateProvider>
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
import * as persisted from '#/state/persisted'
|
|
||||||
|
|
||||||
type StateContext = persisted.Schema['invites']
|
|
||||||
type ApiContext = {
|
|
||||||
setInviteCopied: (code: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateContext = React.createContext<StateContext>(
|
|
||||||
persisted.defaults.invites,
|
|
||||||
)
|
|
||||||
stateContext.displayName = 'InvitesStateContext'
|
|
||||||
const apiContext = React.createContext<ApiContext>({
|
|
||||||
setInviteCopied(_: string) {},
|
|
||||||
})
|
|
||||||
apiContext.displayName = 'InvitesApiContext'
|
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
|
||||||
const [state, setState] = React.useState(persisted.get('invites'))
|
|
||||||
|
|
||||||
const api = React.useMemo(
|
|
||||||
() => ({
|
|
||||||
setInviteCopied(code: string) {
|
|
||||||
setState(state => {
|
|
||||||
state = {
|
|
||||||
...state,
|
|
||||||
copiedInvites: state.copiedInvites.includes(code)
|
|
||||||
? state.copiedInvites
|
|
||||||
: state.copiedInvites.concat([code]),
|
|
||||||
}
|
|
||||||
persisted.write('invites', state)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[setState],
|
|
||||||
)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
return persisted.onUpdate('invites', nextInvites => {
|
|
||||||
setState(nextInvites)
|
|
||||||
})
|
|
||||||
}, [setState])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<stateContext.Provider value={state}>
|
|
||||||
<apiContext.Provider value={api}>{children}</apiContext.Provider>
|
|
||||||
</stateContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInvitesState() {
|
|
||||||
return React.useContext(stateContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInvitesAPI() {
|
|
||||||
return React.useContext(apiContext)
|
|
||||||
}
|
|
||||||
@@ -15,14 +15,6 @@ export interface DeleteAccountModal {
|
|||||||
name: 'delete-account'
|
name: 'delete-account'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WaitlistModal {
|
|
||||||
name: 'waitlist'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InviteCodesModal {
|
|
||||||
name: 'invite-codes'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentLanguagesSettingsModal {
|
export interface ContentLanguagesSettingsModal {
|
||||||
name: 'content-languages-settings'
|
name: 'content-languages-settings'
|
||||||
}
|
}
|
||||||
@@ -40,10 +32,6 @@ export type Modal =
|
|||||||
// Lists
|
// Lists
|
||||||
| UserAddRemoveListsModal
|
| UserAddRemoveListsModal
|
||||||
|
|
||||||
// Bluesky access
|
|
||||||
| WaitlistModal
|
|
||||||
| InviteCodesModal
|
|
||||||
|
|
||||||
const ModalContext = React.createContext<{
|
const ModalContext = React.createContext<{
|
||||||
isModalActive: boolean
|
isModalActive: boolean
|
||||||
activeModals: Modal[]
|
activeModals: Modal[]
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import {type ComAtprotoServerDefs} from '@atproto/api'
|
|
||||||
import {useQuery} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
|
||||||
import {STALE} from '#/state/queries'
|
|
||||||
import {useAgent} from '#/state/session'
|
|
||||||
|
|
||||||
function isInviteAvailable(invite: ComAtprotoServerDefs.InviteCode): boolean {
|
|
||||||
return invite.available - invite.uses.length > 0 && !invite.disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
const inviteCodesQueryKeyRoot = 'inviteCodes'
|
|
||||||
|
|
||||||
export type InviteCodesQueryResponse = Exclude<
|
|
||||||
ReturnType<typeof useInviteCodesQuery>['data'],
|
|
||||||
undefined
|
|
||||||
>
|
|
||||||
export function useInviteCodesQuery() {
|
|
||||||
const agent = useAgent()
|
|
||||||
return useQuery({
|
|
||||||
staleTime: STALE.MINUTES.FIVE,
|
|
||||||
queryKey: [inviteCodesQueryKeyRoot],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await agent.com.atproto.server
|
|
||||||
.getAccountInviteCodes({})
|
|
||||||
.catch(e => {
|
|
||||||
if (cleanError(e) === 'Bad token scope') {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res === null) {
|
|
||||||
return {
|
|
||||||
disabled: true,
|
|
||||||
all: [],
|
|
||||||
available: [],
|
|
||||||
used: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!res.data?.codes) {
|
|
||||||
throw new Error(`useInviteCodesQuery: no codes returned`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const available = res.data.codes.filter(isInviteAvailable)
|
|
||||||
const used = res.data.codes
|
|
||||||
.filter(code => !isInviteAvailable(code))
|
|
||||||
.sort((a, b) => {
|
|
||||||
return (
|
|
||||||
new Date(b.uses[0].usedAt).getTime() -
|
|
||||||
new Date(a.uses[0].usedAt).getTime()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
disabled: false,
|
|
||||||
all: [...available, ...used],
|
|
||||||
available,
|
|
||||||
used,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
import {useCallback, useMemo, useState} from 'react'
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
KeyboardAvoidingView,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
TextInput,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
|
||||||
import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
|
||||||
import {enforceLen} from '#/lib/strings/helpers'
|
|
||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
|
||||||
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
|
||||||
import {colors, gradients, s} from '#/lib/styles'
|
|
||||||
import {useTheme} from '#/lib/ThemeContext'
|
|
||||||
import {type ImageMeta} from '#/state/gallery'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {
|
|
||||||
useListCreateMutation,
|
|
||||||
useListMetadataMutation,
|
|
||||||
} from '#/state/queries/list'
|
|
||||||
import {useAgent} from '#/state/session'
|
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
|
||||||
import {EditableUserAvatar} from '#/view/com/util/UserAvatar'
|
|
||||||
|
|
||||||
const MAX_NAME = 64 // todo
|
|
||||||
const MAX_DESCRIPTION = 300 // todo
|
|
||||||
|
|
||||||
export const snapPoints = ['fullscreen']
|
|
||||||
|
|
||||||
export function Component({
|
|
||||||
purpose,
|
|
||||||
onSave,
|
|
||||||
list,
|
|
||||||
}: {
|
|
||||||
purpose?: string
|
|
||||||
onSave?: (uri: string) => void
|
|
||||||
list?: AppBskyGraphDefs.ListView
|
|
||||||
}) {
|
|
||||||
const {closeModal} = useModalControls()
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const [error, setError] = useState<string>('')
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const theme = useTheme()
|
|
||||||
const {_} = useLingui()
|
|
||||||
const listCreateMutation = useListCreateMutation()
|
|
||||||
const listMetadataMutation = useListMetadataMutation()
|
|
||||||
const agent = useAgent()
|
|
||||||
|
|
||||||
const activePurpose = useMemo(() => {
|
|
||||||
if (list?.purpose) {
|
|
||||||
return list.purpose
|
|
||||||
}
|
|
||||||
if (purpose) {
|
|
||||||
return purpose
|
|
||||||
}
|
|
||||||
return 'app.bsky.graph.defs#curatelist'
|
|
||||||
}, [list, purpose])
|
|
||||||
const isCurateList = activePurpose === 'app.bsky.graph.defs#curatelist'
|
|
||||||
|
|
||||||
const [isProcessing, setProcessing] = useState<boolean>(false)
|
|
||||||
const [name, setName] = useState<string>(list?.name || '')
|
|
||||||
|
|
||||||
const [descriptionRt, setDescriptionRt] = useState<RichTextAPI>(() => {
|
|
||||||
const text = list?.description
|
|
||||||
const facets = list?.descriptionFacets
|
|
||||||
|
|
||||||
if (!text || !facets) {
|
|
||||||
return new RichTextAPI({text: text || ''})
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to be working with a blank state here, so let's get the
|
|
||||||
// serialized version and turn it back into a RichText
|
|
||||||
const serialized = richTextToString(new RichTextAPI({text, facets}), false)
|
|
||||||
|
|
||||||
const richText = new RichTextAPI({text: serialized})
|
|
||||||
richText.detectFacetsWithoutResolution()
|
|
||||||
|
|
||||||
return richText
|
|
||||||
})
|
|
||||||
const graphemeLength = useMemo(() => {
|
|
||||||
return shortenLinks(descriptionRt).graphemeLength
|
|
||||||
}, [descriptionRt])
|
|
||||||
const isDescriptionOver = graphemeLength > MAX_DESCRIPTION
|
|
||||||
|
|
||||||
const [avatar, setAvatar] = useState<string | undefined>(list?.avatar)
|
|
||||||
const [newAvatar, setNewAvatar] = useState<ImageMeta | undefined | null>()
|
|
||||||
|
|
||||||
const onDescriptionChange = useCallback(
|
|
||||||
(newText: string) => {
|
|
||||||
const richText = new RichTextAPI({text: newText})
|
|
||||||
richText.detectFacetsWithoutResolution()
|
|
||||||
|
|
||||||
setDescriptionRt(richText)
|
|
||||||
},
|
|
||||||
[setDescriptionRt],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onPressCancel = useCallback(() => {
|
|
||||||
closeModal()
|
|
||||||
}, [closeModal])
|
|
||||||
|
|
||||||
const onSelectNewAvatar = useCallback(
|
|
||||||
(img: ImageMeta | null) => {
|
|
||||||
if (!img) {
|
|
||||||
setNewAvatar(null)
|
|
||||||
setAvatar(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
setNewAvatar(img)
|
|
||||||
setAvatar(img.path)
|
|
||||||
} catch (e: any) {
|
|
||||||
setError(cleanError(e))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setNewAvatar, setAvatar, setError],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onPressSave = useCallback(async () => {
|
|
||||||
const nameTrimmed = name.trim()
|
|
||||||
if (!nameTrimmed) {
|
|
||||||
setError(_(msg`Name is required`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setProcessing(true)
|
|
||||||
if (error) {
|
|
||||||
setError('')
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
let richText = new RichTextAPI(
|
|
||||||
{text: descriptionRt.text.trimEnd()},
|
|
||||||
{cleanNewlines: true},
|
|
||||||
)
|
|
||||||
|
|
||||||
await richText.detectFacets(agent)
|
|
||||||
richText = shortenLinks(richText)
|
|
||||||
richText = stripInvalidMentions(richText)
|
|
||||||
|
|
||||||
if (list) {
|
|
||||||
await listMetadataMutation.mutateAsync({
|
|
||||||
uri: list.uri,
|
|
||||||
name: nameTrimmed,
|
|
||||||
description: richText.text,
|
|
||||||
descriptionFacets: richText.facets,
|
|
||||||
avatar: newAvatar,
|
|
||||||
})
|
|
||||||
Toast.show(
|
|
||||||
isCurateList
|
|
||||||
? _(msg({message: 'User list updated', context: 'toast'}))
|
|
||||||
: _(msg({message: 'Moderation list updated', context: 'toast'})),
|
|
||||||
)
|
|
||||||
onSave?.(list.uri)
|
|
||||||
} else {
|
|
||||||
const res = await listCreateMutation.mutateAsync({
|
|
||||||
purpose: activePurpose,
|
|
||||||
name,
|
|
||||||
description: richText.text,
|
|
||||||
descriptionFacets: richText.facets,
|
|
||||||
avatar: newAvatar,
|
|
||||||
})
|
|
||||||
Toast.show(
|
|
||||||
isCurateList
|
|
||||||
? _(msg({message: 'User list created', context: 'toast'}))
|
|
||||||
: _(msg({message: 'Moderation list created', context: 'toast'})),
|
|
||||||
)
|
|
||||||
onSave?.(res.uri)
|
|
||||||
}
|
|
||||||
closeModal()
|
|
||||||
} catch (e: any) {
|
|
||||||
if (isNetworkError(e)) {
|
|
||||||
setError(
|
|
||||||
_(
|
|
||||||
msg`Failed to create the list. Check your internet connection and try again.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setError(cleanError(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setProcessing(false)
|
|
||||||
}, [
|
|
||||||
setProcessing,
|
|
||||||
setError,
|
|
||||||
error,
|
|
||||||
onSave,
|
|
||||||
closeModal,
|
|
||||||
activePurpose,
|
|
||||||
isCurateList,
|
|
||||||
name,
|
|
||||||
descriptionRt,
|
|
||||||
newAvatar,
|
|
||||||
list,
|
|
||||||
listMetadataMutation,
|
|
||||||
listCreateMutation,
|
|
||||||
_,
|
|
||||||
agent,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KeyboardAvoidingView behavior="height">
|
|
||||||
<ScrollView
|
|
||||||
style={[
|
|
||||||
pal.view,
|
|
||||||
{
|
|
||||||
paddingHorizontal: isMobile ? 16 : 0,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
testID="createOrEditListModal">
|
|
||||||
<Text style={[styles.title, pal.text]}>
|
|
||||||
{isCurateList ? (
|
|
||||||
list ? (
|
|
||||||
<Trans>Edit User List</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans>New User List</Trans>
|
|
||||||
)
|
|
||||||
) : list ? (
|
|
||||||
<Trans>Edit Moderation List</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans>New Moderation List</Trans>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
{error !== '' && (
|
|
||||||
<View style={styles.errorContainer}>
|
|
||||||
<ErrorMessage message={error} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Text style={[styles.label, pal.text]}>
|
|
||||||
<Trans>List Avatar</Trans>
|
|
||||||
</Text>
|
|
||||||
<View style={[styles.avi, {borderColor: pal.colors.background}]}>
|
|
||||||
<EditableUserAvatar
|
|
||||||
type="list"
|
|
||||||
size={80}
|
|
||||||
avatar={avatar}
|
|
||||||
onSelectNewAvatar={onSelectNewAvatar}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.form}>
|
|
||||||
<View>
|
|
||||||
<View style={styles.labelWrapper}>
|
|
||||||
<Text style={[styles.label, pal.text]} nativeID="list-name">
|
|
||||||
<Trans>List Name</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<TextInput
|
|
||||||
testID="editNameInput"
|
|
||||||
style={[styles.textInput, pal.border, pal.text]}
|
|
||||||
placeholder={
|
|
||||||
isCurateList
|
|
||||||
? _(msg`e.g. Great Posters`)
|
|
||||||
: _(msg`e.g. Spammers`)
|
|
||||||
}
|
|
||||||
placeholderTextColor={colors.gray4}
|
|
||||||
value={name}
|
|
||||||
onChangeText={v => setName(enforceLen(v, MAX_NAME))}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={_(msg`Name`)}
|
|
||||||
accessibilityHint=""
|
|
||||||
accessibilityLabelledBy="list-name"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={s.pb10}>
|
|
||||||
<View style={styles.labelWrapper}>
|
|
||||||
<Text
|
|
||||||
style={[styles.label, pal.text]}
|
|
||||||
nativeID="list-description">
|
|
||||||
<Trans>Description</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[!isDescriptionOver ? pal.textLight : s.red3, s.f13]}>
|
|
||||||
{graphemeLength}/{MAX_DESCRIPTION}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<TextInput
|
|
||||||
testID="editDescriptionInput"
|
|
||||||
style={[styles.textArea, pal.border, pal.text]}
|
|
||||||
placeholder={
|
|
||||||
isCurateList
|
|
||||||
? _(msg`e.g. The posters who never miss.`)
|
|
||||||
: _(msg`e.g. Users that repeatedly reply with ads.`)
|
|
||||||
}
|
|
||||||
placeholderTextColor={colors.gray4}
|
|
||||||
keyboardAppearance={theme.colorScheme}
|
|
||||||
multiline
|
|
||||||
value={descriptionRt.text}
|
|
||||||
onChangeText={onDescriptionChange}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={_(msg`Description`)}
|
|
||||||
accessibilityHint=""
|
|
||||||
accessibilityLabelledBy="list-description"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
{isProcessing ? (
|
|
||||||
<View style={[styles.btn, s.mt10, {backgroundColor: colors.gray2}]}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="saveBtn"
|
|
||||||
style={[s.mt10, isDescriptionOver && s.dimmed]}
|
|
||||||
disabled={isDescriptionOver}
|
|
||||||
onPress={onPressSave}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Save`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<LinearGradient
|
|
||||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={styles.btn}>
|
|
||||||
<Text style={[s.white, s.bold]}>
|
|
||||||
<Trans context="action">Save</Trans>
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="cancelBtn"
|
|
||||||
style={s.mt5}
|
|
||||||
onPress={onPressCancel}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Cancel`)}
|
|
||||||
accessibilityHint=""
|
|
||||||
onAccessibilityEscape={onPressCancel}>
|
|
||||||
<View style={[styles.btn]}>
|
|
||||||
<Text style={[s.black, s.bold, pal.text]}>
|
|
||||||
<Trans context="action">Cancel</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
</KeyboardAvoidingView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: '600',
|
|
||||||
fontSize: 24,
|
|
||||||
marginBottom: 18,
|
|
||||||
},
|
|
||||||
labelWrapper: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 8,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
paddingBottom: 4,
|
|
||||||
marginTop: 20,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
paddingHorizontal: 6,
|
|
||||||
},
|
|
||||||
textInput: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
paddingVertical: 10,
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
textArea: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
paddingTop: 10,
|
|
||||||
fontSize: 16,
|
|
||||||
height: 100,
|
|
||||||
textAlignVertical: 'top',
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: 32,
|
|
||||||
padding: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
avi: {
|
|
||||||
width: 84,
|
|
||||||
height: 84,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderRadius: 42,
|
|
||||||
marginTop: 4,
|
|
||||||
},
|
|
||||||
errorContainer: {marginTop: 20},
|
|
||||||
})
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
|
||||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import ReactCrop, {type PercentCrop} from 'react-image-crop'
|
|
||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {type PickerImage} from '#/lib/media/picker.shared'
|
|
||||||
import {getDataUriSize} from '#/lib/media/util'
|
|
||||||
import {gradients, s} from '#/lib/styles'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
|
||||||
|
|
||||||
export const snapPoints = ['0%']
|
|
||||||
|
|
||||||
export function Component({
|
|
||||||
uri,
|
|
||||||
aspect,
|
|
||||||
circular,
|
|
||||||
onSelect,
|
|
||||||
}: {
|
|
||||||
uri: string
|
|
||||||
aspect?: number
|
|
||||||
circular?: boolean
|
|
||||||
onSelect: (img?: PickerImage) => void
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
const {closeModal} = useModalControls()
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
|
|
||||||
const imageRef = React.useRef<HTMLImageElement>(null)
|
|
||||||
const [crop, setCrop] = React.useState<PercentCrop>()
|
|
||||||
|
|
||||||
const isEmpty = !crop || (crop.width || crop.height) === 0
|
|
||||||
|
|
||||||
const onPressCancel = () => {
|
|
||||||
onSelect(undefined)
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
const onPressDone = async () => {
|
|
||||||
const img = imageRef.current!
|
|
||||||
|
|
||||||
const result = await manipulateAsync(
|
|
||||||
uri,
|
|
||||||
isEmpty
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
crop: {
|
|
||||||
originX: (crop.x * img.naturalWidth) / 100,
|
|
||||||
originY: (crop.y * img.naturalHeight) / 100,
|
|
||||||
width: (crop.width * img.naturalWidth) / 100,
|
|
||||||
height: (crop.height * img.naturalHeight) / 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
base64: true,
|
|
||||||
format: SaveFormat.JPEG,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
onSelect({
|
|
||||||
path: result.uri,
|
|
||||||
mime: 'image/jpeg',
|
|
||||||
size: result.base64 !== undefined ? getDataUriSize(result.base64) : 0,
|
|
||||||
width: result.width,
|
|
||||||
height: result.height,
|
|
||||||
})
|
|
||||||
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<View style={[styles.cropper, pal.borderDark]}>
|
|
||||||
<ReactCrop
|
|
||||||
aspect={aspect}
|
|
||||||
crop={crop}
|
|
||||||
onChange={(_pixelCrop, percentCrop) => setCrop(percentCrop)}
|
|
||||||
circularCrop={circular}>
|
|
||||||
<img ref={imageRef} src={uri} style={{maxHeight: '75vh'}} />
|
|
||||||
</ReactCrop>
|
|
||||||
</View>
|
|
||||||
<View style={[styles.btns, isMobile && {paddingHorizontal: 16}]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onPressCancel}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Cancel image crop`)}
|
|
||||||
accessibilityHint={_(msg`Exits image cropping process`)}>
|
|
||||||
<Text type="xl" style={pal.link}>
|
|
||||||
<Trans>Cancel</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={s.flex1} />
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onPressDone}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Save image crop`)}
|
|
||||||
accessibilityHint={_(msg`Saves image crop settings`)}>
|
|
||||||
<LinearGradient
|
|
||||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={[styles.btn]}>
|
|
||||||
<Text type="xl-medium" style={s.white}>
|
|
||||||
<Trans>Done</Trans>
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
cropper: {
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 4,
|
|
||||||
overflow: 'hidden',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
ctrls: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10,
|
|
||||||
},
|
|
||||||
btns: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10,
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
borderRadius: 4,
|
|
||||||
paddingVertical: 8,
|
|
||||||
paddingHorizontal: 24,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {setStringAsync} from 'expo-clipboard'
|
|
||||||
import {type ComAtprotoServerDefs} from '@atproto/api'
|
|
||||||
import {
|
|
||||||
FontAwesomeIcon,
|
|
||||||
type FontAwesomeIconStyle,
|
|
||||||
} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useInvitesAPI, useInvitesState} from '#/state/invites'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {
|
|
||||||
type InviteCodesQueryResponse,
|
|
||||||
useInviteCodesQuery,
|
|
||||||
} from '#/state/queries/invites'
|
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {Link} from '../util/Link'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import * as Toast from '../util/Toast'
|
|
||||||
import {UserInfoText} from '../util/UserInfoText'
|
|
||||||
import {ScrollView} from './util'
|
|
||||||
|
|
||||||
export const snapPoints = ['70%']
|
|
||||||
|
|
||||||
export function Component() {
|
|
||||||
const {isLoading, data: invites, error} = useInviteCodesQuery()
|
|
||||||
|
|
||||||
return error ? (
|
|
||||||
<ErrorMessage message={cleanError(error)} />
|
|
||||||
) : isLoading || !invites ? (
|
|
||||||
<View style={{padding: 18}}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<Inner invites={invites} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Inner({invites}: {invites: InviteCodesQueryResponse}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {closeModal} = useModalControls()
|
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
|
||||||
|
|
||||||
const onClose = React.useCallback(() => {
|
|
||||||
closeModal()
|
|
||||||
}, [closeModal])
|
|
||||||
|
|
||||||
if (invites.all.length === 0) {
|
|
||||||
return (
|
|
||||||
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
|
|
||||||
<View style={[styles.empty, pal.viewLight]}>
|
|
||||||
<Text type="lg" style={[pal.text, styles.emptyText]}>
|
|
||||||
<Trans>
|
|
||||||
You don't have any invite codes yet! We'll send you some when
|
|
||||||
you've been on Bluesky for a little longer.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.flex1} />
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.btnContainer,
|
|
||||||
isTabletOrDesktop && styles.btnContainerDesktop,
|
|
||||||
]}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
label={_(msg`Done`)}
|
|
||||||
style={styles.btn}
|
|
||||||
labelStyle={styles.btnLabel}
|
|
||||||
onPress={onClose}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
|
|
||||||
<Text type="title-xl" style={[styles.title, pal.text]}>
|
|
||||||
<Trans>Invite a Friend</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="lg" style={[styles.description, pal.text]}>
|
|
||||||
<Trans>
|
|
||||||
Each code works once. You'll receive more invite codes periodically.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
<ScrollView style={[styles.scrollContainer, pal.border]}>
|
|
||||||
{invites.available.map((invite, i) => (
|
|
||||||
<InviteCode
|
|
||||||
testID={`inviteCode-${i}`}
|
|
||||||
key={invite.code}
|
|
||||||
invite={invite}
|
|
||||||
invites={invites}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{invites.used.map((invite, i) => (
|
|
||||||
<InviteCode
|
|
||||||
used
|
|
||||||
testID={`inviteCode-${i}`}
|
|
||||||
key={invite.code}
|
|
||||||
invite={invite}
|
|
||||||
invites={invites}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
<View style={styles.btnContainer}>
|
|
||||||
<Button
|
|
||||||
testID="closeBtn"
|
|
||||||
type="primary"
|
|
||||||
label={_(msg`Done`)}
|
|
||||||
style={styles.btn}
|
|
||||||
labelStyle={styles.btnLabel}
|
|
||||||
onPress={onClose}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function InviteCode({
|
|
||||||
testID,
|
|
||||||
invite,
|
|
||||||
used,
|
|
||||||
invites,
|
|
||||||
}: {
|
|
||||||
testID: string
|
|
||||||
invite: ComAtprotoServerDefs.InviteCode
|
|
||||||
used?: boolean
|
|
||||||
invites: InviteCodesQueryResponse
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const invitesState = useInvitesState()
|
|
||||||
const {setInviteCopied} = useInvitesAPI()
|
|
||||||
const uses = invite.uses
|
|
||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
|
||||||
setStringAsync(invite.code)
|
|
||||||
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
|
||||||
setInviteCopied(invite.code)
|
|
||||||
}, [setInviteCopied, invite, _])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
pal.border,
|
|
||||||
{borderBottomWidth: 1, paddingHorizontal: 20, paddingVertical: 14},
|
|
||||||
]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID={testID}
|
|
||||||
style={[styles.inviteCode]}
|
|
||||||
onPress={onPress}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={
|
|
||||||
invites.available.length === 1
|
|
||||||
? _(msg`Invite codes: 1 available`)
|
|
||||||
: _(msg`Invite codes: ${invites.available.length} available`)
|
|
||||||
}
|
|
||||||
accessibilityHint={_(msg`Opens list of invite codes`)}>
|
|
||||||
<Text
|
|
||||||
testID={`${testID}-code`}
|
|
||||||
type={used ? 'md' : 'md-bold'}
|
|
||||||
style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
|
|
||||||
{invite.code}
|
|
||||||
</Text>
|
|
||||||
<View style={styles.flex1} />
|
|
||||||
{!used && invitesState.copiedInvites.includes(invite.code) && (
|
|
||||||
<Text style={[pal.textLight, styles.codeCopied]}>
|
|
||||||
<Trans>Copied</Trans>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{!used && (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={['far', 'clone']}
|
|
||||||
style={pal.text as FontAwesomeIconStyle}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
{uses.length > 0 ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 8,
|
|
||||||
paddingTop: 6,
|
|
||||||
}}>
|
|
||||||
<Text style={pal.text}>
|
|
||||||
<Trans>Used by:</Trans>{' '}
|
|
||||||
{uses.map((use, i) => (
|
|
||||||
<Link
|
|
||||||
key={use.usedBy}
|
|
||||||
href={makeProfileLink({handle: use.usedBy, did: ''})}
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
}}>
|
|
||||||
<UserInfoText did={use.usedBy} style={pal.link} />
|
|
||||||
{i !== uses.length - 1 && <Text style={pal.text}>, </Text>}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
paddingBottom: isWeb ? 0 : 50,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 12,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingHorizontal: 42,
|
|
||||||
marginBottom: 14,
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollContainer: {
|
|
||||||
flex: 1,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
marginTop: 4,
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
flex1: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
empty: {
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingVertical: 20,
|
|
||||||
borderRadius: 16,
|
|
||||||
marginHorizontal: 24,
|
|
||||||
marginTop: 10,
|
|
||||||
},
|
|
||||||
emptyText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
inviteCode: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
codeCopied: {
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
strikeThrough: {
|
|
||||||
textDecorationLine: 'line-through',
|
|
||||||
textDecorationStyle: 'solid',
|
|
||||||
},
|
|
||||||
|
|
||||||
btnContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
btnContainerDesktop: {
|
|
||||||
marginTop: 14,
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: 32,
|
|
||||||
paddingHorizontal: 60,
|
|
||||||
paddingVertical: 14,
|
|
||||||
},
|
|
||||||
btnLabel: {
|
|
||||||
fontSize: 18,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -8,7 +8,6 @@ import {useModalControls, useModals} from '#/state/modals'
|
|||||||
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
|
||||||
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||||
import * as DeleteAccountModal from './DeleteAccount'
|
import * as DeleteAccountModal from './DeleteAccount'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
|
||||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||||
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
|
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
|
||||||
|
|
||||||
@@ -49,9 +48,6 @@ export function ModalsContainer() {
|
|||||||
} else if (activeModal?.name === 'delete-account') {
|
} else if (activeModal?.name === 'delete-account') {
|
||||||
snapPoints = DeleteAccountModal.snapPoints
|
snapPoints = DeleteAccountModal.snapPoints
|
||||||
element = <DeleteAccountModal.Component />
|
element = <DeleteAccountModal.Component />
|
||||||
} else if (activeModal?.name === 'invite-codes') {
|
|
||||||
snapPoints = InviteCodesModal.snapPoints
|
|
||||||
element = <InviteCodesModal.Component />
|
|
||||||
} else if (activeModal?.name === 'content-languages-settings') {
|
} else if (activeModal?.name === 'content-languages-settings') {
|
||||||
snapPoints = ContentLanguagesSettingsModal.snapPoints
|
snapPoints = ContentLanguagesSettingsModal.snapPoints
|
||||||
element = <ContentLanguagesSettingsModal.Component />
|
element = <ContentLanguagesSettingsModal.Component />
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|||||||
import {type Modal as ModalIface} from '#/state/modals'
|
import {type Modal as ModalIface} from '#/state/modals'
|
||||||
import {useModalControls, useModals} from '#/state/modals'
|
import {useModalControls, useModals} from '#/state/modals'
|
||||||
import * as DeleteAccountModal from './DeleteAccount'
|
import * as DeleteAccountModal from './DeleteAccount'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
|
||||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||||
import * as UserAddRemoveLists from './UserAddRemoveLists'
|
import * as UserAddRemoveLists from './UserAddRemoveLists'
|
||||||
|
|
||||||
@@ -51,8 +50,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||||||
element = <UserAddRemoveLists.Component {...modal} />
|
element = <UserAddRemoveLists.Component {...modal} />
|
||||||
} else if (modal.name === 'delete-account') {
|
} else if (modal.name === 'delete-account') {
|
||||||
element = <DeleteAccountModal.Component />
|
element = <DeleteAccountModal.Component />
|
||||||
} else if (modal.name === 'invite-codes') {
|
|
||||||
element = <InviteCodesModal.Component />
|
|
||||||
} else if (modal.name === 'content-languages-settings') {
|
} else if (modal.name === 'content-languages-settings') {
|
||||||
element = <ContentLanguagesSettingsModal.Component />
|
element = <ContentLanguagesSettingsModal.Component />
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {LogBox, Pressable, View, TextInput} from 'react-native'
|
|||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {BLUESKY_PROXY_HEADER} from '#/lib/constants'
|
import {BLUESKY_PROXY_HEADER} from '#/lib/constants'
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {useSessionApi, useAgent} from '#/state/session'
|
import {useSessionApi, useAgent} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {useOnboardingDispatch} from '#/state/shell/onboarding'
|
import {useOnboardingDispatch} from '#/state/shell/onboarding'
|
||||||
@@ -23,7 +22,6 @@ export function TestCtrls() {
|
|||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {logoutEveryAccount, login} = useSessionApi()
|
const {logoutEveryAccount, login} = useSessionApi()
|
||||||
const {openModal} = useModalControls()
|
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
const onPressSignInAlice = async () => {
|
const onPressSignInAlice = async () => {
|
||||||
@@ -121,12 +119,6 @@ export function TestCtrls() {
|
|||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={BTN}
|
style={BTN}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
|
||||||
testID="e2eOpenInviteCodesModal"
|
|
||||||
onPress={() => openModal({name: 'invite-codes'})}
|
|
||||||
accessibilityRole="button"
|
|
||||||
style={BTN}
|
|
||||||
/>
|
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="e2eOpenLoggedOutView"
|
testID="e2eOpenLoggedOutView"
|
||||||
onPress={() => setShowLoggedOut(true)}
|
onPress={() => setShowLoggedOut(true)}
|
||||||
|
|||||||
Reference in New Issue
Block a user