diff --git a/components/ArticleItem.tsx b/components/ArticleItem.tsx index 31de6c5..a628ecd 100644 --- a/components/ArticleItem.tsx +++ b/components/ArticleItem.tsx @@ -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 (
- + {title} diff --git a/components/Authors.tsx b/components/Authors.tsx index 1300a78..ba69cf6 100644 --- a/components/Authors.tsx +++ b/components/Authors.tsx @@ -1,4 +1,5 @@ import Author from '../interfaces/author'; +import { getUrlWithUtmTrackingParams } from '../utils'; import Text from './common/Text'; function Authors({ authors }: { authors: Array | Array }): JSX.Element { @@ -19,8 +20,10 @@ function Authors({ authors }: { authors: Array | Array }): JSX.E ); } + const authorUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url: author.website }); + return ( - + {isLastElement ? author.name : `${author.name}, `} diff --git a/components/Curators.tsx b/components/Curators.tsx index 6de5b26..e054c68 100644 --- a/components/Curators.tsx +++ b/components/Curators.tsx @@ -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)); @@ -73,6 +74,8 @@ function Curators(): JSX.Element {
{curators.map((curator, index) => { + const curatorUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url: curator.links.website }); + return (
{ const renderMedia = ({ format, source, caption }: RenderMediaProps): JSX.Element => { switch (format) { case 'mp4': + const sourceWithTrackingParams = getUrlWithUtmTrackingParams({ url: source }); + return (

- Your browser doesn't support HTML5 video. Here is a link to the video instead. + Your browser doesn't support HTML5 video. Here is a{' '} + link to the video instead.

); diff --git a/components/ToolItem.tsx b/components/ToolItem.tsx index b98165c..74d440b 100644 --- a/components/ToolItem.tsx +++ b/components/ToolItem.tsx @@ -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 (
{ + const shareUrlWithTrackingParams = getUrlWithUtmTrackingParams({ url }); + return ( { + 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}) -
[Read issue on web](https://scriptified.dev/issues/${currentIssue.id})
+
[Read issue on web](${issueWebUrl})
${currentIssue.description} @@ -112,13 +135,14 @@ ${currentIssue.description} ${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)} ___ @@ -126,60 +150,61 @@ ___ # 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 ${currentIssue.devOfTheWeek.name} + 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} @@ -195,9 +220,16 @@ ___ ${currentIssue.quiz.codeSnippet.code.code} \`\`\` -${currentIssue.quiz.options.map( - option => `[
${option.text.split('\n').join('
')}
](https://scriptified.dev/issues/${currentIssue.id}?section=quiz&option=${option.option_id})` - ).join('\n')} +${currentIssue.quiz.options + .map( + option => + `[
${option.text + .split('\n') + .join('
')}
](https://scriptified.dev/issues/${currentIssue.id}?section=quiz&option=${ + option.option_id + })` + ) + .join('\n')} ___ @@ -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`; diff --git a/utils/index.ts b/utils/index.ts index c326395..2d881ae 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -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; + } +};