- {tags.map((tag) => {
+ {tags.filter(isEntryTypeTag).map((tag) => {
const { name } = tag.fields
return
diff --git a/src/components/Blog/Twitter/index.tsx b/src/components/Blog/Twitter/index.tsx
new file mode 100644
index 000000000..38bfd5681
--- /dev/null
+++ b/src/components/Blog/Twitter/index.tsx
@@ -0,0 +1,15 @@
+import { extractLastPathname } from '@/lib/urlPatterns'
+import { TwitterTweetEmbed } from 'react-twitter-embed'
+import css from './styles.module.css'
+
+const Twitter = ({ url }: { url: string }) => {
+ const tweetId = extractLastPathname(url)
+
+ return (
+
+
+
+ )
+}
+
+export default Twitter
diff --git a/src/components/Blog/Twitter/styles.module.css b/src/components/Blog/Twitter/styles.module.css
new file mode 100644
index 000000000..8f709a6a6
--- /dev/null
+++ b/src/components/Blog/Twitter/styles.module.css
@@ -0,0 +1,9 @@
+.tweetContainer {
+ margin: 24px auto 0;
+ width: 80%;
+}
+
+.tweetContainer > * {
+ display: flex;
+ justify-content: center;
+}
diff --git a/src/components/Blog/Youtube/index.tsx b/src/components/Blog/Youtube/index.tsx
index 8193af91f..83ad3b48a 100644
--- a/src/components/Blog/Youtube/index.tsx
+++ b/src/components/Blog/Youtube/index.tsx
@@ -1,16 +1,23 @@
+import { extractYouTubeVideoId } from '@/lib/urlPatterns'
import css from './styles.module.css'
-const Youtube = ({ embedId }: { embedId: string }) => (
-
-
-
-)
+const YouTube = ({ url }: { url: string }) => {
+ const videoId = extractYouTubeVideoId(url)
-export default Youtube
+ if (!videoId) return null
+
+ return (
+
+
+
+ )
+}
+
+export default YouTube
diff --git a/src/components/Campaign/RichText/index.tsx b/src/components/Campaign/RichText/index.tsx
index e71e12071..fdd42b922 100644
--- a/src/components/Campaign/RichText/index.tsx
+++ b/src/components/Campaign/RichText/index.tsx
@@ -13,9 +13,9 @@ import css from './styles.module.css'
import { Typography } from '@mui/material'
import { isText } from '@/lib/typeGuards'
import kebabCase from 'lodash/kebabCase'
-import { extractLastPathname, extractVideoId, isTwitterUrl, isYouTubeUrl } from '@/lib/urlPatterns'
-import { TwitterTweetEmbed } from 'react-twitter-embed'
-import Youtube from '@/components/Blog/Youtube'
+import { isTwitterUrl, isYouTubeUrl } from '@/lib/urlPatterns'
+import YouTube from '@/components/Blog/YouTube'
+import Twitter from '@/components/Blog/Twitter'
const options: Options = {
renderNode: {
@@ -72,20 +72,11 @@ const options: Options = {
[BLOCKS.EMBEDDED_ENTRY]: (node: Node) => {
const entryUrl = node.data.target.fields.url
- if (isYouTubeUrl(entryUrl)) {
- const videoId = extractVideoId(entryUrl)
- if (!videoId) return null
-
- return
- } else if (isTwitterUrl(entryUrl)) {
- const tweetId = extractLastPathname(entryUrl)
-
- return (
-
-
-
- )
- }
+ return isYouTubeUrl(entryUrl) ? (
+
+ ) : isTwitterUrl(entryUrl) ? (
+
+ ) : null
},
},
} as unknown as RenderNode
diff --git a/src/components/Campaign/RichText/styles.module.css b/src/components/Campaign/RichText/styles.module.css
index cefda1b6a..f746d9e24 100644
--- a/src/components/Campaign/RichText/styles.module.css
+++ b/src/components/Campaign/RichText/styles.module.css
@@ -10,13 +10,3 @@
margin-top: 8px;
font-style: italic;
}
-
-.tweetContainer {
- margin: 24px auto 0;
- width: 80%;
-}
-
-.tweetContainer > * {
- display: flex;
- justify-content: center;
-}
diff --git a/src/config/constants.ts b/src/config/constants.ts
index 2d821f7c7..1ed3b101f 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -26,6 +26,7 @@ export const TWITTER_LINK = 'https://x.com/safe'
export const DISCORD_LINK = 'https://chat.safe.global'
export const YOUTUBE_LINK = 'https://www.youtube.com/@safeglobal'
export const MIRROR_LINK = 'https://safe.mirror.xyz'
+export const MIRROR_SUBSCRIBE_LINK = 'https://safe.mirror.xyz/subscribe/embed'
export const GITHUB_LINK = 'https://github.com/safe-global'
export const PROTOCOL_KIT_LINK = 'https://docs.safe.global/safe-core-aa-sdk/protocol-kit'
diff --git a/src/contentful/types/TypeTag.ts b/src/contentful/types/TypeTag.ts
index 869fc50e0..f45e30f81 100644
--- a/src/contentful/types/TypeTag.ts
+++ b/src/contentful/types/TypeTag.ts
@@ -8,7 +8,7 @@ export interface TypeTagFields {
| 'Events'
| 'Grants'
| 'NFT'
- | 'New feature'
+ | 'New Feature'
| 'Safe{Core}'
| 'Safe{DAO}'
| 'Safe{Wallet}'
diff --git a/src/lib/__test__/urlPatterns.test.ts b/src/lib/__test__/urlPatterns.test.ts
index 3d90f85f0..d83e5624e 100644
--- a/src/lib/__test__/urlPatterns.test.ts
+++ b/src/lib/__test__/urlPatterns.test.ts
@@ -1,4 +1,4 @@
-import { extractLastPathname, extractVideoId, isTwitterUrl, isYouTubeUrl } from '@/lib/urlPatterns'
+import { extractLastPathname, extractYouTubeVideoId, isTwitterUrl, isYouTubeUrl } from '@/lib/urlPatterns'
describe('urlPatterns', () => {
describe('isYouTubeUrl', () => {
@@ -13,15 +13,15 @@ describe('urlPatterns', () => {
})
})
- describe('extractVideoId', () => {
+ describe('extractYouTubeVideoId', () => {
it('should extract video id from YouTube URLs', () => {
- expect(extractVideoId('https://www.youtube.com/watch?v=dQw4w9WgXcQ')).toBe('dQw4w9WgXcQ')
- expect(extractVideoId('https://youtu.be/dQw4w9WgXcQ')).toBe('dQw4w9WgXcQ')
+ expect(extractYouTubeVideoId('https://www.youtube.com/watch?v=dQw4w9WgXcQ')).toBe('dQw4w9WgXcQ')
+ expect(extractYouTubeVideoId('https://youtu.be/dQw4w9WgXcQ')).toBe('dQw4w9WgXcQ')
})
it('should return null for non-YouTube URLs', () => {
- expect(extractVideoId('https://www.google.com')).toBe(null)
- expect(extractVideoId('https://twitter.com')).toBe(null)
+ expect(extractYouTubeVideoId('https://www.google.com')).toBe(null)
+ expect(extractYouTubeVideoId('https://twitter.com')).toBe(null)
})
})
@@ -40,7 +40,7 @@ describe('urlPatterns', () => {
describe('extractLastPathname', () => {
it('should extract last pathname component from URL', () => {
expect(extractLastPathname('https://example.com/foo/bar')).toBe('bar')
- expect(extractLastPathname('https://example.com/foo/bar/')).toBe('')
+ expect(extractLastPathname('https://example.com/foo/bar/')).toBe('bar')
expect(extractLastPathname('https://example.com/')).toBe('')
})
})
diff --git a/src/lib/typeGuards.ts b/src/lib/typeGuards.ts
index 1c8940dbe..5e5baf7d1 100644
--- a/src/lib/typeGuards.ts
+++ b/src/lib/typeGuards.ts
@@ -9,28 +9,32 @@ import type {
import type { Asset, Entry } from 'contentful'
import type { Text } from '@contentful/rich-text-types'
+const getContentTypeSysId = (obj: any): string => {
+ return obj.sys.contentType && obj.sys.contentType.sys.id
+}
+
export const isEntryTypeButton = (obj: any): obj is Entry
=> {
- return obj.sys.contentType.sys.id === 'button'
+ return getContentTypeSysId(obj) === 'button'
}
export const isEntryTypeCardGridItem = (obj: any): obj is Entry => {
- return obj.sys.contentType.sys.id === 'cardGridItem'
+ return getContentTypeSysId(obj) === 'cardGridItem'
}
export const isEntryTypeFaqEntry = (obj: any): obj is Entry => {
- return obj.sys.contentType.sys.id === 'faqEntry'
+ return getContentTypeSysId(obj) === 'faqEntry'
}
export const isEntryTypeTag = (obj: any): obj is Entry => {
- return obj.sys.contentType && obj.sys.contentType.sys.id === 'tag'
+ return getContentTypeSysId(obj) === 'tag'
}
export const isEntryTypeAuthor = (obj: any): obj is Entry => {
- return obj.sys.contentType && obj.sys.contentType.sys.id === 'author'
+ return getContentTypeSysId(obj) === 'author'
}
export const isEntryTypePost = (obj: any): obj is Entry => {
- return obj.sys.contentType && obj.sys.contentType.sys.id === 'post'
+ return getContentTypeSysId(obj) === 'post'
}
export const isEntryType = (obj: any): obj is Entry => {
diff --git a/src/lib/urlPatterns.ts b/src/lib/urlPatterns.ts
index d1d3c8205..a4a3b1283 100644
--- a/src/lib/urlPatterns.ts
+++ b/src/lib/urlPatterns.ts
@@ -3,7 +3,7 @@ export function isYouTubeUrl(url: string) {
return youtubeRegex.test(url)
}
-export function extractVideoId(url: string) {
+export function extractYouTubeVideoId(url: string) {
// short form
if (url.includes('youtu.be')) return url.split('/').pop()
@@ -20,6 +20,6 @@ export function isTwitterUrl(url: string) {
export function extractLastPathname(url: string) {
const urlObj = new URL(url)
- const pathnameParts = urlObj.pathname.split('/')
- return pathnameParts[pathnameParts.length - 1]
+ const pathnameParts = urlObj.pathname.split('/').filter((part) => part !== '')
+ return pathnameParts[pathnameParts.length - 1] || ''
}