Skip to content

feat(calendar): import user calendars - #418

Open
poeticlama wants to merge 5 commits into
mainfrom
feature/import-calendar
Open

feat(calendar): import user calendars#418
poeticlama wants to merge 5 commits into
mainfrom
feature/import-calendar

Conversation

@poeticlama

Copy link
Copy Markdown
Member

Description of changes

Copilot AI lite review requested due to automatic review settings August 3, 2026 19:45
@github-project-automation github-project-automation Bot moved this to 📋 Backlog in InNoHassle Aug 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces “linked/imported calendars” for the schedule calendar, exposing UI to import an external calendar URL, list linked calendars, and control their visibility/removal, plus backend-assisted URL checking for iCal fetching.

Changes:

  • Added UI for importing calendars (URL + metadata + color) and showing a preview before submission.
  • Added “linked calendar” cards with hide/remove actions, and surfaced calendar config/export entrypoint in the user menu.
  • Updated calendar/iCal fetching & viewer logic to support linked calendar sources and success callbacks.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/components/schedule/personal-card/PersonalCard.tsx New reusable “personal calendar” card UI with export/hide/link actions.
src/components/schedule/personal-card/HideButtonPersonal.tsx Refactors to shared HideButtonUI and removes debug logging.
src/components/schedule/linked-card/RemoveButtonLinked.tsx Adds linked-calendar removal UI with confirmation modal and optimistic cache update.
src/components/schedule/linked-card/LinkedCard.tsx Adds linked-calendar card UI and action buttons.
src/components/schedule/linked-card/HideButtonLinked.tsx Adds hide/unhide behavior for linked calendars with optimistic cache patching.
src/components/schedule/linked-card/EditButtonLinked.tsx Adds (currently hidden) linked-calendar edit trigger wired to ImportModal.
src/components/schedule/HideButtonUI.tsx Extracts generic hide button UI to reuse across card types.
src/components/schedule/group-card/HideButtonGroup.tsx Adds group hide button wrapper using shared HideButtonUI.
src/components/schedule/group-card/GroupCard.tsx Switches group cards to the new HideButtonGroup wrapper.
src/components/layout/UserMenu.tsx Adds “Config & Export” entrypoint to open calendar configuration dialog.
src/components/calendar/ScheduleLinkInput.tsx Adds reusable controlled input for calendar URL entry.
src/components/calendar/import/index.ts Exposes ImportModal via barrel export.
src/components/calendar/import/ImportModal.tsx Implements calendar import modal with preview, validation gating, and submission.
src/components/calendar/import/ColorsPalette.tsx Adds a color picker palette UI for imported calendar styling.
src/components/calendar/iCalendarPlugin/event-source-def.ts Routes non-internal iCal URLs through an API “check calendar URL” endpoint before fetching.
src/components/calendar/ConfigCalendarDialog.tsx Shows linked calendars and adds import flow alongside existing export/config UI.
src/components/calendar/CalendarViewer.tsx Adds eventSourceSuccess hook and extends event-source reconciliation logic (incl. color).
src/components/calendar/CalendarPage.tsx Includes linked calendars in the set of calendar sources to display.
src/api/schedule/links.ts Adds getImportedLink() to build the ICS URL for linked calendars.
Suppressed comments (2)

src/components/calendar/CalendarViewer.tsx:378

  • Same issue here: checking meta.color against the string "undefined" will fail when the meta color is actually undefined. Use a nullish comparison so sources with no color are treated consistently.
            source.url === eventSource.url &&
            // @ts-expect-error internalEventSource is presented in eventSource
            (source.internalEventSource.meta.color === eventSource.color ||
              // @ts-expect-error internalEventSource is presented in eventSource
              source.internalEventSource.meta.color === "undefined"),

src/components/schedule/HideButtonUI.tsx:16

  • This button is missing type="button". Without it, it defaults to "submit", which can cause unintended submissions if HideButtonUI is used inside a form.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 72 to 74
}).then((response) => {
if (response.status !== 200) return;
return response.text().then((icsText) => {
Comment thread src/components/calendar/CalendarViewer.tsx
Comment on lines +44 to +55
const removeLinkedCalendar = () => {
remove.mutate(
{
params: { query: { alias } },
},
{
onError: (error) => {
showError("Import failed", formatApiErrorMessage(error));
},
},
);
};
Comment thread src/components/schedule/linked-card/LinkedCard.tsx Outdated
Comment thread src/components/schedule/linked-card/EditButtonLinked.tsx Outdated
Comment thread src/components/layout/UserMenu.tsx
: null;
return (
<LinkedCard
key={linkedCalendar?.id}
Comment on lines +127 to +132
<button
onClick={() => {
setImportModalOpen(true);
}}
className="underline underline-offset-4"
>
Comment thread src/components/calendar/import/ColorsPalette.tsx
Comment thread src/api/schedule/links.ts Outdated
Comment on lines +46 to +51
export function getImportedLink(
userId: number | undefined,
linkedAlias: string,
) {
return `${SCHEDULE_API_URL}/users/${userId}/linked/${linkedAlias}.ics`;
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 1 comment.

Suppressed comments (9)

src/components/calendar/import/ImportModal.tsx:105

  • calendarColor is always initialized to #9747ff. In edit mode (prevAlias provided) this risks overwriting the previously saved color on PATCH even if the user didn’t intend to change it. Initialize the state from the existing linked calendar color when available.
  const [calendarColor, setCalendarColor] = useState("#9747ff");

src/components/calendar/import/ImportModal.tsx:184

  • The submit disabled condition only checks URL/description changes and ignores color changes, so changing only the color keeps the button disabled. Include color (for edit mode) in the “no changes” check.
              disabled={
                calendarName.length === 0 ||
                !isCalendarChecked ||
                (calendarURL === prevUrl &&
                  prevDescription === calendarDescription)

src/components/schedule/linked-card/RemoveButtonLinked.tsx:52

  • The error toast says “Import failed”, but this action is removing a linked calendar. Use an error message that matches the operation so users aren’t confused.
        onError: (error) => {
          showError("Import failed", formatApiErrorMessage(error));
        },

src/components/schedule/HideButtonUI.tsx:15

  • This button is missing type="button". Without it, the button can accidentally submit an enclosing <form> if this component is ever rendered inside one.
    src/components/schedule/linked-card/EditButtonLinked.tsx:29
  • This button is missing type="button", which can cause accidental form submission if the card is ever placed inside a <form>.
        <button
          onClick={(e) => {
            e.stopPropagation();
            e.preventDefault();
            setImportModalOpen(true);
          }}
          className="hover:bg-base-200 rounded-box flex h-10 w-10 items-center justify-center text-3xl"

src/components/layout/UserMenu.tsx:134

  • This menu button is missing type="button", which can cause accidental form submission if the menu is ever rendered inside a <form>.
                  <button
                    onClick={() => setConfigModalOpen(true)}
                    className="bg-base-200 text-base-content/75 hover:bg-base-300 rounded-box flex w-full flex-row items-center justify-center gap-2 px-6 py-2 text-center whitespace-nowrap"
                  >
                    <span className="icon-[material-symbols--settings-outline] text-2xl" />
                    Config & Export
                  </button>

src/components/calendar/iCalendarPlugin/event-source-def.ts:74

  • When the calendar fetch returns a non-200 status, the promise resolves to undefined and expandICalEvents(iCalExpander, ...) will throw. Reject the promise on non-OK responses so errorCallback runs, or otherwise guard against undefined.
        }).then((response) => {
          if (response.status !== 200) return;
          return response.text().then((icsText) => {

src/components/calendar/ConfigCalendarDialog.tsx:124

  • This inline button is missing type="button", which can cause accidental form submission if the dialog content is ever wrapped in a <form>.
        Add favorite calendars using star button or{" "}
        <button
          onClick={() => setImportModalOpen(true)}
          className="underline underline-offset-4"
        >

src/components/calendar/CalendarPage.tsx:138

  • The “unique items” filter uses array.indexOf(value), which does not deduplicate objects (it compares by reference). With imported calendars added, duplicates (same URL/color) can slip through and create duplicate event sources.
  // Add imported calendars
  imported.forEach((v) => {
    if (v.is_active)
      toShow.push({
        url: getImportedLink(userId, v.alias),

@@ -0,0 +1,208 @@
import { $schedule } from "@/api/schedule";
import { Calendar } from "@/components/calendar/Calendar.tsx";
import { useState } from "react";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 📋 Backlog

Development

Successfully merging this pull request may close these issues.

2 participants