Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1a30a65
feat(connect): add account-backed referral points
Aug 26, 2026
cd2fd47
fix(connect): make referral awards race-safe
Aug 26, 2026
2f4596c
fix(web): align referral form controls
Aug 26, 2026
31e6bae
fix(connect): make referral claims atomic
Aug 26, 2026
f793552
fix(connect): preserve referral persistence errors
Aug 26, 2026
1959dd1
fix(connect): keep referral awards recoverable
Aug 26, 2026
5306328
fix(referrals): finish cross-platform sharing flow
Aug 27, 2026
4079852
fix(web): align referral profile controls
Aug 27, 2026
0ac007a
fix(web): preserve referrals when storage fails
Aug 27, 2026
77552a3
fix(web): retry automatic referral claims
Aug 27, 2026
b6cb4f0
fix(web): keep referral sign-in user initiated
Aug 27, 2026
73b43fb
fix(referrals): recover failed awards and share links
Aug 27, 2026
1509b07
fix(web): align referral retry controls
Aug 27, 2026
32d7db7
fix(web): match referral code input hints
Aug 27, 2026
c1e3d87
fix(web): match referral button typography
Aug 27, 2026
3c888ea
fix(connect): keep referral summaries available
Aug 27, 2026
0b412af
fix(web): soften referral profile surfaces
Aug 27, 2026
644d912
fix(web): ignore stale referral claims
Aug 27, 2026
ee8c5c8
refactor(web): align referral profile primitives
Aug 27, 2026
15b7134
fix(relay): use full referral code entropy
Aug 27, 2026
a5d23e1
fix(web): isolate referral claim attempts
Aug 27, 2026
62fade5
fix(web): capture referral links before auth
Aug 27, 2026
2317d84
fix(web): stop superseded referral retries
Aug 27, 2026
12683ba
fix(web): use themed referral error alert
Aug 27, 2026
8c7169f
fix(referrals): preserve claims during first link
Aug 27, 2026
22ee34f
fix(web): guard blocked referral storage
Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions apps/marketing/src/pages/privacy-policy.astro
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ const sections = [
credential hashes, connection status, app and operating-system versions, and
notification preferences.
</li>
<li>
<strong>Referral information.</strong> If you use the referral program, we process
referral codes, account-to-account referral attribution, qualification status, and
point-ledger entries.
</li>
<li>
<strong>Agent activity and notifications.</strong> If you enable notifications or Live
Activities, we process push tokens and limited activity information needed to provide
Expand Down Expand Up @@ -151,6 +156,7 @@ const sections = [
<ul>
<li>To establish and verify your identity and authenticate your account;</li>
<li>To link and protect devices and environments and deliver optional notifications;</li>
<li>To operate the referral program and maintain account point balances;</li>
<li>To process and fulfill requests you make through the Services;</li>
<li>To provide customer service and respond to support requests and feedback;</li>
<li>To communicate with you about the Services and your relationship with us;</li>
Expand Down Expand Up @@ -339,12 +345,12 @@ const sections = [

<h3>Retention and Deletion</h3>
<p>
We retain account, environment-link, device-registration, notification, and security
information while your account or the relevant feature is active and for as long as
reasonably necessary to provide the Services, protect them from abuse, comply with law,
and resolve disputes. Operational diagnostic traces are configured to expire after 30
days. Some records may remain longer in backups or where retention is required for
security, fraud prevention, or legal compliance.
We retain account, environment-link, device-registration, notification, referral, and
security information while your account or the relevant feature is active and for as
long as reasonably necessary to provide the Services, protect them from abuse, comply
with law, and resolve disputes. Operational diagnostic traces are configured to expire
after 30 days. Some records may remain longer in backups or where retention is required
for security, fraud prevention, or legal compliance.
</p>
<p>
To request deletion of your T3 account and associated T3 Connect data, email{" "}<a
Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteSc
import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen";
import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen";
import { SettingsProjectGroupingRouteScreen } from "./features/settings/SettingsProjectGroupingRouteScreen";
import { SettingsReferralsRouteScreen } from "./features/settings/SettingsReferralsRouteScreen";
import { UsageRouteScreen } from "./features/usage/UsageRouteScreen";
import { SettingsRouteScreen } from "./features/settings/SettingsRouteScreen";
import { ShowcaseCaptureCoordinator } from "./features/showcase/ShowcaseCaptureCoordinator";
Expand Down Expand Up @@ -198,6 +199,13 @@ const SettingsContentStack = createNativeStackNavigator({
title: "Usage",
},
}),
SettingsReferrals: createNativeStackScreen({
screen: SettingsReferralsRouteScreen,
linking: "referrals",
options: {
title: "Referrals",
},
}),
},
});

Expand Down
58 changes: 58 additions & 0 deletions apps/mobile/src/features/cloud/managedRelayState.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { useAtomValue } from "@effect/atom-react";
import {
claimManagedRelayReferral,
createManagedRelayQueryManager,
managedRelaySessionAtom,
readManagedRelaySnapshotState,
} from "@t3tools/client-runtime/relay";
import type {
RelayClientEnvironmentRecord,
RelayEnvironmentStatusResponse,
RelayReferralSummary,
} from "@t3tools/contracts/relay";
import {
createAtomCommandScheduler,
createRuntimeCommand,
} from "@t3tools/client-runtime/state/runtime";
import { AsyncResult, Atom } from "effect/unstable/reactivity";
import { useCallback, useEffect } from "react";

Expand All @@ -22,6 +28,18 @@ export const managedRelayQueryManager = createManagedRelayQueryManager(managedRe
cloudDebugLog(`query:${event.operation}:${event.stage}:${event.phase}`, { ...event }),
});

const managedRelayMutationScheduler = createAtomCommandScheduler();

export const claimManagedRelayReferralCommand = createRuntimeCommand(managedRelayAtomRuntime, {
label: "mobile:managed-relay:claim-referral",
scheduler: managedRelayMutationScheduler,
concurrency: {
mode: "serial",
key: (input: { readonly accountId: string; readonly referralCode: string }) => input.accountId,
},
execute: (input, registry) => claimManagedRelayReferral(registry, input),
});

const EMPTY_ENVIRONMENTS_ATOM = Atom.make(
AsyncResult.success<ReadonlyArray<RelayClientEnvironmentRecord>>([]),
).pipe(Atom.keepAlive, Atom.withLabel("managed-relay:mobile:environments:null"));
Expand All @@ -30,6 +48,10 @@ const EMPTY_ENVIRONMENT_STATUS_ATOM = Atom.make(
AsyncResult.initial<RelayEnvironmentStatusResponse, never>(false),
).pipe(Atom.keepAlive, Atom.withLabel("managed-relay:mobile:environment-status:null"));

const EMPTY_REFERRAL_SUMMARY_ATOM = Atom.make(
AsyncResult.initial<RelayReferralSummary, never>(false),
).pipe(Atom.keepAlive, Atom.withLabel("managed-relay:mobile:referrals:null"));

export function useManagedRelayEnvironments() {
const session = useAtomValue(managedRelaySessionAtom);
const accountId = session?.accountId ?? null;
Expand Down Expand Up @@ -92,9 +114,45 @@ export function useManagedRelayEnvironmentStatus(environment: RelayClientEnviron
};
}

export function useManagedRelayReferralSummary() {
const session = useAtomValue(managedRelaySessionAtom);
const accountId = session?.accountId ?? null;
const atom = accountId
? managedRelayQueryManager.referralSummaryAtom(accountId)
: EMPTY_REFERRAL_SUMMARY_ATOM;
const result = useAtomValue(atom);
const snapshot = readManagedRelaySnapshotState(result);
useEffect(() => {
if (snapshot.error) {
console.error("[t3-cloud] Relay referral summary failed", {
message: snapshot.error,
traceId: snapshot.errorTraceId,
});
}
}, [snapshot.error, snapshot.errorTraceId]);
const refresh = useCallback(() => {
if (accountId) {
managedRelayQueryManager.refreshReferralSummary(appAtomRegistry, accountId);
}
}, [accountId]);

return {
...snapshot,
accountId,
refresh,
};
}

export function refreshManagedRelayEnvironments(): void {
const session = appAtomRegistry.get(managedRelaySessionAtom);
if (session) {
managedRelayQueryManager.refreshEnvironments(appAtomRegistry, session.accountId);
}
}

export function refreshManagedRelayReferralSummary(): void {
const session = appAtomRegistry.get(managedRelaySessionAtom);
if (session) {
managedRelayQueryManager.refreshReferralSummary(appAtomRegistry, session.accountId);
}
}
Loading
Loading