diff --git a/.github/workflows/mobile-update.yml b/.github/workflows/mobile-update.yml index 1d1c4f68d5..8ae35830fd 100644 --- a/.github/workflows/mobile-update.yml +++ b/.github/workflows/mobile-update.yml @@ -60,3 +60,4 @@ jobs: RECAPTCHA_SITE_KEY_ANDROID: ${{ secrets.RECAPTCHA_SITE_KEY_ANDROID }} RECAPTCHA_SITE_KEY_IOS: ${{ secrets.RECAPTCHA_SITE_KEY_IOS }} TLON_EMPLOYEE_GROUP: ${{ secrets.TLON_EMPLOYEE_GROUP }} + INVITE_SERVICE_ENDPOINT: ${{ secrets.INVITE_SERVICE_ENDPOINT }} diff --git a/apps/tlon-mobile/android/app/build.gradle b/apps/tlon-mobile/android/app/build.gradle index db9e98d1c7..ff603cc3f1 100644 --- a/apps/tlon-mobile/android/app/build.gradle +++ b/apps/tlon-mobile/android/app/build.gradle @@ -88,7 +88,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion versionCode 108 - versionName "4.1.7" + versionName "4.2.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) } diff --git a/apps/tlon-mobile/app.config.ts b/apps/tlon-mobile/app.config.ts index 32510a51fc..8118cc6d99 100644 --- a/apps/tlon-mobile/app.config.ts +++ b/apps/tlon-mobile/app.config.ts @@ -51,6 +51,8 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ branchDomain: isPreview ? process.env.BRANCH_DOMAIN_TEST : process.env.BRANCH_DOMAIN_PROD, + inviteServiceEndpoint: process.env.INVITE_SERVICE_ENDPOINT, + inviteServiceIsDev: process.env.INVITE_SERVICE_IS_DEV, }, ios: { runtimeVersion: '4.0.2', diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index cb8f7be105..cfb69afc36 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1427,7 +1427,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.1.7; + MARKETING_VERSION = 4.2.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1465,7 +1465,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.1.7; + MARKETING_VERSION = 4.2.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1689,7 +1689,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.1.7; + MARKETING_VERSION = 4.2.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1732,7 +1732,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.1.7; + MARKETING_VERSION = 4.2.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/apps/tlon-mobile/src/fixtures/Onboarding.fixture.tsx b/apps/tlon-mobile/src/fixtures/Onboarding.fixture.tsx index c5683a933f..1aad521cd6 100644 --- a/apps/tlon-mobile/src/fixtures/Onboarding.fixture.tsx +++ b/apps/tlon-mobile/src/fixtures/Onboarding.fixture.tsx @@ -1,15 +1,8 @@ import { NavigationContainer } from '@react-navigation/native'; -import { - Context as BranchContext, - LureData, -} from '@tloncorp/app/contexts/branch'; import { exampleContacts } from '@tloncorp/app/fixtures/contentHelpers'; import { group } from '@tloncorp/app/fixtures/fakeData'; -import { - DeepLinkData, - QueryClientProvider, - queryClient, -} from '@tloncorp/shared'; +import { Context as BranchContext } from '@tloncorp/app/contexts/branch'; +import { AppInvite, QueryClientProvider, queryClient } from '@tloncorp/shared'; import { Theme } from '@tloncorp/ui'; import { PropsWithChildren, useState } from 'react'; import { useFixtureSelect } from 'react-cosmos/client'; @@ -47,7 +40,7 @@ function OnboardingFixture({ hasGroupInvite, children, }: PropsWithChildren<{ hasGroupInvite: boolean }>) { - const [lure, setLure] = useState( + const [lure, setLure] = useState( hasGroupInvite ? { id: group.id, @@ -108,7 +101,7 @@ function OnboardingFixture({ void, + setLure: setLure as unknown as (lure: AppInvite) => void, clearLure: () => setLure(undefined), clearDeepLink: () => {}, deepLinkPath: undefined, diff --git a/apps/tlon-mobile/src/lib/signupContext.tsx b/apps/tlon-mobile/src/lib/signupContext.tsx index f4f8e3b9bb..65e6ad707c 100644 --- a/apps/tlon-mobile/src/lib/signupContext.tsx +++ b/apps/tlon-mobile/src/lib/signupContext.tsx @@ -7,6 +7,7 @@ import { createDevLogger } from '@tloncorp/shared'; import * as api from '@tloncorp/shared/api'; import { SignupParams, didSignUp, signupData } from '@tloncorp/shared/db'; import * as store from '@tloncorp/shared/store'; +import PostHog, { usePostHog } from 'posthog-react-native'; import { createContext, useCallback, @@ -14,6 +15,7 @@ import { useEffect, useState, } from 'react'; +import branch from 'react-native-branch'; const logger = createDevLogger('signup', true); @@ -56,6 +58,7 @@ export const SignupProvider = ({ children }: { children: React.ReactNode }) => { const [reviveCheckComplete, setReviveCheckComplete] = useState(false); const { bootPhase, bootReport, kickOffBootSequence } = useBootSequence(values); + const postHog = usePostHog(); const setOnboardingValues = useCallback( (newValues: Partial) => { @@ -80,6 +83,7 @@ export const SignupProvider = ({ children }: { children: React.ReactNode }) => { nickname: values.nickname, telemetry: values.telemetry, notificationToken: values.notificationToken, + postHog, }; runPostSignupActions(postSignupParams); logger.trackEvent('hosted signup report', { @@ -139,6 +143,7 @@ async function runPostSignupActions(params: { nickname?: string; telemetry?: boolean; notificationToken?: string; + postHog?: PostHog; }) { if (params.nickname) { try { @@ -156,6 +161,15 @@ async function runPostSignupActions(params: { if (typeof params.telemetry !== 'undefined') { try { await api.updateTelemetrySetting(params.telemetry); + if (!params.telemetry) { + // we give some wiggle room here before disabling telemetry to allow + // the initial signup flow to complete before severing analytics + const tenMinutes = 10 * 60 * 1000; + setTimeout(() => { + params.postHog?.optOut(); + branch.disableTracking(true); + }, tenMinutes); + } } catch (e) { logger.trackError('post signup: failed to set telemetry', { errorMessage: e.message, diff --git a/apps/tlon-mobile/src/screens/Onboarding/CheckOTPScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/CheckOTPScreen.tsx index f9cf46b0ad..858bf80fb5 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/CheckOTPScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/CheckOTPScreen.tsx @@ -175,6 +175,7 @@ export const CheckOTPScreen = ({ navigation, route: { params } }: Props) => { phoneNumber: otpMethod === 'phone' ? signupContext.phoneNumber! : undefined, email: otpMethod === 'email' ? signupContext.email! : undefined, + hostingUser: user, }); navigation.navigate('ReserveShip', { user }); } diff --git a/apps/tlon-mobile/src/screens/Onboarding/PasteInviteLinkScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/PasteInviteLinkScreen.tsx index 87e90b729b..2b2a0856a4 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/PasteInviteLinkScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/PasteInviteLinkScreen.tsx @@ -11,7 +11,7 @@ import { DeepLinkData, createInviteLinkRegex, extractNormalizedInviteLink, - getMetadaFromInviteLink, + getInviteLinkMeta, } from '@tloncorp/shared'; import { Field, @@ -68,12 +68,13 @@ export const PasteInviteLinkScreen = ({ navigation }: Props) => { setMetadataError(null); if (extractedLink) { try { - const inviteLinkMeta = await getMetadaFromInviteLink( - extractedLink, - BRANCH_KEY - ); - if (inviteLinkMeta) { - setLure(inviteLinkMeta as DeepLinkData); + const appInvite = await getInviteLinkMeta({ + inviteLink: extractedLink, + branchDomain: BRANCH_DOMAIN, + branchKey: BRANCH_KEY, + }); + if (appInvite) { + setLure(appInvite); return; } else { throw new Error('Failed to retrieve invite metadata'); diff --git a/apps/tlon-mobile/src/screens/Onboarding/SetTelemetryScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/SetTelemetryScreen.tsx index 2bca512f15..537a324dda 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/SetTelemetryScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/SetTelemetryScreen.tsx @@ -1,5 +1,5 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; -import { useSignupContext } from '.././../lib/signupContext'; +import { trackOnboardingAction } from '@tloncorp/app/utils/posthog'; import { ScreenHeader, SizableText, @@ -8,12 +8,11 @@ import { XStack, YStack, } from '@tloncorp/ui'; -import { usePostHog } from 'posthog-react-native'; import { useCallback, useState } from 'react'; import { Switch } from 'react-native'; -import branch from 'react-native-branch'; import type { OnboardingStackParamList } from '../../types'; +import { useSignupContext } from '.././../lib/signupContext'; type Props = NativeStackScreenProps; @@ -24,21 +23,19 @@ export const SetTelemetryScreen = ({ }, }: Props) => { const [isEnabled, setIsEnabled] = useState(true); - const postHog = usePostHog(); const signupContext = useSignupContext(); const handleNext = useCallback(() => { signupContext.setOnboardingValues({ telemetry: isEnabled }); - - if (!isEnabled) { - postHog?.optOut(); - branch.disableTracking(true); - } + trackOnboardingAction({ + actionName: 'SetTelemetry', + telemetryEnabled: isEnabled, + }); navigation.push('ReserveShip', { user, }); - }, [isEnabled, user, postHog, navigation, signupContext]); + }, [isEnabled, user, navigation, signupContext]); return ( diff --git a/apps/tlon-mobile/src/screens/Onboarding/SignupScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/SignupScreen.tsx index d8a27adfb2..96606c846d 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/SignupScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/SignupScreen.tsx @@ -10,7 +10,7 @@ import { } from '@tloncorp/app/contexts/branch'; import { HostingError } from '@tloncorp/app/lib/hostingApi'; import { trackOnboardingAction } from '@tloncorp/app/utils/posthog'; -import { createDevLogger } from '@tloncorp/shared'; +import { AnalyticsEvent, createDevLogger } from '@tloncorp/shared'; import { Field, KeyboardAvoidingView, @@ -81,7 +81,7 @@ export const SignupScreen = ({ navigation }: Props) => { actionName: 'Phone or Email Submitted', phoneNumber: phoneForm.getValues().phoneNumber, email: emailForm.getValues().email, - lure: signupParams.lureId, + lure: lureMeta?.id, }); signupContext.setOnboardingValues({ @@ -93,14 +93,7 @@ export const SignupScreen = ({ navigation }: Props) => { mode: 'signup', otpMethod, }); - }, [ - phoneForm, - emailForm, - signupParams.lureId, - signupContext, - navigation, - otpMethod, - ]); + }, [phoneForm, emailForm, lureMeta, signupContext, navigation, otpMethod]); const toggleSignupMode = useCallback(() => { setRemoteError(undefined); @@ -113,10 +106,10 @@ export const SignupScreen = ({ navigation }: Props) => { setIsSubmitting(true); try { const { enabled } = await hostingApi.getHostingAvailability({ - lure: signupParams.lureId, priorityToken: signupParams.priorityToken, }); if (!enabled) { + logger.trackError(AnalyticsEvent.InvitedUserFailedInventoryCheck); navigation.navigate('JoinWaitList', {}); return; } @@ -167,7 +160,6 @@ export const SignupScreen = ({ navigation }: Props) => { setIsSubmitting(false); }, [ hostingApi, - signupParams.lureId, signupParams.priorityToken, recaptcha, otpMethod, @@ -276,8 +268,8 @@ export const SignupScreen = ({ navigation }: Props) => { diff --git a/apps/tlon-mobile/src/screens/Onboarding/TlonLogin.tsx b/apps/tlon-mobile/src/screens/Onboarding/TlonLogin.tsx index 6f5b5f2cf5..e890e8fa14 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/TlonLogin.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/TlonLogin.tsx @@ -192,7 +192,6 @@ export const TlonLoginScreen = ({ navigation, route }: Props) => { loading={isSubmitting} disabled={ isSubmitting || - remoteError !== undefined || (otpMethod === 'phone' ? !phoneForm.formState.isValid : !emailForm.formState.isValid) @@ -256,6 +255,7 @@ export const TlonLoginScreen = ({ navigation, route }: Props) => { ) : ( { }); return ( - + { )} name="password" /> + + + By logging in you agree to Tlon’s{' '} + + Terms of Service + + + Forgot password? - - - By logging in you agree to Tlon’s{' '} - - Terms of Service - - - diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index e4c2b18c17..b336755ce0 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -1180,8 +1180,7 @@ =. importing & =. indices (~(put by indices) [%base ~] *index:a) =. cor set-chat-reads - ::REVIEW maybe need a scry api version bump here? - =+ .^(=channels:c %gx (scry-path %channels /v2/channels/full/noun)) + =+ .^(=channels:c %gx (scry-path %channels /v3/channels/full/noun)) =. cor (set-volumes channels) =. cor (set-channel-reads channels) =. cor refresh-all-summaries diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 364f628953..daafc35638 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -734,6 +734,9 @@ :: [%x %v2 %channels full=?(~ [%full ~])] ``channels-2+!>((uv-channels-2:utils v-channels ?=(^ full.pole))) + :: + [%x %v3 %channels full=?(~ [%full ~])] + ``channels-3+!>((uv-channels-3:utils v-channels ?=(^ full.pole))) :: [%x ?(%v0 %v1) %init ~] ``noun+!>([unreads (uv-channels-1:utils v-channels)]) [%x %v2 %init ~] ``noun+!>([unreads (uv-channels-2:utils v-channels |)]) diff --git a/desk/app/grouper.hoon b/desk/app/grouper.hoon index 394f13c088..97b6226ae5 100644 --- a/desk/app/grouper.hoon +++ b/desk/app/grouper.hoon @@ -58,8 +58,10 @@ :: %grouper-answer-enabled =/ [name=cord enabled=?] !<([cord ?] vase) - :_ this - ~[[%give %fact ~[[%group-enabled (scot %p src.bowl) name ~]] %json !>(b+enabled)]] + :- ~[[%give %fact ~[[%group-enabled (scot %p src.bowl) name ~]] %json !>(b+enabled)]] + ?: enabled + this(enabled-groups (~(put in enabled-groups) name)) + this(enabled-groups (~(del in enabled-groups) name)) :: %grouper-check-link =+ !<(=(pole knot) vase) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index d5d88e2958..2eb3f1a433 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v37cg6.ouoi0.g7doo.ng5jr.bh9aa.glob' 0v37cg6.ouoi0.g7doo.ng5jr.bh9aa] + glob-http+['https://bootstrap.urbit.org/glob-0v4.179bj.emcg0.l6hks.j94gb.4vu3p.glob' 0v4.179bj.emcg0.l6hks.j94gb.4vu3p] base+'groups' version+[6 4 2] website+'https://tlon.io' diff --git a/desk/mar/channels-2.hoon b/desk/mar/channels-2.hoon index 751f868751..54e6239c44 100644 --- a/desk/mar/channels-2.hoon +++ b/desk/mar/channels-2.hoon @@ -9,6 +9,6 @@ -- ++ grab |% - ++ noun channels:d + ++ noun channels:v1:old:d -- -- diff --git a/desk/mar/channels-3.hoon b/desk/mar/channels-3.hoon new file mode 100644 index 0000000000..e498ae30d5 --- /dev/null +++ b/desk/mar/channels-3.hoon @@ -0,0 +1,14 @@ +/- c=channels +/+ j=channel-json +|_ =channels:c +++ grad %noun +++ grow + |% + ++ noun channels + ++ json (channels-2:enjs:j channels) + -- +++ grab + |% + ++ noun channels:c + -- +-- diff --git a/packages/app/constants.ts b/packages/app/constants.ts index c7fc35ff82..62c1e2d0a8 100644 --- a/packages/app/constants.ts +++ b/packages/app/constants.ts @@ -47,3 +47,5 @@ export const IGNORE_COSMOS = extra.ignoreCosmos === 'true'; export const TLON_EMPLOYEE_GROUP = extra.TlonEmployeeGroup ?? ''; export const BRANCH_KEY = extra.branchKey ?? ''; export const BRANCH_DOMAIN = extra.branchDomain ?? ''; +export const INVITE_SERVICE_ENDPOINT = extra.inviteServiceEndpoint ?? ''; +export const INVITE_SERVICE_IS_DEV = extra.inviteServiceIsDev === 'true'; diff --git a/packages/app/contexts/branch.tsx b/packages/app/contexts/branch.tsx index 4fcb4f3426..3c6cba6e60 100644 --- a/packages/app/contexts/branch.tsx +++ b/packages/app/contexts/branch.tsx @@ -1,5 +1,10 @@ -import { DeepLinkMetadata, createDevLogger } from '@tloncorp/shared'; -import { DeepLinkData, extractLureMetadata } from '@tloncorp/shared/logic'; +import { createDevLogger } from '@tloncorp/shared'; +import { + AppInvite, + DeepLinkData, + Lure, + extractLureMetadata, +} from '@tloncorp/shared/logic'; import { type ReactNode, createContext, @@ -16,22 +21,12 @@ import storage from '../lib/storage'; import { getPathFromWer } from '../utils/string'; import { useShip } from './ship'; -export interface LureData extends DeepLinkMetadata { - id: string; - shouldAutoJoin: boolean; -} - -type Lure = { - lure: LureData | undefined; - priorityToken: string | undefined; -}; - type State = Lure & { deepLinkPath: string | undefined; }; type ContextValue = State & { - setLure: (metadata: DeepLinkData) => void; + setLure: (invite: AppInvite) => void; clearLure: () => void; clearDeepLink: () => void; }; @@ -153,6 +148,7 @@ export const BranchProvider = ({ children }: { children: ReactNode }) => { }, priorityToken: params.token as string | undefined, }; + console.log(`setting deeplink lure`, nextLure); setState({ ...nextLure, deepLinkPath: undefined, @@ -191,11 +187,10 @@ export const BranchProvider = ({ children }: { children: ReactNode }) => { }, [isAuthenticated]); const setLure = useCallback( - (metadata: DeepLinkData) => { + (invite: AppInvite) => { const nextLure: Lure = { lure: { - ...metadata, - id: metadata.lure as string, + ...invite, // if not already authenticated, we should run Lure's invite auto-join capability after signing in shouldAutoJoin: !isAuthenticated, }, diff --git a/packages/app/features/groups/GroupMetaScreen.tsx b/packages/app/features/groups/GroupMetaScreen.tsx index 95966b3f4a..2c8876cf96 100644 --- a/packages/app/features/groups/GroupMetaScreen.tsx +++ b/packages/app/features/groups/GroupMetaScreen.tsx @@ -10,7 +10,10 @@ import { } from '@tloncorp/ui'; import { useCallback, useState } from 'react'; -import { BRANCH_DOMAIN, BRANCH_KEY } from '../../constants'; +import { + INVITE_SERVICE_ENDPOINT, + INVITE_SERVICE_IS_DEV, +} from '../../constants'; import { useGroupContext } from '../../hooks/useGroupContext'; import { GroupSettingsStackParamList } from '../../navigation/types'; @@ -30,8 +33,8 @@ export function GroupMetaScreen(props: Props) { const [showDeleteSheet, setShowDeleteSheet] = useState(false); const { enabled, describe } = store.useLure({ flag: groupId, - branchDomain: BRANCH_DOMAIN, - branchKey: BRANCH_KEY, + inviteServiceEndpoint: INVITE_SERVICE_ENDPOINT, + inviteServiceIsDev: INVITE_SERVICE_IS_DEV, }); const handleSubmit = useCallback( @@ -39,12 +42,7 @@ export function GroupMetaScreen(props: Props) { setGroupMetadata(data); props.navigation.goBack(); if (enabled) { - describe({ - title: data.title ?? '', - description: data.description ?? '', - image: data.iconImage ?? '', - cover: data.coverImage ?? '', - }); + describe(); } }, [setGroupMetadata, props.navigation, enabled, describe] diff --git a/packages/app/features/settings/AppInfoScreen.tsx b/packages/app/features/settings/AppInfoScreen.tsx index e1a04b635e..1c4bd9ec35 100644 --- a/packages/app/features/settings/AppInfoScreen.tsx +++ b/packages/app/features/settings/AppInfoScreen.tsx @@ -101,7 +101,7 @@ export function AppInfoScreen(props: Props) { }, [hasClients]); return ( - + props.navigation.goBack()} diff --git a/packages/app/features/settings/BlockedUsersScreen.tsx b/packages/app/features/settings/BlockedUsersScreen.tsx index cdec57e7de..e67432e055 100644 --- a/packages/app/features/settings/BlockedUsersScreen.tsx +++ b/packages/app/features/settings/BlockedUsersScreen.tsx @@ -36,7 +36,7 @@ export function BlockedUsersScreen(props: Props) { ); return ( - + props.navigation.goBack()} title="Blocked users" diff --git a/packages/app/features/settings/ManageAccountScreen.tsx b/packages/app/features/settings/ManageAccountScreen.tsx index d4e262da02..8e42041325 100644 --- a/packages/app/features/settings/ManageAccountScreen.tsx +++ b/packages/app/features/settings/ManageAccountScreen.tsx @@ -6,13 +6,13 @@ import { YStack, isWeb, } from '@tloncorp/ui'; -import { useResetDb } from '../../hooks/useResetDb'; -import { useWebView } from '../../hooks/useWebview'; import { useCallback, useEffect, useState } from 'react'; import { Alert } from 'react-native'; import { WebView } from 'react-native-webview'; import { useHandleLogout } from '../../hooks/useHandleLogout'; +import { useResetDb } from '../../hooks/useResetDb'; +import { useWebView } from '../../hooks/useWebview'; import { checkIfAccountDeleted } from '../../lib/hostingApi'; import { RootStackParamList } from '../../navigation/types'; import { getHostingToken, getHostingUserId } from '../../utils/hosting'; @@ -89,7 +89,7 @@ export function ManageAccountScreen(props: Props) { }, [hostingSession?.isExpired, handleLogout, props.navigation]); return ( - + + navigation.goBack()} @@ -90,30 +91,36 @@ export function PushNotificationSettingsScreen({ navigation }: Props) { - setLevel('medium')}> - - All group activity - + setLevel('medium')}> + + + All group activity + + - setLevel('soft')}> - - - Mentions and replies only - - Direct messages will still notify unless you mute them. - - - + setLevel('soft')}> + + + + Mentions and replies only + + Direct messages will still notify unless you mute them. + + + + - setLevel('hush')}> - - Nothing - + setLevel('hush')}> + + + Nothing + + {numExceptions > 0 ? ( diff --git a/packages/app/features/settings/UserBugReportScreen.tsx b/packages/app/features/settings/UserBugReportScreen.tsx index 555e450d37..f303819521 100644 --- a/packages/app/features/settings/UserBugReportScreen.tsx +++ b/packages/app/features/settings/UserBugReportScreen.tsx @@ -7,7 +7,7 @@ import { FormText, ScreenHeader, ScrollView, - YStack, + View, } from '@tloncorp/ui'; import { useCallback } from 'react'; import { useForm } from 'react-hook-form'; @@ -43,7 +43,7 @@ export function UserBugReportScreen({ navigation }: Props) { ); return ( - <> + navigation.goBack()} @@ -81,6 +81,6 @@ export function UserBugReportScreen({ navigation }: Props) { - + ); } diff --git a/packages/app/lib/bootHelpers.ts b/packages/app/lib/bootHelpers.ts index f10126e889..1331264b6a 100644 --- a/packages/app/lib/bootHelpers.ts +++ b/packages/app/lib/bootHelpers.ts @@ -1,7 +1,7 @@ +import { AppInvite } from '@tloncorp/shared'; import { getLandscapeAuthCookie } from '@tloncorp/shared/api'; import * as db from '@tloncorp/shared/db'; -import { LureData } from '../contexts/branch'; import * as hostingApi from '../lib/hostingApi'; import { trackOnboardingAction } from '../utils/posthog'; import { getShipFromCookie, getShipUrl } from '../utils/ship'; @@ -121,7 +121,7 @@ async function authenticateNode( }; } -async function getInvitedGroupAndDm(lureMeta: LureData | null): Promise<{ +async function getInvitedGroupAndDm(lureMeta: AppInvite | null): Promise<{ invitedDm: db.Channel | null; tlonTeamDM: db.Channel | null; invitedGroup: db.Group | null; diff --git a/packages/app/navigation/desktop/ProfileScreenNavigator.tsx b/packages/app/navigation/desktop/ProfileScreenNavigator.tsx new file mode 100644 index 0000000000..f8f1407113 --- /dev/null +++ b/packages/app/navigation/desktop/ProfileScreenNavigator.tsx @@ -0,0 +1,53 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +import { AppInfoScreen } from '../../features/settings/AppInfoScreen'; +import { BlockedUsersScreen } from '../../features/settings/BlockedUsersScreen'; +import { FeatureFlagScreen } from '../../features/settings/FeatureFlagScreen'; +import { ManageAccountScreen } from '../../features/settings/ManageAccountScreen'; +import ProfileScreen from '../../features/settings/ProfileScreen'; +import { PushNotificationSettingsScreen } from '../../features/settings/PushNotificationSettingsScreen'; +import { UserBugReportScreen } from '../../features/settings/UserBugReportScreen'; +import { UserProfileScreen } from '../../features/top/UserProfileScreen'; + +const ProfileScreenStack = createNativeStackNavigator(); + +export const ProfileScreenNavigator = () => { + return ( + + + + + + + + + + + ); +}; diff --git a/packages/app/navigation/desktop/TopLevelDrawer.tsx b/packages/app/navigation/desktop/TopLevelDrawer.tsx index 36c1ba1a82..6ff8c53641 100644 --- a/packages/app/navigation/desktop/TopLevelDrawer.tsx +++ b/packages/app/navigation/desktop/TopLevelDrawer.tsx @@ -5,11 +5,11 @@ import { import * as store from '@tloncorp/shared/store'; import { AvatarNavIcon, NavIcon, YStack, useWebAppUpdate } from '@tloncorp/ui'; -import ProfileScreen from '../../features/settings/ProfileScreen'; import { ActivityScreen } from '../../features/top/ActivityScreen'; import { useCurrentUserId } from '../../hooks/useCurrentUser'; import { RootDrawerParamList } from '../types'; import { HomeNavigator } from './HomeNavigator'; +import { ProfileScreenNavigator } from './ProfileScreenNavigator'; const Drawer = createDrawerNavigator(); @@ -77,7 +77,7 @@ export const TopLevelDrawer = () => { > - + ); }; diff --git a/packages/app/provider/AppDataProvider.tsx b/packages/app/provider/AppDataProvider.tsx index 06df37d459..c1caa88841 100644 --- a/packages/app/provider/AppDataProvider.tsx +++ b/packages/app/provider/AppDataProvider.tsx @@ -2,7 +2,12 @@ import * as store from '@tloncorp/shared/store'; import { AppDataContextProvider } from '@tloncorp/ui'; import { PropsWithChildren } from 'react'; -import { BRANCH_DOMAIN, BRANCH_KEY } from '../constants'; +import { + BRANCH_DOMAIN, + BRANCH_KEY, + INVITE_SERVICE_ENDPOINT, + INVITE_SERVICE_IS_DEV, +} from '../constants'; import { useCurrentUserId } from '../hooks/useCurrentUser'; export function AppDataProvider({ @@ -23,6 +28,8 @@ export function AppDataProvider({ contacts={contactsQuery.data} branchKey={BRANCH_KEY} branchDomain={BRANCH_DOMAIN} + inviteServiceEndpoint={INVITE_SERVICE_ENDPOINT} + inviteServiceIsDev={INVITE_SERVICE_IS_DEV} calmSettings={calmSettingsQuery.data} session={session} webAppNeedsUpdate={webAppNeedsUpdate} diff --git a/packages/app/utils/posthog.ts b/packages/app/utils/posthog.ts index 5de42f6081..4600a94ef0 100644 --- a/packages/app/utils/posthog.ts +++ b/packages/app/utils/posthog.ts @@ -11,6 +11,7 @@ export type OnboardingProperties = { email?: string; phoneNumber?: string; ship?: string; + telemetryEnabled?: boolean; }; export let posthog: PostHog | undefined; @@ -19,7 +20,7 @@ export const posthogAsync = process.env.NODE_ENV === 'test' && !process.env.POST_HOG_IN_DEV ? undefined : PostHog.initAsync(POST_HOG_API_KEY, { - host: 'https://eu.posthog.com', + host: 'https://data-bridge-v1.vercel.app/ingest', enable: true, }); diff --git a/packages/shared/src/logic/analytics.ts b/packages/shared/src/logic/analytics.ts index 803e8e4090..2b64c9c306 100644 --- a/packages/shared/src/logic/analytics.ts +++ b/packages/shared/src/logic/analytics.ts @@ -6,4 +6,5 @@ export enum AnalyticsEvent { LoggedInBeforeSignup = 'Logged In Without Signing Up', FailedSignupOTP = 'Failed to send Signup OTP', FailedLoginOTP = 'Failed to send Login OTP', + InvitedUserFailedInventoryCheck = 'Invited User Failed Inventory Check', } diff --git a/packages/shared/src/logic/branch.ts b/packages/shared/src/logic/branch.ts index 565dcd0e62..fe07bf9857 100644 --- a/packages/shared/src/logic/branch.ts +++ b/packages/shared/src/logic/branch.ts @@ -77,6 +77,17 @@ export interface DeepLinkMetadata { invitedGroupIconImageUrl?: string; invitedGroupiconImageColor?: string; } + +export interface AppInvite extends DeepLinkMetadata { + id: string; + shouldAutoJoin: boolean; +} + +export type Lure = { + lure: AppInvite | undefined; + priorityToken: string | undefined; +}; + export interface DeepLinkData extends DeepLinkMetadata { $desktop_url: string; $canonical_url: string; @@ -114,31 +125,23 @@ export async function getDmLink( branchDomain: string, branchKey: string ): Promise { - const dmPath = `dm/${ship}`; - const fallbackUrl = `https://tlon.network/lure/~loshut-lonreg/tlon`; // for now, send to generic signup page on desktop - const link = await createDeepLink({ - fallbackUrl, - type: 'wer', - path: dmPath, - branchDomain, - branchKey, - }); - return link || ''; + // Not implemented + return ''; } export const createDeepLink = async ({ fallbackUrl, type, path, - branchDomain, - branchKey, + inviteServiceEndpoint, + inviteServiceIsDev, metadata, }: { fallbackUrl: string | undefined; type: DeepLinkType; path: string; - branchDomain: string; - branchKey: string; + inviteServiceEndpoint: string; + inviteServiceIsDev: boolean; metadata?: DeepLinkMetadata; }) => { if (!fallbackUrl || !path) { @@ -170,30 +173,53 @@ export const createDeepLink = async ({ } try { - let url = await getDeepLink(alias, branchDomain, branchKey).catch( - () => null - ); - if (!url) { - logger.crumb(`No existing deeplink for ${alias}, creating new one`); - const response = await fetchBranchApi('/v1/url', { - method: 'POST', - body: JSON.stringify({ - branch_key: branchKey, - alias, - data, - }), - }); - if (!response.ok) { - return fallbackUrl; - } - ({ url } = (await response.json()) as { url: string }); - } - logger.crumb(`Created new deeplink: ${url}`); - return url; + const inviteLink = await getLinkFromInviteService({ + alias, + data, + inviteServiceEndpoint, + inviteServiceIsDev, + }); + return inviteLink; } catch (e) { - logger.trackError('Failed to get or create deeplink', { + logger.trackError('Failed to get or create invite link', { errorMessage: e?.message, }); return ''; } }; + +async function getLinkFromInviteService({ + alias, + data, + inviteServiceEndpoint, + inviteServiceIsDev, +}: { + alias: string; + data: DeepLinkData; + inviteServiceEndpoint: string; + inviteServiceIsDev: boolean; +}): Promise { + const response = await fetch(inviteServiceEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + inviteId: alias, + data: data, + testEnv: inviteServiceIsDev, + }), + }); + if (!response.ok) { + throw new Error( + `Failed to get invite link from service [${response.status}]: ${alias}` + ); + } + + const { inviteLink }: { inviteLink: string } = await response.json(); + if (!inviteLink) { + throw new Error('Inalid invite service response'); + } + + return inviteLink; +} diff --git a/packages/shared/src/logic/deeplinks.ts b/packages/shared/src/logic/deeplinks.ts index 562a8cfd4d..03984de345 100644 --- a/packages/shared/src/logic/deeplinks.ts +++ b/packages/shared/src/logic/deeplinks.ts @@ -1,12 +1,26 @@ import { ContentReference } from '../api'; import { citeToPath } from '../urbit'; -import { DeepLinkMetadata, getBranchLinkMeta, isLureMeta } from './branch'; +import { + AppInvite, + DeepLinkMetadata, + getBranchLinkMeta, + isLureMeta, +} from './branch'; -export async function getReferenceFromDeeplink( - url: string, - branchKey: string -): Promise<{ reference: ContentReference; path: string } | null> { - const linkMeta = await getBranchLinkMeta(url, branchKey); +export async function getReferenceFromDeeplink({ + deepLink, + branchKey, + branchDomain, +}: { + deepLink: string; + branchKey: string; + branchDomain: string; +}): Promise<{ reference: ContentReference; path: string } | null> { + const linkMeta = await getInviteLinkMeta({ + inviteLink: deepLink, + branchKey, + branchDomain, + }); if (linkMeta && typeof linkMeta === 'object') { if (isLureMeta(linkMeta) && linkMeta.invitedGroupId) { @@ -24,17 +38,80 @@ export async function getReferenceFromDeeplink( return null; } -export async function getMetadaFromInviteLink( - url: string, - branchKey: string -): Promise { - const linkMeta = await getBranchLinkMeta(url, branchKey); - if (linkMeta && typeof linkMeta === 'object') { - if (isLureMeta(linkMeta)) { - return linkMeta; +interface ProviderMetadataResponse { + fields: { + image?: string; + title?: string; + cover?: string; + description?: string; + group?: string; + inviter?: string; + inviterNickname?: string; + inviterAvatarImage?: string; + }; +} + +export async function getInviteLinkMeta({ + inviteLink, + branchDomain, + branchKey, +}: { + inviteLink: string; + branchDomain: string; + branchKey: string; +}): Promise { + const token = extractTokenFromInviteLink(inviteLink, branchDomain); + if (!token) { + return null; + } + + const providerResponse = await fetch( + `https://loshut-lonreg.tlon.network/lure/${token}/metadata` + ); + if (!providerResponse.ok) { + return null; + } + + // fetch invite link metadata from lure provider + const responseMeta: ProviderMetadataResponse = await providerResponse.json(); + if ( + !responseMeta.fields || + !responseMeta.fields.group || + !responseMeta.fields.inviter + ) { + return null; + } + + const metadata: AppInvite = { + id: token, + shouldAutoJoin: true, + inviterUserId: responseMeta.fields.inviter, + invitedGroupId: responseMeta.fields.group, + invitedGroupTitle: responseMeta.fields.title, + invitedGroupDescription: responseMeta.fields.description, + invitedGroupIconImageUrl: responseMeta.fields.image, + inviterNickname: responseMeta.fields.inviterNickname, + inviterAvatarImage: responseMeta.fields.inviterAvatarImage, + }; + + // some links might not have everything, try to extend with branch (fine if fails) + if (!metadata.inviterNickname) { + try { + const branchMeta = await getBranchLinkMeta(inviteLink, branchKey); + if (branchMeta) { + if (branchMeta.inviterNickname && !metadata.inviterNickname) { + metadata.inviterNickname = branchMeta.inviterNickname; + } + if (branchMeta.inviterAvatarImage && !metadata.inviterAvatarImage) { + metadata.inviterAvatarImage = branchMeta.inviterAvatarImage; + } + } + } catch (e) { + console.error('Failed to fetch branch metadata. Ignoring', e); } } - return null; + + return metadata; } export function createInviteLinkRegex(branchDomain: string) { @@ -43,6 +120,23 @@ export function createInviteLinkRegex(branchDomain: string) { ); } +export function extractTokenFromInviteLink( + url: string, + branchDomain: string +): string | null { + if (!url) return null; + const INVITE_LINK_REGEX = createInviteLinkRegex(branchDomain); + const match = url.trim().match(INVITE_LINK_REGEX); + + if (match) { + const parts = match[0].split('/'); + const token = parts[parts.length - 1]; + return token ?? null; + } + + return null; +} + export function extractNormalizedInviteLink( url: string, branchDomain: string diff --git a/packages/shared/src/store/lure.ts b/packages/shared/src/store/lure.ts index caee1556b6..017d33f4b9 100644 --- a/packages/shared/src/store/lure.ts +++ b/packages/shared/src/store/lure.ts @@ -38,27 +38,17 @@ interface LureState { lures: Lures; fetchLure: ( flag: string, - branchDomain: string, - branchKey: string - ) => Promise; - describe: ( - flag: string, - metadata: LureMetadata, - branchDomain: string, - branchKey: string - ) => Promise; - toggle: ( - flag: string, - metadata: GroupMeta, - branchDomain: string, - branchKey: string + inviteServiceEndpoint: string, + inviteServiceIsDev: boolean ) => Promise; + describe: (flag: string) => Promise; + toggle: (flag: string) => Promise; start: () => Promise; } -const lureLogger = createDevLogger('lure', true); +const lureLogger = createDevLogger('lure', false); -function groupsDescribe(meta: GroupMeta) { +function groupsDescribe(meta: GroupMeta & DeepLinkMetadata) { return { tag: 'groups-0', fields: { ...meta }, // makes typescript happy @@ -68,17 +58,44 @@ function groupsDescribe(meta: GroupMeta) { export const useLureState = create((set, get) => ({ bait: null, lures: {}, - describe: async (flag, metadata, branchDomain, branchKey) => { + describe: async (flag) => { + const currentUserId = getCurrentUserId(); + const group = await db.getGroup({ id: flag }); + const user = await db.getContact({ id: currentUserId }); + + if (!group || !user) { + lureLogger.trackError('[describe] Error looking up group or user', { + groupId: flag, + group, + user, + }); + } + await poke({ app: 'reel', mark: 'reel-describe', json: { token: flag, - metadata, + metadata: groupsDescribe({ + // legacy keys + title: group?.title ?? '', + description: group?.description ?? '', + cover: group?.coverImage ?? '', + image: group?.iconImage ?? '', + + // new-style metadata keys + inviterUserId: currentUserId, + inviterNickname: user?.nickname ?? '', + inviterAvatarImage: user?.avatarImage ?? '', + invitedGroupId: flag, + invitedGroupTitle: group?.title ?? '', + invitedGroupDescription: group?.description ?? '', + invitedGroupIconImageUrl: group?.iconImage ?? '', + }), }, }); }, - toggle: async (flag, meta, branchDomain, branchKey) => { + toggle: async (flag) => { const { name } = getFlagParts(flag); const lure = get().lures[flag]; const enabled = !lure?.enabled; @@ -92,7 +109,7 @@ export const useLureState = create((set, get) => ({ }, }); } else { - get().describe(flag, groupsDescribe(meta), branchDomain, branchKey); + get().describe(flag); } set( @@ -122,7 +139,7 @@ export const useLureState = create((set, get) => ({ }) ); }, - fetchLure: async (flag, branchDomain, branchKey) => { + fetchLure: async (flag, inviteServiceEndpoint, inviteServiceIsDev) => { const { name } = getFlagParts(flag); const prevLure = get().lures[flag]; lureLogger.crumb('fetching', flag, 'prevLure', prevLure); @@ -203,8 +220,8 @@ export const useLureState = create((set, get) => ({ fallbackUrl: url, type: 'lure', path: flag, - branchDomain, - branchKey, + inviteServiceEndpoint, + inviteServiceIsDev, metadata, }); lureLogger.crumb('deepLinkUrl created', deepLinkUrl); @@ -230,13 +247,13 @@ const selLure = (flag: string) => (s: LureState) => ({ export function useLure({ flag, - branchDomain, - branchKey, + inviteServiceEndpoint, + inviteServiceIsDev, disableLoading = false, }: { flag: string; - branchDomain: string; - branchKey: string; + inviteServiceEndpoint: string; + inviteServiceIsDev: boolean; disableLoading?: boolean; }) { const fetchLure = useLureState((state) => state.fetchLure); @@ -260,27 +277,27 @@ export function useLure({ useQuery({ queryKey: ['lureFetcher', flag], queryFn: async () => { - lureLogger.crumb('fetching', flag, branchDomain, branchKey); - await fetchLure(flag, branchDomain, branchKey); + lureLogger.crumb( + 'fetching', + flag, + inviteServiceEndpoint, + inviteServiceIsDev + ); + await fetchLure(flag, inviteServiceEndpoint, inviteServiceIsDev); return true; }, enabled: canCheckForUpdate && uninitialized, refetchInterval: 5000, }); - const toggle = async (meta: GroupMeta) => { - lureLogger.crumb('toggling', flag, meta, branchDomain, branchKey); - return useLureState.getState().toggle(flag, meta, branchDomain, branchKey); + const toggle = async () => { + lureLogger.crumb('toggling', flag); + return useLureState.getState().toggle(flag); }; - const describe = useCallback( - (meta: GroupMeta) => { - return useLureState - .getState() - .describe(flag, groupsDescribe(meta), branchDomain, branchKey); - }, - [flag, branchDomain, branchKey] - ); + const describe = useCallback(() => { + return useLureState.getState().describe(flag); + }, [flag]); lureLogger.crumb('useLure', flag, bait, lure, describe); @@ -319,18 +336,18 @@ export function useLureLinkChecked(url: string | undefined, enabled: boolean) { export function useLureLinkStatus({ flag, - branchDomain, - branchKey, + inviteServiceEndpoint, + inviteServiceIsDev, }: { flag: string; - branchDomain: string; - branchKey: string; + inviteServiceEndpoint: string; + inviteServiceIsDev: boolean; }) { const { supported, fetched, enabled, url, deepLinkUrl, toggle, describe } = useLure({ flag, - branchDomain, - branchKey, + inviteServiceEndpoint, + inviteServiceIsDev, }); const { good, checked } = useLureLinkChecked(url, !!enabled); diff --git a/packages/ui/src/components/ChatOptionsSheet.tsx b/packages/ui/src/components/ChatOptionsSheet.tsx index e21e03652d..c1df193204 100644 --- a/packages/ui/src/components/ChatOptionsSheet.tsx +++ b/packages/ui/src/components/ChatOptionsSheet.tsx @@ -12,6 +12,7 @@ import React, { useState, } from 'react'; import { Alert } from 'react-native'; +import { isWeb } from 'tamagui'; import { ChevronLeft } from '../assets/icons'; import { useChatOptions, useCurrentUserId } from '../contexts'; @@ -738,35 +739,47 @@ export function ChannelOptions({ if (!channel) { return; } - Alert.alert( - `Leave ${title}?`, - 'This will be removed from the list', - [ - { - text: 'Cancel', - onPress: () => console.log('Cancel Pressed'), - style: 'cancel', - }, - { - text: 'Leave', - style: 'destructive', - onPress: () => { - onOpenChange(false); - if ( - channel.type === 'dm' || - channel.type === 'groupDm' - ) { - store.respondToDMInvite({ - channel, - accept: false, - }); - } else { - store.leaveGroupChannel(channel.id); - } + if (!isWeb) { + Alert.alert( + `Leave ${title}?`, + 'This will be removed from the list', + [ + { + text: 'Cancel', + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', }, - }, - ] - ); + { + text: 'Leave', + style: 'destructive', + onPress: () => { + onOpenChange(false); + if ( + channel.type === 'dm' || + channel.type === 'groupDm' + ) { + store.respondToDMInvite({ + channel, + accept: false, + }); + } else { + store.leaveGroupChannel(channel.id); + } + }, + }, + ] + ); + return; + } + onOpenChange(false); + if (channel.type === 'dm' || channel.type === 'groupDm') { + store.respondToDMInvite({ + channel, + accept: false, + }); + } else { + store.leaveGroupChannel(channel.id); + } }, }, ], diff --git a/packages/ui/src/components/FeatureFlagScreenView.tsx b/packages/ui/src/components/FeatureFlagScreenView.tsx index f775bc44a7..b49d1364b7 100644 --- a/packages/ui/src/components/FeatureFlagScreenView.tsx +++ b/packages/ui/src/components/FeatureFlagScreenView.tsx @@ -14,10 +14,9 @@ export function FeatureFlagScreenView({ onFlagToggled: (flagName: string, enabled: boolean) => void; }) { const insets = useSafeAreaInsets(); - console.log('render', features); return ( - + { - const meta = { - title: group?.title ?? '', - description: group?.description ?? '', - cover: group?.coverImage ?? '', - image: group?.iconImage ?? '', - }; - const toggleLink = async () => { if (!group) return; - await toggle(meta); + await toggle(); }; if (status === 'disabled' && isGroupAdmin) { toggleLink(); } if (status === 'stale') { - describe(meta); + describe(); } - }, [group, branchDomain, branchKey, toggle, status, isGroupAdmin, describe]); + }, [group, toggle, status, isGroupAdmin, describe]); if ( (group?.privacy === 'private' || group?.privacy === 'secret') && diff --git a/packages/ui/src/components/MessageInput/index.native.tsx b/packages/ui/src/components/MessageInput/index.native.tsx index 3c62759e49..1b8cb86865 100644 --- a/packages/ui/src/components/MessageInput/index.native.tsx +++ b/packages/ui/src/components/MessageInput/index.native.tsx @@ -486,10 +486,11 @@ export const MessageInput = forwardRef( pastedText, matchRegex: DEEPLINK_REGEX, processMatch: async (deeplink) => { - const deeplinkRef = await logic.getReferenceFromDeeplink( - deeplink, - branchKey - ); + const deeplinkRef = await logic.getReferenceFromDeeplink({ + deepLink: deeplink, + branchKey, + branchDomain, + }); return deeplinkRef ? { type: 'reference', @@ -515,10 +516,11 @@ export const MessageInput = forwardRef( const parts = tlonLure.split('/'); const token = parts[parts.length - 1]; if (!token) return null; - const deeplinkRef = await logic.getReferenceFromDeeplink( - `https://${branchDomain}/${token}`, - branchKey - ); + const deeplinkRef = await logic.getReferenceFromDeeplink({ + deepLink: `https://${branchDomain}/${token}`, + branchKey, + branchDomain, + }); return deeplinkRef ? { type: 'reference', diff --git a/packages/ui/src/components/PostContent/BlockRenderer.tsx b/packages/ui/src/components/PostContent/BlockRenderer.tsx index f812dcf7e0..94dc26fa67 100644 --- a/packages/ui/src/components/PostContent/BlockRenderer.tsx +++ b/packages/ui/src/components/PostContent/BlockRenderer.tsx @@ -244,6 +244,8 @@ export function ImageBlock({ }); }, []); + const shouldUseAspectRatio = imageProps?.aspectRatio !== 'unset'; + return ( | null; branchDomain: string; branchKey: string; + inviteServiceEndpoint: string; + inviteServiceIsDev: boolean; session: Session | null; calmSettings: CalmState; webAppNeedsUpdate?: boolean; @@ -30,6 +32,8 @@ export const AppDataContextProvider = ({ contacts, branchDomain, branchKey, + inviteServiceEndpoint, + inviteServiceIsDev, calmSettings, session, webAppNeedsUpdate, @@ -42,6 +46,8 @@ export const AppDataContextProvider = ({ contactIndex: buildContactIndex(contacts ?? []), branchDomain: branchDomain ?? '', branchKey: branchKey ?? '', + inviteServiceEndpoint: inviteServiceEndpoint ?? '', + inviteServiceIsDev: inviteServiceIsDev ?? false, calmSettings: calmSettings ?? { disableRemoteContent: false, disableAvatars: false, @@ -56,8 +62,10 @@ export const AppDataContextProvider = ({ contacts, branchDomain, branchKey, - session, + inviteServiceEndpoint, + inviteServiceIsDev, calmSettings, + session, webAppNeedsUpdate, triggerWebAppUpdate, ] @@ -85,6 +93,14 @@ export const useContact = (ship: string) => { return contactIndex?.[ship] ?? null; }; +export const useInviteService = () => { + const context = useAppDataContext(); + return { + endpoint: context.inviteServiceEndpoint, + isDev: context.inviteServiceIsDev, + }; +}; + export const useBranchDomain = () => { const context = useAppDataContext(); return context.branchDomain; diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index f0dc044f14..abcd5063db 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v1.8qptj.e342t.ju4l0.hro0m.8b627.glob' 0v1.8qptj.e342t.ju4l0.hro0m.8b627] + glob-http+['https://bootstrap.urbit.org/glob-0v6.lqi3b.foeos.9jpg1.5m4kh.t8imj.glob' 0v6.lqi3b.foeos.9jpg1.5m4kh.t8imj] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io'