diff --git a/package.json b/package.json
index 4167e42..03392b7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
- "version": "1.1.0",
+ "version": "1.1.1",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx
index 1e13f7c..7de4978 100644
--- a/src/AdminPage.tsx
+++ b/src/AdminPage.tsx
@@ -141,7 +141,10 @@ interface StudyUserRecord {
displayName: string
createdAt: string | null
lastLoginAt: string | null
- enrolledStudies: Array<{ slug: string; title: string }>
+ lastStudiedDate: string | null
+ currentStreak: number
+ longestStreak: number
+ enrolledStudies: Array<{ slug: string; title: string; completedLessons?: number; totalLessons?: number }>
noteCount: number
subscribeNewsletter: boolean
studyRemindersEnabled: boolean
@@ -276,6 +279,7 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
{user.enrolledStudies.length} enrolled
{user.noteCount} notes
+ {(user.currentStreak ?? 0) > 0 && {user.currentStreak} 🔥}
{isExpanded ? 'â–²' : 'â–¼'}
@@ -285,6 +289,8 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
Joined: {user.createdAt ? new Date(user.createdAt).toLocaleDateString() : '—'}
Last login: {user.lastLoginAt ? new Date(user.lastLoginAt).toLocaleDateString() : '—'}
+ Last studied: {user.lastStudiedDate ?? '—'}
+ Streak: {user.currentStreak ?? 0} 🔥 (best: {user.longestStreak ?? 0})
Newsletter: {user.subscribeNewsletter ? 'Yes' : 'No'}
Reminders: {user.studyRemindersEnabled ? 'On' : 'Off'}
@@ -329,7 +335,14 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
{user.enrolledStudies.map(e => (
-
- {e.title}
+
+ {e.title}
+ {e.totalLessons != null && (
+
+ {e.completedLessons ?? 0}/{e.totalLessons} lessons
+
+ )}
+
))}
@@ -748,7 +761,7 @@ const ADMIN_VIEW_OPTIONS: Array<{ group: string; options: Array<{ value: AdminVi
],
},
{
- group: 'Manage',
+ group: 'Audience',
options: [
{ value: 'questions', label: 'Questions' },
{ value: 'study-comments', label: 'Study Comments' },
@@ -756,8 +769,17 @@ const ADMIN_VIEW_OPTIONS: Array<{ group: string; options: Array<{ value: AdminVi
{ value: 'contacts', label: 'Contacts' },
{ value: 'subscribers', label: 'Subscribers' },
{ value: 'study-users', label: 'Study Users' },
- { value: 'email-templates', label: 'Email Templates' },
+ ],
+ },
+ {
+ group: 'Insights',
+ options: [
{ value: 'analytics', label: 'Analytics' },
+ ],
+ },
+ {
+ group: 'Tools',
+ options: [
{ value: 'assets', label: 'Asset Manager' },
{ value: 'qr-codes', label: 'QR Codes' },
],
@@ -767,6 +789,7 @@ const ADMIN_VIEW_OPTIONS: Array<{ group: string; options: Array<{ value: AdminVi
options: [
{ value: 'global', label: 'Footer & Global' },
{ value: 'seo', label: 'SEO & Redirects' },
+ { value: 'email-templates', label: 'Email Templates' },
{ value: 'legal', label: 'Legal Pages' },
{ value: 'security', label: 'Security' },
{ value: 'brand', label: 'Brand Kit' },
@@ -792,6 +815,7 @@ const ADMIN_SECTION_LINKS: Partial> = {
{ id: 'about-show-copy', label: 'Show Copy' },
{ id: 'about-nate-bio', label: 'Nate Bio' },
{ id: 'about-images', label: 'Images' },
+ { id: 'about-testimonials', label: 'Testimonials' },
],
contact: [
{ id: 'contact-profile', label: 'Profile' },
@@ -820,7 +844,7 @@ const ADMIN_SECTION_LINKS: Partial> = {
],
}
-type PodcastTab = 'current-series' | 'episode-highlights' | 'finished-books' | 'podcast-checklist' | 'episode-scripts'
+type PodcastTab = 'current-series' | 'episode-highlights' | 'finished-books' | 'podcast-checklist' | 'episode-scripts' | 'rss-feed'
type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global' | 'email-templates'
@@ -3815,6 +3839,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
+
)}
@@ -4816,6 +4841,40 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
)}
+ {/* RSS FEED */}
+ {(adminView === 'podcast' && podcastTab === 'rss-feed') && (() => {
+ const canonicalUrl = (form as { seo?: { canonicalUrl?: string } }).seo?.canonicalUrl?.replace(/\/$/, '') ?? ''
+ const feedUrl = canonicalUrl ? `${canonicalUrl}/feed.xml` : '/feed.xml'
+ const anchorFeedUrl = 'https://anchor.fm/s/11068d290/podcast/rss'
+ return (
+
+
+
RSS Feed
+
Your podcast feed is available at your own domain and proxies the Anchor source feed.
+
+
+
+
Your Feed URL
+
+
{feedUrl}
+
+
Open ↗
+
+
Submit this URL to Apple Podcasts, Spotify, and other directories to keep your feed on your own domain.
+
+
+
Anchor Source Feed
+
+
Your feed.xml proxies this source and rewrites the channel link to your domain. Episodes are cached for 30 minutes.
+
+
+
+ )
+ })()}
+
{/* STUDY COMMENTS */}
{adminView === 'study-comments' && (() => {
const filtered = studyComments.filter(c => {