Adds new green color palette, removes any requirement of a remote calendar description as validation, fixes language settings pane
This commit is contained in:
parent
1a92f09e3b
commit
6242a9772a
@ -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)
|
||||
|
||||
@ -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'),
|
||||
]);
|
||||
|
||||
@ -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' => [
|
||||
|
||||
@ -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',
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
1
resources/svg/icons/return.svg
Normal file
1
resources/svg/icons/return.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo2-icon lucide-undo-2"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/></svg>
|
||||
|
After Width: | Height: | Size: 327 B |
@ -2,7 +2,10 @@
|
||||
|
||||
<x-slot name="aside">
|
||||
<h1>
|
||||
{{ __('common.calendar') }}
|
||||
<a href="{{ route('calendar.index') }}" class="app-return">
|
||||
<span>{{ __('common.calendar') }}</span>
|
||||
<x-icon-return width="20" />
|
||||
</a>
|
||||
</h1>
|
||||
<x-menu.calendar-settings :calendars="$calendars" />
|
||||
</x-slot>
|
||||
|
||||
@ -9,70 +9,67 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post"
|
||||
action="{{ route('calendar.settings.language.store') }}"
|
||||
class="form-grid-1 mt-8">
|
||||
<form method="post" action="{{ route('calendar.settings.language.store') }}" class="settings">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="language">{{ __('Language') }}</label>
|
||||
<select id="language" name="language">
|
||||
@foreach(($options['languages'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('language', $values['language'] ?? '') === $value)>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('language')
|
||||
<div class="text-danger">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="input-row input-row--1-1">
|
||||
<div class="input-cell">
|
||||
<x-input.label for="language" :value="__('common.language')" />
|
||||
<x-input.select
|
||||
id="language"
|
||||
name="language"
|
||||
placeholder="{{ __('common.language_select') }}"
|
||||
:value="$values['language']"
|
||||
:options="$options['languages']"
|
||||
:selected="old('language', $values['language'])"
|
||||
/>
|
||||
<x-input.error :messages="$errors->get('language')" />
|
||||
</div>
|
||||
<div class="input-cell">
|
||||
<x-input.label for="region" :value="__('common.region')" />
|
||||
<x-input.select
|
||||
id="region"
|
||||
name="region"
|
||||
placeholder="{{ __('common.region_select') }}"
|
||||
:value="$values['region']"
|
||||
:options="$options['regions']"
|
||||
:selected="old('region', $values['region'])"
|
||||
/>
|
||||
<x-input.error :messages="$errors->get('region')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="region">{{ __('Region') }}</label>
|
||||
<select id="region" name="region">
|
||||
@foreach(($options['regions'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('region', $values['region'] ?? '') === $value)>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('region')
|
||||
<div class="text-danger">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="input-row input-row--1-1">
|
||||
<div class="input-cell">
|
||||
<x-input.label for="date_format" :value="__('common.date_format')" />
|
||||
<x-input.select
|
||||
id="date_format"
|
||||
name="date_format"
|
||||
placeholder="{{ __('common.date_format_select') }}"
|
||||
:value="$values['date_format']"
|
||||
:options="$options['date_formats']"
|
||||
:selected="old('date_format', $values['date_format'])"
|
||||
/>
|
||||
<x-input.error :messages="$errors->get('date_format')" />
|
||||
</div>
|
||||
<div class="input-cell">
|
||||
<x-input.label for="time_format" :value="__('common.time_format')" />
|
||||
<x-input.select
|
||||
id="time_format"
|
||||
name="time_format"
|
||||
placeholder="{{ __('common.time_format_select') }}"
|
||||
:value="$values['time_format']"
|
||||
:options="$options['time_formats']"
|
||||
:selected="old('time_format', $values['time_format'])"
|
||||
/>
|
||||
<x-input.error :messages="$errors->get('time_format')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="date_format">{{ __('Date format') }}</label>
|
||||
<select id="date_format" name="date_format">
|
||||
@foreach(($options['date_formats'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('date_format', $values['date_format'] ?? '') === $value)>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('date_format')
|
||||
<div class="text-danger">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="time_format">{{ __('Time format') }}</label>
|
||||
<select id="time_format" name="time_format">
|
||||
@foreach(($options['time_formats'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('time_format', $values['time_format'] ?? '') === $value)>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('time_format')
|
||||
<div class="text-danger">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<x-button variant="primary" type="submit">{{ __('Save') }}</x-button>
|
||||
<a href="{{ route('calendar.index') }}"
|
||||
class="button button--secondary">{{ __('Cancel and go back') }}</a>
|
||||
<div class="input-row input-row--actions input-row--start sticky-bottom">
|
||||
<x-button variant="primary" type="submit">{{ __('common.save_changes') }}</x-button>
|
||||
<x-button type="anchor"
|
||||
variant="tertiary"
|
||||
href="{{ route('calendar.settings.language') }}">{{ __('common.cancel') }}</x-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user