Skip to content

Commit 49a8a04

Browse files
committed
2 parents 4a5b72c + 52f6575 commit 49a8a04

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1789
-512
lines changed

app/Http/Requests/UserFormRequest.php

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public function rules()
3131
// Customization
3232
'theme' => ['required', Rule::in(Form::THEMES)],
3333
'width' => ['required', Rule::in(Form::WIDTHS)],
34+
'size' => ['required', Rule::in(Form::SIZES)],
35+
'border_radius' => ['required', Rule::in(Form::BORDER_RADIUS)],
3436
'cover_picture' => 'url|nullable',
3537
'logo_picture' => 'url|nullable',
3638
'dark_mode' => ['required', Rule::in(Form::DARK_MODE_VALUES)],

app/Models/Forms/Form.php

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Spatie\Sluggable\HasSlug;
2020
use Spatie\Sluggable\SlugOptions;
2121
use Stevebauman\Purify\Facades\Purify;
22+
use Carbon\Carbon;
2223

2324
class Form extends Model implements CachableAttributes
2425
{
@@ -30,6 +31,10 @@ class Form extends Model implements CachableAttributes
3031

3132
public const DARK_MODE_VALUES = ['auto', 'light', 'dark'];
3233

34+
public const SIZES = ['sm','md','lg'];
35+
36+
public const BORDER_RADIUS = ['none','small','full'];
37+
3338
public const THEMES = ['default', 'simple', 'notion'];
3439

3540
public const WIDTHS = ['centered', 'full'];
@@ -49,6 +54,8 @@ class Form extends Model implements CachableAttributes
4954

5055
// Customization
5156
'custom_domain',
57+
'size',
58+
'border_radius',
5259
'theme',
5360
'width',
5461
'cover_picture',
@@ -188,6 +195,21 @@ public function setTagsAttribute($value)
188195
$this->attributes['tags'] = json_encode($value);
189196
}
190197

198+
public function setClosesAtAttribute($value)
199+
{
200+
$this->attributes['closes_at'] = ($value) ? Carbon::parse($value)->setTimezone('UTC') : null;
201+
}
202+
203+
public function getClosesAtAttribute($value)
204+
{
205+
if (!$value) {
206+
return $value;
207+
}
208+
// Retrieve the desired timezone from the request or default to 'UTC'
209+
$timezone = request()->get('timezone', 'UTC');
210+
return Carbon::parse($value)->setTimezone($timezone)->toIso8601String();
211+
}
212+
191213
public function getIsClosedAttribute()
192214
{
193215
return $this->closes_at && now()->gt($this->closes_at);

client/components/forms/CheckboxInput.vue

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,16 @@
1010
:disabled="disabled ? true : null"
1111
:name="name"
1212
:color="color"
13+
:theme="theme"
1314
>
14-
<slot name="label">
15-
{{ label }}
15+
<slot
16+
name="label"
17+
>
18+
<span
19+
:class="[
20+
theme.SelectInput.fontSize,
21+
]"
22+
>{{ label }}</span>
1623
<span
1724
v-if="required"
1825
class="text-red-500 required-dot"

client/components/forms/CodeInput.client.vue

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<div
1212
:class="[
1313
theme.CodeInput.input,
14+
theme.CodeInput.borderRadius,
1415
{
1516
'!ring-red-500 !ring-2 !border-transparent': hasError,
1617
'!cursor-not-allowed !bg-gray-200': disabled,

client/components/forms/DateInput.vue

+18-10
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,43 @@
1010
:popper="{ placement: 'bottom-start' }"
1111
>
1212
<button
13+
ref="datepicker"
1314
class="cursor-pointer overflow-hidden"
1415
:class="inputClasses"
1516
:disabled="props.disabled"
16-
ref="datepicker"
1717
>
18-
<div class="flex items-center min-w-0">
18+
<div class="flex items-stretch min-w-0">
1919
<div
2020
class="flex-grow min-w-0 flex items-center gap-x-2"
2121
:class="[
22-
props.theme.default.inputSpacing.vertical,
23-
props.theme.default.inputSpacing.horizontal,
24-
{'hover:bg-gray-50 dark:hover:bg-gray-900': !props.disabled}
22+
props.theme.DateInput.spacing.horizontal,
23+
props.theme.DateInput.spacing.vertical,
24+
props.theme.DateInput.fontSize,
2525
]"
2626
>
2727
<Icon
2828
name="heroicons:calendar-20-solid"
2929
class="w-4 h-4 flex-shrink-0"
3030
dynamic
3131
/>
32-
<div class="flex-grow truncate overflow-hidden">
33-
<p class="flex-grow truncate h-[24px]">
32+
<div class="flex-grow truncate overflow-hidden flex items-center">
33+
<p
34+
v-if="formattedDatePreview"
35+
class="flex-grow truncate"
36+
>
3437
{{ formattedDatePreview }}
3538
</p>
39+
<p
40+
v-else
41+
class="text-transparent"
42+
>
43+
-
44+
</p>
3645
</div>
3746
</div>
3847
<button
3948
v-if="fromDate && !props.disabled"
40-
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2"
41-
:class="[props.theme.default.inputSpacing.vertical]"
49+
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2 flex items-center"
4250
@click.prevent="clear()"
4351
>
4452
<Icon
@@ -133,7 +141,7 @@ const modeledValue = computed({
133141
})
134142
135143
const inputClasses = computed(() => {
136-
const classes = [props.theme.DateInput.input, 'w-full']
144+
const classes = [props.theme.DateInput.input, props.theme.DateInput.borderRadius]
137145
if (props.disabled) {
138146
classes.push('!cursor-not-allowed dark:!bg-gray-600 !bg-gray-200')
139147
}

client/components/forms/FileInput.vue

+29-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
</template>
66
<div
77
v-if="cameraUpload && isInWebcam"
8-
class="hidden sm:block w-full min-h-40"
8+
class="hidden sm:block w-full"
9+
:class="[
10+
theme.fileInput.minHeight
11+
]"
912
>
1013
<camera-upload
1114
v-if="cameraUpload"
@@ -17,9 +20,17 @@
1720
<div
1821
v-else
1922
class="flex flex-col w-full items-center justify-center transition-colors duration-40"
20-
:class="[{'!cursor-not-allowed':disabled, 'cursor-pointer':!disabled,
21-
[theme.fileInput.inputHover.light + ' dark:'+theme.fileInput.inputHover.dark]: uploadDragoverEvent,
22-
['hover:'+theme.fileInput.inputHover.light +' dark:hover:'+theme.fileInput.inputHover.dark]: !loading}, theme.fileInput.input]"
23+
:class="[
24+
{'!cursor-not-allowed':disabled, 'cursor-pointer':!disabled,
25+
[theme.fileInput.inputHover.light + ' dark:'+theme.fileInput.inputHover.dark]: uploadDragoverEvent,
26+
['hover:'+theme.fileInput.inputHover.light +' dark:hover:'+theme.fileInput.inputHover.dark]: !loading},
27+
theme.fileInput.input,
28+
theme.fileInput.borderRadius,
29+
theme.fileInput.spacing.horizontal,
30+
theme.fileInput.spacing.vertical,
31+
theme.fileInput.fontSize,
32+
theme.fileInput.minHeight
33+
]"
2334
@dragover.prevent="uploadDragoverEvent=true"
2435
@dragleave.prevent="uploadDragoverEvent=false"
2536
@drop.prevent="onUploadDropEvent"
@@ -66,7 +77,7 @@
6677
viewBox="0 0 24 24"
6778
stroke-width="1.5"
6879
stroke="currentColor"
69-
class="w-6 h-6"
80+
class="w-5 h-5"
7081
>
7182
<path
7283
stroke-linecap="round"
@@ -76,7 +87,7 @@
7687
</svg>
7788
</div>
7889

79-
<p class="mt-2 text-sm text-gray-500 font-semibold select-none">
90+
<p class="mt-2 text-sm text-gray-500 font-medium select-none">
8091
Click to choose {{ multiple ? 'file(s)' : 'a file' }} or drag here
8192
</p>
8293
<p class="mt-1 text-xs text-gray-400 dark:text-gray-600 select-none">
@@ -129,24 +140,24 @@
129140
</template>
130141

131142
<script>
132-
import { inputProps, useFormInput } from './useFormInput.js'
143+
import {inputProps, useFormInput} from './useFormInput.js'
133144
import InputWrapper from './components/InputWrapper.vue'
134145
import UploadedFile from './components/UploadedFile.vue'
135146
import OpenFormButton from '../open/forms/OpenFormButton.vue'
136147
import CameraUpload from './components/CameraUpload.vue'
137-
import { storeFile } from "~/lib/file-uploads.js"
148+
import {storeFile} from "~/lib/file-uploads.js"
138149
139150
export default {
140151
name: 'FileInput',
141-
components: { InputWrapper, UploadedFile, OpenFormButton },
152+
components: {InputWrapper, UploadedFile, OpenFormButton},
142153
mixins: [],
143154
props: {
144155
...inputProps,
145-
multiple: { type: Boolean, default: true },
146-
cameraUpload: { type: Boolean, default: false },
147-
mbLimit: { type: Number, default: 5 },
148-
accept: { type: String, default: '' },
149-
moveToFormAssets: { type: Boolean, default: false }
156+
multiple: {type: Boolean, default: true},
157+
cameraUpload: {type: Boolean, default: false},
158+
mbLimit: {type: Number, default: 5},
159+
accept: {type: String, default: ''},
160+
moveToFormAssets: {type: Boolean, default: false}
150161
},
151162
152163
setup(props, context) {
@@ -159,7 +170,7 @@ export default {
159170
files: [],
160171
uploadDragoverEvent: false,
161172
loading: false,
162-
isInWebcam:false
173+
isInWebcam: false
163174
}),
164175
165176
computed: {
@@ -254,13 +265,13 @@ export default {
254265
this.uploadFileToServer(files.item(i))
255266
}
256267
},
257-
openWebcam(){
258-
if(!this.cameraUpload){
268+
openWebcam() {
269+
if (!this.cameraUpload) {
259270
return
260271
}
261272
this.isInWebcam = true
262273
},
263-
cameraFileUpload(file){
274+
cameraFileUpload(file) {
264275
this.isInWebcam = false
265276
this.isUploading = false
266277
this.uploadFileToServer(file)

client/components/forms/FlatSelectInput.vue

+63-30
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,74 @@
1010
class="h-6 w-6 text-nt-blue mx-auto"
1111
/>
1212
<div
13-
v-for="(option, index) in options"
1413
v-else
15-
:key="option[optionKey]"
16-
role="button"
14+
class="relative overflow-hidden"
1715
:class="[
1816
theme.default.input,
19-
'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 flex',
17+
theme.default.borderRadius,
2018
{
2119
'mb-2': index !== options.length,
2220
'!ring-red-500 !ring-2 !border-transparent': hasError,
2321
'!cursor-not-allowed !bg-gray-200': disabled,
2422
},
2523
]"
26-
@click="onSelect(option[optionKey])"
2724
>
28-
<p class="flex-grow">
29-
{{ option[displayKey] }}
30-
</p>
3125
<div
32-
v-if="isSelected(option[optionKey])"
33-
class="flex items-center"
26+
v-for="(option, index) in options"
27+
v-if="options && options.length"
28+
:key="option[optionKey]"
29+
:role="multiple?'checkbox':'radio'"
30+
:aria-checked="isSelected(option[optionKey])"
31+
:class="[
32+
theme.FlatSelectInput.spacing.vertical,
33+
theme.FlatSelectInput.fontSize,
34+
theme.FlatSelectInput.option,
35+
]"
36+
@click="onSelect(option[optionKey])"
3437
>
35-
<svg
36-
:color="color"
37-
xmlns="http://www.w3.org/2000/svg"
38-
class="h-5 w-5"
39-
viewBox="0 0 20 20"
40-
fill="currentColor"
41-
>
42-
<path
43-
fill-rule="evenodd"
44-
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
45-
clip-rule="evenodd"
38+
<template v-if="multiple">
39+
<Icon
40+
v-if="isSelected(option[optionKey])"
41+
name="material-symbols:check-box"
42+
class="text-inherit"
43+
:color="color"
44+
:class="[theme.FlatSelectInput.icon]"
4645
/>
47-
</svg>
46+
<Icon
47+
v-else
48+
name="material-symbols:check-box-outline-blank"
49+
:class="[theme.FlatSelectInput.icon,theme.FlatSelectInput.unselectedIcon]"
50+
/>
51+
</template>
52+
<template v-else>
53+
<Icon
54+
v-if="isSelected(option[optionKey])"
55+
name="material-symbols:radio-button-checked-outline"
56+
class="text-inherit"
57+
:color="color"
58+
:class="[theme.FlatSelectInput.icon]"
59+
/>
60+
<Icon
61+
v-else
62+
name="material-symbols:radio-button-unchecked"
63+
:class="[theme.FlatSelectInput.icon,theme.FlatSelectInput.unselectedIcon]"
64+
/>
65+
</template>
66+
<p class="flex-grow">
67+
{{ option[displayKey] }}
68+
</p>
69+
</div>
70+
<div
71+
v-else
72+
:class="[
73+
theme.FlatSelectInput.spacing.horizontal,
74+
theme.FlatSelectInput.spacing.vertical,
75+
theme.FlatSelectInput.fontSize,
76+
theme.FlatSelectInput.option,
77+
'!text-gray-500 !cursor-not-allowed'
78+
]"
79+
>
80+
No options available.
4881
</div>
4982
</div>
5083

@@ -58,24 +91,24 @@
5891
</template>
5992

6093
<script>
61-
import { inputProps, useFormInput } from "./useFormInput.js"
94+
import {inputProps, useFormInput} from "./useFormInput.js"
6295
import InputWrapper from "./components/InputWrapper.vue"
6396
6497
/**
6598
* Options: {name,value} objects
6699
*/
67100
export default {
68101
name: "FlatSelectInput",
69-
components: { InputWrapper },
102+
components: {InputWrapper},
70103
71104
props: {
72105
...inputProps,
73-
options: { type: Array, required: true },
74-
optionKey: { type: String, default: "value" },
75-
emitKey: { type: String, default: "value" },
76-
displayKey: { type: String, default: "name" },
77-
loading: { type: Boolean, default: false },
78-
multiple: { type: Boolean, default: false },
106+
options: {type: Array, required: true},
107+
optionKey: {type: String, default: "value"},
108+
emitKey: {type: String, default: "value"},
109+
displayKey: {type: String, default: "name"},
110+
loading: {type: Boolean, default: false},
111+
multiple: {type: Boolean, default: false},
79112
},
80113
setup(props, context) {
81114
return {

0 commit comments

Comments
 (0)