From 6242a9772aa59eb3f429e2e33ab44c6aebbe6448 Mon Sep 17 00:00:00 2001
From: Andrew Gioia
Date: Fri, 23 Jan 2026 14:49:52 -0500
Subject: [PATCH] Adds new green color palette, removes any requirement of a
remote calendar description as validation, fixes language settings pane
---
.../Controllers/SubscriptionController.php | 78 +++++-------
app/Jobs/SyncSubscription.php | 14 +--
lang/en/calendar.php | 2 +-
lang/en/common.php | 12 ++
resources/css/etc/layout.css | 21 +++-
resources/css/etc/theme.css | 17 +++
resources/css/lib/button.css | 7 +-
resources/css/lib/toast.css | 2 +-
resources/svg/icons/return.svg | 1 +
.../views/calendar/settings/index.blade.php | 5 +-
.../calendar/settings/language.blade.php | 115 +++++++++---------
11 files changed, 153 insertions(+), 121 deletions(-)
create mode 100644 resources/svg/icons/return.svg
diff --git a/app/Http/Controllers/SubscriptionController.php b/app/Http/Controllers/SubscriptionController.php
index dda683e..ff77724 100644
--- a/app/Http/Controllers/SubscriptionController.php
+++ b/app/Http/Controllers/SubscriptionController.php
@@ -28,6 +28,7 @@ class SubscriptionController extends Controller
public function store(Request $request)
{
+ /* validate the submission */
$data = $request->validate([
'source' => 'required|url',
'displayname' => 'nullable|string|max:255',
@@ -35,25 +36,26 @@ class SubscriptionController extends Controller
'refreshrate' => 'nullable|string|max:10',
]);
- $principalUri = $request->user()->uri;
+ /* normalize hard to check against this url */
$source = rtrim(trim($data['source']), '/');
- $desc = 'Remote feed: '.$source;
- /* if they already subscribed to this exact feed, don’t create duplicates */
- if (Subscription::where('principaluri', $principalUri)->where('source', $source)->exists()) {
+ /* set the principal URI */
+ $principalUri = $request->user()->uri;
+
+ /* check for existing subscriptions */
+ $already = Subscription::query()
+ ->where('principaluri', $principalUri)
+ ->where('source', $source)
+ ->exists();
+
+ if ($already) {
return Redirect::route('calendar.index')->with('toast', [
'message' => __('You are already subscribed to that calendar.'),
'type' => 'info',
]);
}
- $sub = DB::transaction(function () use ($request, $data, $principalUri, $source, $desc) {
-
- /* check if a mirror instance already exists */
- $existingInstance = CalendarInstance::where('principaluri', $principalUri)
- ->where('description', $desc)
- ->first();
-
+ $sub = DB::transaction(function () use ($data, $principalUri, $source) {
/* create the calendarsubscriptions record */
$sub = Subscription::create([
@@ -66,36 +68,24 @@ class SubscriptionController extends Controller
'lastmodified' => now()->timestamp,
]);
- // choose the calendar container
- if ($existingInstance) {
- $calId = $existingInstance->calendarid;
+ // create new empty calendar container
+ $calId = Calendar::create([
+ 'synctoken' => 1,
+ 'components' => 'VEVENT',
+ ])->id;
- // keep the mirror instance’s user-facing bits up to date
- $existingInstance->update([
- 'displayname' => $sub->displayname,
- 'calendarcolor' => $sub->calendarcolor,
- 'timezone' => config('app.timezone', 'UTC'),
- ]);
- } else {
- // create new empty calendar container
- $calId = Calendar::create([
- 'synctoken' => 1,
- 'components' => 'VEVENT',
- ])->id;
+ // create mirror calendarinstance row (description can be user-editable, not relied on)
+ CalendarInstance::create([
+ 'calendarid' => $calId,
+ 'principaluri' => $principalUri,
+ 'uri' => (string) Str::uuid(),
+ 'displayname' => $sub->displayname,
+ 'description' => 'Remote feed: '.$source, // informational only
+ 'calendarcolor' => $sub->calendarcolor,
+ 'timezone' => config('app.timezone', 'UTC'),
+ ]);
- // create mirror calendarinstance row
- CalendarInstance::create([
- 'calendarid' => $calId,
- 'principaluri' => $sub->principaluri,
- 'uri' => Str::uuid(),
- 'displayname' => $sub->displayname,
- 'description' => $desc,
- 'calendarcolor' => $sub->calendarcolor,
- 'timezone' => config('app.timezone', 'UTC'),
- ]);
- }
-
- // upsert our calendar_meta entry by calendar_id (since that’s your pk)
+ // meta entry
CalendarMeta::updateOrCreate(
['calendar_id' => $calId],
[
@@ -114,12 +104,10 @@ class SubscriptionController extends Controller
// sync immediately so events appear without waiting for the */10 dispatcher
SyncSubscription::dispatch($sub)->afterCommit();
- return redirect()
- ->route('calendar.index')
- ->with('toast', [
- 'message' => __('Subscription added! Syncing events now...'),
- 'type' => 'success',
- ]);
+ return Redirect::route('calendar.index')->with('toast', [
+ 'message' => __('Subscription added! Syncing events now...'),
+ 'type' => 'success',
+ ]);
}
public function edit(Subscription $subscription)
diff --git a/app/Jobs/SyncSubscription.php b/app/Jobs/SyncSubscription.php
index 0a96eb7..55a493f 100644
--- a/app/Jobs/SyncSubscription.php
+++ b/app/Jobs/SyncSubscription.php
@@ -188,27 +188,19 @@ class SyncSubscription implements ShouldQueue
return (int) $meta->calendar_id;
}
- $desc = $this->mirrorDescription($source);
-
- $existing = CalendarInstance::where('principaluri', $this->subscription->principaluri)
- ->where('description', $desc)
- ->first();
-
- if ($existing) {
- return (int) $existing->calendarid;
- }
-
+ // create a new master calendar in `calendars`
$calendar = Calendar::create([
'synctoken' => 1,
'components' => 'VEVENT',
]);
+ // create the per-user instance (description is display-only; never used for lookup)
CalendarInstance::create([
'calendarid' => $calendar->id,
'principaluri' => $this->subscription->principaluri,
'uri' => (string) Str::uuid(),
'displayname' => $this->subscription->displayname,
- 'description' => $desc,
+ 'description' => $this->mirrorDescription($source),
'calendarcolor' => $meta->color ?? '#1a1a1a',
'timezone' => config('app.timezone', 'UTC'),
]);
diff --git a/lang/en/calendar.php b/lang/en/calendar.php
index 59d9052..7434722 100644
--- a/lang/en/calendar.php
+++ b/lang/en/calendar.php
@@ -26,7 +26,7 @@ return [
],
'language_region' => [
'title' => 'Language and region',
- 'subtitle' => 'Choose your default language, region, and formatting preferences for calendars. These affect how dates and times are displayed throughout Kithkin.',
+ 'subtitle' => 'Choose your default language, region, and formatting preferences. These affect how dates and times are displayed in your calendars and events.',
],
'my_calendars' => 'Settings for my calendars',
'subscribe' => [
diff --git a/lang/en/common.php b/lang/en/common.php
index 5e3945e..cb56168 100644
--- a/lang/en/common.php
+++ b/lang/en/common.php
@@ -17,11 +17,23 @@ return [
'calendars' => 'Calendars',
'cancel' => 'Cancel',
'cancel_funny' => 'Get me out of here',
+ 'date' => 'Date',
+ 'date_select' => 'Select a date',
+ 'date_format' => 'Date format',
+ 'date_format_select' => 'Select a date format',
'event' => 'Event',
'events' => 'Events',
+ 'language' => 'Language',
+ 'language_select' => 'Select a language',
'password' => 'Password',
+ 'region' => 'Region',
+ 'region_select' => 'Select a region',
'save_changes' => 'Save changes',
'settings' => 'Settings',
+ 'time' => 'Time',
+ 'time_select' => 'Select a time',
+ 'time_format' => 'Time format',
+ 'time_format_select' => 'Select a time format',
'timezone' => 'Time zone',
'timezone_select' => 'Select a time zone',
diff --git a/resources/css/etc/layout.css b/resources/css/etc/layout.css
index 69d7d8a..be47b82 100644
--- a/resources/css/etc/layout.css
+++ b/resources/css/etc/layout.css
@@ -125,6 +125,23 @@ main {
@apply flex items-center h-20 min-h-20 px-6 2xl:px-8;
@apply backdrop-blur-xs sticky top-0 z-1 shrink-0;
background-color: rgba(255, 255, 255, 0.9);
+
+ a.app-return {
+ @apply flex flex-row gap-2 items-center;
+
+ > svg {
+ @apply opacity-0 invisible -translate-x-1 text-white mt-1;
+ transition:
+ color 150ms ease-in,
+ opacity 150ms ease-in,
+ visibility 150ms ease-in,
+ translate 150ms ease-in;
+ }
+
+ &:hover > svg {
+ @apply opacity-100 visible translate-x-0 text-secondary;
+ }
+ }
}
> .aside-inset {
@@ -248,11 +265,11 @@ main {
@keyframes title-drop {
from {
opacity: 0;
- transform: translateY(-1rem);
+ transform: translateX(-0.5rem);
}
to {
opacity: 1;
- transform: translateY(0);
+ transform: translateX(0);
}
}
diff --git a/resources/css/etc/theme.css b/resources/css/etc/theme.css
index 8515977..8be6b5d 100644
--- a/resources/css/etc/theme.css
+++ b/resources/css/etc/theme.css
@@ -18,6 +18,7 @@
--color-primary-hover: #000000;
--color-secondary: #555;
--color-secondary-hover: #444;
+
--color-cyan-50: oklch(98.97% 0.015 196.79);
--color-cyan-100: oklch(97.92% 0.03 196.61);
--color-cyan-200: oklch(95.79% 0.063 196.12);
@@ -30,6 +31,19 @@
--color-cyan-800: oklch(59.15% 0.101 194.76);
--color-cyan-900: oklch(49.05% 0.084 194.76);
--color-cyan-950: oklch(43.96% 0.075 194.76);
+
+ --color-green-50: oklch(0.975 0.014 162.33);
+ --color-green-100: oklch(0.953 0.029 163.16);
+ --color-green-200: oklch(0.907 0.058 162.25);
+ --color-green-300: oklch(0.864 0.085 160.81);
+ --color-green-400: oklch(0.821 0.112 159.55);
+ --color-green-500: oklch(0.782 0.136 157.79); /* #61d296 as 500 */
+ --color-green-600: oklch(0.734 0.161 155.16);
+ --color-green-700: oklch(0.624 0.137 155.13);
+ --color-green-800: oklch(0.507 0.108 155.72);
+ --color-green-900: oklch(0.382 0.078 156.05);
+ --color-green-950: oklch(0.319 0.063 156.43);
+
--color-magenta-50: oklch(96.93% 0.027 325.87);
--color-magenta-100: oklch(93.76% 0.056 326.06);
--color-magenta-200: oklch(87.58% 0.117 326.54);
@@ -42,6 +56,7 @@
--color-magenta-800: oklch(47.69% 0.219 328.37);
--color-magenta-900: oklch(40.42% 0.186 328.37);
--color-magenta-950: oklch(36.79% 0.169 328.37);
+
--color-red-50: oklch(0.975 0.012 23.84);
--color-red-100: oklch(0.951 0.024 20.79);
--color-red-200: oklch(0.895 0.055 23.81);
@@ -56,6 +71,8 @@
--border-width-md: 1.5px;
+ --outline-width-md: 1.5px;
+
--radius-xs: 0.25rem;
--radius-sm: 0.375rem;
--radius-md: 0.6667rem;
diff --git a/resources/css/lib/button.css b/resources/css/lib/button.css
index df6ec30..08a2f6b 100644
--- a/resources/css/lib/button.css
+++ b/resources/css/lib/button.css
@@ -2,6 +2,7 @@ button,
.button {
@apply relative inline-flex items-center cursor-pointer gap-2 rounded-md h-11 px-4 text-lg font-medium;
/*transition: background-color 125ms ease-in-out; */
+ @apply focus:outline-md focus:outline-offset-2 focus:outline-secondary;
--button-border: var(--color-primary);
--button-accent: var(--color-primary-hover);
@@ -16,7 +17,11 @@ button,
}
&:focus {
- box-shadow: none;
+ @apply shadow-none;
+ }
+
+ &:active {
+ @apply shadow-none outline-none;
left: 2.5px;
top: 2.5px;
}
diff --git a/resources/css/lib/toast.css b/resources/css/lib/toast.css
index a3bbfe4..de396fb 100644
--- a/resources/css/lib/toast.css
+++ b/resources/css/lib/toast.css
@@ -7,7 +7,7 @@ dl.toasts {
@apply h-0 invisible overflow-hidden;
&.success + dd {
- @apply bg-green-500 text-white;
+ @apply bg-green-500 text-primary;
}
&.error + dd {
diff --git a/resources/svg/icons/return.svg b/resources/svg/icons/return.svg
new file mode 100644
index 0000000..4d5e65a
--- /dev/null
+++ b/resources/svg/icons/return.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/views/calendar/settings/index.blade.php b/resources/views/calendar/settings/index.blade.php
index 460ea1d..223c763 100644
--- a/resources/views/calendar/settings/index.blade.php
+++ b/resources/views/calendar/settings/index.blade.php
@@ -2,7 +2,10 @@
diff --git a/resources/views/calendar/settings/language.blade.php b/resources/views/calendar/settings/language.blade.php
index 37b0c3e..88796d9 100644
--- a/resources/views/calendar/settings/language.blade.php
+++ b/resources/views/calendar/settings/language.blade.php
@@ -9,70 +9,67 @@
-