Journal dashboard widgets and quick-add; rich-text quick-add dialogs. Dashboard draft sync for live edit previews; assistant bubble + API tools. Journal UX: stress slider, mood grid, query cap fix.
168 lines
5.0 KiB
TypeScript
168 lines
5.0 KiB
TypeScript
import { Calendar as CalendarIcon, MapPin } from "lucide-react";
|
||
import { RichTextContent } from "@/components/rich-text";
|
||
import type { CalendarShareData, EventShareData } from "../server/share-queries";
|
||
import { ShareEyebrow } from "@/components/share/share-eyebrow";
|
||
import { MiniDayCard } from "@/components/share/mini-day-card";
|
||
import { MiniMapCard } from "@/components/share/mini-map-card";
|
||
import { ShareDetailCard, ShareRow } from "@/components/share/share-detail-card";
|
||
|
||
function formatTime(d: Date): string {
|
||
return d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
|
||
}
|
||
|
||
function formatTimeRange(startAt: string, endAt: string, allDay: boolean): string {
|
||
const start = new Date(startAt);
|
||
const end = new Date(endAt);
|
||
if (allDay) return "All day";
|
||
return `${formatTime(start)} – ${formatTime(end)}`;
|
||
}
|
||
|
||
function formatFullDate(d: Date): string {
|
||
return d.toLocaleDateString(undefined, {
|
||
weekday: "long",
|
||
month: "long",
|
||
day: "numeric",
|
||
year: "numeric",
|
||
});
|
||
}
|
||
|
||
export function EventSharedView({ data }: { data: EventShareData }) {
|
||
const start = new Date(data.startAt);
|
||
const end = new Date(data.endAt);
|
||
const time = data.allDay ? "All day" : `${formatTime(start)} – ${formatTime(end)}`;
|
||
|
||
return (
|
||
<>
|
||
<ShareEyebrow>
|
||
<CalendarIcon className="size-3" />
|
||
Event
|
||
</ShareEyebrow>
|
||
<h1
|
||
className="serif"
|
||
style={{
|
||
fontSize: "clamp(28px, 6vw, 38px)",
|
||
lineHeight: 1.15,
|
||
letterSpacing: "-0.02em",
|
||
color: "var(--ink)",
|
||
margin: "0 0 8px",
|
||
textWrap: "pretty",
|
||
}}
|
||
>
|
||
{data.title}
|
||
</h1>
|
||
{data.calendarName && (
|
||
<p
|
||
className="serif"
|
||
style={{
|
||
fontSize: 17,
|
||
color: "var(--ink-soft)",
|
||
margin: "0 0 28px",
|
||
}}
|
||
>
|
||
On the {data.calendarName} calendar.
|
||
</p>
|
||
)}
|
||
|
||
<div
|
||
className="grid gap-3.5 mb-6"
|
||
style={{ gridTemplateColumns: data.location ? "1fr 1fr" : "1fr" }}
|
||
>
|
||
<MiniDayCard date={start} time={time} />
|
||
{data.location && <MiniMapCard name={data.location} />}
|
||
</div>
|
||
|
||
<ShareDetailCard>
|
||
<ShareRow label="When">
|
||
{formatFullDate(start)}
|
||
{!data.allDay && (
|
||
<>
|
||
{" · "}
|
||
{formatTimeRange(data.startAt, data.endAt, data.allDay)}
|
||
</>
|
||
)}
|
||
</ShareRow>
|
||
{data.location && (
|
||
<ShareRow label="Where">
|
||
<span className="inline-flex items-center gap-1.5">
|
||
<MapPin className="size-3.5 text-[var(--ink-mute)]" />
|
||
{data.location}
|
||
</span>
|
||
</ShareRow>
|
||
)}
|
||
<ShareRow label="Calendar">{data.calendarName}</ShareRow>
|
||
</ShareDetailCard>
|
||
|
||
{data.notes && (
|
||
<div
|
||
className="serif"
|
||
style={{
|
||
fontSize: 16,
|
||
lineHeight: 1.65,
|
||
color: "var(--ink-2)",
|
||
background: "var(--paper-2)",
|
||
borderRadius: 8,
|
||
padding: "18px 22px",
|
||
margin: "14px 0 24px",
|
||
textWrap: "pretty",
|
||
}}
|
||
>
|
||
<RichTextContent html={data.notes} />
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
|
||
export function CalendarSharedView({ data }: { data: CalendarShareData }) {
|
||
return (
|
||
<>
|
||
<ShareEyebrow>
|
||
<CalendarIcon className="size-3" />
|
||
Calendar
|
||
</ShareEyebrow>
|
||
<h1
|
||
className="serif"
|
||
style={{
|
||
fontSize: "clamp(28px, 6vw, 38px)",
|
||
lineHeight: 1.15,
|
||
letterSpacing: "-0.02em",
|
||
color: "var(--ink)",
|
||
margin: "0 0 8px",
|
||
textWrap: "pretty",
|
||
}}
|
||
>
|
||
{data.name}
|
||
</h1>
|
||
<p className="serif" style={{ fontSize: 17, color: "var(--ink-soft)", margin: "0 0 28px" }}>
|
||
Upcoming events — next 90 days.
|
||
</p>
|
||
|
||
<ShareDetailCard>
|
||
{data.events.length === 0 ? (
|
||
<p className="muted text-[13.5px] py-2">No upcoming events.</p>
|
||
) : (
|
||
data.events.map((event) => {
|
||
const start = new Date(event.startAt);
|
||
return (
|
||
<div key={event.id} className="list-row" style={{ padding: "10px 0" }}>
|
||
<span className="dot" style={{ background: data.color ?? "var(--c-household)" }} />
|
||
<div className="flex-1 min-w-0">
|
||
<div className="text-[14px] font-medium text-[var(--ink-2)]">{event.title}</div>
|
||
<div className="muted tnum text-[12px] mt-0.5">
|
||
{start.toLocaleDateString(undefined, {
|
||
month: "short",
|
||
day: "numeric",
|
||
})}
|
||
{!event.allDay && ` · ${formatTime(start)}`}
|
||
{event.location && ` · ${event.location}`}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})
|
||
)}
|
||
</ShareDetailCard>
|
||
</>
|
||
);
|
||
}
|