diff --git a/unlock-app/src/components/interface/LatestBlogs.tsx b/unlock-app/src/components/interface/LatestBlogs.tsx
new file mode 100644
index 00000000000..ce6134f76ed
--- /dev/null
+++ b/unlock-app/src/components/interface/LatestBlogs.tsx
@@ -0,0 +1,175 @@
+import axios from 'axios'
+import dayjs from 'dayjs'
+import isToday from 'dayjs/plugin/isToday'
+import { useEffect } from 'react'
+dayjs.extend(isToday)
+
+interface Blog {
+ title: string
+ link: string
+ id: string
+ updated: string
+ content: string
+ viewed: boolean
+}
+
+export function LatestBlogsLink({
+ setModalOpen,
+}: {
+ setModalOpen: (modalOpen: boolean) => void
+}) {
+ useEffect(() => {
+ saveLatestBlogs('https://unlock-protocol.com/blog.rss')
+ }, [])
+
+ const handleClick = async (e: React.MouseEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+ setModalOpen(true)
+ }
+
+ return (
+
+
Show Latest Blogs
+
+ Check out the latest updates from our blog.
+
+
+ )
+}
+
+export const LatestBlogs = () => {
+ const blogs: Blog[] = getLatestBlogs()
+
+ return (
+
+ )
+}
+
+async function parseAtomFeed(url: string) {
+ const response = await axios.get(url)
+ const text = response?.data
+
+ const parser = new DOMParser()
+ const doc = parser.parseFromString(text, 'application/xml')
+
+ if (doc.querySelector('parsererror')) {
+ throw new Error('Error parsing Atom feed.')
+ }
+
+ const atomNS = 'http://www.w3.org/2005/Atom'
+ const entries = doc.getElementsByTagNameNS(atomNS, 'entry')
+
+ const storedData = localStorage.getItem('latest_blogs')
+ let viewedMap = new Map()
+ if (storedData) {
+ const parsed = JSON.parse(storedData)
+ viewedMap = new Map(parsed.blogs.map((b: any) => [b.id, b.viewed]))
+ }
+
+ const unreadItems: Blog[] = []
+ for (const entry of Array.from(entries)) {
+ const id =
+ entry.getElementsByTagNameNS(atomNS, 'id')[0]?.textContent?.trim() || ''
+ const title =
+ entry.getElementsByTagNameNS(atomNS, 'title')[0]?.textContent?.trim() ||
+ ''
+ const linkElement = entry.getElementsByTagNameNS(atomNS, 'link')[0]
+ const link = linkElement?.getAttribute('href')?.trim() || ''
+ const updated =
+ entry.getElementsByTagNameNS(atomNS, 'updated')[0]?.textContent?.trim() ||
+ ''
+ const content =
+ entry.getElementsByTagNameNS(atomNS, 'content')[0]?.textContent?.trim() ||
+ ''
+
+ const viewed = viewedMap.has(id) ? viewedMap.get(id)! : false
+ if (!viewed) {
+ unreadItems.push({ title, link, id, updated, content, viewed: false })
+ if (unreadItems.length === 10) break
+ }
+ }
+
+ return {
+ entries: unreadItems,
+ }
+}
+
+export async function saveLatestBlogs(url: string) {
+ const storedDate =
+ localStorage.getItem('latest_blogs') &&
+ JSON.parse(localStorage.getItem('latest_blogs')!).fetched_on
+ if (storedDate && dayjs(storedDate).isToday()) {
+ return
+ }
+
+ const { entries } = await parseAtomFeed(url)
+ localStorage.setItem(
+ 'latest_blogs',
+ JSON.stringify({ blogs: entries, fetched_on: new Date().toISOString() })
+ )
+}
+
+function getLatestBlogs() {
+ const storedData = localStorage.getItem('latest_blogs')
+ if (!storedData) {
+ return null
+ }
+
+ const parsed = JSON.parse(storedData)
+ return parsed.blogs
+}
+
+function updateBlog(blogId: string) {
+ const storedData = localStorage.getItem('latest_blogs')
+ if (!storedData) return
+
+ const parsed = JSON.parse(storedData)
+ const updatedBlogs = parsed.blogs.map((b: Blog) => {
+ if (b.id === blogId) {
+ return { ...b, viewed: true }
+ }
+ return b
+ })
+
+ localStorage.setItem(
+ 'latest_blogs',
+ JSON.stringify({
+ blogs: updatedBlogs,
+ fetched_on: parsed.fetched_on,
+ })
+ )
+}
diff --git a/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx b/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx
index 761016b3fc7..f302f195f34 100644
--- a/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx
+++ b/unlock-app/src/components/interface/layouts/index/NotificationsMenu.tsx
@@ -3,6 +3,7 @@ import { Fragment, ReactNode, useState } from 'react'
import { BiBell as BellIcon } from 'react-icons/bi'
import { Button } from '@unlock-protocol/ui'
import { PromptEmailLink } from '../../PromptEmailLink'
+import { LatestBlogs, LatestBlogsLink } from '../../LatestBlogs'
import { useAuthenticate } from '~/hooks/useAuthenticate'
import { usePathname } from 'next/navigation'
import { Modal } from '@unlock-protocol/ui'
@@ -23,6 +24,7 @@ interface NotificationProps {
export function NotificationsMenu() {
const [isOpen, setIsOpen] = useState(false)
const [showModal, setShowModal] = useState(false)
+ const [showBlogsModal, setShowBlogsModal] = useState(false)
const { account, email } = useAuthenticate()
const pathname = usePathname()
@@ -46,6 +48,12 @@ export function NotificationsMenu() {
})
}
+ notifications.push({
+ id: '2',
+ content: ,
+ timestamp: new Date(),
+ })
+
return (
<>