FIX FLEXIAPI-441 add color picker and logo uploader to space settings

This commit is contained in:
m2cci-bartetj 2026-04-16 11:54:12 +02:00
parent 40eab70d11
commit 7bfc0b61d9
11 changed files with 387 additions and 108 deletions

View file

@ -27,6 +27,7 @@ use App\Rules\Domain;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Storage;
class SpaceController extends Controller
{
@ -113,7 +114,39 @@ class SpaceController extends Controller
public function configurationUpdate(Request $request, Space $space)
{
$space = $this->setConfiguration($request, $space);
$request->validate([
'newsletter_registration_address' => 'nullable|email',
'custom_provisioning_entries' => ['nullable', new Ini(Space::FORBIDDEN_KEYS)],
'logo' => ['nullable', 'image', 'mimes:png', 'max:2048'],
]);
if ($request->hasFile('logo')) {
if($space->logo){
Storage::disk('public')->delete($space->LOGO_PATH);
}
$filename = $request->file('logo')->hashName();
$request->file('logo')->storeAs('img', $filename, 'public');
$space->logo = $filename;
}
$space->theme_hue = $request->get('theme_hue');
$space->copyright_text = $request->get('copyright_text');
$space->intro_registration_text = $request->get('intro_registration_text');
$space->newsletter_registration_address = $request->get('newsletter_registration_address');
$space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address');
if ($space->accounts()->count() == 0) {
$space->account_realm = $request->get('account_realm');
}
$space->custom_provisioning_entries = $request->get('custom_provisioning_entries');
$space->custom_provisioning_overwrite_all = getRequestBoolean($request, 'custom_provisioning_overwrite_all');
$space->provisioning_use_linphone_provisioning_header = getRequestBoolean($request, 'provisioning_use_linphone_provisioning_header');
$space->public_registration = getRequestBoolean($request, 'public_registration');
$space->phone_registration = getRequestBoolean($request, 'phone_registration');
$space->intercom_features = getRequestBoolean($request, 'intercom_features');
$space->save();
return redirect()->route('admin.spaces.configuration', $space);
@ -152,33 +185,6 @@ class SpaceController extends Controller
return redirect()->route('admin.spaces.show', $space);
}
private function setConfiguration(Request $request, Space $space)
{
$request->validate([
'newsletter_registration_address' => 'nullable|email',
'custom_provisioning_entries' => ['nullable', new Ini(Space::FORBIDDEN_KEYS)]
]);
$space->copyright_text = $request->get('copyright_text');
$space->intro_registration_text = $request->get('intro_registration_text');
$space->newsletter_registration_address = $request->get('newsletter_registration_address');
$space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address');
if ($space->accounts()->count() == 0) {
$space->account_realm = $request->get('account_realm');
}
$space->custom_provisioning_entries = $request->get('custom_provisioning_entries');
$space->custom_provisioning_overwrite_all = getRequestBoolean($request, 'custom_provisioning_overwrite_all');
$space->provisioning_use_linphone_provisioning_header = getRequestBoolean($request, 'provisioning_use_linphone_provisioning_header');
$space->public_registration = getRequestBoolean($request, 'public_registration');
$space->phone_registration = getRequestBoolean($request, 'phone_registration');
$space->intercom_features = getRequestBoolean($request, 'intercom_features');
return $space;
}
private function setAppConfiguration(Request $request, Space $space)
{
$request->validate([

View file

@ -68,6 +68,7 @@ class Space extends Model
public const HOST_REGEX = '[\w\-]+';
public const DOMAIN_REGEX = '(?=^.{4,253}$)(^((?!-)[a-z0-9-]{1,63}(?<!-)\.)+[a-z]{2,63}$)';
public const LOGO_PATH = 'img';
protected static function booted()
{
@ -122,6 +123,11 @@ class Space extends Model
return Command::SUCCESS;
}
public function getLogoPathAttribute(): string
{
return self::LOGO_PATH . '/' . $this->attributes['logo'];
}
public function isFull(): bool
{
return $this->max_accounts > 0 && ($this->accounts()->count() >= $this->max_accounts);

2
flexiapi/app/bootstrap/cache/.gitignore vendored Executable file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -0,0 +1,24 @@
<?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('spaces', function (Blueprint $table) {
$table->integer('theme_hue')->nullable();
$table->string('logo')->nullable();
});
}
public function down(): void
{
Schema::table('spaces', function (Blueprint $table) {
$table->dropColumn('theme_hue');
$table->dropColumn('logo');
});
}
};

View file

@ -121,6 +121,7 @@
"Files": "Fichiers",
"From": "Depuis",
"Hello":"Bonjour",
"Hex" : "Hex",
"Host": "Hôte",
"I accept the Privacy Policy": "J'accepte la Politique de Confidentialité",
"I accept the Terms and Conditions": "J'accepte les Conditions Générales",
@ -183,6 +184,7 @@
"Phone Countries": "Numéros Internationaux",
"Phone number": "Numéro de téléphone",
"Phone registration": "Inscription par téléphone",
"Platform customization": "Customisation de la plateforme",
"Please enter the new email that you would like to link to your account.": "Veuillez entre l'adresse email que vous souhaitez lier à votre compte.",
"Please enter the new phone number that you would like to link to your account.": "Veuillez entrer le numéro de téléphone que vous souhaitez lier à votre compte.",
"Priority rule": "Rêgle prioritaire",
@ -215,9 +217,11 @@
"Scan the following QR Code using an authenticated device and wait a few seconds.": "Scanner le QR Code avec un appareil authentifié et attendez quelques secondes",
"Search by username":"Rechercher par nom d'utilisateur",
"Search": "Rechercher",
"Select a color": "Sélectionner une couleur",
"Select a contacts list": "Sélectionner une liste de contact",
"Select a domain": "Sélectionner un domaine",
"Select a file": "Choisir un fichier",
"Select an image": "Choisir une image",
"Send an email to the user to reset the password": "Envoyer un email à l'utilisateur pour réinitialiser son mot de passe",
"Send an email to the user with provisioning information": "Envoyer un email à l'utilisateur avec les informations de déploiement",
"Send": "Envoyer",
@ -293,8 +297,9 @@
"You can now continue your registration process in the application": "Vous pouvez maintenant continuer le processus d'inscription dans l'application",
"You didn't receive the code?": "Vous n'avez pas reçu le code ?",
"Your account recovery code":"Votre code de récupération de compte",
"Your color will be snapped to the nearest 500 shade" : "La couleur que vous entrez sera automatiquement remplacée par sa variante 500 la plus proche.",
"Your password was updated properly.": "Votre mot de passe a été mis à jour.",
"Your password" : "Votre mot de passe",
"Your space :space is expiring in :count days": "Votre espace :space expire dans :count jours",
"Your space has expired. Access to your interface is now disabled, and your users can no longer benefit from the service. To reactivate your space, please contact your account manager.": "Votre espace est arrivé à expiration. Laccès à votre interface est désormais désactivé, et vos utilisateurs ne peuvent plus bénéficier du service. Pour réactiver votre espace, veuillez contacter votre responsable de compte."
}
}

View file

@ -183,13 +183,13 @@ form div .btn.oppose {
right: 0;
}
form div input,
form div input:not([type=range]),
form div textarea,
form div select {
padding: 1rem 2rem;
background-color: var(--grey-1);
border-radius: 3rem;
border: 1px solid var(--grey-2);
border: 0.063rem solid var(--grey-2);
font-size: 1.5rem;
resize: vertical;
}
@ -261,6 +261,7 @@ input[type=number].digit {
height: 5rem;
-moz-appearance: textfield;
-webkit-appearance: textfield;
appearance:textfield;
border-radius: 1.5rem;
background-color: white;
padding: 0.5rem;
@ -282,7 +283,7 @@ form div input[type=checkbox] {
margin-right: 1rem;
}
form div input:not([type=checkbox]):not([type=radio]):not([type="number"].digit):not(.btn),
form div input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type="number"].digit):not(.btn),
form div textarea,
form div select {
margin-top: 2.5rem;
@ -338,6 +339,71 @@ form div textarea:invalid:not(:placeholder-shown)+label {
color: var(--danger);
}
/* Image and Logo Picker */
form div .picker {
display: flex;
align-items: center;
height: 11rem;
gap: 1.5rem;
margin-top: 1rem;
}
form canvas,
form .picker .color-div {
aspect-ratio: 1/1;
height: 100%;
border: 1px solid var(--grey-2);
border-radius: 1.25rem;
}
form canvas {
background-color: var(--grey-1);
}
form .picker .color-div {
background-color: var(--main-5);
}
form input[type=range] {
appearance: none;
margin-top: 2.5rem;
box-sizing: border-box;
width: 100%;
height: 1.5rem;
border-radius: 3rem;
overflow: visible;
background: linear-gradient(
to right,
hsl(0, 100%, 50%),
hsl(60, 100%, 50%),
hsl(120, 100%, 50%),
hsl(180, 100%, 50%),
hsl(240, 100%, 50%),
hsl(300, 100%, 50%),
hsl(360, 100%, 50%)
);
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
border: 0.25rem solid var(--grey-1);
cursor: pointer;
}
/* Mandatory for Mozilla Firefox */
input[type=range]::-moz-range-thumb {
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
border: 0.25rem solid var(--grey-1);
cursor: pointer;
}
/* Checkbox element */
form div.checkbox {
@ -417,4 +483,4 @@ div.select[data-value=sip_uri] ~ div.togglable.sip_uri {
form section.block div.checkbox:has(input:not(:checked)) ~ div {
opacity: 0.25;
pointer-events: none;
}
}

View file

@ -285,7 +285,7 @@ header nav a#menu:after {
}
body.show_menu header nav a#menu:after {
font-family: 'Phosphor'
font-family: 'Phosphor';
header nav a#logo {
position: absolute;
@ -300,9 +300,8 @@ header nav a#logo::before {
width: 3rem;
height: 3rem;
padding: 1rem;
background-image: url('../img/logo.svg');
background-color: var(--main-5);
background-size: 3rem;
background-image: var(--space-logo ,url('../img/logo.svg'));
background-size: 5rem;
background-position: center;
background-repeat: no-repeat;
display: block;
@ -1118,4 +1117,4 @@ dialog p {
dialog::backdrop {
background-color: black;
opacity: 0.5;
}
}

View file

@ -1,44 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="287.47433"
height="292.50049"
viewBox="0 0 287.47433 292.50049"
fill="none"
version="1.1"
id="svg2705"
sodipodi:docname="logo.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2709" />
<sodipodi:namedview
id="namedview2707"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="0.4609375"
inkscape:cx="144.27119"
inkscape:cy="147.52542"
inkscape:window-width="1920"
inkscape:window-height="1148"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2705" />
<path
d="m 24.974336,57.579473 c 0,0 -29,40 -24.5,58.000997 -0.155,-0.698 19,-21.000997 19,-21.000997 1,5.499997 47.5,19.000997 47.5,19.000997 6.174,3.935 8.302,6.761 9.5,13 v 24 c 16.063,9.994 30.818004,13.31 66.500004,15.5 37.913,-1.616 53.572,-4.974 67.5,-15.5 0,0 -1.5,-18.501 0,-25.001 1.5,-6.5 4.829,-10.217 10,-11.999 19.293,-4.414 28.816,-9.243 47,-18.500997 l 20,20.500997 c -3.039,-22.539997 -8.262,-35.015997 -24,-56.999997 -12.494,-14.278 -20.505,-21.766 -37,-34 -17.932,-10.596 -27.882,-14.6409998 -45.5,-19.4999998 -30.58,-6.262 -47.438,-6.243 -77,0 -18.113004,5.4609998 -27.141004,9.5809998 -40.500004,19.4999998 0,0 -50.5,-29.0009998 -57,-24.00099984 -6.5,5.00000004 18.5,56.99999984 18.5,56.99999984 z"
fill="#ffffff"
id="path2701" />
<path
d="m 59.500336,212.50047 c -23.228,-10.168 -35.814,-17.849 -57.5,-35 5.214,21.587 10.076,32.953 22,52 12.706,17.195 20.513,24.868 35.5,35.5 16.208,11.124 25.928,15.987 44.500004,22 16.684,4.01 25.987,5.288 42.5,5.5 16.837,-0.843 25.574,-2.678 40,-6 15.912,-6.341 24.018,-10.339 37.5,-18 22.355,12.62 50,27 54,21 4,-6 -18,-54.5 -18,-54.5 14.673,-21.015 20.675,-35.167 26.5,-59 -20.086,15.757 -33.135,24.951 -58,36.5 -30.758,12.132 -48.672,16.028 -82,17 -38.827,-0.677 -57.775004,-4.519 -87.000004,-17 z"
fill="#ffffff"
id="path2703" />
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_361_1459)">
<rect width="100" height="100" rx="20" fill="#FE5E00"/>
<rect width="170" height="170" fill="#FE5E00"/>
<path d="M27.8723 32.7597C25.4582 36.0095 23.4114 41.2328 23.0002 43.5646C22.9712 43.4345 26.8489 39.6619 26.8489 39.6619C29.0866 40.8597 35.6873 43.2063 35.6873 43.2063C36.836 43.9405 37.232 44.4676 37.455 45.6314V50.1085C40.4438 51.9729 43.1892 52.5916 49.8288 53C56.8833 52.6985 59.7969 52.0722 62.3886 50.1085C62.3886 50.1085 62.1095 46.6574 62.3886 45.4448C62.6677 44.2323 63.2871 43.5388 64.2493 43.2063C67.8391 42.383 69.6111 41.4822 72.9947 39.7552L77 43.4713C76.4418 40.9529 75.1788 37.0473 72.2504 32.9462C69.9255 30.2829 68.4349 28.8859 65.3657 26.6037C62.029 24.6272 60.1824 23.783 56.9043 22.8766C51.6012 21.5521 46.8564 21.8319 42.5768 23.0258C38.9484 24.038 37.3405 24.7013 34.8548 26.5515C34.8548 26.5515 25.6395 21.1938 24.43 22.1265C23.2205 23.0593 27.8723 32.7597 27.8723 32.7597Z" fill="white"/>
<path d="M33.9831 62.8886C29.6288 60.9696 27.0652 59.6127 23 56.3758C23.7619 60.0404 25.8059 64.0808 27.3148 66.0971C29.6153 69.3426 31.1736 70.8483 33.9831 72.8549C36.8273 74.8864 38.8434 75.8141 42.3248 76.949C45.4523 77.7058 47.1767 78.0758 50.2917 77.987C53.5864 77.8931 55.1896 77.7052 57.7899 76.8547C60.6476 75.9199 62.4129 75.1682 64.8286 73.6122C69.0192 75.9939 74.1923 78.5532 74.9421 77.4209C75.6919 76.2885 71.7412 67.2754 71.7412 67.2754C73.9339 64.4566 76.1638 60.4162 77 56C73.2347 58.9737 70.3242 60.709 65.663 62.8886C59.8973 65.1784 56.5392 65.9135 50.2917 66.0971C43.0134 65.9692 39.4614 65.2441 33.9831 62.8886Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_361_1459">
<rect width="100" height="100" rx="20" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

138
flexiapi/public/scripts/colorPicker.js vendored Normal file
View file

@ -0,0 +1,138 @@
// Color picker
function onColorSliderChange(input) {
const group = input.closest('.color-picker-group');
const hue = input.value;
group.querySelector('input[type=hidden]').value = hue;
group.querySelector('input[type=text]').value = hslToHex(hue, 100, 50);
group.querySelector('.color-div').style.backgroundColor = hslToHex(hue, 100, 50);
}
function onColorInputChange(input) {
const hex = input.value.trim();
if (!hex.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/)) {
return;
}
const group = input.closest('.color-picker-group');
const colorHsl = hexToHsl(hex);
group.querySelector('input[type=hidden]').value = colorHsl;
group.querySelector('input[type=range]').value = colorHsl;
group.querySelector('input[type=text]').value = hslToHex(colorHsl.h, 100, 50);
group.querySelector('.color-div').style.backgroundColor = hslToHex(colorHsl.h, 100, 50);
}
function hslToHex(h, s, l) {
s /= 100;
l /= 100;
const k = n => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = n => Math.round(255 * (l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))));
const toHex = v => v.toString(16).padStart(2, '0');
return `#${toHex(f(0))}${toHex(f(8))}${toHex(f(4))}`;
}
function hexToHsl(hex) {
hex = hex.replace(/^#/, '');
if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
const r = parseInt(hex.slice(0, 2), 16) / 255;
const g = parseInt(hex.slice(2, 4), 16) / 255;
const b = parseInt(hex.slice(4, 6), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const d = max - min;
let h, s;
let l = (max + min) / 2;
if (d === 0) {
h = s = 0;
} else {
s = d / (l > 0.5 ? 2 - max - min : max + min);
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100),
};
}
// Image picker
function onImageLoad(input) {
const file = input.files[0];
if (!file) return;
if (!file.type.match('image.*')) {
console.log("Error: not an image");
imgInput.value = "";
return;
}
const url = URL.createObjectURL(file);
const img = new Image();
const group = input.closest('.image-picker-group');
const canvas = group.querySelector('canvas');
const ctx = canvas.getContext("2d");
img.onload = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawImageProp(ctx, img);
URL.revokeObjectURL(url);
canvas.toBlob((blob) => {
const croppedFile = new File([blob], 'logo.png', { type: 'image/png' });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(croppedFile);
input.files = dataTransfer.files;
}, 'image/png');
};
img.src = url;
}
function initImageLoader() {
document.querySelectorAll('.image-picker-group').forEach(group => {
const canvas = group.querySelector('canvas');
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#4e6074";
ctx.font = "20px Noto Sans";
ctx.textBaseline = "middle";
const text = "Logo";
const textWidth = ctx.measureText(text).width;
ctx.fillText(text, (canvas.width - textWidth) / 2, canvas.height / 2);
});
}
function drawImageProp(ctx, img, padding = 1) {
const canvas = ctx.canvas;
const maxW = canvas.width - padding * 2;
const maxH = canvas.height - padding * 2;
const scale = Math.min(maxW / img.width, maxH / img.height);
const nw = img.width * scale;
const nh = img.height * scale;
const maxWH = Math.max(nw, nh);
const x = (canvas.width - maxWH) / 2;
const y = (canvas.height - maxWH) / 2;
ctx.drawImage(img, x, y, maxWH, maxWH);
}

View file

@ -17,12 +17,37 @@
@include('admin.space.head')
@include('admin.space.tabs')
<form method="POST"
action="{{ route('admin.spaces.configuration.update', $space) }}"
accept-charset="UTF-8">
<form method="POST" action="{{ route('admin.spaces.configuration.update', $space) }}" accept-charset="UTF-8"
enctype="multipart/form-data">
@csrf
@method('put')
<h3 class="large">{{ __('Platform customization') }}</h3>
<div class="image-picker-group">
<label for="logo" style="font-weight: 700;">{{ __('Select an image') }} </label>
<div class="picker">
<canvas></canvas>
<input name="logo" type="file" accept="image/*" onchange="onImageLoad(this)">
@include('parts.errors', ['name' => 'logo'])
</div>
</div>
<div class="color-picker-group">
<label for="color-slider" style="font-weight: 700;">{{ __('Select a color') }} </label>
<input name="theme_hue" type="hidden" value="{{ $space->theme_hue ?? 22}}">
<div class="picker">
<div id="color-div" class="color-div"></div>
<div>
<input id="hex-color"type="text" value="#ff5e00" onblur="onColorInputChange(this)">
<label for="color-input">{{ __('Hex') }}</label>
<input class="range" type="range" min="0" max="360" value="{{ $space->theme_hue ?? 22}}"
oninput="onColorSliderChange(this)">
</div>
</div>
<span class="supporting"> {{ __('Your color will be snapped to the nearest 500 shade') }}</span>
</div>
<div class="large">
<textarea name="copyright_text" id="copyright_text">{{ $space->copyright_text }}</textarea>
<label for="copyright_text">{{ __('Copyright text') }}</label>
@ -37,21 +62,26 @@
</div>
<div>
<input name="newsletter_registration_address" id="newsletter_registration_address" placeholder="email@server.tld" type="email" value="{{ $space->newsletter_registration_address }}">
<input name="newsletter_registration_address" id="newsletter_registration_address"
placeholder="email@server.tld" type="email" value="{{ $space->newsletter_registration_address }}">
<label for="newsletter_registration_address">{{ __('Newsletter registration email address') }}</label>
<span class="supporting">{{ __('An email will be sent to this email when someone join the newsletter') }}</span>
<span
class="supporting">{{ __('An email will be sent to this email when someone join the newsletter') }}</span>
@include('parts.errors', ['name' => 'newsletter_registration_address'])
</div>
<div>
<input name="account_proxy_registrar_address" id="account_proxy_registrar_address" placeholder="server.tld" value="{{ $space->account_proxy_registrar_address }}">
<input name="account_proxy_registrar_address" id="account_proxy_registrar_address" placeholder="server.tld"
value="{{ $space->account_proxy_registrar_address }}">
<label for="account_proxy_registrar_address">Account proxy registrar address</label>
<span class="supporting">Will be used for informational purpose in the user panel and communication emails</span>
<span class="supporting">Will be used for informational purpose in the user panel and communication
emails</span>
@include('parts.errors', ['name' => 'account_proxy_registrar_address'])
</div>
<div>
<input name="account_realm" @if ($space->accounts()->count() > 0)disabled @endif id="account_realm" placeholder="server.tld" value="{{ $space->account_realm }}">
<input name="account_realm" @if ($space->accounts()->count() > 0) disabled @endif id="account_realm"
placeholder="server.tld" value="{{ $space->account_realm }}">
<label for="account_realm">Account realm</label>
<span class="supporting">A custom realm for the Space accounts</span>
@include('parts.errors', ['name' => 'account_realm'])
@ -63,31 +93,60 @@
<textarea style="min-height: 200px;" name="custom_provisioning_entries" id="custom_provisioning_entries">{{ $space->custom_provisioning_entries }}</textarea>
<label for="custom_provisioning_entries">{{ __('Custom entries') }}</label>
<span class="supporting">{{ __('In ini format, will complete the other settings.') }}</span>
<span class="supporting">{{ __('Use ; to comment, key="value" to declare a complex string.') }} <a target="_blank" href="https://cheatsheets.zip/ini.html">{{ __('Checkout the cheatsheets to know how to format things correctly.') }}</a></span>
<span class="supporting">{{ __('Use ; to comment, key="value" to declare a complex string.') }} <a
target="_blank"
href="https://cheatsheets.zip/ini.html">{{ __('Checkout the cheatsheets to know how to format things correctly.') }}</a></span>
@include('parts.errors', ['name' => 'custom_provisioning_entries'])
</div>
<div>
@include('parts.form.toggle', ['object' => $space, 'key' => 'custom_provisioning_overwrite_all', 'label' => __('Allow client settings to be overwritten by the provisioning ones')])
@include('parts.form.toggle', [
'object' => $space,
'key' => 'custom_provisioning_overwrite_all',
'label' => __('Allow client settings to be overwritten by the provisioning ones'),
])
</div>
<div>
@include('parts.form.toggle', ['object' => $space, 'key' => 'provisioning_use_linphone_provisioning_header', 'label' => 'Enforce X-Linphone-Provisioning header'])
@include('parts.form.toggle', [
'object' => $space,
'key' => 'provisioning_use_linphone_provisioning_header',
'label' => 'Enforce X-Linphone-Provisioning header',
])
</div>
<h3 class="large">{{ __('Features') }}</h3>
<div>
@include('parts.form.toggle', ['object' => $space, 'key' => 'public_registration', 'label' => __('Public registration')])
@include('parts.form.toggle', [
'object' => $space,
'key' => 'public_registration',
'label' => __('Public registration'),
])
</div>
<div
@include('parts.form.toggle', ['object' => $space, 'key' => 'phone_registration', 'label' => __('Phone registration')])
<div @include('parts.form.toggle', [
'object' => $space,
'key' => 'phone_registration',
'label' => __('Phone registration'),
])
</div>
<div>
@include('parts.form.toggle', ['object' => $space, 'key' => 'intercom_features', 'label' => __('Intercom features')])
@include('parts.form.toggle', [
'object' => $space,
'key' => 'intercom_features',
'label' => __('Intercom features'),
])
</div>
<div class="large">
<input class="btn" type="submit" value="{{ __('Update') }}">
</div>
<div class="large">
<input class="btn" type="submit" value="{{ __('Update') }}">
</div>
</form>
<script src="{{ asset('scripts/colorPicker.js') }}"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.getElementById('hex-color').value = hslToHex({{ $space->theme_hue ?? 22}},100,50);
document.getElementById('color-div').style.backgroundColor = hslToHex({{ $space->theme_hue ?? 22}},100,50);
initImageLoader();
});
</script>
@endsection

View file

@ -14,6 +14,10 @@
$space = space();
@endphp
@if ($space->theme_hue) <style> body{ --hue: {{$space->theme_hue}}; } </style> @endif
@if ($space->logo) <style> body{ --space-logo: url('{{ asset('storage/img/' . $space->logo) }}'); } </style> @endif
@if (space()?->custom_theme && file_exists(public_path('css/' . space()?->host . '.style.css')))
<link rel="stylesheet" type="text/css" href="{{ asset('css/' . space()?->host . '.style.css') }}">
@endif
@ -26,8 +30,9 @@
<body class="@if (isset($welcome) && $welcome) welcome @endif">
<header>
<nav>
<a id="logo" href="{{ route('account.home') }}"><span
class="on_desktop">{{ space()->name }}</span></a>
<a id="logo" href="{{ route('account.home') }}">
<span class="on_desktop">{{ space()->name }}</span>
</a>
@if (!isset($welcome) || $welcome == false)
<a id="menu" class="on_mobile" href="#"
@ -67,13 +72,13 @@
@if (!isset($welcome) || $welcome == false)
<section @if (isset($grid) && $grid) class="grid" @endif>
@hasSection('breadcrumb')
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
@yield('breadcrumb')
</ol>
</nav>
@endif
@hasSection('breadcrumb')
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
@yield('breadcrumb')
</ol>
</nav>
@endif
@endif
@include('parts.errors')