Skip to content

Commit 7ee278c

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(branding): refresh Google logo
1 parent d152fad commit 7ee278c

8 files changed

Lines changed: 213 additions & 37 deletions

File tree

apps/docs/components/icons.tsx

Lines changed: 28 additions & 16 deletions
Large diffs are not rendered by default.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
8+
const { mockSocialSignIn } = vi.hoisted(() => ({ mockSocialSignIn: vi.fn() }))
9+
10+
vi.mock('@/lib/auth/auth-client', () => ({
11+
client: { signIn: { social: mockSocialSignIn } },
12+
}))
13+
14+
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
15+
16+
let container: HTMLDivElement
17+
let root: Root
18+
19+
function renderGoogleButton() {
20+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
21+
container = document.createElement('div')
22+
document.body.appendChild(container)
23+
root = createRoot(container)
24+
act(() => {
25+
root.render(
26+
<SocialLoginButtons
27+
githubAvailable={false}
28+
googleAvailable
29+
microsoftAvailable={false}
30+
callbackURL='/after-login'
31+
isProduction
32+
/>
33+
)
34+
})
35+
}
36+
37+
beforeEach(() => {
38+
vi.clearAllMocks()
39+
})
40+
41+
afterEach(() => {
42+
act(() => root.unmount())
43+
container.remove()
44+
})
45+
46+
describe('SocialLoginButtons Google sign-in', () => {
47+
it('renders the compliant CTA and 20px logo while preserving the loading state', async () => {
48+
let finishSignIn: (() => void) | undefined
49+
mockSocialSignIn.mockReturnValue(
50+
new Promise<void>((resolve) => {
51+
finishSignIn = resolve
52+
})
53+
)
54+
renderGoogleButton()
55+
56+
const button = container.querySelector('button') as HTMLButtonElement
57+
const icon = button.querySelector('svg')
58+
59+
expect(button).toBeEnabled()
60+
expect(button).toHaveTextContent('Continue with Google')
61+
expect(icon).toHaveClass('size-[20px]')
62+
63+
await act(async () => {
64+
button.click()
65+
})
66+
67+
expect(mockSocialSignIn).toHaveBeenCalledWith({
68+
provider: 'google',
69+
callbackURL: '/after-login',
70+
})
71+
expect(button).toBeDisabled()
72+
expect(button).toHaveTextContent('Connecting…')
73+
74+
await act(async () => {
75+
finishSignIn?.()
76+
})
77+
78+
expect(button).toBeEnabled()
79+
expect(button).toHaveTextContent('Continue with Google')
80+
})
81+
})

apps/sim/app/(auth)/components/social-login-buttons.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@ export function SocialLoginButtons({
8585
const googleButton = (
8686
<Chip
8787
fullWidth
88-
leftIcon={GoogleIcon}
88+
leftAdornment={<GoogleIcon className='size-[20px] flex-shrink-0' />}
8989
className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border-1)]')}
9090
disabled={!googleAvailable || isGoogleLoading}
9191
onClick={signInWithGoogle}
9292
>
93-
{isGoogleLoading ? 'Connecting…' : 'Google'}
93+
{isGoogleLoading ? 'Connecting…' : 'Continue with Google'}
9494
</Chip>
9595
)
9696

apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export function AuthModal({ children, defaultView = 'login', source }: AuthModal
202202
disabled={!!socialLoading}
203203
className={SOCIAL_BTN}
204204
>
205-
<GoogleIcon className='absolute left-4 size-[18px] shrink-0' />
205+
<GoogleIcon className='absolute left-4 size-[20px] shrink-0' />
206206
<span>
207207
{socialLoading === 'google' ? 'Connecting...' : 'Continue with Google'}
208208
</span>

apps/sim/components/icons.test.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { createHash } from 'node:crypto'
5+
import { renderToStaticMarkup } from 'react-dom/server'
6+
import { describe, expect, it } from 'vitest'
7+
import { GoogleIcon } from '@/components/icons'
8+
9+
const PNG_DATA_URI_PATTERN = /href="data:image\/png;base64,([^"]+)"/g
10+
const GOOGLE_ICON_PNG_SHA256 = 'd1ce9c2af0b10a7333abc99bc706f9a6a199e5b65bf3e3009624f076b8638e6a'
11+
12+
function getEmbeddedPng(markup: string): Buffer {
13+
const match = PNG_DATA_URI_PATTERN.exec(markup)
14+
expect(match).not.toBeNull()
15+
PNG_DATA_URI_PATTERN.lastIndex = 0
16+
return Buffer.from(match?.[1] ?? '', 'base64')
17+
}
18+
19+
describe('GoogleIcon', () => {
20+
it('renders the exact official transparent artwork and forwards SVG props', () => {
21+
const markup = renderToStaticMarkup(
22+
<GoogleIcon className='google-icon' data-testid='google-icon' aria-label='Google' />
23+
)
24+
25+
expect(markup).toContain('viewBox="0 0 204 204"')
26+
expect(markup).toContain('width="24"')
27+
expect(markup).toContain('height="24"')
28+
expect(markup).toContain('class="google-icon"')
29+
expect(markup).toContain('data-testid="google-icon"')
30+
expect(markup).toContain('aria-label="Google"')
31+
expect(markup).toContain('<image')
32+
expect(markup).toContain('width="200"')
33+
expect(markup).toContain('height="204"')
34+
expect(markup).toContain('preserveAspectRatio="xMinYMin meet"')
35+
36+
const png = getEmbeddedPng(markup)
37+
expect(png).toHaveLength(33_661)
38+
expect(png.subarray(0, 8)).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
39+
expect(png.readUInt32BE(16)).toBe(200)
40+
expect(png.readUInt32BE(20)).toBe(204)
41+
expect(createHash('sha256').update(png).digest('hex')).toBe(GOOGLE_ICON_PNG_SHA256)
42+
})
43+
44+
it('avoids the WebKit-fragile SVG paint stack across multiple instances', () => {
45+
const markup = renderToStaticMarkup(
46+
<>
47+
<GoogleIcon />
48+
<GoogleIcon />
49+
</>
50+
)
51+
52+
expect(markup.match(/<svg\b/g)).toHaveLength(2)
53+
expect(markup.match(/<image\b/g)).toHaveLength(2)
54+
expect(markup.match(PNG_DATA_URI_PATTERN)).toHaveLength(2)
55+
expect(markup).not.toMatch(/<(?:foreignObject|mask|filter|clipPath)\b/)
56+
expect(markup).not.toContain('conic-gradient')
57+
expect(markup).not.toContain('url(#')
58+
expect(markup).not.toMatch(/\sid=/)
59+
})
60+
})

apps/sim/components/icons.tsx

Lines changed: 28 additions & 16 deletions
Large diffs are not rendered by default.

apps/sim/lib/oauth/oauth.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ beforeAll(() => {
6363

6464
afterAll(resetEnvMock)
6565

66+
import { GoogleIcon, GoogleVaultIcon } from '@/components/icons'
6667
import { DEFAULT_MAX_ERROR_BODY_BYTES } from '@/lib/core/utils/stream-limits'
67-
import { refreshOAuthToken } from '@/lib/oauth'
68+
import { OAUTH_PROVIDERS, refreshOAuthToken } from '@/lib/oauth'
6869
import { REDDIT_USER_AGENT } from '@/tools/reddit/constants'
6970

7071
/**
@@ -90,6 +91,15 @@ function withMockFetch<T>(mockFetch: ReturnType<typeof vi.fn>, fn: () => Promise
9091
})
9192
}
9293

94+
describe('OAuth Provider Branding', () => {
95+
it('should use the Google Vault product icon and Google base-provider icon', () => {
96+
const googleVault = OAUTH_PROVIDERS.google.services['google-vault']
97+
98+
expect(googleVault.icon).toBe(GoogleVaultIcon)
99+
expect(googleVault.baseProviderIcon).toBe(GoogleIcon)
100+
})
101+
})
102+
93103
describe('OAuth Token Refresh', () => {
94104
describe('Basic Auth Providers', () => {
95105
const basicAuthProviders = [

apps/sim/lib/oauth/oauth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
GoogleMeetIcon,
2828
GoogleSheetsIcon,
2929
GoogleTasksIcon,
30+
GoogleVaultIcon,
3031
HubspotIcon,
3132
InstagramIcon,
3233
JiraIcon,
@@ -255,7 +256,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
255256
name: 'Google Vault',
256257
description: 'Search, export, and manage matters/holds via Google Vault.',
257258
providerId: 'google-vault',
258-
icon: GoogleIcon,
259+
icon: GoogleVaultIcon,
259260
baseProviderIcon: GoogleIcon,
260261
scopes: [
261262
'https://www.googleapis.com/auth/userinfo.email',

0 commit comments

Comments
 (0)