Skip to content

feat: add utm params in external urls #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion components/ArticleItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import Tags from './common/Tags';
import { useThemeState } from '../theme/ThemeContext';
import Markdown from './Markdown';
import Authors from './Authors';
import { getUrlWithUtmTrackingParams } from '../utils';

const ArticleItem = ({ article }: { article: Article }): JSX.Element => {
const theme = useThemeState();
const { title, desc, url, authors, tags } = article;

const articleUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url });

return (
<div className="mt-0 mx-0 py-4" key={url}>
<a href={url} target="_blank" rel="noreferrer">
<a href={articleUrlWithTrackingParams} target="_blank" rel="noreferrer">
<Text size="2xl" as="h4" color={`text-${theme}-600`} additionalStyles="hover:underline">
{title}
</Text>
Expand Down
5 changes: 4 additions & 1 deletion components/Authors.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Author from '../interfaces/author';
import { getUrlWithUtmTrackingParams } from '../utils';
import Text from './common/Text';

function Authors({ authors }: { authors: Array<string> | Array<Author> }): JSX.Element {
Expand All @@ -19,8 +20,10 @@ function Authors({ authors }: { authors: Array<string> | Array<Author> }): JSX.E
);
}

const authorUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url: author.website });

return (
<a href={author.website} target="_blank" rel="noreferrer" key={author.id}>
<a href={authorUrlWithTrackingParams} target="_blank" rel="noreferrer" key={author.id}>
<Text as="span" additionalStyles="uppercase hover:underline" size="sm" color="text-gray-600">
{isLastElement ? author.name : `${author.name}, `}
</Text>
Expand Down
7 changes: 5 additions & 2 deletions components/Curators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useThemeState } from '../theme/ThemeContext';
import SocialLinks from './common/SocialLinks';
import Text from './common/Text';
import Markdown from './Markdown';
import { getUrlWithUtmTrackingParams } from '../utils';

function shuffle(array: unknown[]): unknown[] {
const shuffledArray = JSON.parse(JSON.stringify(array));
Expand Down Expand Up @@ -73,14 +74,16 @@ function Curators(): JSX.Element {
<div className="flex justify-evenly items-center flex-col flex-wrap">
<div className="grid px-0 lg:px-12">
{curators.map((curator, index) => {
const curatorUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url: curator.links.website });

return (
<div className="flex sm:flex-col md:flex-row flex-wrap pb-8" key={index}>
<div
className={`shrink mr-4 mb-4 p-1 bg-gradient-to-br from-${theme}-300 to-${theme}-700 rounded transition duration-300`}
>
<div className={`bg-${theme}-100 p-1 rounded`}>
<a
href={curator.links.website}
href={curatorUrlWithTrackingParams}
target="_blank"
rel="noreferrer"
className="flex transition duration-700 hover:scale-105"
Expand All @@ -102,7 +105,7 @@ function Curators(): JSX.Element {
</div>
<div className="flex flex-col">
<a
href={curator.links.website}
href={curatorUrlWithTrackingParams}
target="_blank"
rel="noreferrer"
className={`text-${theme}-800 hover:underline`}
Expand Down
9 changes: 6 additions & 3 deletions components/GIFItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Image from 'next/image';
import Gif from '../interfaces/gif';
import { useThemeState } from '../theme/ThemeContext';
import { getMediaFormat } from '../utils';
import { getMediaFormat, getUrlWithUtmTrackingParams } from '../utils';
import Text from './common/Text';
import Markdown from './Markdown';

Expand All @@ -22,6 +22,8 @@ const GIFItem = ({ gif }: { gif: Gif }): JSX.Element => {
const renderMedia = ({ format, source, caption }: RenderMediaProps): JSX.Element => {
switch (format) {
case 'mp4':
const sourceWithTrackingParams = getUrlWithUtmTrackingParams({ url: source });

return (
<video
autoPlay
Expand All @@ -34,9 +36,10 @@ const GIFItem = ({ gif }: { gif: Gif }): JSX.Element => {
title={caption}
preload="metadata"
>
<source src={source} type="video/mp4" />
<source src={sourceWithTrackingParams} type="video/mp4" />
<p>
Your browser doesn&apos;t support HTML5 video. Here is a <a href={source}>link to the video</a> instead.
Your browser doesn&apos;t support HTML5 video. Here is a{' '}
<a href={sourceWithTrackingParams}>link to the video</a> instead.
</p>
</video>
);
Expand Down
5 changes: 4 additions & 1 deletion components/ToolItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import Image from 'next/image';
import ArticleItem from './ArticleItem';
import Tool from '../interfaces/tool';
import { getUrlWithUtmTrackingParams } from '../utils';

const ToolItem = ({ tool: { title, url, logo, authors, desc, tags } }: { tool: Tool }): JSX.Element => {
const article = { title, url, desc, authors, tags };

const toolUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url });

return (
<div className="flex flex-wrap sm:flex-nowrap flex-row items-center">
<a
href={url}
href={toolUrlWithTrackingParams}
className="w-full mr-8 max-w-[fit-content] my-4 p-1 transition duration-700 hover:scale-105 img-shadow-sm hover:img-shadow-none"
target="_blank"
rel="noreferrer"
Expand Down
5 changes: 4 additions & 1 deletion components/common/SocialShare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';

import { CopyIcon, CheckSqaureIcon, TwitterIcon, ShareIcon, WhatsAppIcon, FacebookIcon } from '../icons/icons';
import { useThemeState } from '../../theme/ThemeContext';
import { getUrlWithUtmTrackingParams } from '../../utils';

interface SocialShareProps {
url?: string;
Expand All @@ -23,10 +24,12 @@ interface ShareLinkProps {
}

export const ShareLink = ({ text, label, url, icon, showText }: ShareLinkProps): JSX.Element => {
const shareUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url });

return (
<a
aria-label={label}
href={url}
href={shareUrlWithTrackingParams}
className="transition duration-500 ease-in-out hover:scale-125"
target="_blank"
rel="noreferrer"
Expand Down
94 changes: 63 additions & 31 deletions scripts/issueEmailGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,87 +99,112 @@ if ('issueNumber' in options && typeof options.issueNumber === 'number') {
}
}

const getUrlWithUtmTrackingParams = ({ url, medium = 'newsletter' }) => {
const utmParams = {
utm_source: 'scriptified.dev',
utm_medium: medium,
};

try {
const urlWithParams = new URL(url);

Object.keys(utmParams).forEach(key => {
urlWithParams.searchParams.set(key, utmParams[key]);
});

return urlWithParams.toString();
} catch (err) {
console.error(err);
// return url as is if URL is invalid
return url;
}
};

const ogImgURL = getOGImage(currentIssue.title, currentIssue.id, currentIssue.date);

const issueWebUrl = getUrlWithUtmTrackingParams({ url: `https://scriptified.dev/issues/${currentIssue.id}` });

const emailTemplate = `
![Headshot](${ogImgURL})

<center>[Read issue on web](https://scriptified.dev/issues/${currentIssue.id})</center>
<center>[Read issue on web](${issueWebUrl})</center>

${currentIssue.description}

# Tip of the day

${currentIssue.tip_of_the_week.description}

${currentIssue.tip_of_the_week.codeSnippet &&
`
${
currentIssue.tip_of_the_week.codeSnippet &&
`
\`\`\`${currentIssue.tip_of_the_week.codeSnippet.language}
${currentIssue.tip_of_the_week.codeSnippet.code.code}
\`\`\`
`
}
}

${getAuthors(currentIssue.tip_of_the_week?.authors)}
___

# Articles

${currentIssue.articles
.map(
article =>
`[**${article.title}**](${article.url})
.map(
article =>
`[**${article.title}**](${getUrlWithUtmTrackingParams({ url: article.url })})

${article.description}

${getAuthors(article?.authors)}
`
)
.join('\n')}
)
.join('\n')}

___

${currentIssue.devOfTheWeek
? `# Dev of the Week
${
currentIssue.devOfTheWeek
? `# Dev of the Week

<img alt="${currentIssue.devOfTheWeek.name}" src="${getAssetURL(
currentIssue.id,
currentIssue.devOfTheWeek.profileImg
)}" style="width:200px;"/>
currentIssue.id,
currentIssue.devOfTheWeek.profileImg
)}" style="width:200px;"/>

## ${currentIssue.devOfTheWeek.name}
${currentIssue.devOfTheWeek.bio}

${Object.keys(currentIssue.devOfTheWeek)
.filter(key => PROFILE_KEYS.includes(key) && currentIssue.devOfTheWeek[key] !== null)
.map(profile => `[${PROFILE_TYPES[profile]}](${currentIssue.devOfTheWeek[profile]})`)
.join(' | ')}
.filter(key => PROFILE_KEYS.includes(key) && currentIssue.devOfTheWeek[key] !== null)
.map(profile => `[${PROFILE_TYPES[profile]}](${currentIssue.devOfTheWeek[profile]})`)
.join(' | ')}

___`
: ''
}
: ''
}

# Tools

${currentIssue.tools
.map(
tool =>
`[**${tool.name}**](${tool.url})
.map(
tool =>
`[**${tool.name}**](${getUrlWithUtmTrackingParams({ url: tool.url })})

${tool.description}

${getAuthors(tool?.authors)}
`
)
.join('\n')}
)
.join('\n')}

___

# Tech Talks

[**${currentIssue.talks[0].title}**](${currentIssue.talks[0].url})
[**${currentIssue.talks[0].title}**](${getUrlWithUtmTrackingParams({ url: currentIssue.talks[0].url })})

${currentIssue.talks[0].url}
${getUrlWithUtmTrackingParams({ url: currentIssue.talks[0].url })})

${currentIssue.talks[0].description}

Expand All @@ -195,9 +220,16 @@ ___
${currentIssue.quiz.codeSnippet.code.code}
\`\`\`

${currentIssue.quiz.options.map(
option => `[<div style="margin: 12px 0px; border: 1px solid gray; padding: 16px; background: #F2F3F5;">${option.text.split('\n').join('<br>')}</div>](https://scriptified.dev/issues/${currentIssue.id}?section=quiz&option=${option.option_id})`
).join('\n')}
${currentIssue.quiz.options
.map(
option =>
`[<div style="margin: 12px 0px; border: 1px solid gray; padding: 16px; background: #F2F3F5;">${option.text
.split('\n')
.join('<br>')}</div>](https://scriptified.dev/issues/${currentIssue.id}?section=quiz&option=${
option.option_id
})`
)
.join('\n')}


___
Expand All @@ -213,8 +245,8 @@ ___
Liked this issue? [Share on Twitter](https://twitter.com/intent/tweet?text=${encodeURIComponent(`Have a look at issue #${currentIssue.id} of Scriptified.

Subscribe to @scriptified_dev for more.`)}&url=${encodeURIComponent(
`https://scriptified.dev/issues/${currentIssue.id}`
)}) or [read previous issues](https://scriptified.dev/issues).
`${issueWebUrl}`
)}) or [read previous issues](https://scriptified.dev/issues).
`;
const archiveDirectory = './archives';
const issueFile = `${archiveDirectory}/issue${currentIssue.id}.md`;
Expand Down
41 changes: 41 additions & 0 deletions utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,44 @@ export const getMediaFormat = (mediaUrl: string): string => {
return mediaFormat;
}
};

/* =================================================================== */

type UtmMedium = 'newsletter' | 'website';

interface UtmParams {
utm_source: string;
utm_medium: UtmMedium;
}

/**
* Get URL with UTM tracking params
* @param url URL to be appended with UTM tracking params
* @returns {string} URL with UTM tracking params
*/
export const getUrlWithUtmTrackingParams = ({
url,
medium = 'website',
}: {
url: string;
medium?: UtmMedium;
}): string => {
const utmParams: UtmParams = {
utm_source: 'scriptified.dev',
utm_medium: medium,
};

try {
const urlWithParams = new URL(url);

Object.keys(utmParams).forEach(key => {
urlWithParams.searchParams.set(key, utmParams[key]);
});

return urlWithParams.toString();
} catch (err) {
console.error(err);
// return url as is if URL is invalid
return url;
}
};