route('calendar.settings.language'); } /** * Language and region **/ /* language and region form */ public function languageForm(Request $request) { $user = $request->user(); $settings = (array) ($user->settings ?? []); return $this->frame('calendar.settings.language', [ 'title' => __('calendar.settings.language_region.title'), 'values' => [ 'language' => $user->getSetting('app.language', app()->getLocale()), 'region' => $user->getSetting('app.region', 'US'), 'date_format' => $user->getSetting('app.date_format', 'mdy'), 'time_format' => $user->getSetting('app.time_format', '12'), ], 'options' => [ 'languages' => [ 'en' => 'English', 'es' => 'Spanish', 'fr' => 'French', 'de' => 'German', 'it' => 'Italian', 'pt' => 'Portuguese', 'nl' => 'Dutch', ], 'regions' => [ 'US' => 'United States', 'CA' => 'Canada', 'GB' => 'United Kingdom', 'AU' => 'Australia', 'NZ' => 'New Zealand', 'IE' => 'Ireland', 'DE' => 'Germany', 'FR' => 'France', 'ES' => 'Spain', 'IT' => 'Italy', 'NL' => 'Netherlands', ], 'date_formats' => [ 'mdy' => 'MM/DD/YYYY (01/15/2026)', 'dmy' => 'DD/MM/YYYY (15/01/2026)', 'ymd' => 'YYYY-MM-DD (2026-01-15)', ], 'time_formats' => [ '12' => '12-hour (1:30 PM)', '24' => '24-hour (13:30)', ], ], ]); } /* handle POST from language/region pane */ public function languageStore(Request $request) { $data = $request->validate([ 'language' => ['required', 'string', 'max:10', 'regex:/^[a-z]{2}([-_][A-Z]{2})?$/'], 'region' => ['required', 'string', 'size:2', 'regex:/^[A-Z]{2}$/'], 'date_format' => ['required', 'in:mdy,dmy,ymd'], 'time_format' => ['required', 'in:12,24'], ]); $user = $request->user(); $user->setSettings([ 'app.language' => $data['language'], 'app.region' => $data['region'], 'app.date_format' => $data['date_format'], 'app.time_format' => $data['time_format'], ]); // apply immediately for the current request cycle going forward app()->setLocale($data['language']); return redirect() ->route('calendar.settings.language') ->with('toast', __('Settings saved!')); } /** * Subscribe **/ /* show “Subscribe to a calendar” form */ public function subscribeForm() { return $this->frame( 'calendar.settings.subscribe', [ 'title' => __('calendar.settings.subscribe.title'), 'sub' => __('calendar.settings.subscribe.subtitle'), ]); } /* handle POST from the subscribe form */ public function subscribeStore(Request $request) { $data = $request->validate([ 'source' => ['required', 'url'], 'displayname' => ['nullable', 'string', 'max:100'], 'color' => ['nullable', 'regex:/^#[0-9A-F]{6}$/i'], ]); // create the calendarsubscription and calendar_meta rows in one call via Subscription model Subscription::createWithMeta( $request->user(), [ 'source' => $data['source'], 'displayname' => $data['displayname'] ?: $data['source'], 'calendarcolor' => $data['color'] ?? '#1a1a1a', // you can add 'refreshrate' => 'P1D' here if you like ] ); return redirect() ->route('calendar.index') ->with('toast', __('Subscription added successfully!')); } /** * individual calendar settings */ public function calendarForm(Request $request, string $calendarUri) { $user = $request->user(); $principalUri = $user->uri; // this is the user's "instance" of the calendar (displayname/color/etc live here in sabre) $instance = CalendarInstance::query() ->where('principaluri', $principalUri) ->where('uri', $calendarUri) ->firstOrFail(); // your app meta (optional row) $meta = $instance->meta; // if it's remote $icsUrl = null; if (($meta?->is_remote ?? false) && $meta?->subscription_id) { $icsUrl = Subscription::query() ->whereKey($meta->subscription_id) ->value('source'); } return $this->frame( 'calendar.settings.calendar', [ 'title' => __('calendar.settings.calendar.title'), 'sub' => __('calendar.settings.calendar.subtitle'), 'instance' => $instance, 'meta' => $meta, 'icsUrl' => $icsUrl, 'userTz' => $user->timezone, ]); } public function calendarStore(Request $request, string $calendarUri): RedirectResponse { $user = $request->user(); $principalUri = $user->uri; $instance = CalendarInstance::query() ->where('principaluri', $principalUri) ->where('uri', $calendarUri) ->with('meta') ->firstOrFail(); $data = $request->validate([ 'displayname' => ['required', 'string', 'max:100'], 'description' => ['nullable', 'string', 'max:500'], 'timezone' => ['nullable', 'string', 'max:64'], 'color' => ['nullable', 'regex:/^#[0-9A-F]{6}$/i'], ]); $timezone = filled($data['timezone'] ?? null) ? $data['timezone'] : $user->timezone; $color = $data['color'] ?? $instance->resolvedColor(); DB::transaction(function () use ($instance, $data, $timezone, $color) { // update sabre calendar instance (dav-facing) $instance->update([ 'displayname' => $data['displayname'], 'description' => $data['description'] ?? null, 'timezone' => $timezone, 'calendarcolor' => $color, ]); // update ui meta (kithkin-facing) CalendarMeta::updateOrCreate( ['calendar_id' => $instance->calendarid], [ 'title' => $data['displayname'], 'color' => $color, 'color_fg' => contrast_text_color($color), ] ); }); return Redirect::route('calendar.settings.calendars.show', $calendarUri) ->with('toast', [ 'message' => __('calendar.settings.saved'), 'type' => 'success', ]); } /** * content frame handler */ private function frame(?string $view = null, array $data = []) { $user = request()->user(); /* pull user's calendar instances and adapt ordering keys to calendarorder */ $calendars = CalendarInstance::query() ->forUser($user) ->withUiMeta() ->ordered() ->get(); return view('calendar.settings.index', [ 'view' => $view, 'data' => $data, 'calendars' => $calendars, 'timezones' => config('timezones'), ]); } }