Moves locale preferences from calendar settings to account settings; beefs up config with the date and time formatting and region options; refactors button groups and focus handling; refactors shadow complexity in button groups
This commit is contained in:
parent
0b82c88333
commit
5fd9628dc9
@ -13,12 +13,14 @@ use Illuminate\Support\Facades\DB;
|
|||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
|
||||||
class AccountController extends Controller
|
class AccountController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
* landing page
|
* landing page
|
||||||
*/
|
*/
|
||||||
public function index(): RedirectResponse
|
public function index(): RedirectResponse
|
||||||
@ -27,6 +29,7 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
* info pane (name, email, timezone, etc.)
|
* info pane (name, email, timezone, etc.)
|
||||||
*/
|
*/
|
||||||
public function infoForm(Request $request)
|
public function infoForm(Request $request)
|
||||||
@ -40,9 +43,6 @@ class AccountController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* save info pane
|
|
||||||
*/
|
|
||||||
public function infoStore(AccountUpdateRequest $request): RedirectResponse
|
public function infoStore(AccountUpdateRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
@ -59,6 +59,88 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
|
* locale pane
|
||||||
|
*/
|
||||||
|
public function localeForm(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
return $this->frame('account.settings.locale', [
|
||||||
|
'title' => __('account.settings.locale.title'),
|
||||||
|
'sub' => __('account.settings.locale.subtitle'),
|
||||||
|
'values' => [
|
||||||
|
// language comes from column
|
||||||
|
'language' => $user->locale ?? config('app.locale'),
|
||||||
|
// timezone comes from column (fallback to app timezone then UTC)
|
||||||
|
'timezone' => $user->timezone ?? config('app.timezone', 'UTC'),
|
||||||
|
// the other three live in user.settings json
|
||||||
|
'region' => $user->getSetting('app.region', 'US'),
|
||||||
|
'date_format' => $user->getSetting('app.date_format', 'mdy'),
|
||||||
|
'time_format' => $user->getSetting('app.time_format', '12'),
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
'languages' => config('kithkin.locales', []), // optgroups
|
||||||
|
'regions' => config('kithkin.regions', []), // optgroups
|
||||||
|
'date_formats' => config('kithkin.date_formats', []), // flat
|
||||||
|
'time_formats' => config('kithkin.time_formats', []), // flat
|
||||||
|
'timezones' => config('timezones', []), // optgroups
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function localeStore(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$allowedLocales = collect(config('kithkin.locales', []))
|
||||||
|
->flatMap(fn ($group) => is_array($group) ? array_keys($group) : [])
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$allowedRegions = collect(config('kithkin.regions', []))
|
||||||
|
->flatMap(fn ($group) => is_array($group) ? array_keys($group) : [])
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$allowedTimezones = collect(config('timezones', []))
|
||||||
|
->flatMap(fn ($group) => is_array($group) ? array_keys($group) : [])
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'language' => ['required', Rule::in($allowedLocales)],
|
||||||
|
'region' => ['required', Rule::in($allowedRegions)],
|
||||||
|
'date_format' => ['required', 'in:mdy,dmy,ymd'],
|
||||||
|
'time_format' => ['required', 'in:12,24'],
|
||||||
|
'timezone' => ['required', 'string', Rule::in($allowedTimezones)],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// set language and timezone to their dedicated columns
|
||||||
|
$user->locale = $data['language'];
|
||||||
|
$user->timezone = $data['timezone'];
|
||||||
|
|
||||||
|
// everything else to json settings
|
||||||
|
$user->setSettings([
|
||||||
|
'app.region' => $data['region'],
|
||||||
|
'app.date_format' => $data['date_format'],
|
||||||
|
'app.time_format' => $data['time_format'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
// apply locale immediately
|
||||||
|
app()->setLocale($user->locale);
|
||||||
|
|
||||||
|
return Redirect::route('account.locale')
|
||||||
|
->with('toast', __('Settings saved!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
* addresses pane (home + work)
|
* addresses pane (home + work)
|
||||||
*/
|
*/
|
||||||
public function addressesForm(Request $request)
|
public function addressesForm(Request $request)
|
||||||
|
|||||||
@ -14,93 +14,10 @@ use Illuminate\Support\Facades\Redirect;
|
|||||||
|
|
||||||
class CalendarSettingsController extends Controller
|
class CalendarSettingsController extends Controller
|
||||||
{
|
{
|
||||||
/* landing page shows the first settings pane (language/region) */
|
/* landing page shows the first settings pane */
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return redirect()->route('calendar.settings.language');
|
return redirect()->route('calendar.settings.create');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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!'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,6 +41,7 @@ class CalendarSettingsController extends Controller
|
|||||||
];
|
];
|
||||||
|
|
||||||
if ($request->header('HX-Request')) {
|
if ($request->header('HX-Request')) {
|
||||||
|
$data['redirect'] = route('calendar.index');
|
||||||
return view('calendar.partials.create-modal', $data);
|
return view('calendar.partials.create-modal', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ namespace App\Http\Middleware;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
class SetUserLocale
|
class SetUserLocale
|
||||||
{
|
{
|
||||||
@ -12,7 +13,13 @@ class SetUserLocale
|
|||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$locale = $user->getSetting('app.language');
|
// prefer dedicated column
|
||||||
|
$locale = $user->locale;
|
||||||
|
|
||||||
|
// fallback for legacy
|
||||||
|
if (!is_string($locale) || $locale === '') {
|
||||||
|
$locale = $user->getSetting('app.language');
|
||||||
|
}
|
||||||
|
|
||||||
if (is_string($locale) && $locale !== '') {
|
if (is_string($locale) && $locale !== '') {
|
||||||
app()->setLocale($locale);
|
app()->setLocale($locale);
|
||||||
|
|||||||
@ -33,6 +33,7 @@ class User extends Authenticatable
|
|||||||
'email',
|
'email',
|
||||||
'timezone',
|
'timezone',
|
||||||
'phone',
|
'phone',
|
||||||
|
'locale',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -16,4 +16,53 @@ return [
|
|||||||
// options: 'first' | 'random'
|
// options: 'first' | 'random'
|
||||||
'default_color_strategy' => 'random',
|
'default_color_strategy' => 'random',
|
||||||
],
|
],
|
||||||
|
'locales' => [
|
||||||
|
'English' => [
|
||||||
|
'en' => 'English',
|
||||||
|
'en_US' => 'English (United States)',
|
||||||
|
'en_GB' => 'English (United Kingdom)',
|
||||||
|
],
|
||||||
|
'Deutsch' => [
|
||||||
|
'de' => 'Deutsch',
|
||||||
|
'de_DE' => 'Deutsch (Deutschland)',
|
||||||
|
'de_CH' => 'Deutsch (Schweiz)',
|
||||||
|
],
|
||||||
|
'Español' => [
|
||||||
|
'es' => 'Español',
|
||||||
|
'es_MX' => 'Español (México)',
|
||||||
|
],
|
||||||
|
'Italiano' => [
|
||||||
|
'it' => 'Italiano',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'regions' => [
|
||||||
|
'North America' => [
|
||||||
|
'US' => 'United States',
|
||||||
|
'CA' => 'Canada',
|
||||||
|
'MX' => 'Mexico',
|
||||||
|
],
|
||||||
|
'Europe' => [
|
||||||
|
'GB' => 'United Kingdom',
|
||||||
|
'DE' => 'Germany',
|
||||||
|
'FR' => 'France',
|
||||||
|
'IE' => 'Ireland',
|
||||||
|
'IT' => 'Italy',
|
||||||
|
'NL' => 'Netherlands',
|
||||||
|
'ES' => 'Spain',
|
||||||
|
'CH' => 'Switzerland',
|
||||||
|
],
|
||||||
|
'Oceania' => [
|
||||||
|
'AU' => 'Australia',
|
||||||
|
'NZ' => 'New Zealand',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'date_formats' => [
|
||||||
|
'mdy' => 'MM/DD/YYYY (12/31/2026)',
|
||||||
|
'dmy' => 'DD/MM/YYYY (31/12/2026)',
|
||||||
|
'ymd' => 'YYYY-MM-DD (2026-12-31)',
|
||||||
|
],
|
||||||
|
'time_formats' => [
|
||||||
|
'12' => '12-hour (1:30 PM)',
|
||||||
|
'24' => '24-hour (13:30)',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// store a laravel locale string, e.g. "en", "es", "pt_BR"
|
||||||
|
$table->string('locale', 12)->nullable()->after('timezone');
|
||||||
|
$table->index('locale');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropIndex(['locale']);
|
||||||
|
$table->dropColumn('locale');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -50,9 +50,13 @@ return [
|
|||||||
'subtitle' => 'Please enter your password and confirm that you would like to permanently delete your account.',
|
'subtitle' => 'Please enter your password and confirm that you would like to permanently delete your account.',
|
||||||
],
|
],
|
||||||
'information' => [
|
'information' => [
|
||||||
'title' => 'Account information',
|
'title' => 'Personal information',
|
||||||
'subtitle' => 'Your name, email address, and other primary account details.',
|
'subtitle' => 'Your name, email address, and other primary account details.',
|
||||||
],
|
],
|
||||||
|
'locale' => [
|
||||||
|
'title' => 'Locale preferences',
|
||||||
|
'subtitle' => 'Location, timezone, and other regional preferences for calendars and events.'
|
||||||
|
],
|
||||||
'password' => [
|
'password' => [
|
||||||
'title' => 'Password',
|
'title' => 'Password',
|
||||||
'subtitle' => 'Ensure your account is using a long, random password to stay secure. We always recommend a password manager as well!',
|
'subtitle' => 'Ensure your account is using a long, random password to stay secure. We always recommend a password manager as well!',
|
||||||
|
|||||||
@ -36,6 +36,7 @@ return [
|
|||||||
'time_format' => 'Time format',
|
'time_format' => 'Time format',
|
||||||
'time_format_select' => 'Select a time format',
|
'time_format_select' => 'Select a time format',
|
||||||
'timezone' => 'Time zone',
|
'timezone' => 'Time zone',
|
||||||
|
'timezone_default' => 'Default time zone',
|
||||||
'timezone_select' => 'Select a time zone',
|
'timezone_select' => 'Select a time zone',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
@import './lib/calendar.css';
|
@import './lib/calendar.css';
|
||||||
@import './lib/checkbox.css';
|
@import './lib/checkbox.css';
|
||||||
@import './lib/color.css';
|
@import './lib/color.css';
|
||||||
|
@import './lib/icon.css';
|
||||||
@import './lib/indicator.css';
|
@import './lib/indicator.css';
|
||||||
@import './lib/input.css';
|
@import './lib/input.css';
|
||||||
@import './lib/mini.css';
|
@import './lib/mini.css';
|
||||||
|
|||||||
@ -87,6 +87,9 @@
|
|||||||
--shadow-drop: 2.5px 2.5px 0 0 var(--color-primary);
|
--shadow-drop: 2.5px 2.5px 0 0 var(--color-primary);
|
||||||
--shadow-input: inset 0 0.25rem 0 0 var(--color-gray-100);
|
--shadow-input: inset 0 0.25rem 0 0 var(--color-gray-100);
|
||||||
--shadow-input-hover: inset 0 0.25rem 0 0 var(--color-teal-200);
|
--shadow-input-hover: inset 0 0.25rem 0 0 var(--color-teal-200);
|
||||||
|
--shadow-checked: inset 2.5px 0 0 var(--color-primary), inset 0 0.25rem 0 0 var(--color-cyan-500);
|
||||||
|
--shadow-active: inset 0 0.25rem 0 0 var(--color-cyan-500);
|
||||||
|
--shadow-sibling: inset 1.5px 0 0 0 var(--color-primary);
|
||||||
|
|
||||||
--spacing-md: 1.5px;
|
--spacing-md: 1.5px;
|
||||||
--spacing-2px: 2px;
|
--spacing-2px: 2px;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
button,
|
button,
|
||||||
.button {
|
.button {
|
||||||
@apply relative inline-flex items-center cursor-pointer gap-2 rounded-md h-11 px-4 text-lg font-medium;
|
@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 outline-0 outline-transparent outline-offset-0;
|
||||||
@apply focus:ring-2 focus:ring-offset-2 focus:ring-cyan-600 focus:outline-none;
|
@apply focus:outline-2 focus:outline-offset-2 focus:outline-cyan-600;
|
||||||
transition: border-color 125ms ease-in-out;
|
transition: border-color 125ms ease-in-out, outline 125ms ease-in-out;
|
||||||
--button-border: var(--color-primary);
|
--button-border: var(--color-primary);
|
||||||
--button-accent: var(--color-primary-hover);
|
--button-accent: var(--color-primary-hover);
|
||||||
|
|
||||||
@ -18,15 +18,7 @@ button,
|
|||||||
border-color: var(--button-accent);
|
border-color: var(--button-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
|
||||||
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 {
|
&:active {
|
||||||
@apply shadow-none outline-none;
|
|
||||||
left: 2.5px;
|
left: 2.5px;
|
||||||
top: 2.5px;
|
top: 2.5px;
|
||||||
}
|
}
|
||||||
@ -65,7 +57,7 @@ button,
|
|||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0,0,0,0.075);
|
background-color: rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +75,9 @@ button,
|
|||||||
> button {
|
> button {
|
||||||
@apply relative flex items-center justify-center h-full pl-3.5 pr-3 cursor-pointer;
|
@apply relative flex items-center justify-center h-full pl-3.5 pr-3 cursor-pointer;
|
||||||
@apply border-md border-primary border-l-0 font-medium rounded-none;
|
@apply border-md border-primary border-l-0 font-medium rounded-none;
|
||||||
/*transition: background-color 100ms ease-in-out; */
|
transition: outline 125ms ease-in-out;
|
||||||
|
box-shadow: var(--shadows);
|
||||||
|
--shadows: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-cyan-200;
|
@apply bg-cyan-200;
|
||||||
@ -92,27 +86,36 @@ button,
|
|||||||
&:has(input:checked),
|
&:has(input:checked),
|
||||||
&:active {
|
&:active {
|
||||||
@apply bg-cyan-400 border-r-transparent;
|
@apply bg-cyan-400 border-r-transparent;
|
||||||
box-shadow:
|
|
||||||
inset 2.5px 0 0 0 var(--color-primary),
|
|
||||||
inset 0 0.25rem 0 0 var(--color-cyan-500);
|
|
||||||
top: 2.5px;
|
top: 2.5px;
|
||||||
|
--shadows: var(--shadow-checked);
|
||||||
+ label,
|
|
||||||
+ button {
|
|
||||||
box-shadow: inset 1.5px 0 0 0 var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-cyan-500;
|
@apply bg-cyan-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ label,
|
||||||
|
+ button {
|
||||||
|
--shadows: var(--shadow-sibling);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:has(input:checked) {}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
@apply rounded-l-md border-l-md;
|
@apply rounded-l-md border-l-md;
|
||||||
|
|
||||||
&:has(input:checked),
|
&:has(input:checked),
|
||||||
&:active {
|
&:active {
|
||||||
box-shadow: inset 0 0.25rem 0 0 var(--color-cyan-500);
|
--shadows: var(--shadow-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--color-white),
|
||||||
|
0 0 0 4px var(--color-cyan-600),
|
||||||
|
var(--focus-ring);
|
||||||
|
--focus-ring: ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +129,10 @@ button,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:active + button {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
> label {
|
> label {
|
||||||
> input[type="radio"] {
|
> input[type="radio"] {
|
||||||
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
||||||
|
|||||||
10
resources/css/lib/icon.css
Normal file
10
resources/css/lib/icon.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* icons
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* sizes */
|
||||||
|
.icon-12 { @apply w-3 h-3; }
|
||||||
|
.icon-16 { @apply w-4 h-4; }
|
||||||
|
.icon-20 { @apply w-5 h-5; }
|
||||||
|
.icon-24 { @apply w-6 h-6; }
|
||||||
|
.icon-32 { @apply w-8 h-8; }
|
||||||
1
resources/svg/icons/earth.svg
Normal file
1
resources/svg/icons/earth.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"><path d="M21.54 15H17a2 2 0 0 0-2 2v4.54"/><path d="M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17"/><path d="M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05"/><circle cx="12" cy="12" r="10"/></svg>
|
||||||
|
After Width: | Height: | Size: 433 B |
1
resources/svg/icons/solid/earth.svg
Normal file
1
resources/svg/icons/solid/earth.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M22.116,7.676c0.569,1.328 0.884,2.789 0.884,4.324c0,6.071 -4.929,11 -11,11c-6.071,0 -11,-4.929 -11,-11c0,-6.071 4.929,-11 11,-11c4.479,0 8.336,2.682 10.051,6.527c0.024,0.049 0.046,0.098 0.065,0.149Zm-16.106,-2.391c-1.371,1.224 -2.365,2.861 -2.787,4.715l1.777,-0c1.646,0 3,1.354 3,3l0,1c0,0.549 0.451,1 1,1c1.646,0 3,1.354 3,3l0,3c0.687,0 1.357,-0.077 2,-0.223l-0,-3.777c0,-1.646 1.354,-3 3,-3l3.777,0c0.146,-0.643 0.223,-1.313 0.223,-2c0,-1.052 -0.181,-2.061 -0.513,-3l-2.487,0c-0.55,0 -1,0.45 -1,1c0,1.646 -1.354,3 -3,3c-1.65,0 -3,-1.35 -3,-3c0,-0.549 -0.451,-1 -1,-1c-2.099,0 -3.842,-1.652 -3.99,-3.715Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 740 B |
@ -5,12 +5,13 @@
|
|||||||
|
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<p>
|
<p>
|
||||||
{{ __('calendar.settings.language_region.subtitle') }}
|
{{ __('account.settings.locale.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="{{ route('calendar.settings.language.store') }}" class="settings">
|
<form method="post" action="{{ route('account.locale.store') }}" class="settings">
|
||||||
@csrf
|
@csrf
|
||||||
|
@method('patch')
|
||||||
|
|
||||||
<div class="input-row input-row--1-1">
|
<div class="input-row input-row--1-1">
|
||||||
<div class="input-cell">
|
<div class="input-cell">
|
||||||
@ -66,10 +67,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="input-row input-row--1-1">
|
||||||
|
<div class="input-cell">
|
||||||
|
<x-input.select-label
|
||||||
|
:label="__('common.timezone_default')"
|
||||||
|
id="timezone"
|
||||||
|
name="timezone"
|
||||||
|
placeholder="{{ __('common.timezone_select') }}"
|
||||||
|
:value="$values['timezone']"
|
||||||
|
:options="$options['timezones']"
|
||||||
|
:selected="old('timezone', $values['timezone'])"
|
||||||
|
description="This can be overridden for each calendar."
|
||||||
|
/>
|
||||||
|
<x-input.error :messages="$errors->get('date_format')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="input-row input-row--actions input-row--start sticky-bottom">
|
<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 variant="primary" type="submit">{{ __('common.save_changes') }}</x-button>
|
||||||
<x-button type="anchor"
|
<x-button type="anchor"
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
href="{{ route('calendar.settings.language') }}">{{ __('common.cancel') }}</x-button>
|
href="{{ route('account.locale') }}">{{ __('common.cancel') }}</x-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<x-app-layout>
|
|
||||||
<x-slot name="header">
|
|
||||||
<h2 class="text-xl font-semibold leading-tight">
|
|
||||||
{{ __('Create Calendar') }}
|
|
||||||
</h2>
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
<div class="py-6">
|
|
||||||
<div class="max-w-2xl mx-auto sm:px-6 lg:px-8">
|
|
||||||
<div class="bg-white shadow-sm sm:rounded-lg p-6">
|
|
||||||
<form method="POST" action="{{ route('calendar.store') }}">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
{{-- just render the form component --}}
|
|
||||||
<x-calendar-form />
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-app-layout>
|
|
||||||
@ -27,7 +27,11 @@
|
|||||||
<div class="input-row input-row--1-1">
|
<div class="input-row input-row--1-1">
|
||||||
<div class="input-cell">
|
<div class="input-cell">
|
||||||
<x-input.label for="timezone" :value="__('Timezone')" />
|
<x-input.label for="timezone" :value="__('Timezone')" />
|
||||||
<x-input.select id="timezone" name="timezone" :options="config('timezones')" :selected="old('timezone', $defaults['timezone'] ?? 'UTC')" />
|
<x-input.select
|
||||||
|
id="timezone"
|
||||||
|
name="timezone"
|
||||||
|
:options="config('timezones')"
|
||||||
|
:selected="old('timezone', $defaults['timezone'] ?? 'UTC')" />
|
||||||
<x-input.error :messages="$errors->get('timezone')" />
|
<x-input.error :messages="$errors->get('timezone')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||||
@if ($iconComponent)
|
@if ($iconComponent)
|
||||||
<x-dynamic-component :component="$iconComponent" width="20" :color="$color" />
|
<x-dynamic-component :component="$iconComponent" class="icon-20" :color="$color" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if (!is_null($label))
|
@if (!is_null($label))
|
||||||
|
|||||||
30
resources/views/components/input/select-label.blade.php
Normal file
30
resources/views/components/input/select-label.blade.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
@props([
|
||||||
|
'label' => '', // label text
|
||||||
|
'labelclass' => '', // extra CSS classes for the label
|
||||||
|
'inputclass' => '', // input classes
|
||||||
|
'id' => '',
|
||||||
|
'name' => '', // input name
|
||||||
|
'disabled' => false, // disabled flag
|
||||||
|
'options' => [],
|
||||||
|
'selected' => '', // input value
|
||||||
|
'placeholder' => '', // placeholder text
|
||||||
|
'style' => '', // raw style string for the input
|
||||||
|
'required' => false, // true/false or truthy value
|
||||||
|
'autocomplete' => false,
|
||||||
|
'description' => '', // optional descriptive text below the input
|
||||||
|
])
|
||||||
|
|
||||||
|
<label {{ $attributes->class("text-label $labelclass") }}>
|
||||||
|
<span class="label">{{ $label }}</span>
|
||||||
|
<x-input.select
|
||||||
|
:id="$id"
|
||||||
|
:name="$name"
|
||||||
|
:class="$inputclass"
|
||||||
|
:options="$options"
|
||||||
|
:selected="$selected"
|
||||||
|
:placeholder="$placeholder"
|
||||||
|
:required="$required"
|
||||||
|
:autocomplete="$autocomplete"
|
||||||
|
{{ $attributes }} />
|
||||||
|
@if($description !== '')<span class="description">{{ $description}}</span>@endif
|
||||||
|
</label>
|
||||||
@ -12,10 +12,10 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<x-app.pagelink
|
<x-app.pagelink
|
||||||
href="{{ route('account.password') }}"
|
href="{{ route('account.locale') }}"
|
||||||
:active="request()->routeIs('account.password')"
|
:active="request()->routeIs('account.locale', 'account.delete.*')"
|
||||||
:label="__('common.password')"
|
:label="__('account.settings.locale.title')"
|
||||||
icon="key"
|
icon="earth"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@ -26,6 +26,19 @@
|
|||||||
icon="pin"
|
icon="pin"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
</menu>
|
||||||
|
</details>
|
||||||
|
<details open>
|
||||||
|
<summary>{{ __('Account settings') }}</summary>
|
||||||
|
<menu class="content pagelinks">
|
||||||
|
<li>
|
||||||
|
<x-app.pagelink
|
||||||
|
href="{{ route('account.password') }}"
|
||||||
|
:active="request()->routeIs('account.password')"
|
||||||
|
:label="__('common.password')"
|
||||||
|
icon="key"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<x-app.pagelink
|
<x-app.pagelink
|
||||||
href="{{ route('account.delete') }}"
|
href="{{ route('account.delete') }}"
|
||||||
|
|||||||
@ -2,14 +2,6 @@
|
|||||||
<details open>
|
<details open>
|
||||||
<summary>{{ __('General settings') }}</summary>
|
<summary>{{ __('General settings') }}</summary>
|
||||||
<menu class="content pagelinks">
|
<menu class="content pagelinks">
|
||||||
<li>
|
|
||||||
<x-app.pagelink
|
|
||||||
href="{{ route('calendar.settings.language') }}"
|
|
||||||
:active="request()->routeIs('calendar.settings.language')"
|
|
||||||
:label="__('calendar.settings.language_region.title')"
|
|
||||||
icon="globe"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</menu>
|
</menu>
|
||||||
</details>
|
</details>
|
||||||
<details open>
|
<details open>
|
||||||
|
|||||||
@ -54,6 +54,9 @@ Route::middleware('auth')->group(function ()
|
|||||||
Route::get('info', [AccountController::class, 'infoForm'])->name('info');
|
Route::get('info', [AccountController::class, 'infoForm'])->name('info');
|
||||||
Route::patch('info', [AccountController::class, 'infoStore'])->name('info.store');
|
Route::patch('info', [AccountController::class, 'infoStore'])->name('info.store');
|
||||||
|
|
||||||
|
Route::get('locale', [AccountController::class, 'localeForm'])->name('locale');
|
||||||
|
Route::patch('locale', [AccountController::class, 'localeStore'])->name('locale.store');
|
||||||
|
|
||||||
Route::get('addresses', [AccountController::class, 'addressesForm'])->name('addresses');
|
Route::get('addresses', [AccountController::class, 'addressesForm'])->name('addresses');
|
||||||
Route::patch('addresses', [AccountController::class, 'addressesStore'])->name('addresses.store');
|
Route::patch('addresses', [AccountController::class, 'addressesStore'])->name('addresses.store');
|
||||||
|
|
||||||
@ -81,10 +84,6 @@ Route::middleware('auth')->group(function ()
|
|||||||
// settings landing
|
// settings landing
|
||||||
Route::get('settings', [CalendarSettingsController::class, 'index'])->name('settings');
|
Route::get('settings', [CalendarSettingsController::class, 'index'])->name('settings');
|
||||||
|
|
||||||
// language/region settings
|
|
||||||
Route::get('settings/language', [CalendarSettingsController::class, 'languageForm'])->name('settings.language');
|
|
||||||
Route::post('settings/language', [CalendarSettingsController::class, 'languageStore'])->name('settings.language.store');
|
|
||||||
|
|
||||||
// settings / subscribe to a calendar
|
// settings / subscribe to a calendar
|
||||||
Route::get('settings/subscribe', [CalendarSettingsController::class, 'subscribeForm'])->name('settings.subscribe');
|
Route::get('settings/subscribe', [CalendarSettingsController::class, 'subscribeForm'])->name('settings.subscribe');
|
||||||
Route::post('settings/subscribe', [CalendarSettingsController::class, 'subscribeStore'])->name('settings.subscribe.store');
|
Route::post('settings/subscribe', [CalendarSettingsController::class, 'subscribeStore'])->name('settings.subscribe.store');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user