diff --git a/shared/constants/settings.tsx b/shared/constants/settings.tsx index b54c44d6f26e..b3be63c307c3 100644 --- a/shared/constants/settings.tsx +++ b/shared/constants/settings.tsx @@ -12,7 +12,6 @@ export const settingsFeedbackTab = 'settingsTabs.feedbackTab' export const settingsFoldersTab = 'settingsTabs.foldersTab' export const settingsFsTab = 'settingsTabs.fsTab' export const settingsGitTab = 'settingsTabs.gitTab' -export const settingsInvitationsTab = 'settingsTabs.invitationsTab' export const settingsAccountTab = 'settingsTabs.accountTab' export const settingsNotificationsTab = 'settingsTabs.notificationsTab' export const settingsPasswordTab = 'settingsTabs.password' @@ -26,7 +25,6 @@ export const settingsWhatsNewTab = 'settingsTabs.whatsNewTab' export type SettingsTab = | typeof settingsAccountTab | typeof settingsUpdatePaymentTab - | typeof settingsInvitationsTab | typeof settingsNotificationsTab | typeof settingsAdvancedTab | typeof settingsFeedbackTab diff --git a/shared/settings/invite-generated/index.d.ts b/shared/settings/invite-generated/index.d.ts deleted file mode 100644 index b9eaba61f7e5..000000000000 --- a/shared/settings/invite-generated/index.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type * as React from 'react' - -export type Props = { - email?: string - link: string -} - -export declare const InviteGeneratedRender: (p: Props) => React.ReactNode -declare const InviteGenerated: (p: Props) => React.ReactNode -export default InviteGenerated diff --git a/shared/settings/invite-generated/index.desktop.tsx b/shared/settings/invite-generated/index.desktop.tsx deleted file mode 100644 index fb4f70956367..000000000000 --- a/shared/settings/invite-generated/index.desktop.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as Kb from '@/common-adapters' -import * as C from '@/constants' -import type {Props} from '.' - -const InviteGeneratedRender = (props: Props) => { - const {link, email} = props - const onClose = C.useRouterState(s => s.dispatch.navigateUp) - return ( - - - - {email ? ( - - Yay! We emailed {email}, but you can also give them the below - link: - - ) : ( - - Yay! Please share the below link with your friend. It contains signup & install instructions. - - )} - - - - {link} - - - - - ) -} - -const styles = Kb.Styles.styleSheetCreate(() => ({ - icon: Kb.Styles.platformStyles({ - isElectron: { - ...Kb.Styles.desktopStyles.clickable, - position: 'absolute', - right: Kb.Styles.globalMargins.small, - top: Kb.Styles.globalMargins.small, - }, - }), - linkContainer: { - backgroundColor: Kb.Styles.globalColors.greenLighter, - borderRadius: Kb.Styles.borderRadius, - height: 32, - marginTop: Kb.Styles.globalMargins.tiny, - paddingLeft: Kb.Styles.globalMargins.xsmall, - paddingRight: Kb.Styles.globalMargins.xsmall, - }, - - text: { - paddingTop: Kb.Styles.globalMargins.medium, - width: 440, - }, -})) -export default InviteGeneratedRender diff --git a/shared/settings/invite-generated/index.native.tsx b/shared/settings/invite-generated/index.native.tsx deleted file mode 100644 index db77d626ea6b..000000000000 --- a/shared/settings/invite-generated/index.native.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function InviteGenerated() { - return null -} - -export default InviteGenerated diff --git a/shared/settings/invites/index.d.ts b/shared/settings/invites/index.d.ts deleted file mode 100644 index 234efc321d71..000000000000 --- a/shared/settings/invites/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type * as React from 'react' -declare const Invites: () => React.ReactNode -export default Invites diff --git a/shared/settings/invites/index.desktop.tsx b/shared/settings/invites/index.desktop.tsx deleted file mode 100644 index 9389385972b9..000000000000 --- a/shared/settings/invites/index.desktop.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import * as Kb from '@/common-adapters' -import type {AcceptedInvite, PendingInvite} from '@/stores/settings-invites' -import * as React from 'react' -import SubHeading from '../subheading' -import * as dateFns from 'date-fns' -import * as C from '@/constants' -import {useProfileState} from '@/stores/profile' -import {useState as useSettingsInvitesState} from '@/stores/settings-invites' - -// Like intersperse but takes a function to define the separator -function intersperseFn( - separatorFn: (index: number, x: A, a: Array) => B, - arr: Array -): Array { - if (arr.length === 0) { - return arr - } - - const toReturn = new Array(arr.length * 2 - 1) - toReturn[0] = arr[0]! - for (let i = 1; i < arr.length; i++) { - toReturn[i * 2 - 1] = separatorFn(i, arr[i]!, arr) - toReturn[i * 2] = arr[i]! - } - return toReturn -} - -const Invites = () => { - const invitesState = useSettingsInvitesState( - C.useShallow(s => ({ - acceptedInvites: s.acceptedInvites, - error: s.error, - loadInvites: s.dispatch.loadInvites, - pendingInvites: s.pendingInvites, - reclaimInvite: s.dispatch.reclaimInvite, - resetError: s.dispatch.resetError, - sendInvite: s.dispatch.sendInvite, - })) - ) - const {acceptedInvites, error, loadInvites, pendingInvites} = invitesState - const {reclaimInvite, resetError, sendInvite} = invitesState - const waitingForResponse = C.Waiting.useAnyWaiting(C.waitingKeySettingsGeneric) - const onClearError = resetError - const onGenerateInvitation = sendInvite - const onReclaimInvitation = reclaimInvite - const onRefresh = loadInvites - const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) - const onSelectPendingInvite = (invite: PendingInvite) => { - navigateAppend({props: {email: invite.email, link: invite.url}, selected: 'inviteSent'}) - } - const onSelectUser = useProfileState(s => s.dispatch.showUserProfile) - - const [inviteEmail, setInviteEmail] = React.useState('') - const [inviteMessage, setInviteMessage] = React.useState('') - const [showMessageField, setShowMessageField] = React.useState(false) - - React.useEffect(() => { - onRefresh() - }, [onRefresh]) - - React.useEffect(() => { - return () => { - onClearError() - } - }, [error, onClearError]) - - const handleChangeEmail = (email: string) => { - setInviteEmail(email) - setShowMessageField(showMessageField || email.length > 0) - if (error) onClearError() - } - - const invite = () => { - onGenerateInvitation(inviteEmail, inviteMessage) - } - - return ( - - {!!error && ( - - - - )} - - - - {showMessageField && ( - - )} - - - {pendingInvites.length > 0 && ( - - Pending invites ({pendingInvites.length}) - {intersperseDividers( - pendingInvites.map(invite => ( - onReclaimInvitation(id)} - onSelectPendingInvite={invite => onSelectPendingInvite(invite)} - /> - )) - )} - - )} - - Accepted invites ({acceptedInvites.length}) - {intersperseDividers( - acceptedInvites.map(invite => ( - onSelectUser(invite.username)} - /> - )) - )} - - - - ) -} - -function intersperseDividers(arr: Array) { - return intersperseFn(i => , arr) -} - -function PendingInviteItem({ - invite, - onReclaimInvitation, - onSelectPendingInvite, -}: { - invite: PendingInvite - onReclaimInvitation: (id: string) => void - onSelectPendingInvite: (invite: PendingInvite) => void -}) { - return ( - - {invite.email ? ( - - ) : ( - - )} - - onReclaimInvitation(invite.id)} - style={{color: Kb.Styles.globalColors.redDark}} - > - Reclaim - - - ) -} - -function PendingEmailContent({ - invite, - onSelectPendingInvite, -}: { - invite: PendingInvite - onSelectPendingInvite: (invite: PendingInvite) => void -}) { - return ( - - - - onSelectPendingInvite(invite)}> - {invite.email} - - - Invited {dateFns.format(dateFns.fromUnixTime(invite.created), 'MMM d, yyyy')} - - - - ) -} - -function PendingURLContent({invite}: {invite: PendingInvite}) { - return ( - - - - {invite.url} - - - ) -} - -function AcceptedInviteItem(p: {invite: AcceptedInvite; onClick: () => void}) { - const {invite, onClick} = p - return ( - - - - - - - ) -} - -const styles = Kb.Styles.styleSheetCreate(() => ({ - container: { - alignSelf: 'flex-start' as const, - marginTop: Kb.Styles.globalMargins.small, - minHeight: 269, - width: 400, - }, - inviteItem: { - ...Kb.Styles.globalStyles.flexBoxRow, - alignItems: 'center' as const, - flexShrink: 0, - height: 40, - marginLeft: Kb.Styles.globalMargins.tiny, - marginRight: Kb.Styles.globalMargins.tiny, - }, -})) - -export default Invites diff --git a/shared/settings/invites/index.native.tsx b/shared/settings/invites/index.native.tsx deleted file mode 100644 index 0e65003e748d..000000000000 --- a/shared/settings/invites/index.native.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function Invites() { - return null -} - -export default Invites diff --git a/shared/settings/routes.tsx b/shared/settings/routes.tsx index dead7baabb06..7f1f0232cfd8 100644 --- a/shared/settings/routes.tsx +++ b/shared/settings/routes.tsx @@ -48,7 +48,6 @@ export const sharedNewRoutes = { screen: React.lazy(async () => import('./files')), }, [Settings.settingsGitTab]: gitRoutes.gitRoot, - [Settings.settingsInvitationsTab]: {screen: React.lazy(async () => import('./invites'))}, [Settings.settingsNotificationsTab]: { getOptions: {title: 'Notifications'}, screen: React.lazy(async () => import('./notifications')), @@ -66,7 +65,6 @@ export const sharedNewRoutes = { getOptions: {title: 'Confirm'}, screen: React.lazy(async () => import('./db-nuke.confirm')), }, - inviteSent: C.makeScreen(React.lazy(async () => import('./invite-generated'))), keybaseLinkError: {screen: React.lazy(async () => import('../deeplinks/error'))}, makeIcons: {screen: React.lazy(async () => import('./make-icons.page'))}, removeDevice: devicesRoutes.deviceRevoke, diff --git a/shared/settings/sub-nav/left-nav.tsx b/shared/settings/sub-nav/left-nav.tsx index 6e7337a14961..041386f3f131 100644 --- a/shared/settings/sub-nav/left-nav.tsx +++ b/shared/settings/sub-nav/left-nav.tsx @@ -114,14 +114,6 @@ const LeftNav = (props: Props) => { selected={props.selected === Settings.settingsFsTab} onClick={props.onClick} /> - {!Kb.Styles.isTablet && ( - - )} - acceptedInvites: Array - error: string -}> - -const initialStore: Store = { - acceptedInvites: [], - error: '', - pendingInvites: [], -} - -export interface State extends Store { - dispatch: { - loadInvites: () => void - reclaimInvite: (inviteId: string) => void - resetError: () => void - resetState: 'default' - sendInvite: (email: string, message: string) => void - } -} - -export const useState = Z.createZustand((set, get) => { - const dispatch: State['dispatch'] = { - loadInvites: () => { - const f = async () => { - const json = await T.RPCGen.apiserverGetWithSessionRpcPromise( - { - args: [], - endpoint: 'invitations_sent', - }, - waitingKeySettingsGeneric - ) - const results = JSON.parse(json.body) as - | { - invitations: Array<{ - assertion: string | undefined - ctime: number - email: string - invitation_id: string - short_code: string - type: string - uid: string - username: string - }> - } - | undefined - - const acceptedInvites: Array = [] - const pendingInvites: Array = [] - - results?.invitations.forEach(i => { - const invite: Invitation = { - created: i.ctime, - email: i.email, - id: i.invitation_id, - // type will get filled in later - type: '', - uid: i.uid, - // First ten chars of invite code is sufficient - url: 'keybase.io/inv/' + i.invitation_id.slice(0, 10), - username: i.username, - } - // Here's an algorithm for interpreting invitation entries. - // 1: username+uid => accepted invite, else - // 2: email set => pending email invite, else - // 3: pending invitation code invite - if (i.username && i.uid) { - invite.type = 'accepted' - acceptedInvites.push(invite) - } else { - invite.type = 'pending' - pendingInvites.push(invite) - } - }) - set(s => { - s.acceptedInvites = acceptedInvites - s.pendingInvites = pendingInvites - }) - } - ignorePromise(f()) - }, - reclaimInvite: inviteId => { - const f = async () => { - try { - await T.RPCGen.apiserverPostRpcPromise( - { - args: [{key: 'invitation_id', value: inviteId}], - endpoint: 'cancel_invitation', - }, - waitingKeySettingsGeneric - ) - } catch (e) { - logger.warn('Error reclaiming an invite:', e) - } - get().dispatch.loadInvites() - } - ignorePromise(f()) - }, - resetError: () => { - set(s => { - s.error = '' - }) - }, - resetState: 'default', - sendInvite: (email, message) => { - const f = async () => { - try { - const args = [{key: 'email', value: trim(email)}] - if (message) { - args.push({key: 'invitation_message', value: message}) - } - - const response = await T.RPCGen.apiserverPostRpcPromise( - {args, endpoint: 'send_invitation'}, - waitingKeySettingsGeneric - ) - const parsedBody = JSON.parse(response.body) as undefined | Partial<{invitation_id: string}> - const invitationId = parsedBody?.invitation_id?.slice(0, 10) ?? '' - if (!invitationId) return - const link = 'keybase.io/inv/' + invitationId - set(s => { - s.error = '' - }) - get().dispatch.loadInvites() - navigateAppend({props: {email, link}, selected: 'inviteSent'}) - } catch (error) { - if (!(error instanceof RPCError)) { - return - } - logger.warn('Error sending an invite:', error) - const msg = error.desc - set(s => { - s.error = msg - }) - get().dispatch.loadInvites() - } - } - ignorePromise(f()) - }, - } - return { - ...initialStore, - dispatch, - } -}) diff --git a/shared/stores/signup.tsx b/shared/stores/signup.tsx index c2bf965d4802..a73a84250abb 100644 --- a/shared/stores/signup.tsx +++ b/shared/stores/signup.tsx @@ -7,7 +7,7 @@ import * as Z from '@/util/zustand' import logger from '@/logger' import trim from 'lodash/trim' import {RPCError} from '@/util/errors' -import {isValidEmail, isValidName, isValidUsername} from '@/util/simple-validators' +import {isValidUsername} from '@/util/simple-validators' import {navigateAppend, navigateUp} from '@/constants/router' import {useConfigState} from '@/stores/config' @@ -50,13 +50,11 @@ export interface State extends Store { onShowPermissionsPrompt?: (p: {justSignedUp?: boolean}) => void } checkDeviceName: (devicename: string) => void - checkInviteCode: () => void checkUsername: (username: string) => void clearJustSignedUpEmail: () => void goBackAndClearErrors: () => void onEngineIncomingImpl: (action: EngineGen.Actions) => void requestAutoInvite: (username?: string) => void - requestInvite: (email: string, name: string) => void resetState: () => void restartSignup: () => void setJustSignedUpEmail: (email: string) => void @@ -78,8 +76,8 @@ export const useSignupState = Z.createZustand((set, get) => { } const {username, inviteCode, devicename} = get() - if (!username || !inviteCode || !devicename) { - logger.warn('Missing data during signup phase', username, inviteCode, devicename) + if (!username || !devicename) { + logger.warn('Missing data during signup phase', username, devicename) throw new Error('Missing data for signup') } @@ -166,29 +164,6 @@ export const useSignupState = Z.createZustand((set, get) => { } ignorePromise(f()) }, - checkInviteCode: () => { - const invitationCode = get().inviteCode - const f = async () => { - try { - await T.RPCGen.signupCheckInvitationCodeRpcPromise({invitationCode}, S.waitingKeySignup) - set(s => { - s.signupError = undefined - }) - if (noErrors()) { - navigateUp() - navigateAppend('signupEnterUsername') - } - } catch (error) { - if (error instanceof RPCError) { - const e = error - set(s => { - s.signupError = e - }) - } - } - } - ignorePromise(f()) - }, checkUsername: username => { set(s => { s.username = username @@ -275,43 +250,13 @@ export const useSignupState = Z.createZustand((set, get) => { set(s => { s.inviteCode = inviteCode }) - get().dispatch.checkInviteCode() } catch { set(s => { s.inviteCode = '' }) - navigateAppend('signupError') - } - } - ignorePromise(f()) - }, - // shouldn't ever be used - requestInvite: (email, name) => { - set(s => { - s.email = email - s.emailError = isValidEmail(email) - s.name = name - s.nameError = isValidName(name) - }) - const f = async () => { - if (!noErrors()) { - return - } - try { - await T.RPCGen.signupInviteRequestRpcPromise( - {email, fullname: name, notes: 'Requested through GUI app'}, - S.waitingKeySignup - ) - // C.useRouterState.getState().dispatch.navigateAppend('signupRequestInviteSuccess') - } catch (error) { - if (error instanceof RPCError) { - const emailError = `Sorry can't get an invite: ${error.desc}` - set(s => { - s.emailError = emailError - s.nameError = '' - }) - } } + navigateUp() + navigateAppend('signupEnterUsername') } ignorePromise(f()) },