Skip to content

Commit 69bb8c5

Browse files
committed
feat(analytics): add custom event tracking across the site
1 parent a832a37 commit 69bb8c5

File tree

10 files changed

+438
-5
lines changed

10 files changed

+438
-5
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
NUXT_PUBLIC_EMAILJS_SERVICE_ID: ${{ secrets.NUXT_PUBLIC_EMAILJS_SERVICE_ID }}
3838
NUXT_PUBLIC_EMAILJS_TEMPLATE_ID: ${{ secrets.NUXT_PUBLIC_EMAILJS_TEMPLATE_ID }}
3939
NUXT_PUBLIC_EMAILJS_PUBLIC_KEY: ${{ secrets.NUXT_PUBLIC_EMAILJS_PUBLIC_KEY }}
40+
NUXT_PUBLIC_GTAG_ID: ${{ secrets.NUXT_PUBLIC_GTAG_ID }}
4041

4142
- name: Upload artifact
4243
uses: actions/upload-pages-artifact@v3

app/components/project-card/ProjectCard.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
rel="noopener noreferrer"
7171
class="text-stone-400 hover:text-stone-700 transition-colors"
7272
title="Ver código"
73+
@click="handleGithubClick"
7374
>
7475
<Icon name="lucide:github" class="w-4 h-4" />
7576
</a>
@@ -80,6 +81,7 @@
8081
rel="noopener noreferrer"
8182
class="text-stone-400 hover:text-stone-700 transition-colors"
8283
title="Ver demo"
84+
@click="handleDemoClick"
8385
>
8486
<Icon name="lucide:external-link" class="w-4 h-4" />
8587
</a>
@@ -88,6 +90,7 @@
8890
:to="project.links.caseStudy"
8991
class="text-stone-400 hover:text-stone-700 transition-colors"
9092
title="Ver case study"
93+
@click="handleCaseStudyClick"
9194
>
9295
<Icon name="lucide:file-text" class="w-4 h-4" />
9396
</NuxtLink>
@@ -97,6 +100,12 @@
97100
</template>
98101

99102
<script setup lang="ts">
103+
const {
104+
trackProjectGithubClick,
105+
trackProjectDemoClick,
106+
trackProjectCaseStudyClick,
107+
} = useAnalytics();
108+
100109
interface Project {
101110
id: string;
102111
title: string;
@@ -120,6 +129,18 @@ const props = defineProps<{
120129
121130
const imageError = ref(false);
122131
132+
const handleGithubClick = () => {
133+
trackProjectGithubClick(props.project.id, props.project.title);
134+
};
135+
136+
const handleDemoClick = () => {
137+
trackProjectDemoClick(props.project.id, props.project.title);
138+
};
139+
140+
const handleCaseStudyClick = () => {
141+
trackProjectCaseStudyClick(props.project.id, props.project.title);
142+
};
143+
123144
const statusLabel = computed(() => {
124145
const labels = {
125146
development: "Em dev",

app/composables/useAnalytics.ts

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
export const useAnalytics = () => {
2+
const { gtag } = useGtag()
3+
4+
const trackContactFormStart = () => {
5+
gtag('event', 'contact_form_start', {
6+
event_category: 'contact',
7+
event_label: 'form_interaction',
8+
})
9+
}
10+
11+
const trackContactSubjectSelected = (subject: string) => {
12+
gtag('event', 'contact_subject_selected', {
13+
event_category: 'contact',
14+
event_label: subject,
15+
subject_value: subject,
16+
})
17+
}
18+
19+
const trackContactFormSubmit = () => {
20+
gtag('event', 'contact_form_submit', {
21+
event_category: 'contact',
22+
event_label: 'form_submission',
23+
})
24+
}
25+
26+
const trackContactFormSuccess = () => {
27+
gtag('event', 'contact_form_success', {
28+
event_category: 'contact',
29+
event_label: 'success',
30+
})
31+
}
32+
33+
const trackContactFormError = (errorType?: string) => {
34+
gtag('event', 'contact_form_error', {
35+
event_category: 'contact',
36+
event_label: 'error',
37+
error_type: errorType || 'unknown',
38+
})
39+
}
40+
41+
const trackCtaClick = (ctaText: string, ctaLocation: string, destinationPage: string) => {
42+
gtag('event', 'cta_click', {
43+
event_category: 'cta',
44+
event_label: ctaText,
45+
cta_text: ctaText,
46+
cta_location: ctaLocation,
47+
destination_page: destinationPage,
48+
})
49+
}
50+
51+
const trackHeroCtaClick = (ctaButton: string) => {
52+
gtag('event', 'hero_cta_click', {
53+
event_category: 'cta',
54+
event_label: ctaButton,
55+
cta_button: ctaButton,
56+
})
57+
}
58+
59+
const trackProjectGithubClick = (projectId: string, projectTitle: string) => {
60+
gtag('event', 'project_github_click', {
61+
event_category: 'project',
62+
event_label: projectTitle,
63+
project_id: projectId,
64+
project_title: projectTitle,
65+
})
66+
}
67+
68+
const trackProjectDemoClick = (projectId: string, projectTitle: string) => {
69+
gtag('event', 'project_demo_click', {
70+
event_category: 'project',
71+
event_label: projectTitle,
72+
project_id: projectId,
73+
project_title: projectTitle,
74+
})
75+
}
76+
77+
const trackProjectCaseStudyClick = (projectId: string, projectTitle: string) => {
78+
gtag('event', 'project_case_study_click', {
79+
event_category: 'project',
80+
event_label: projectTitle,
81+
project_id: projectId,
82+
project_title: projectTitle,
83+
})
84+
}
85+
86+
const trackProjectFilterClick = (filterType: 'type' | 'tech', filterValue: string, selected: boolean) => {
87+
gtag('event', 'project_filter_click', {
88+
event_category: 'project',
89+
event_label: filterValue,
90+
filter_type: filterType,
91+
filter_value: filterValue,
92+
selected: selected,
93+
})
94+
}
95+
96+
const trackProjectSearch = (searchQuery: string, resultCount: number) => {
97+
gtag('event', 'project_search', {
98+
event_category: 'project',
99+
event_label: searchQuery,
100+
search_query: searchQuery,
101+
result_count: resultCount,
102+
})
103+
}
104+
105+
const trackProjectClearFilters = (activeFiltersCount: number) => {
106+
gtag('event', 'project_clear_filters', {
107+
event_category: 'project',
108+
event_label: 'clear_filters',
109+
active_filters_count: activeFiltersCount,
110+
})
111+
}
112+
113+
const trackSocialClick = (platform: string) => {
114+
gtag('event', 'social_click', {
115+
event_category: 'social',
116+
event_label: platform,
117+
social_platform: platform,
118+
})
119+
}
120+
121+
const trackWhatsappClick = (fromPage: string) => {
122+
gtag('event', 'whatsapp_click', {
123+
event_category: 'contact',
124+
event_label: 'whatsapp',
125+
from_page: fromPage,
126+
})
127+
}
128+
129+
const trackEmailClick = () => {
130+
gtag('event', 'email_click', {
131+
event_category: 'contact',
132+
event_label: 'email',
133+
})
134+
}
135+
136+
const trackPhoneClick = () => {
137+
gtag('event', 'phone_click', {
138+
event_category: 'contact',
139+
event_label: 'phone',
140+
})
141+
}
142+
143+
const trackFaqOpen = (faqIndex: number, questionText: string) => {
144+
gtag('event', 'faq_open', {
145+
event_category: 'faq',
146+
event_label: questionText,
147+
faq_index: faqIndex,
148+
question_text: questionText,
149+
})
150+
}
151+
152+
const trackFaqClose = (faqIndex: number) => {
153+
gtag('event', 'faq_close', {
154+
event_category: 'faq',
155+
event_label: `faq_${faqIndex}`,
156+
faq_index: faqIndex,
157+
})
158+
}
159+
160+
const trackScrollDepth = (percentScrolled: number, pagePath: string) => {
161+
gtag('event', 'scroll_depth', {
162+
event_category: 'engagement',
163+
event_label: `${percentScrolled}%`,
164+
percent_scrolled: percentScrolled,
165+
page_path: pagePath,
166+
})
167+
}
168+
169+
const trackTimeOnPage = (timeSpent: number, pagePath: string) => {
170+
gtag('event', 'time_on_page', {
171+
event_category: 'engagement',
172+
event_label: pagePath,
173+
time_spent_seconds: timeSpent,
174+
page_path: pagePath,
175+
})
176+
}
177+
178+
return {
179+
// Formulário de contato
180+
trackContactFormStart,
181+
trackContactSubjectSelected,
182+
trackContactFormSubmit,
183+
trackContactFormSuccess,
184+
trackContactFormError,
185+
// CTAs
186+
trackCtaClick,
187+
trackHeroCtaClick,
188+
// Projetos
189+
trackProjectGithubClick,
190+
trackProjectDemoClick,
191+
trackProjectCaseStudyClick,
192+
trackProjectFilterClick,
193+
trackProjectSearch,
194+
trackProjectClearFilters,
195+
// Social e contato
196+
trackSocialClick,
197+
trackWhatsappClick,
198+
trackEmailClick,
199+
trackPhoneClick,
200+
// FAQ
201+
trackFaqOpen,
202+
trackFaqClose,
203+
// Engajamento
204+
trackScrollDepth,
205+
trackTimeOnPage,
206+
}
207+
}

app/composables/useContact.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,44 @@ import { companies, contactInfo, faqs, getWhatsAppLink, socialLinks } from "~/da
33

44
export const useContact = () => {
55
const config = useRuntimeConfig();
6+
const {
7+
trackContactFormStart,
8+
trackContactSubjectSelected,
9+
trackContactFormSubmit,
10+
trackContactFormSuccess,
11+
trackContactFormError,
12+
trackFaqOpen,
13+
trackFaqClose,
14+
} = useAnalytics();
615

716
const openFaq = ref<number | null>(null);
17+
const formStarted = ref(false);
818

919
const toggleFaq = (index: number) => {
10-
openFaq.value = openFaq.value === index ? null : index;
20+
const wasOpen = openFaq.value === index;
21+
openFaq.value = wasOpen ? null : index;
22+
23+
if (wasOpen) {
24+
trackFaqClose(index);
25+
} else {
26+
const faq = faqs[index];
27+
if (faq) {
28+
trackFaqOpen(index, faq.question);
29+
}
30+
}
31+
};
32+
33+
const handleFormStart = () => {
34+
if (!formStarted.value) {
35+
formStarted.value = true;
36+
trackContactFormStart();
37+
}
38+
};
39+
40+
const handleSubjectChange = (subject: string) => {
41+
if (subject) {
42+
trackContactSubjectSelected(subject);
43+
}
1144
};
1245

1346
const form = reactive({
@@ -24,7 +57,9 @@ export const useContact = () => {
2457
const handleSubmit = async () => {
2558
isSubmitting.value = true;
2659
submitError.value = false;
27-
60+
61+
trackContactFormSubmit();
62+
2863
try {
2964
await emailjs.send(
3065
config.public.emailjsServiceId,
@@ -38,17 +73,23 @@ export const useContact = () => {
3873
config.public.emailjsPublicKey
3974
);
4075

76+
trackContactFormSuccess();
77+
4178
submitSuccess.value = true;
4279
form.name = "";
4380
form.email = "";
4481
form.subject = "";
4582
form.message = "";
83+
formStarted.value = false;
4684

4785
setTimeout(() => {
4886
submitSuccess.value = false;
4987
}, 5000);
5088
} catch (error) {
5189
console.error("Erro ao enviar email:", error);
90+
91+
trackContactFormError(error instanceof Error ? error.message : 'unknown');
92+
5293
submitError.value = true;
5394

5495
setTimeout(() => {
@@ -82,5 +123,7 @@ export const useContact = () => {
82123
submitError,
83124
handleSubmit,
84125
resetForm,
126+
handleFormStart,
127+
handleSubjectChange,
85128
};
86129
};

0 commit comments

Comments
 (0)