Skip to content

Commit 5c32c5b

Browse files
authored
add rybbit cta click events (#170)
1 parent b330f01 commit 5c32c5b

10 files changed

Lines changed: 87 additions & 14 deletions

File tree

src/components/header/CTAButton.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22
import { Button } from '@/components/ui/button';
33
import { buildCloudEntryUrl } from '@/lib/cloudEntryUrl';
4+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
45

56
import Link from 'next/link';
67

@@ -15,10 +16,11 @@ const CTAButton = ({ locale }: { locale: any }) => {
1516
return (
1617
<div className="flex items-center gap-6">
1718
<Link
18-
href="https://fael3z0zfze.feishu.cn/share/base/form/shrcnjJWtKqjOI9NbQTzhNyzljc?prefill_S=C2&hide_S=1"
19-
target="_blank"
20-
rel="noopener noreferrer nofollow"
21-
>
19+
href="https://fael3z0zfze.feishu.cn/share/base/form/shrcnjJWtKqjOI9NbQTzhNyzljc?prefill_S=C2&hide_S=1"
20+
target="_blank"
21+
rel="noopener noreferrer nofollow"
22+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'enterprise_footer_consult')}
23+
>
2224
<Button
2325
variant="default"
2426
className="flex items-center gap-3 bg-[#B9DFFF] hover:bg-[#91C2EB] text-[#3941DD] px-6 py-4 text-sm ask-btn"
@@ -28,10 +30,11 @@ const CTAButton = ({ locale }: { locale: any }) => {
2830
</Button>
2931
</Link>
3032
<div className="inline-block rotating-border-button">
31-
<Link
32-
href={getLinkConfig()}
33-
rel="noopener noreferrer nofollow"
34-
>
33+
<Link
34+
href={getLinkConfig()}
35+
rel="noopener noreferrer nofollow"
36+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, 'enterprise_footer_trial')}
37+
>
3538
<Button
3639
variant="default"
3740
className="flex items-center gap-3 bg-[#B9DFFF] hover:bg-[#91C2EB] text-[#3941DD] px-6 py-4 text-sm start-btn"

src/components/home/CTA.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
55
import dynamic from 'next/dynamic';
66
import FadeIn from '@/components/home/motion/FadeIn';
77
import { useStartUrl, CONSULT_URL } from '@/components/home/hooks/useStartUrl';
8+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
89

910
// Globe is heavy (WebGL + 16k samples + rAF loop). Load on the client only,
1011
// and defer even that to when the CTA card nears the viewport so the earlier
@@ -94,6 +95,7 @@ export default function CTA({ t }: { t: CTAT }) {
9495
<motion.a
9596
href={startUrl}
9697
rel="noopener noreferrer nofollow"
98+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, 'home_bottom_trial')}
9799
whileHover={{ scale: 1.04 }}
98100
whileTap={{ scale: 0.97 }}
99101
aria-label={t.trial}
@@ -105,6 +107,7 @@ export default function CTA({ t }: { t: CTAT }) {
105107
href={CONSULT_URL}
106108
target="_blank"
107109
rel="noopener noreferrer nofollow"
110+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_bottom_consult')}
108111
whileHover={{ scale: 1.04 }}
109112
whileTap={{ scale: 0.97 }}
110113
aria-label={t.consult}

src/components/home/CaseStudies.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SectionHeader from '@/components/home/SectionHeader';
77
import FadeIn from '@/components/home/motion/FadeIn';
88
import { assets } from '@/components/home/assets';
99
import { CONSULT_URL } from '@/components/home/hooks/useStartUrl';
10+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
1011

1112
type MetricIcon = 'arrow' | 'zap' | 'medal';
1213
type CaseMetric = { value: string; label: string; icon: MetricIcon };
@@ -81,7 +82,7 @@ function MetricIconSvg({ kind, size }: { kind: MetricIcon; size?: number }) {
8182
);
8283
}
8384

84-
type CaseStudy = { title: string; image: string; metrics: CaseMetric[] };
85+
type CaseStudy = { key: string; title: string; image: string; metrics: CaseMetric[] };
8586

8687
export default function CaseStudies({ t }: { t: CasesT }) {
8788
const [index, setIndex] = useState(0);
@@ -92,6 +93,7 @@ export default function CaseStudies({ t }: { t: CasesT }) {
9293
const dragStartVal = useRef(0);
9394

9495
const cases: CaseStudy[] = t.items.map((it) => ({
96+
key: it.key,
9597
title: it.title,
9698
image: imageByCaseKey[it.key],
9799
metrics: it.metrics.map((m, i) => ({ ...m, icon: iconByCaseKey[it.key]?.[i] ?? 'arrow' }))
@@ -305,6 +307,9 @@ function CaseCard({ data, learnMore }: { data: CaseStudy; learnMore: string }) {
305307
href={CONSULT_URL}
306308
target="_blank"
307309
rel="noopener noreferrer nofollow"
310+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_case_study_consult', {
311+
case: data.key
312+
})}
308313
className="inline-flex items-center justify-center self-start transition-colors h-9 md:h-[46px] px-3 md:px-4 rounded-full text-[13px] md:text-base"
309314
style={{
310315
border: '1px solid #d3d4d4',

src/components/home/CloudEntryLink.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type { ComponentProps, ReactNode } from 'react';
44
import { useStartUrl } from '@/components/home/hooks/useStartUrl';
5+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
56

67
type CloudEntryLinkProps = Omit<ComponentProps<'a'>, 'href'> & {
78
source: string;
@@ -18,7 +19,11 @@ export default function CloudEntryLink({
1819
const href = useStartUrl(source, targetUrl);
1920

2021
return (
21-
<a href={href} {...props}>
22+
<a
23+
href={href}
24+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, source)}
25+
{...props}
26+
>
2227
{children}
2328
</a>
2429
);

src/components/home/Footer.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { assets } from '@/components/home/assets';
33
import Image from 'next/image';
44
import { siteConfig } from '@/config/site';
55
import CloudEntryLink from '@/components/home/CloudEntryLink';
6+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
67

78
type ColumnLink = { label: string; href: string; external?: boolean; cloudEntrySource?: string };
89
type Column = { title: string; width: number; items: (ColumnLink | { label: string })[] };
@@ -215,6 +216,9 @@ export default function Footer({ t }: { t: FooterT }) {
215216
{...(item.external
216217
? { target: '_blank', rel: 'noopener noreferrer nofollow' }
217218
: {})}
219+
{...(item.href.includes('feishu.cn')
220+
? rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'footer_private_deploy')
221+
: {})}
218222
className="hover:text-black text-ink-sub"
219223
style={{ ...linkStyle, textAlign: 'left' }}
220224

src/components/home/Hero.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Image from 'next/image';
77
import { assets } from '@/components/home/assets';
88
import { useStartUrl, CONSULT_URL } from '@/components/home/hooks/useStartUrl';
99
import { formatGitHubStars } from '@/lib/githubStarsDisplay';
10+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
1011

1112
interface HeroProps {
1213
stars: number;
@@ -184,6 +185,7 @@ export default function Hero({ stars: initialStars, t, children }: HeroProps) {
184185
href={CONSULT_URL}
185186
target="_blank"
186187
rel="noopener noreferrer nofollow"
188+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_hero_consult')}
187189
whileHover={{ scale: 1.04 }}
188190
whileTap={{ scale: 0.97 }}
189191
aria-label={t.consult}
@@ -194,6 +196,7 @@ export default function Hero({ stars: initialStars, t, children }: HeroProps) {
194196
<motion.a
195197
href={startUrl}
196198
rel="noopener noreferrer nofollow"
199+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, 'home_hero_trial')}
197200
whileHover={{ scale: 1.04 }}
198201
whileTap={{ scale: 0.97 }}
199202
aria-label={t.trial}

src/components/home/Navbar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useStartUrl, CONSULT_URL } from '@/components/home/hooks/useStartUrl';
1010
import { LangSwitcher } from '@/components/header/LangSwitcher';
1111
import Image from 'next/image';
1212
import { localeConfigs } from '@/lib/locales';
13+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
1314

1415
interface NavLink {
1516
label: string;
@@ -176,6 +177,7 @@ export default function Navbar({ links = [], t }: { links?: NavLink[]; t: NavCta
176177
<a
177178
href={desktopStartUrl}
178179
rel="noopener noreferrer nofollow"
180+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, 'home_nav_trial')}
179181
aria-label={t.trial}
180182
className="px-4 py-1.5 rounded-full bg-white border border-hairline-soft text-[12px] font-medium text-ink hover:bg-gray-50 transition-colors"
181183
>
@@ -185,6 +187,7 @@ export default function Navbar({ links = [], t }: { links?: NavLink[]; t: NavCta
185187
href={CONSULT_URL}
186188
target="_blank"
187189
rel="noopener noreferrer nofollow"
190+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_nav_consult')}
188191
aria-label={t.consult}
189192
className="px-4 py-1.5 rounded-full text-[12px] font-medium text-white bg-btn-dark hover:opacity-90 transition-opacity"
190193
>
@@ -197,6 +200,7 @@ export default function Navbar({ links = [], t }: { links?: NavLink[]; t: NavCta
197200
href={CONSULT_URL}
198201
target="_blank"
199202
rel="noopener noreferrer nofollow"
203+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_nav_mobile_consult')}
200204
aria-label={t.consult}
201205
className={`px-4 py-1.5 rounded-full text-[12px] font-medium text-white bg-btn-dark transition-opacity duration-300 ${
202206
showMobileCta && !mobileOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
@@ -290,6 +294,7 @@ export default function Navbar({ links = [], t }: { links?: NavLink[]; t: NavCta
290294
<a
291295
href={mobileStartUrl}
292296
rel="noopener noreferrer nofollow"
297+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, 'home_nav_mobile_trial')}
293298
className="h-10 inline-flex items-center justify-center rounded-full bg-white border border-hairline-soft text-[13px] font-medium text-ink"
294299
onClick={() => setMobileOpen(false)}
295300
>
@@ -299,6 +304,7 @@ export default function Navbar({ links = [], t }: { links?: NavLink[]; t: NavCta
299304
href={CONSULT_URL}
300305
target="_blank"
301306
rel="noopener noreferrer nofollow"
307+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_nav_mobile_menu_consult')}
302308
className="h-10 inline-flex items-center justify-center rounded-full text-[13px] font-medium text-white bg-btn-dark"
303309
onClick={() => setMobileOpen(false)}
304310
>

src/components/home/Solutions.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SectionHeader from '@/components/home/SectionHeader';
2121
import FadeIn from '@/components/home/motion/FadeIn';
2222
import { assets } from '@/components/home/assets';
2323
import { CONSULT_URL } from '@/components/home/hooks/useStartUrl';
24+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
2425

2526
const iconByKey: Record<string, React.ComponentType<{ size?: number; className?: string; strokeWidth?: number }>> = {
2627
assistant: Tag, report: AudioLines, training: Users,
@@ -174,6 +175,9 @@ export default function Solutions({ t }: { t: SolutionsT }) {
174175
href={CONSULT_URL}
175176
target="_blank"
176177
rel="noopener noreferrer nofollow"
178+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'home_solutions_consult', {
179+
solution: current.key
180+
})}
177181
className="relative inline-flex px-8 py-3 rounded-full text-[13px] font-medium text-white w-fit bg-btn-dark hover:opacity-90 transition-opacity"
178182
style={{ zIndex: 1 }}
179183
>

src/components/price/PPlan.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PRICE_PLANS_CLOUD, PRICE_PLANS_SELF, PRICE_PLANS_SELF_BUTTON_MAP } from
77
import Link from 'next/link';
88
import { siteConfig } from '@/config/site';
99
import { useStartUrl } from '@/components/home/hooks/useStartUrl';
10+
import { RYBBIT_EVENTS, rybbitClickAttrs } from '@/lib/rybbitEvents';
1011

1112
type Key = 'cloud' | 'self';
1213

@@ -20,7 +21,12 @@ function CloudPlanLink({
2021
const href = useStartUrl(source);
2122

2223
return (
23-
<Link href={href} target="_blank" className="mt-auto pt-4">
24+
<Link
25+
href={href}
26+
target="_blank"
27+
className="mt-auto pt-4"
28+
{...rybbitClickAttrs(RYBBIT_EVENTS.cloudServiceClick, source)}
29+
>
2430
{children}
2531
</Link>
2632
);
@@ -217,7 +223,12 @@ export default function PPlan({ langName, locale }: { langName: string; locale:
217223
</button>
218224
</CloudPlanLink>
219225
) : (
220-
<Link href={siteConfig.customPlanUrl} target="_blank" className="mt-auto pt-4">
226+
<Link
227+
href={siteConfig.customPlanUrl}
228+
target="_blank"
229+
className="mt-auto pt-4"
230+
{...rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, 'price_cloud_custom')}
231+
>
221232
<button
222233
className="w-full py-2.5 rounded-full text-[14px] font-medium transition-colors"
223234
style={{
@@ -237,6 +248,7 @@ export default function PPlan({ langName, locale }: { langName: string; locale:
237248
/* Self-hosted plans */
238249
<div className="mt-[40px] grid lg:grid-cols-3 grid-cols-1 gap-5">
239250
{selfPlans.map((item) => {
251+
const buttonConfig = PRICE_PLANS_SELF_BUTTON_MAP[item.key];
240252
return (
241253
<div
242254
key={item.key}
@@ -296,15 +308,18 @@ export default function PPlan({ langName, locale }: { langName: string; locale:
296308
</div>
297309

298310
<Link
299-
href={PRICE_PLANS_SELF_BUTTON_MAP[item.key].href}
311+
href={buttonConfig.href}
300312
target="_blank"
301313
className="mt-auto pt-4"
314+
{...(buttonConfig.href.includes('feishu.cn')
315+
? rybbitClickAttrs(RYBBIT_EVENTS.businessConsultClick, `price_self_${item.key}`)
316+
: {})}
302317
>
303318
<button
304319
className="w-full py-2.5 rounded-full text-[14px] font-medium transition-colors"
305320
style={{ backgroundColor: '#f5f6f8', color: '#020617' }}
306321
>
307-
{PRICE_PLANS_SELF_BUTTON_MAP[item.key][langName] || PRICE_PLANS_SELF_BUTTON_MAP[item.key].en}
322+
{buttonConfig[langName] || buttonConfig.en}
308323
</button>
309324
</Link>
310325
</div>

src/lib/rybbitEvents.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const RYBBIT_EVENTS = {
2+
businessConsultClick: 'business_consult_click',
3+
cloudServiceClick: 'cloud_service_click'
4+
} as const;
5+
6+
type RybbitEventName = (typeof RYBBIT_EVENTS)[keyof typeof RYBBIT_EVENTS];
7+
type RybbitPropValue = string | number | boolean | null | undefined;
8+
9+
export function rybbitClickAttrs(
10+
eventName: RybbitEventName,
11+
source: string,
12+
properties: Record<string, RybbitPropValue> = {}
13+
) {
14+
const attrs: Record<string, string | number> = {
15+
'data-rybbit-event': eventName,
16+
'data-rybbit-prop-source': source
17+
};
18+
19+
Object.entries(properties).forEach(([key, value]) => {
20+
if (value === null || value === undefined) return;
21+
attrs[`data-rybbit-prop-${key}`] = typeof value === 'boolean' ? String(value) : value;
22+
});
23+
24+
return attrs;
25+
}

0 commit comments

Comments
 (0)