diff --git a/app/Http/Controllers/CalendarController.php b/app/Http/Controllers/CalendarController.php index e0c9882..ceb3f34 100644 --- a/app/Http/Controllers/CalendarController.php +++ b/app/Http/Controllers/CalendarController.php @@ -13,6 +13,7 @@ use App\Models\CalendarInstance; use App\Models\Event; use App\Models\EventMeta; use App\Models\Subscription; +use App\Services\Calendar\CreateCalendar; class CalendarController extends Controller { @@ -130,6 +131,13 @@ class CalendarController extends Controller $start_local = $start_utc->copy()->timezone($timezone); $end_local = optional($end_utc)->copy()->timezone($timezone); + // color handling + $color = $cal['meta_color'] + ?? $cal['calendarcolor'] + ?? default_calendar_color(); + $colorFg = $cal['meta_color_fg'] + ?? contrast_text_color($color); + // return events array return [ 'id' => $e->id, @@ -143,8 +151,8 @@ class CalendarController extends Controller 'end_ui' => optional($end_local)->format('g:ia'), 'timezone' => $timezone, 'visible' => $cal->visible, - 'color' => $cal->meta_color ?? $cal->calendarcolor ?? '#1a1a1a', - 'color_fg' => $cal->meta_color_fg ?? '#ffffff', + 'color' => $color, + 'color_fg' => $colorFg, ]; })->keyBy('id'); @@ -183,6 +191,12 @@ class CalendarController extends Controller $tz = $cal->timezone ?? config('app.timezone'); + $color = $cal->meta_color + ?? $cal->calendarcolor + ?? default_calendar_color(); + $colorFg = $cal->meta_color_fg + ?? contrast_text_color($color); + return [ 'id' => $e->id, 'calendar_id' => $e->calendarid, @@ -193,8 +207,8 @@ class CalendarController extends Controller 'end' => optional($end_utc)->toIso8601String(), 'timezone' => $tz, 'visible' => $cal->visible, - 'color' => $cal->meta_color ?? $cal->calendarcolor ?? '#1a1a1a', - 'color_fg' => $cal->meta_color_fg ?? '#ffffff', + 'color' => $color, + 'color_fg' => $colorFg, ]; })->keyBy('id'); @@ -223,14 +237,22 @@ class CalendarController extends Controller 'month' => $range['start']->format("F"), 'day' => $range['start']->format("d"), ], - 'calendars' => $calendars->mapWithKeys(function ($cal) { + 'calendars' => $calendars->mapWithKeys(function ($cal) + { + // compute colors + $color = $cal->meta_color + ?? $cal->calendarcolor + ?? default_calendar_color(); + $colorFg = $cal->meta_color_fg + ?? contrast_text_color($color); + return [ $cal->id => [ 'id' => $cal->id, 'slug' => $cal->slug, 'name' => $cal->displayname, - 'color' => $cal->meta_color ?? $cal->calendarcolor ?? '#1a1a1a', - 'color_fg' => $cal->meta_color_fg ?? '#ffffff', + 'color' => $color, + 'color_fg' => $colorFg, 'visible' => $cal->visible, 'is_remote' => $cal->is_remote, ], @@ -245,50 +267,26 @@ class CalendarController extends Controller return view('calendar.index', $payload); } - public function create() - { - return view('calendar.create'); - } - /** * create sabre calendar + meta */ - public function store(Request $request) + public function store(Request $request, CreateCalendar $creator) { $data = $request->validate([ 'name' => 'required|string|max:100', 'description' => 'nullable|string|max:255', - 'timezone' => 'required|string', + 'timezone' => 'required|string|max:64', 'color' => 'nullable|regex:/^#[0-9A-Fa-f]{6}$/', + 'redirect' => 'nullable|string', // where to go after creating ]); - // update master calendar entry - $calId = DB::table('calendars')->insertGetId([ - 'synctoken' => 1, - 'components' => 'VEVENT', // or 'VEVENT,VTODO' if you add tasks - ]); + $creator->create($request->user(), $data); + $redirect = $data['redirect'] ?? route('calendar.index'); - // update the calendar instance row - $instance = CalendarInstance::create([ - 'calendarid' => $calId, - 'principaluri' => auth()->user()->uri, - 'uri' => Str::uuid(), - 'displayname' => $data['name'], - 'description' => $data['description'] ?? null, - 'calendarcolor'=> $data['color'] ?? null, - 'timezone' => $data['timezone'], + return redirect($redirect)->with('toast', [ + 'message' => __('Calendar created!'), + 'type' => 'success', ]); - - // update calendar meta - $instance->meta()->create([ - 'calendar_id' => $instanceId, - 'color' => $data['color'] ?? '#1a1a1a', - 'color_fg' => contrast_text_color($data['color'] ?? '#1a1a1a'), - 'created_at' => now(), - 'updated_at' => now(), - ]); - - return redirect()->route('calendar.index'); } /** @@ -365,9 +363,10 @@ class CalendarController extends Controller $calendar->increment('synctoken'); // update calendar meta (our table) + $color = calendar_color($data); $calendar->meta()->updateOrCreate([], [ - 'color' => $data['color'] ?? '#1a1a1a', - 'color_fg' => contrast_text_color($data['color'] ?? '#1a1a1a') + 'color' => $color, + 'color_fg' => contrast_text_color($color), ]); return redirect() diff --git a/app/Http/Controllers/CalendarSettingsController.php b/app/Http/Controllers/CalendarSettingsController.php index 8c16c52..4d55219 100644 --- a/app/Http/Controllers/CalendarSettingsController.php +++ b/app/Http/Controllers/CalendarSettingsController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Models\CalendarInstance; use App\Models\CalendarMeta; use App\Models\Subscription; +use App\Services\Calendar\CreateCalendar; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Str; @@ -102,6 +103,51 @@ class CalendarSettingsController extends Controller ->with('toast', __('Settings saved!')); } + /** + * Create a local calendar + **/ + + public function createForm(Request $request) + { + $user = $request->user(); + + $data = [ + 'title' => __('calendar.settings.create.title'), + 'sub' => __('calendar.settings.create.subtitle'), + 'defaults' => [ + 'name' => '', + 'description' => '', + 'timezone' => $user->timezone ?? config('app.timezone', 'UTC'), + 'color' => default_calendar_color(), + ], + 'redirect' => route('calendar.settings'), + ]; + + if ($request->header('HX-Request')) { + return view('calendar.partials.create-modal', $data); + } + + return $this->frame('calendar.settings.create', $data); + } + + public function createStore(Request $request, CreateCalendar $creator) + { + $data = $request->validate([ + 'name' => 'required|string|max:100', + 'description' => 'nullable|string|max:255', + 'timezone' => 'required|string', + 'color' => 'nullable|regex:/^#[0-9A-Fa-f]{6}$/', + ]); + + $creator->create($request->user(), $data); + $redirect = $data['redirect'] ?? route('calendar.index'); + + return redirect($redirect)->with('toast', [ + 'message' => __('Calendar created!'), + 'type' => 'success', + ]); + } + /** * Subscribe @@ -133,7 +179,7 @@ class CalendarSettingsController extends Controller [ 'source' => $data['source'], 'displayname' => $data['displayname'] ?: $data['source'], - 'calendarcolor' => $data['color'] ?? '#1a1a1a', + 'calendarcolor' => calendar_color($data), // you can add 'refreshrate' => 'P1D' here if you like ] ); diff --git a/app/Http/Controllers/SubscriptionController.php b/app/Http/Controllers/SubscriptionController.php index ff77724..5a23af8 100644 --- a/app/Http/Controllers/SubscriptionController.php +++ b/app/Http/Controllers/SubscriptionController.php @@ -63,7 +63,7 @@ class SubscriptionController extends Controller 'principaluri' => $principalUri, 'source' => $source, 'displayname' => $data['displayname'] ?: $source, - 'calendarcolor' => $data['color'] ?? '#1a1a1a', + 'calendarcolor' => calendar_color($data), 'refreshrate' => 'P1D', 'lastmodified' => now()->timestamp, ]); @@ -133,14 +133,13 @@ class SubscriptionController extends Controller $subscription->update($data); // update corresponding calendar_meta record + $color = calendar_color(['color' => $subscription->calendarcolor]); $subscription->meta()->updateOrCreate( [], // no “where” clause → look at subscription_id FK [ 'title' => $subscription->displayname, - 'color' => $subscription->calendarcolor ?? '#1a1a1a', - 'color_fg' => contrast_text_color( - $subscription->calendarcolor ?? '#1a1a1a' - ), + 'color' => $color, + 'color_fg' => contrast_text_color($color), 'updated_at'=> now(), ] ); diff --git a/app/Jobs/SyncSubscription.php b/app/Jobs/SyncSubscription.php index 55a493f..1d48615 100644 --- a/app/Jobs/SyncSubscription.php +++ b/app/Jobs/SyncSubscription.php @@ -194,6 +194,9 @@ class SyncSubscription implements ShouldQueue 'components' => 'VEVENT', ]); + // set the color + $color = calendar_color([], $meta->color); + // create the per-user instance (description is display-only; never used for lookup) CalendarInstance::create([ 'calendarid' => $calendar->id, @@ -201,7 +204,7 @@ class SyncSubscription implements ShouldQueue 'uri' => (string) Str::uuid(), 'displayname' => $this->subscription->displayname, 'description' => $this->mirrorDescription($source), - 'calendarcolor' => $meta->color ?? '#1a1a1a', + 'calendarcolor' => $color, 'timezone' => config('app.timezone', 'UTC'), ]); diff --git a/app/Models/CalendarInstance.php b/app/Models/CalendarInstance.php index c89ddd6..e5d8488 100644 --- a/app/Models/CalendarInstance.php +++ b/app/Models/CalendarInstance.php @@ -69,10 +69,10 @@ class CalendarInstance extends Model public function resolvedColor(?string $fallback = null): string { // prefer meta color, fall back to sabre color, then default - return $this->meta?->color - ?? $this->calendarcolor - ?? $fallback - ?? '#1a1a1a'; + return calendar_color( + ['color' => $this->meta?->color ?? $this->calendarcolor], + $fallback + ); } public function resolvedColorFg(?string $fallback = null): string diff --git a/app/Models/CalendarMeta.php b/app/Models/CalendarMeta.php index 5efbb69..4cc2e85 100644 --- a/app/Models/CalendarMeta.php +++ b/app/Models/CalendarMeta.php @@ -46,6 +46,8 @@ class CalendarMeta extends Model */ public static function forSubscription(Subscription $sub): self { + $color = calendar_color([], $sub->calendarcolor); + return static::updateOrCreate( // ---- unique match-key (subscription_id is unique, nullable) ---- ['subscription_id' => $sub->id], @@ -53,8 +55,8 @@ class CalendarMeta extends Model // ---- columns to fill / update ---- [ 'title' => $sub->displayname, - 'color' => $sub->calendarcolor ?? '#1a1a1a', - 'color_fg' => contrast_text_color($sub->calendarcolor ?? '#1a1a1a'), + 'color' => $color, + 'color_fg' => contrast_text_color($color), 'is_shared' => true, 'is_remote' => true, // mirror_calendar_id is set later by the sync-job once the diff --git a/app/Services/Calendar/CreateCalendar.php b/app/Services/Calendar/CreateCalendar.php new file mode 100644 index 0000000..759afd6 --- /dev/null +++ b/app/Services/Calendar/CreateCalendar.php @@ -0,0 +1,51 @@ +insertGetId([ + 'synctoken' => 1, + 'components' => 'VEVENT', + ]); + + /* create per-user instance (sabre table) */ + $instance = CalendarInstance::create([ + 'calendarid' => $calId, + 'principaluri' => $user->uri, // your principal uri + 'uri' => (string) Str::uuid(), // instance slug + 'displayname' => $data['name'], + 'description' => $data['description'] ?? null, + 'calendarcolor' => $color, // keep sabre in sync + 'timezone' => $data['timezone'], + ]); + + /* create ui meta */ + CalendarMeta::updateOrCreate( + ['calendar_id' => $calId], // calendar_id = sabre calendars.id (int) + [ + 'title' => $data['name'], + 'color' => $color, + 'color_fg' => contrast_text_color($color), + 'is_remote' => false, + 'is_shared' => false, + ] + ); + + return $instance; + }); + } +} diff --git a/app/Support/helpers.php b/app/Support/helpers.php index ad03fb7..05fd31e 100644 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -65,3 +65,89 @@ if (! function_exists('contrast_text_color')) { return $useDark ? $dark : $light; } } + +if (! function_exists('is_hex_color')) { + function is_hex_color(mixed $value): bool + { + return is_string($value) && preg_match('/^#[0-9A-Fa-f]{6}$/', $value) === 1; + } +} + +if (! function_exists('calendar_color_palette')) { + /** + * returns a cleaned palette from config (valid hex only). + * + * @return array + */ + function calendar_color_palette(): array + { + $palette = config('kithkin.calendar.color_palette', []); + + if (! is_array($palette)) { + $palette = []; + } + + return array_values(array_filter($palette, fn ($c) => is_hex_color($c))); + } +} + +if (! function_exists('calendar_color_failsafe')) { + function calendar_color_failsafe(): string + { + $fallback = config('kithkin.calendar.color_failsafe', '#1a1a1a'); + + return is_hex_color($fallback) ? $fallback : '#1a1a1a'; + } +} + +if (! function_exists('default_calendar_color')) { + /** + * returns the default calendar color from the configured palette. + * if the palette is empty/invalid, falls back to the failsafe. + */ + function default_calendar_color(): string + { + $palette = calendar_color_palette(); + + if (count($palette) === 0) { + return calendar_color_failsafe(); + } + + $strategy = config('kithkin.calendar.default_color_strategy', 'first'); + + if ($strategy === 'random') { + return $palette[array_rand($palette)]; + } + + return $palette[0]; + } +} + +if (! function_exists('calendar_color')) { + /** + * pick a calendar color with a single source of truth for defaults. + * + * order: + * 1) $data['color'] if valid + * 2) $fallback if valid + * 3) default_calendar_color() (palette-based) + * 4) hard failsafe from config, then '#1a1a1a' + * + * @param array $data + */ + function calendar_color(array $data = [], ?string $fallback = null): string + { + $raw = $data['color'] ?? null; + + if (is_hex_color($raw)) { + return $raw; + } + + if (is_hex_color($fallback)) { + return $fallback; + } + + $default = default_calendar_color(); + return is_hex_color($default) ? $default : calendar_color_failsafe(); + } +} diff --git a/config/kithkin.php b/config/kithkin.php new file mode 100644 index 0000000..94980fa --- /dev/null +++ b/config/kithkin.php @@ -0,0 +1,19 @@ + [ + // used by the color picker and as the default pool for calendars + 'color_palette' => [ + '#2563eb', '#7c3aed', '#db2777', '#ef4444', '#f97316', + '#f59e0b', '#84cc16', '#22c55e', '#14b8a6', '#06b6d4', + '#0ea5e9', '#64748b', '#d051cc', '#ffec05', '#739399', + ], + + // absolute failsafe if everything else is missing or invalid + 'color_failsafe' => '#1a1a1a', + + // how default_calendar_color() chooses from the palette + // options: 'first' | 'random' + 'default_color_strategy' => 'random', + ], +]; diff --git a/lang/en/calendar.php b/lang/en/calendar.php index 7434722..8f4a9c6 100644 --- a/lang/en/calendar.php +++ b/lang/en/calendar.php @@ -13,17 +13,23 @@ return [ */ 'color' => 'Color', + 'create' => 'Create calendar', 'description' => 'Description', 'ics' => [ 'url' => 'ICS URL', 'url_help' => 'You can\'t edit a public calendar URL. If you need to make a change, unsubscribe and add it again.', ], + 'mine' => 'My calendars', 'name' => 'Calendar name', 'settings' => [ 'calendar' => [ 'title' => 'Calendar settings', 'subtitle' => 'Details and settings for :calendar.' ], + 'create' => [ + 'title' => 'Create a calendar', + 'subtitle' => 'Create a new local calendar.', + ], 'language_region' => [ 'title' => 'Language and region', 'subtitle' => 'Choose your default language, region, and formatting preferences. These affect how dates and times are displayed in your calendars and events.', diff --git a/lang/en/common.php b/lang/en/common.php index cb56168..0b0d632 100644 --- a/lang/en/common.php +++ b/lang/en/common.php @@ -16,6 +16,7 @@ return [ 'calendar' => 'Calendar', 'calendars' => 'Calendars', 'cancel' => 'Cancel', + 'cancel_back' => 'Cancel and go back', 'cancel_funny' => 'Get me out of here', 'date' => 'Date', 'date_select' => 'Select a date', diff --git a/resources/css/etc/layout.css b/resources/css/etc/layout.css index be47b82..6006c2f 100644 --- a/resources/css/etc/layout.css +++ b/resources/css/etc/layout.css @@ -105,7 +105,7 @@ main { /* if there's an aside, set the cols */ &:has(aside) { - grid-template-columns: minmax(20rem, 20dvw) auto; + grid-template-rows: 4rem 1fr; } } @@ -118,13 +118,10 @@ main { /* left column */ aside { - @apply bg-white flex flex-col col-span-1 pb-8 h-full rounded-l-lg; - @apply overflow-y-auto; + @apply flex flex-col col-span-1 pb-8 h-16 overflow-hidden rounded-l-lg; > h1 { - @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); + @apply flex items-center h-16 min-h-16 px-6 2xl:px-8; a.app-return { @apply flex flex-row gap-2 items-center; @@ -185,7 +182,7 @@ main { /* main content wrapper */ article { - @apply bg-white grid grid-cols-1 w-full pl-3 2xl:pl-4 pr-6 2xl:pr-8 rounded-r-lg; + @apply bg-white grid grid-cols-1 ml-2 rounded-md; @apply overflow-y-auto; grid-template-rows: 5rem auto; @@ -309,6 +306,26 @@ main { } } } + + main { + &:has(aside) { + grid-template-columns: minmax(20rem, 20dvw) auto; + grid-template-rows: 1fr; + } + + aside { + @apply bg-white overflow-y-auto h-full; + + > h1 { + @apply backdrop-blur-xs sticky top-0 z-1 shrink-0 h-20 min-h-20; + background-color: rgba(255, 255, 255, 0.9); + } + } + + article { + @apply w-full ml-0 pl-3 2xl:pl-4 pr-6 2xl:pr-8 rounded-l-none rounded-r-lg; + } + } } } diff --git a/resources/css/etc/type.css b/resources/css/etc/type.css index dea7dec..a859132 100644 --- a/resources/css/etc/type.css +++ b/resources/css/etc/type.css @@ -26,12 +26,12 @@ /* app name */ h1 { - @apply font-serif text-3xl font-extrabold leading-tight; + @apply font-serif text-2xl md:text-3xl font-extrabold leading-tight; } /* page header */ h2 { - @apply font-serif text-2xl font-extrabold leading-tight text-primary; + @apply font-serif text-xl md:text-2xl font-extrabold leading-tight text-primary; } /* section dividers */ diff --git a/resources/css/lib/button.css b/resources/css/lib/button.css index 08a2f6b..e72e32c 100644 --- a/resources/css/lib/button.css +++ b/resources/css/lib/button.css @@ -2,12 +2,14 @@ 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; + @apply focus:ring-2 focus:ring-offset-2 focus:ring-cyan-600 focus:outline-none; + transition: border-color 125ms ease-in-out; --button-border: var(--color-primary); --button-accent: var(--color-primary-hover); + &.button--primary { - @apply bg-cyan-400 border-md border-solid; + @apply bg-cyan-400 border-md border-solid focus:border-primary; border-color: var(--button-border); box-shadow: 2.5px 2.5px 0 0 var(--button-border); @@ -17,7 +19,10 @@ button, } &:focus { - @apply shadow-none; + box-shadow: + 2.5px 2.5px 0 0 var(--button-border), + 0 0 0 2px var(--color-white), + 0 0 0 4px var(--color-cyan-600); } &:active { diff --git a/resources/css/lib/calendar.css b/resources/css/lib/calendar.css index fb61783..bc92fb7 100644 --- a/resources/css/lib/calendar.css +++ b/resources/css/lib/calendar.css @@ -74,6 +74,27 @@ } /* calendar list in the left bar */ +#calendar-toggles { + + summary { + @apply flex items-center gap-1 justify-start; + + span { + @apply capitalize; + } + + a { + @apply hidden -mt-2px; + } + + &:hover { + a { + @apply flex; + } + } + } +} + li.calendar-toggle { @apply relative; diff --git a/resources/css/lib/color.css b/resources/css/lib/color.css index 1bdcbfb..2e00cea 100644 --- a/resources/css/lib/color.css +++ b/resources/css/lib/color.css @@ -1,17 +1,27 @@ .colorpicker { - @apply inline-flex items-center rounded-md h-11; - @apply border-md border-secondary shadow-input; + @apply inline-flex items-center rounded-md h-11 shadow-input; input[type="color"] { @apply rounded-l-md-inset rounded-r-none h-full shrink-0 min-w-11; + @apply border-md border-secondary border-r-0 outline-transparent; + transition: outline 125ms ease-in-out; + + &:focus { + @apply border-primary outline-2 outline-offset-2 outline-cyan-600 z-2; + } } input[type="text"] { - @apply rounded-none grow min-w-12; + @apply rounded-none grow min-w-12 font-mono; } button { - @apply w-11 min-w-11 h-full shrink-0 flex items-center justify-center rounded-l-none rounded-r-md; + @apply w-11 min-w-11 h-11 min-h-11 shrink-0 flex items-center justify-center relative right-0; + @apply border-md border-secondary rounded-l-none rounded-r-md -ml-[1.5px]; + @apply focus:border-primary; + transition: background-color 125ms ease-in-out, + box-shadow 125ms ease-in-out, + border-color 125ms ease-in-out; &:hover { @apply bg-teal-100 shadow-input-hover; @@ -22,23 +32,3 @@ } } } - -.colorpicker__swatch { - width: 2.5rem; - height: 2.25rem; - padding: 0; - border: 1px solid var(--border, #d1d5db); - border-radius: .5rem; - background: transparent; -} - -.colorpicker__hex { - @apply w-32 font-mono; -} - -.colorpicker__random:disabled, -.colorpicker__swatch:disabled, -.colorpicker__hex:disabled { - opacity: .6; - cursor: not-allowed; -} diff --git a/resources/css/lib/input.css b/resources/css/lib/input.css index d5436ed..945c1b8 100644 --- a/resources/css/lib/input.css +++ b/resources/css/lib/input.css @@ -55,6 +55,13 @@ input[type="radio"] { @apply flex flex-row gap-2 items-center; } +/** + * textareas + */ +textarea { + @apply min-h-20; +} + /** * specific minor types @@ -89,6 +96,14 @@ form { &.settings { @apply mt-8; @apply 2xl:max-w-3xl; + + &.modal { + @apply mt-0; + + .input-row { + @apply !mt-0; + } + } } } article.settings { diff --git a/resources/svg/icons/calendar-plus.svg b/resources/svg/icons/calendar-plus.svg new file mode 100644 index 0000000..a59377e --- /dev/null +++ b/resources/svg/icons/calendar-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/svg/icons/home.svg b/resources/svg/icons/home.svg index 8e2a689..57f8420 100644 --- a/resources/svg/icons/home.svg +++ b/resources/svg/icons/home.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/resources/svg/icons/solid/calendar-plus.svg b/resources/svg/icons/solid/calendar-plus.svg new file mode 100644 index 0000000..2b30560 --- /dev/null +++ b/resources/svg/icons/solid/calendar-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/svg/icons/solid/home.svg b/resources/svg/icons/solid/home.svg new file mode 100644 index 0000000..d7d0926 --- /dev/null +++ b/resources/svg/icons/solid/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/views/calendar/index.blade.php b/resources/views/calendar/index.blade.php index a4b4ace..5ecddec 100644 --- a/resources/views/calendar/index.blade.php +++ b/resources/views/calendar/index.blade.php @@ -10,7 +10,15 @@ action="{{ route('calendar.index') }}" method="get">
- {{ __('My Calendars') }} + + {{ __('calendar.mine') }} + + +
    @foreach ($calendars->where('is_remote', false) as $cal)
  • diff --git a/resources/views/calendar/partials/create-modal.blade.php b/resources/views/calendar/partials/create-modal.blade.php new file mode 100644 index 0000000..cf1f129 --- /dev/null +++ b/resources/views/calendar/partials/create-modal.blade.php @@ -0,0 +1,51 @@ + + + {{ __('calendar.settings.create.title') }} + + + + + + + {{ __('common.cancel') }} + + + {{ __('calendar.create') }} + + + diff --git a/resources/views/calendar/settings/create.blade.php b/resources/views/calendar/settings/create.blade.php new file mode 100644 index 0000000..23658dd --- /dev/null +++ b/resources/views/calendar/settings/create.blade.php @@ -0,0 +1,51 @@ +
    +

    + {{ $data['sub'] }} +

    +
    +
    + @csrf + + + +
    +
    + + + +
    +
    + +
    +
    + + + +
    +
    + +
    +
    + + + +
    + +
    + + + +
    +
    + +
    + {{ __('calendar.create') }} + {{ __('common.cancel_back') }} +
    +
    diff --git a/resources/views/components/input/color-picker.blade.php b/resources/views/components/input/color-picker.blade.php index a050d22..6998e6a 100644 --- a/resources/views/components/input/color-picker.blade.php +++ b/resources/views/components/input/color-picker.blade.php @@ -1,25 +1,28 @@ @props([ 'id' => null, 'name' => 'color', - 'value' => null, // initial hex, e.g. "#0038ff" - 'palette' => null, // optional override array of hex strings + 'value' => null, + 'palette' => null, 'required' => false, 'disabled' => false, ]) @php $id = $id ?: 'color_'.Str::uuid(); - $initial = old($name, $value) ?: '#1a1a1a'; - // palette of pre-selected colors to cycle through - $pleasant = $palette ?: [ - '#2563eb', '#7c3aed', '#db2777', '#ef4444', '#f97316', - '#f59e0b', '#84cc16', '#22c55e', '#14b8a6', '#06b6d4', - '#0ea5e9', '#64748b', '#d051cc', '#ffec05', '#739399', - ]; + $paletteColors = is_array($palette) && count($palette) + ? array_values(array_filter($palette, fn ($c) => is_hex_color($c))) + : calendar_color_palette(); + + // initial: old() -> explicit value -> palette-based default -> failsafe + $initial = old($name, $value) ?: default_calendar_color(); + + if (! is_hex_color($initial)) { + $initial = calendar_color_failsafe(); + } @endphp -
    +
    -
    diff --git a/resources/views/components/menu/calendar-settings.blade.php b/resources/views/components/menu/calendar-settings.blade.php index a63c414..99c7797 100644 --- a/resources/views/components/menu/calendar-settings.blade.php +++ b/resources/views/components/menu/calendar-settings.blade.php @@ -15,6 +15,14 @@
    {{ __('Add a calendar') }} +
  • + +
  • group(function () Route::get('settings/subscribe', [CalendarSettingsController::class, 'subscribeForm'])->name('settings.subscribe'); Route::post('settings/subscribe', [CalendarSettingsController::class, 'subscribeStore'])->name('settings.subscribe.store'); + // settings / create a calendar + Route::get('settings/create', [CalendarSettingsController::class, 'createForm'])->name('settings.create'); + Route::post('settings/create', [CalendarSettingsController::class, 'createStore'])->name('settings.create.store'); + // remote calendar subscriptions Route::resource('subscriptions', SubscriptionController::class) ->except(['show']); // index, create, store, edit, update, destroy // calendar settings for a specific calendar instance/container Route::get('settings/calendars/{calendarUri}', [CalendarSettingsController::class, 'calendarForm']) - ->whereUuid('calendarUri') // sabre calendarid is an int + ->whereUuid('calendarUri') ->name('settings.calendars.show'); Route::patch('settings/calendars/{calendarUri}', [CalendarSettingsController::class, 'calendarStore']) ->whereUuid('calendarUri')