{milestone.title || 'Untitled milestone'}
{milestone.date ? new Date(milestone.date).toLocaleDateString() : 'No date provided'}
{milestone.description}
: null} {milestone.notes ?{milestone.notes}
: null}// DEPLOY_CONFIG: {"cron": [{"name": "monthly_family_milestone_summary", "schedule": "0 9 1 * *", "action": "event", "config": {"event_type": "generate_monthly_family_milestone_summary"}}], "triggers": [{"name": "send_monthly_family_milestone_summary_email", "on": "event.generate_monthly_family_milestone_summary", "actions": [{"type": "email", "to": "{{family_member_emails}}", "subject": "Monthly Milestone Summary", "body": "Hello family member,\n\nHere is your monthly milestone summary for the family.\n\n{{milestone_summary}}\n\nBest,\nDeplixo"}]}]}
import { useMemo, useRef, useState } from 'react';
import { useAuth, useCollection, generatePDF } from '@deplixo/sdk';
import { Timeline } from './components/Timeline.jsx';
import { Suggestions } from './components/Suggestions.jsx';
function App() {
const { user, loading, login, logout } = useAuth();
const { items: shareLinks, add: addShareLink, loading: shareLinksLoading } = useCollection('share_links', { personal: true });
const { items: milestones = [], loading: milestonesLoading } = useCollection('milestones', { personal: true });
const [activeTab, setActiveTab] = useState('timeline');
const [shareLinkCopied, setShareLinkCopied] = useState(false);
const [shareError, setShareError] = useState('');
const [linkMode, setLinkMode] = useState(false);
const [accessCode, setAccessCode] = useState('');
const [shareTitle, setShareTitle] = useState('Grandparent Viewer');
const [pdfExporting, setPdfExporting] = useState(false);
const pdfRef = useRef(null);
const tabs = [
{ id: 'timeline', label: '๐
Timeline', icon: '๐
' },
{ id: 'suggestions', label: '๐ก Suggestions', icon: '๐ก' }
];
const currentShareLink = useMemo(() => {
if (!shareLinks?.length) return null;
return [...shareLinks]
.filter(link => link && link.token)
.sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0))[0] || null;
}, [shareLinks]);
const printableMilestones = useMemo(() => {
return [...(milestones || [])].sort((a, b) => new Date(b.date || b.createdAt || 0) - new Date(a.date || a.createdAt || 0));
}, [milestones]);
const readQueryParam = (key) => {
try {
return new URLSearchParams(window.location.search).get(key) || '';
} catch {
return '';
}
};
const normalizeToken = (value) => (value || '').trim();
const hasTimelineAccess = () => {
const linkToken = normalizeToken(readQueryParam('share'));
if (!linkToken) return true;
return shareLinks?.some(link => normalizeToken(link.token) === linkToken && link.active !== false) || false;
};
const canCreateShareLink = () => !!user && !linkMode && !shareLinksLoading;
const generateShareLink = async () => {
setShareError('');
setShareLinkCopied(false);
try {
const token = `${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
const baseUrl = window.location.origin + window.location.pathname;
const url = `${baseUrl}?share=${encodeURIComponent(token)}`;
await addShareLink({
token,
url,
title: shareTitle.trim() || 'Grandparent Viewer',
familyName: user?.name || 'Family',
active: true,
createdAt: new Date().toISOString(),
createdBy: user?.email || user?.name || 'unknown'
});
try {
await navigator.clipboard.writeText(url);
setShareLinkCopied(true);
} catch {
setShareLinkCopied(false);
}
} catch (error) {
setShareError('Could not generate a share link right now. Please try again.');
}
};
const handlePdfExport = async () => {
if (!pdfRef.current || pdfExporting) return;
setPdfExporting(true);
try {
await generatePDF(pdfRef.current, {
filename: 'milestone-book.pdf',
margin: 10
});
} finally {
setPdfExporting(false);
}
};
const renderShareAccessBanner = () => {
const shareToken = normalizeToken(readQueryParam('share'));
if (!shareToken) return null;
const valid = shareLinks?.some(link => normalizeToken(link.token) === shareToken && link.active !== false);
return (
{valid
? 'You are viewing a timeline shared by this family. You can browse the shared milestones without signing in.'
: 'This link is not active anymore. Please ask the family for a new shared link.'}
Loading your family account...
Signing you in...
Shared timeline access
Ask the family to generate a new share link so you can view the timeline again.
Cherish every first moment
Create or join a shared family account so parents and caregivers can record milestones together.
By continuing, your Google account becomes part of this family workspace.
Cherish every first moment
{user ? `Signed in as ${user.name} ยท ${user.email}` : 'Viewing shared family timeline'}
Create a view-only link that lets grandparents open the shared family timeline.
Link copied to clipboard.
: null}{shareError}
: null}Sign in to access milestone suggestions and manage your family workspace.