feat(invite): drop themeKey prop from AddToWalletButton, share icon assets

This commit is contained in:
vineyardbovines
2026-06-26 11:12:19 -04:00
parent 7027dfdd3b
commit 54deb3ca0f
4 changed files with 24 additions and 35 deletions
+11 -6
View File
@@ -220,12 +220,15 @@ func (srv *Server) WebInviteWalletHero(c echo.Context) error {
}
func (srv *Server) buildPassAssets(theme, handle string, avatarBytes []byte) ([]PassAsset, error) {
icon1, err := readFS(srv.cfg.InvitePass.StripFS, "passes/"+theme+"/icon.png")
if err != nil {
return nil, fmt.Errorf("icon: %w", err)
_ = theme
// Icons are shared across themes (matches the Pass Designer layout - the
// pass.json hardcodes one backgroundColor + one background image).
icon1, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/icon.png")
icon2, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/icon@2x.png")
icon3, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/icon@3x.png")
if len(icon1) == 0 && len(icon2) == 0 && len(icon3) == 0 {
return nil, fmt.Errorf("icon: no icon asset found at any density")
}
icon2, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/"+theme+"/icon@2x.png")
icon3, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/"+theme+"/icon@3x.png")
logo1, err := readFS(srv.cfg.InvitePass.StripFS, "passes/logo.png")
if err != nil {
return nil, fmt.Errorf("logo: %w", err)
@@ -247,9 +250,11 @@ func (srv *Server) buildPassAssets(theme, handle string, avatarBytes []byte) ([]
_ = avatarBytes
assets := []PassAsset{
{Name: "icon.png", Data: icon1},
{Name: "logo.png", Data: logo1},
}
if len(icon1) > 0 {
assets = append(assets, PassAsset{Name: "icon.png", Data: icon1})
}
if len(icon2) > 0 {
assets = append(assets, PassAsset{Name: "icon@2x.png", Data: icon2})
}
@@ -203,7 +203,7 @@ export function InviteFriendsDialogInner({
{isHandleValid && handle ? (
<View style={[a.w_full, a.align_center, {marginTop: 24}]}>
<AddToWalletButton themeKey={themeKey} handle={handle} />
<AddToWalletButton handle={handle} />
</View>
) : null}
@@ -85,9 +85,7 @@ afterEach(async () => {
// Test 1: IS_WEB gate
test('renders null on web', async () => {
mockIsWeb = true
const {toJSON} = render(
<AddToWalletButton themeKey="dusk" handle="alice.bsky.social" />,
)
const {toJSON} = render(<AddToWalletButton handle="alice.bsky.social" />)
// Flush effects - with IS_WEB=true the canAddPasses effect exits early,
// so the flush is just a guard.
await act(async () => {})
@@ -97,9 +95,7 @@ test('renders null on web', async () => {
// Test 2: canAddPasses gate
test('renders null when canAddPasses() resolves false', async () => {
RNWallet.canAddPasses.mockResolvedValue(false)
const {toJSON} = render(
<AddToWalletButton themeKey="dusk" handle="alice.bsky.social" />,
)
const {toJSON} = render(<AddToWalletButton handle="alice.bsky.social" />)
await waitFor(() => expect(RNWallet.canAddPasses).toHaveBeenCalled())
// Flush promise resolution so setCanAdd(false) runs inside act
await act(async () => {})
@@ -108,9 +104,7 @@ test('renders null when canAddPasses() resolves false', async () => {
// Test 3: handle sentinel gate
test('renders null when handle is invalid', async () => {
const {toJSON} = render(
<AddToWalletButton themeKey="dusk" handle="handle.invalid" />,
)
const {toJSON} = render(<AddToWalletButton handle="handle.invalid" />)
// handle is invalid so canAddPasses is not called; flush anyway for safety
await act(async () => {})
expect(toJSON()).toBeNull()
@@ -118,7 +112,7 @@ test('renders null when handle is invalid', async () => {
// Test 4: empty handle gate
test('renders null when handle is empty', async () => {
const {toJSON} = render(<AddToWalletButton themeKey="dusk" handle="" />)
const {toJSON} = render(<AddToWalletButton handle="" />)
await act(async () => {})
expect(toJSON()).toBeNull()
})
@@ -133,7 +127,7 @@ test('iOS: mints token then calls addPass with the returned URL', async () => {
}),
})
const {UNSAFE_getByType} = render(
<AddToWalletButton themeKey="dusk" handle="alice.bsky.social" />,
<AddToWalletButton handle="alice.bsky.social" />,
)
await waitFor(() =>
UNSAFE_getByType('RNWalletView' as unknown as ComponentType),
@@ -159,9 +153,7 @@ test('iOS: mints token then calls addPass with the returned URL', async () => {
// Test 6: Android is gated out for iOS-only launch (Google Wallet backend not provisioned)
test('Android: renders null until Google Wallet backend is wired', () => {
;(Platform as {OS: string}).OS = 'android'
const {toJSON} = render(
<AddToWalletButton themeKey="day" handle="bob.bsky.social" />,
)
const {toJSON} = render(<AddToWalletButton handle="bob.bsky.social" />)
expect(toJSON()).toBeNull()
})
@@ -178,7 +170,7 @@ test('addPass throws error toast and logger.error are called', async () => {
const {logger} = require('#/logger')
const loggerSpy = jest.spyOn(logger, 'error')
const {UNSAFE_getByType} = render(
<AddToWalletButton themeKey="day" handle="alice.bsky.social" />,
<AddToWalletButton handle="alice.bsky.social" />,
)
await waitFor(() =>
UNSAFE_getByType('RNWalletView' as unknown as ComponentType),
@@ -8,17 +8,10 @@ import {useAgent} from '#/state/session'
import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import {type InviteThemeKey} from '../themes'
const BSKY_WEB = 'https://bsky.app'
export function AddToWalletButton({
themeKey,
handle,
}: {
themeKey: InviteThemeKey
handle: string
}) {
export function AddToWalletButton({handle}: {handle: string}) {
const {t: l} = useLingui()
const ax = useAnalytics()
const agent = useAgent()
@@ -64,16 +57,15 @@ export function AddToWalletButton({
Authorization: `Bearer ${accessJwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({theme: themeKey}),
body: JSON.stringify({}),
})
if (!res.ok) throw new Error(`pass.url ${res.status}`)
const {url} = (await res.json()) as {url: string}
added = await RNWallet.addPass(url)
} else {
const res = await fetch(
`${BSKY_WEB}/invite/wallet/jwt?theme=${themeKey}`,
{headers: {Authorization: `Bearer ${accessJwt}`}},
)
const res = await fetch(`${BSKY_WEB}/invite/wallet/jwt`, {
headers: {Authorization: `Bearer ${accessJwt}`},
})
if (!res.ok) throw new Error(`wallet/jwt ${res.status}`)
const {jwt} = (await res.json()) as {jwt: string}
added = await RNWallet.addPassWithSignedJwt(jwt)