Skip to content

Commit e58c305

Browse files
committed
Harden partner integrations and directory
1 parent 888fadd commit e58c305

25 files changed

Lines changed: 1299 additions & 515 deletions

.agents/analytics.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ User changed the filter state on the partners directory.
101101

102102
User clicked a "get in touch", "let's chat", or "become a partner" CTA.
103103

104-
| Prop | Type | Notes |
105-
| ----------- | ---- | ---------------------------------------------------------- |
106-
| `placement` | enum | `partners_index_cta`, `library_callout`, `docs_right_rail` |
104+
| Prop | Type | Notes |
105+
| ----------- | ---- | ---------------------------- |
106+
| `placement` | enum | See `PartnerPlacement` below |
107107

108108
---
109109

@@ -191,6 +191,7 @@ User took an action on the generated result. Single event with `action` prop cov
191191
| `ecosystem_game` | 3D ecosystem game islands |
192192
| `partners_index_cta` | "Get in touch" mailto on `/partners` |
193193
| `library_callout` | "Let's chat" callout per library |
194+
| `navbar` | "Partnership Inquiry" mailto in the Support menu |
194195

195196
### `BuilderAction`
196197

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"@tanstack/ai-anthropic": "^0.10.1",
6262
"@tanstack/ai-client": "^0.11.3",
6363
"@tanstack/ai-openai": "^0.9.5",
64-
"@tanstack/create": "^0.68.4",
64+
"@tanstack/create": "^0.69.0",
6565
"@tanstack/highlight": "^0.0.3",
6666
"@tanstack/markdown": "^0.0.11",
6767
"@tanstack/pacer": "^0.21.1",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/builder/api/create-worker.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type GeneratedTemplateContext = WorkerTemplateContext & {
2121
projectName: unknown
2222
typescript: unknown
2323
tailwind: unknown
24+
blank: unknown
2425
js: unknown
2526
jsx: unknown
2627
fileRouter: unknown
@@ -62,6 +63,7 @@ type WorkerAddOnPhase = WorkerAddOnManifest['phase']
6263
type WorkerAddOnCategory = NonNullable<WorkerAddOnManifest['category']>
6364
type WorkerAddOnExclusive = NonNullable<WorkerAddOnManifest['exclusive']>[number]
6465
type WorkerAddOnEnvVar = NonNullable<WorkerAddOnManifest['envVars']>[number]
66+
type WorkerAddOnPartner = NonNullable<WorkerAddOnManifest['partner']>
6567
type WorkerAddOnOptions = NonNullable<WorkerAddOnManifest['options']>
6668
type WorkerAddOnOption = WorkerAddOnOptions[string]
6769
type WorkerAddOnPackageAdditions = NonNullable<
@@ -71,6 +73,10 @@ type GeneratedStringRecord = Record<string, string | undefined>
7173
type GeneratedAddOnPackageAdditions = {
7274
dependencies?: GeneratedStringRecord
7375
devDependencies?: GeneratedStringRecord
76+
engines?: GeneratedStringRecord
77+
pnpm?: {
78+
onlyBuiltDependencies?: Array<string>
79+
}
7480
scripts?: GeneratedStringRecord
7581
}
7682
type GeneratedAddOnEnvVar = Omit<WorkerAddOnEnvVar, 'file'> & {
@@ -79,6 +85,9 @@ type GeneratedAddOnEnvVar = Omit<WorkerAddOnEnvVar, 'file'> & {
7985
type GeneratedAddOnOption = Omit<WorkerAddOnOption, 'type'> & {
8086
type: string
8187
}
88+
type GeneratedAddOnPartner = Omit<WorkerAddOnPartner, 'tier'> & {
89+
tier: string
90+
}
8291
type GeneratedAddOnOptions = Record<string, GeneratedAddOnOption | undefined>
8392
type GeneratedAddOnFields = {
8493
type: string
@@ -87,6 +96,7 @@ type GeneratedAddOnFields = {
8796
exclusive?: Array<string>
8897
envVars?: Array<GeneratedAddOnEnvVar>
8998
options?: GeneratedAddOnOptions
99+
partner?: GeneratedAddOnPartner
90100
}
91101
type GeneratedAddOnManifest = Omit<
92102
WorkerAddOnManifest,
@@ -96,6 +106,7 @@ type GeneratedAddOnManifest = Omit<
96106
| 'exclusive'
97107
| 'envVars'
98108
| 'options'
109+
| 'partner'
99110
| 'packageAdditions'
100111
> &
101112
GeneratedAddOnFields & {
@@ -109,6 +120,7 @@ type GeneratedAddOnMetadata = Omit<
109120
| 'exclusive'
110121
| 'envVars'
111122
| 'options'
123+
| 'partner'
112124
| 'packageAdditions'
113125
> &
114126
GeneratedAddOnFields & {
@@ -136,6 +148,7 @@ function getGeneratedTemplateContext(
136148
projectName: context.projectName,
137149
typescript: context.typescript,
138150
tailwind: context.tailwind,
151+
blank: context.blank,
139152
js: context.js,
140153
jsx: context.jsx,
141154
fileRouter: context.fileRouter,
@@ -238,6 +251,28 @@ function getAddOnExclusive(exclusive: string): WorkerAddOnExclusive {
238251
}
239252
}
240253

254+
function getAddOnPartnerTier(tier: string): WorkerAddOnPartner['tier'] {
255+
switch (tier) {
256+
case 'gold':
257+
case 'silver':
258+
case 'bronze':
259+
return tier
260+
default:
261+
throw new Error(`Unsupported partner tier: ${tier}`)
262+
}
263+
}
264+
265+
function getAddOnPartner(
266+
partner: GeneratedAddOnPartner | undefined,
267+
): WorkerAddOnManifest['partner'] {
268+
if (!partner) return undefined
269+
270+
return {
271+
...partner,
272+
tier: getAddOnPartnerTier(partner.tier),
273+
}
274+
}
275+
241276
function getAddOnEnvFile(file: string | undefined): WorkerAddOnEnvVar['file'] {
242277
switch (file) {
243278
case undefined:
@@ -310,6 +345,8 @@ function getPackageAdditions(
310345
const normalizedPackageAdditions: WorkerAddOnPackageAdditions = {
311346
dependencies: getStringRecord(packageAdditions.dependencies),
312347
devDependencies: getStringRecord(packageAdditions.devDependencies),
348+
engines: getStringRecord(packageAdditions.engines),
349+
pnpm: packageAdditions.pnpm,
313350
scripts: getStringRecord(packageAdditions.scripts),
314351
}
315352

@@ -327,6 +364,7 @@ function getAddOnMetadata(
327364
exclusive: addOn.exclusive?.map(getAddOnExclusive),
328365
envVars: getAddOnEnvVars(addOn.envVars),
329366
options: getAddOnOptions(addOn.options),
367+
partner: getAddOnPartner(addOn.partner),
330368
packageAdditions: getPackageAdditions(addOn.packageAdditions),
331369
}
332370
}
@@ -340,6 +378,7 @@ function getAddOnManifest(addOn: GeneratedAddOnManifest): WorkerAddOnManifest {
340378
exclusive: addOn.exclusive?.map(getAddOnExclusive),
341379
envVars: getAddOnEnvVars(addOn.envVars),
342380
options: getAddOnOptions(addOn.options),
381+
partner: getAddOnPartner(addOn.partner),
343382
packageAdditions: getPackageAdditions(addOn.packageAdditions),
344383
}
345384
}
@@ -376,6 +415,12 @@ function loadAddOn(
376415
}
377416
}
378417

418+
function defineAddOnLoaders(
419+
loaders: Record<string, () => Promise<WorkerAddOnManifestModule>>,
420+
) {
421+
return loaders
422+
}
423+
379424
const frameworkLoaders: Record<
380425
string,
381426
() => Promise<WorkerFrameworkManifestModule>
@@ -388,7 +433,7 @@ const frameworkLoaders: Record<
388433
),
389434
}
390435

391-
const reactAddOnLoaders = {
436+
const reactAddOnLoaders = defineAddOnLoaders({
392437
ai: loadAddOn(() =>
393438
import('@tanstack/create/worker-manifest/frameworks/react/add-ons/ai'),
394439
),
@@ -512,9 +557,9 @@ const reactAddOnLoaders = {
512557
workos: loadAddOn(() =>
513558
import('@tanstack/create/worker-manifest/frameworks/react/add-ons/workos'),
514559
),
515-
} satisfies Record<string, () => Promise<WorkerAddOnManifestModule>>
560+
})
516561

517-
const solidAddOnLoaders = {
562+
const solidAddOnLoaders = defineAddOnLoaders({
518563
'better-auth': loadAddOn(() =>
519564
import(
520565
'@tanstack/create/worker-manifest/frameworks/solid/add-ons/better-auth'
@@ -571,7 +616,7 @@ const solidAddOnLoaders = {
571616
'@tanstack/create/worker-manifest/frameworks/solid/add-ons/tanstack-query'
572617
),
573618
),
574-
} satisfies Record<string, () => Promise<WorkerAddOnManifestModule>>
619+
})
575620

576621
const addOnLoaders: Record<
577622
string,

src/components/Navbar.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ import { groupToSlug } from '~/components/stack/stack-categories'
6363
import { getProducts } from '~/utils/shop.functions'
6464
import { formatMoney, shopifyImageUrl } from '~/utils/shopify-format'
6565
import type { ProductListItem } from '~/utils/shopify-queries'
66+
import type { PartnerPlacement } from '~/utils/analytics'
67+
import {
68+
PARTNER_INQUIRY_HREF,
69+
trackPartnerInquiry,
70+
} from '~/utils/partner-inquiry'
6671

6772
type LogoProps = {
6873
title?: React.ComponentType | null
@@ -112,6 +117,7 @@ type NavMenuKey =
112117
| 'support'
113118

114119
type NavMenuItem = {
120+
analyticsPlacement?: PartnerPlacement
115121
label: string
116122
to: string
117123
hash?: string
@@ -344,8 +350,9 @@ const NAV_GROUPS = [
344350
title: 'Work with TanStack',
345351
description: 'Sponsorships, placements, and partner pages.',
346352
item: {
353+
analyticsPlacement: 'navbar',
347354
label: 'Partnership Inquiry',
348-
to: 'mailto:partners@tanstack.com?subject=TanStack Partnership Inquiry',
355+
to: PARTNER_INQUIRY_HREF,
349356
icon: Mail,
350357
},
351358
},
@@ -1414,7 +1421,12 @@ function MenuItemLink({
14141421
target={item.to.startsWith('mailto:') ? undefined : '_blank'}
14151422
rel={item.to.startsWith('mailto:') ? undefined : 'noopener noreferrer'}
14161423
className={className}
1417-
onClick={onNavigate}
1424+
onClick={() => {
1425+
onNavigate()
1426+
if (item.analyticsPlacement) {
1427+
trackPartnerInquiry(item.analyticsPlacement)
1428+
}
1429+
}}
14181430
>
14191431
{content}
14201432
</a>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { PartnerDirectoryStatus } from '~/utils/partner-directory'
2+
3+
const partnerStatusOptions: Array<{
4+
label: string
5+
status: PartnerDirectoryStatus
6+
}> = [
7+
{ label: 'Current', status: 'active' },
8+
{ label: 'Previous', status: 'inactive' },
9+
]
10+
11+
export function PartnerStatusFilter({
12+
selectedStatus,
13+
onStatusChange,
14+
}: {
15+
selectedStatus: PartnerDirectoryStatus
16+
onStatusChange: (status: PartnerDirectoryStatus) => void
17+
}) {
18+
return (
19+
<fieldset className="mb-6">
20+
<legend className="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
21+
Partner status
22+
</legend>
23+
<div className="inline-flex rounded-lg border border-gray-200 bg-white p-1 dark:border-gray-700 dark:bg-gray-800">
24+
{partnerStatusOptions.map(({ status, label }) => {
25+
const isSelected = selectedStatus === status
26+
27+
return (
28+
<label key={status} className="cursor-pointer">
29+
<input
30+
type="radio"
31+
name="partner-status"
32+
value={status}
33+
checked={isSelected}
34+
onChange={() => onStatusChange(status)}
35+
className="peer sr-only"
36+
/>
37+
<span
38+
className={`block rounded-md px-3 py-2 text-sm font-medium transition-colors peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-blue-500 ${
39+
isSelected
40+
? status === 'active'
41+
? 'bg-green-700 text-white'
42+
: 'bg-orange-700 text-white'
43+
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700'
44+
}`}
45+
>
46+
{label}
47+
</span>
48+
</label>
49+
)
50+
})}
51+
</div>
52+
</fieldset>
53+
)
54+
}

src/components/PartnershipCallout.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { HeartHandshake } from 'lucide-react'
22
import { Card } from './Card'
3-
import { trackEvent } from '~/utils/analytics'
3+
import {
4+
PARTNER_INQUIRY_HREF,
5+
trackPartnerInquiry,
6+
} from '~/utils/partner-inquiry'
47

58
export function PartnershipCallout() {
69
return (
@@ -18,13 +21,9 @@ export function PartnershipCallout() {
1821
and build useful integrations for the ecosystem.
1922
</div>
2023
<a
21-
href="mailto:partners@tanstack.com?subject=TanStack Partnership"
24+
href={PARTNER_INQUIRY_HREF}
2225
className="text-blue-500 uppercase font-black text-sm"
23-
onClick={() => {
24-
trackEvent('partner_inquiry_started', {
25-
placement: 'library_callout',
26-
})
27-
}}
26+
onClick={() => trackPartnerInquiry('library_callout')}
2827
>
2928
Let's chat
3029
</a>

src/components/RightRail.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import {
1919
useTrackedImpression,
2020
type PartnerPlacement,
2121
} from '~/utils/analytics'
22+
import {
23+
PARTNER_INQUIRY_HREF,
24+
trackPartnerInquiry,
25+
} from '~/utils/partner-inquiry'
2226

2327
type RailPartner = {
2428
category: Partner['category']
@@ -148,15 +152,9 @@ export function PartnersRail({
148152
{title}
149153
</Link>
150154
<a
151-
href="https://docs.google.com/document/d/1Hg2MzY2TU6U3hFEZ3MLe2oEOM3JS4-eByti3kdJU3I8"
152-
target="_blank"
153-
rel="noopener noreferrer"
155+
href={PARTNER_INQUIRY_HREF}
154156
className="font-medium opacity-60 hover:opacity-100 text-xs hover:underline"
155-
onClick={() => {
156-
trackEvent('partner_inquiry_started', {
157-
placement: analyticsPlacement,
158-
})
159-
}}
157+
onClick={() => trackPartnerInquiry(analyticsPlacement)}
160158
>
161159
Become a Partner
162160
</a>

src/routes/partners.$partner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,12 @@ function PartnerDetailPage() {
300300
<Card>
301301
<div className="p-6 md:p-8">
302302
<h2 className="text-2xl font-black text-gray-950 dark:text-white">
303-
Quick Fit
303+
Partnership History
304304
</h2>
305305
<div className="mt-5">
306306
<div>
307307
<h3 className="text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
308-
Partner Window
308+
Dates
309309
</h3>
310310
<p className="mt-3 text-base leading-7 text-gray-700 dark:text-gray-300">
311311
{partnerWindow}

0 commit comments

Comments
 (0)