Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
height: 100vh;
width: 100%;
position: relative;
background-color: #f9f9f9;
background-color: var(--bg-primary);
}

/* Responsive layout */
Expand All @@ -37,8 +37,8 @@
z-index: 200;
padding: 8px 8px 4px;
border-radius: 50%;
background: #f9f9f9;
border: 1px solid #ccc;
background: var(--bg-primary);
border: 1px solid var(--border-primary);
cursor: pointer;
display: none;
transition: transform 0.3s ease;
Expand All @@ -59,3 +59,38 @@
margin: 0 auto;
padding: 20px;
}

/* Embed mode */
.app-container.embed-mode {
flex-direction: column;
position: relative;
}

.app-container.embed-mode .calendar-container {
cursor: default;
}

.app-container.embed-mode .calendar-day {
cursor: default;
}

.embed-edit-badge {
position: fixed;
bottom: 8px;
right: 12px;
background-color: var(--accent);
color: white;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
text-decoration: none;
opacity: 0.85;
transition: opacity 0.2s;
z-index: 100;
}

.embed-edit-badge:hover {
opacity: 1;
color: white;
}
49 changes: 47 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import Calendar from "./components/Calendar";
import ChevronIcon from "./components/icons/ChevronIcon";
import HelpModal from "./components/HelpModal";
import LicenseModal from "./components/LicenseModal";
import EmbedModal from "./components/EmbedModal";

function App() {
const [isSidebarHidden, setIsSidebarHidden] = useState(false);
const [showLicenseModal, setShowLicenseModal] = useState(false);
const [showEmbedModal, setShowEmbedModal] = useState(false);
const isEmbedMode = useStore((state) => state.isEmbedMode);
const getAppStateFromUrl = useStore((state) => state.getAppStateFromUrl);
const generateShareableUrl = useStore((state) => state.generateShareableUrl);
const showHelpModal = useStore((state) => state.showHelpModal);
Expand All @@ -23,6 +26,25 @@ function App() {
const showToday = useStore((state) => state.showToday);
const eventGroups = useStore((state) => state.eventGroups);
const firstDayOfWeek = useStore((state) => state.firstDayOfWeek);
const theme = useStore((state) => state.theme);

// Apply theme to document
useEffect(() => {
const applyTheme = (resolved: "light" | "dark") => {
document.documentElement.setAttribute("data-theme", resolved);
};

if (theme === "system") {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
applyTheme(mq.matches ? "dark" : "light");
const handler = (e: MediaQueryListEvent) =>
applyTheme(e.matches ? "dark" : "light");
mq.addEventListener("change", handler);
return () => mq.removeEventListener("change", handler);
} else {
applyTheme(theme);
}
}, [theme]);

// Load state from URL on initial mount
useEffect(() => {
Expand Down Expand Up @@ -71,6 +93,23 @@ function App() {
}
};

if (isEmbedMode) {
const editUrl = window.location.href.replace(/[?&]embed=true/, "");
return (
<div className="app-container embed-mode">
<Calendar />
<a
className="embed-edit-badge"
href={editUrl}
target="_blank"
rel="noopener noreferrer"
>
Edit on PocketCal
</a>
</div>
);
}

return (
<div className={`app-container ${isSidebarHidden ? "sidebar-hidden" : ""}`}>
<button
Expand All @@ -79,14 +118,20 @@ function App() {
aria-label={isSidebarHidden ? "Show sidebar" : "Hide sidebar"}
aria-expanded={!isSidebarHidden}
>
<ChevronIcon color="black" />
<ChevronIcon color="var(--icon-color)" />
</button>
<Sidebar setShowLicenseModal={setShowLicenseModal} />
<Sidebar
setShowLicenseModal={setShowLicenseModal}
setShowEmbedModal={setShowEmbedModal}
/>
<Calendar />
{showHelpModal && <HelpModal onClose={() => setShowHelpModal(false)} />}
{showLicenseModal && (
<LicenseModal onClose={() => setShowLicenseModal(false)} />
)}
{showEmbedModal && (
<EmbedModal onClose={() => setShowEmbedModal(false)} />
)}
</div>
);
}
Expand Down
40 changes: 20 additions & 20 deletions src/components/Calendar.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 10px;
align-content: start;
background-color: #f9f9f9;
background-color: var(--bg-primary);
overflow-y: auto;
height: 100%;
box-sizing: border-box;
Expand All @@ -26,18 +26,18 @@
}

.calendar-month {
border: 1px solid #eee;
border: 1px solid var(--border-light);
padding: 8px;
border-radius: 6px;
background-color: white;
background-color: var(--bg-card);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
min-width: 0;
}

.calendar-month h3 {
text-align: center;
margin: 0 0 8px 0;
color: #333;
color: var(--text-primary);
font-size: 0.9em;
font-weight: 600;
}
Expand All @@ -46,7 +46,7 @@
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background-color: #f5f5f5;
background-color: var(--bg-grid);
padding: 1px;
border-radius: 4px;
font-size: 0.8em;
Expand All @@ -61,12 +61,12 @@
font-weight: 600;
font-size: 0.7em;
padding: 2px;
background-color: #f8f9fa;
color: #666;
background-color: var(--bg-weekday);
color: var(--text-secondary);
}

.calendar-day {
background-color: #ffffff;
background-color: var(--bg-secondary);
border: 1px solid transparent;
min-height: 24px;
height: 24px;
Expand All @@ -81,7 +81,7 @@
}

.calendar-day:hover {
background-color: #f8f9fa;
background-color: var(--bg-weekday);
}

.calendar-day.empty {
Expand All @@ -94,9 +94,9 @@
}

.calendar-day.today .day-number {
color: #333;
color: var(--text-primary);
font-weight: bold;
border: 2px solid #eb4888;
border: 2px solid var(--today-border);
border-radius: 50%;
box-sizing: border-box;
width: 20px;
Expand All @@ -108,20 +108,20 @@
}

.calendar-day:has(.range-indicator).today .day-number {
color: #333;
background-color: #fff;
color: var(--today-text-on-range);
background-color: var(--today-bg-on-range);
border-color: transparent;
text-shadow: none;
}

.calendar-day.dragging {
background-color: #e3f2fd;
border: 1px dashed #2196f3;
background-color: var(--drag-bg);
border: 1px dashed var(--drag-border);
}

.day-number {
font-size: 0.9em;
color: #333;
color: var(--text-primary);
width: 20px;
height: 20px;
display: flex;
Expand All @@ -134,9 +134,9 @@

/* When the calendar day has range indicators, make the number white and bold */
.calendar-day:has(.range-indicator) .day-number {
color: white;
color: var(--day-text-on-range);
font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
text-shadow: 0 1px 2px var(--day-shadow-on-range);
}

.range-indicators {
Expand All @@ -159,7 +159,7 @@
}

.calendar-container:focus {
outline: 2px solid rgba(138, 53, 222, 0.5);
outline: 2px solid var(--accent-outline);
outline-offset: 2px;
}

Expand All @@ -168,7 +168,7 @@
}

.calendar-day.focused {
outline: 2px solid rgba(138, 53, 222, 0.8);
outline: 2px solid var(--accent-outline-strong);
outline-offset: -2px;
z-index: 3;
}
5 changes: 3 additions & 2 deletions src/components/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const Calendar: React.FC = () => {
addDateRange,
deleteDateRange,
firstDayOfWeek,
isEmbedMode,
} = useStore();

const calendarDates = getCalendarDates(startDate);
Expand Down Expand Up @@ -130,7 +131,7 @@ const Calendar: React.FC = () => {
};

const handleDateSelection = (date: Date) => {
if (!selectedGroupId) return;
if (isEmbedMode || !selectedGroupId) return;

const selectedGroup = eventGroups.find(
(group) => group.id === selectedGroupId
Expand Down Expand Up @@ -173,7 +174,7 @@ const Calendar: React.FC = () => {
};

const handleMouseDown = (date: Date) => {
if (!selectedGroupId) return;
if (isEmbedMode || !selectedGroupId) return;
setFocusedDate(date);

const selectedGroup = eventGroups.find(
Expand Down
55 changes: 55 additions & 0 deletions src/components/EmbedModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState } from "react";
import XIcon from "./icons/XIcon";
import CopyIcon from "./icons/CopyIcon";
import { useStore } from "../store";
import "./Modal.css";

interface EmbedModalProps {
onClose: () => void;
}

const EmbedModal: React.FC<EmbedModalProps> = ({ onClose }) => {
const generateShareableUrl = useStore((state) => state.generateShareableUrl);
const [copied, setCopied] = useState(false);

const shareableUrl = generateShareableUrl();
const embedUrl = shareableUrl.includes("?")
? `${shareableUrl}&embed=true`
: `${shareableUrl.split("#")[0]}?embed=true#${shareableUrl.split("#")[1] || ""}`;

const embedCode = `<iframe src="${embedUrl}" width="100%" height="600" style="border: none; border-radius: 8px;" title="PocketCal Calendar"></iframe>`;

const handleCopy = () => {
navigator.clipboard.writeText(embedCode);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button
className="modal-close"
onClick={onClose}
aria-label="Close embed modal"
>
<XIcon color="var(--icon-color)" />
</button>
<h2>Embed Calendar</h2>
<p>
Copy the code below to embed a read-only version of your calendar on
any website.
</p>
<pre className="license-key-display" style={{ whiteSpace: "pre-wrap", wordBreak: "break-all" }}>
{embedCode}
</pre>
<button onClick={handleCopy} className="btn" style={{ marginTop: "12px", display: "flex", alignItems: "center", gap: "6px" }}>
<CopyIcon width={16} height={16} color="white" />
{copied ? "Copied!" : "Copy Embed Code"}
</button>
</div>
</div>
);
};

export default EmbedModal;
2 changes: 1 addition & 1 deletion src/components/HelpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const HelpModal: React.FC<HelpModalProps> = ({ onClose }) => {
onClick={onClose}
aria-label="Close instructions"
>
<XIcon color="#000" />
<XIcon color="var(--icon-color)" />
</button>
<h2>
Pocket<span>Cal</span> Instructions
Expand Down
4 changes: 2 additions & 2 deletions src/components/LicenseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const LicenseModal: React.FC<LicenseModalProps> = ({ onClose }) => {
onClick={onClose}
aria-label="Close license modal"
>
<XIcon color="#000" />
<XIcon color="var(--icon-color)" />
</button>
<h2>
Pocket<span className="logo-cal">Cal</span>{" "}
Expand All @@ -87,7 +87,7 @@ const LicenseModal: React.FC<LicenseModalProps> = ({ onClose }) => {
{isProUser ? (
<div className="license-status">
<p className="license-active">
<CalIcon color="#000" />
<CalIcon color="var(--icon-color)" />
<strong>Pro license active!</strong>
</p>

Expand Down
Loading