initial commit
This commit is contained in:
15
resources/js/Jetstream/ActionMessage.vue
Normal file
15
resources/js/Jetstream/ActionMessage.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
on: Boolean,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<transition leave-active-class="transition ease-in duration-1000" leave-from-class="opacity-100" leave-to-class="opacity-0">
|
||||
<div v-show="on" class="text-sm text-gray-600">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
22
resources/js/Jetstream/ActionSection.vue
Normal file
22
resources/js/Jetstream/ActionSection.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
import SectionTitle from './SectionTitle.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||
<SectionTitle>
|
||||
<template #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<slot name="description" />
|
||||
</template>
|
||||
</SectionTitle>
|
||||
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
<div class="px-4 py-5 sm:p-6 bg-white shadow sm:rounded-lg">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
7
resources/js/Jetstream/ApplicationLogo.vue
Normal file
7
resources/js/Jetstream/ApplicationLogo.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 317 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M74.09 30.04V13h-4.14v21H82.1v-3.96h-8.01zM95.379 19v1.77c-1.08-1.35-2.7-2.19-4.89-2.19-3.99 0-7.29 3.45-7.29 7.92s3.3 7.92 7.29 7.92c2.19 0 3.81-.84 4.89-2.19V34h3.87V19h-3.87zm-4.17 11.73c-2.37 0-4.14-1.71-4.14-4.23 0-2.52 1.77-4.23 4.14-4.23 2.4 0 4.17 1.71 4.17 4.23 0 2.52-1.77 4.23-4.17 4.23zM106.628 21.58V19h-3.87v15h3.87v-7.17c0-3.15 2.55-4.05 4.56-3.81V18.7c-1.89 0-3.78.84-4.56 2.88zM124.295 19v1.77c-1.08-1.35-2.7-2.19-4.89-2.19-3.99 0-7.29 3.45-7.29 7.92s3.3 7.92 7.29 7.92c2.19 0 3.81-.84 4.89-2.19V34h3.87V19h-3.87zm-4.17 11.73c-2.37 0-4.14-1.71-4.14-4.23 0-2.52 1.77-4.23 4.14-4.23 2.4 0 4.17 1.71 4.17 4.23 0 2.52-1.77 4.23-4.17 4.23zM141.544 19l-3.66 10.5-3.63-10.5h-4.26l5.7 15h4.41l5.7-15h-4.26zM150.354 28.09h11.31c.09-.51.15-1.02.15-1.59 0-4.41-3.15-7.92-7.59-7.92-4.71 0-7.92 3.45-7.92 7.92s3.18 7.92 8.22 7.92c2.88 0 5.13-1.17 6.54-3.21l-3.12-1.8c-.66.87-1.86 1.5-3.36 1.5-2.04 0-3.69-.84-4.23-2.82zm-.06-3c.45-1.92 1.86-3.03 3.93-3.03 1.62 0 3.24.87 3.72 3.03h-7.65zM164.516 34h3.87V12.1h-3.87V34zM185.248 34.36c3.69 0 6.9-2.01 6.9-6.3V13h-2.1v15.06c0 3.03-2.07 4.26-4.8 4.26-2.19 0-3.93-.78-4.62-2.61l-1.77 1.05c1.05 2.43 3.57 3.6 6.39 3.6zM203.124 18.64c-4.65 0-7.83 3.45-7.83 7.86 0 4.53 3.24 7.86 7.98 7.86 3.03 0 5.34-1.41 6.6-3.45l-1.74-1.02c-.81 1.44-2.46 2.55-4.83 2.55-3.18 0-5.55-1.89-5.97-4.95h13.17c.03-.3.06-.63.06-.93 0-4.11-2.85-7.92-7.44-7.92zm0 1.92c2.58 0 4.98 1.71 5.4 5.01h-11.19c.39-2.94 2.64-5.01 5.79-5.01zM221.224 20.92V19h-4.32v-4.2l-1.98.6V19h-3.15v1.92h3.15v9.09c0 3.6 2.25 4.59 6.3 3.99v-1.74c-2.91.12-4.32.33-4.32-2.25v-9.09h4.32zM225.176 22.93c0-1.62 1.59-2.37 3.15-2.37 1.44 0 2.97.57 3.6 2.1l1.65-.96c-.87-1.86-2.79-3.06-5.25-3.06-3 0-5.13 1.89-5.13 4.29 0 5.52 8.76 3.39 8.76 7.11 0 1.77-1.68 2.4-3.45 2.4-2.01 0-3.57-.99-4.11-2.52l-1.68.99c.75 1.92 2.79 3.45 5.79 3.45 3.21 0 5.43-1.77 5.43-4.32 0-5.52-8.76-3.39-8.76-7.11zM244.603 20.92V19h-4.32v-4.2l-1.98.6V19h-3.15v1.92h3.15v9.09c0 3.6 2.25 4.59 6.3 3.99v-1.74c-2.91.12-4.32.33-4.32-2.25v-9.09h4.32zM249.883 21.49V19h-1.98v15h1.98v-8.34c0-3.72 2.34-4.98 4.74-4.98v-1.92c-1.92 0-3.69.63-4.74 2.73zM263.358 18.64c-4.65 0-7.83 3.45-7.83 7.86 0 4.53 3.24 7.86 7.98 7.86 3.03 0 5.34-1.41 6.6-3.45l-1.74-1.02c-.81 1.44-2.46 2.55-4.83 2.55-3.18 0-5.55-1.89-5.97-4.95h13.17c.03-.3.06-.63.06-.93 0-4.11-2.85-7.92-7.44-7.92zm0 1.92c2.58 0 4.98 1.71 5.4 5.01h-11.19c.39-2.94 2.64-5.01 5.79-5.01zM286.848 19v2.94c-1.26-2.01-3.39-3.3-6.06-3.3-4.23 0-7.74 3.42-7.74 7.86s3.51 7.86 7.74 7.86c2.67 0 4.8-1.29 6.06-3.3V34h1.98V19h-1.98zm-5.91 13.44c-3.33 0-5.91-2.61-5.91-5.94 0-3.33 2.58-5.94 5.91-5.94s5.91 2.61 5.91 5.94c0 3.33-2.58 5.94-5.91 5.94zM309.01 18.64c-1.92 0-3.75.87-4.86 2.73-.84-1.74-2.46-2.73-4.56-2.73-1.8 0-3.42.72-4.59 2.55V19h-1.98v15H295v-8.31c0-3.72 2.16-5.13 4.32-5.13 2.13 0 3.51 1.41 3.51 4.08V34h1.98v-8.31c0-3.72 1.86-5.13 4.17-5.13 2.13 0 3.66 1.41 3.66 4.08V34h1.98v-9.36c0-3.75-2.31-6-5.61-6z" fill="#000" />
|
||||
<path d="M11.395 44.428C4.557 40.198 0 32.632 0 24 0 10.745 10.745 0 24 0a23.891 23.891 0 0113.997 4.502c-.2 17.907-11.097 33.245-26.602 39.926z" fill="#6875F5" />
|
||||
<path d="M14.134 45.885A23.914 23.914 0 0024 48c13.255 0 24-10.745 24-24 0-3.516-.756-6.856-2.115-9.866-4.659 15.143-16.608 27.092-31.75 31.751z" fill="#6875F5" />
|
||||
</svg>
|
||||
</template>
|
6
resources/js/Jetstream/ApplicationMark.vue
Normal file
6
resources/js/Jetstream/ApplicationMark.vue
Normal file
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.395 44.428C4.557 40.198 0 32.632 0 24 0 10.745 10.745 0 24 0a23.891 23.891 0 0113.997 4.502c-.2 17.907-11.097 33.245-26.602 39.926z" fill="#6875F5" />
|
||||
<path d="M14.134 45.885A23.914 23.914 0 0024 48c13.255 0 24-10.745 24-24 0-3.516-.756-6.856-2.115-9.866-4.659 15.143-16.608 27.092-31.75 31.751z" fill="#6875F5" />
|
||||
</svg>
|
||||
</template>
|
11
resources/js/Jetstream/AuthenticationCard.vue
Normal file
11
resources/js/Jetstream/AuthenticationCard.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
|
||||
<div>
|
||||
<slot name="logo" />
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
17
resources/js/Jetstream/AuthenticationCardLogo.vue
Normal file
17
resources/js/Jetstream/AuthenticationCardLogo.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link :href="'/'">
|
||||
<svg
|
||||
class="w-16 h-16"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M11.395 44.428C4.557 40.198 0 32.632 0 24 0 10.745 10.745 0 24 0a23.891 23.891 0 0113.997 4.502c-.2 17.907-11.097 33.245-26.602 39.926z" fill="#6875F5" />
|
||||
<path d="M14.134 45.885A23.914 23.914 0 0024 48c13.255 0 24-10.745 24-24 0-3.516-.756-6.856-2.115-9.866-4.659 15.143-16.608 27.092-31.75 31.751z" fill="#6875F5" />
|
||||
</svg>
|
||||
</Link>
|
||||
</template>
|
87
resources/js/Jetstream/Banner.vue
Normal file
87
resources/js/Jetstream/Banner.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { usePage } from '@inertiajs/inertia-vue3';
|
||||
|
||||
const show = ref(true);
|
||||
const style = computed(() => usePage().props.value.jetstream.flash?.bannerStyle || 'success');
|
||||
const message = computed(() => usePage().props.value.jetstream.flash?.banner || '');
|
||||
|
||||
watch(message, async () => {
|
||||
show.value = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="show && message" :class="{ 'bg-indigo-500': style == 'success', 'bg-red-700': style == 'danger' }">
|
||||
<div class="max-w-screen-xl mx-auto py-2 px-3 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between flex-wrap">
|
||||
<div class="w-0 flex-1 flex items-center min-w-0">
|
||||
<span class="flex p-2 rounded-lg" :class="{ 'bg-indigo-600': style == 'success', 'bg-red-600': style == 'danger' }">
|
||||
<svg
|
||||
v-if="style == 'success'"
|
||||
class="h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
v-if="style == 'danger'"
|
||||
class="h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<p class="ml-3 font-medium text-sm text-white truncate">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 sm:ml-3">
|
||||
<button
|
||||
type="button"
|
||||
class="-mr-1 flex p-2 rounded-md focus:outline-none sm:-mr-2 transition"
|
||||
:class="{ 'hover:bg-indigo-600 focus:bg-indigo-600': style == 'success', 'hover:bg-red-600 focus:bg-red-600': style == 'danger' }"
|
||||
aria-label="Dismiss"
|
||||
@click.prevent="show = false"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
36
resources/js/Jetstream/Checkbox.vue
Normal file
36
resources/js/Jetstream/Checkbox.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update:checked']);
|
||||
|
||||
const props = defineProps({
|
||||
checked: {
|
||||
type: [Array, Boolean],
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const proxyChecked = computed({
|
||||
get() {
|
||||
return props.checked;
|
||||
},
|
||||
|
||||
set(val) {
|
||||
emit('update:checked', val);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="proxyChecked"
|
||||
type="checkbox"
|
||||
:value="value"
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
>
|
||||
</template>
|
67
resources/js/Jetstream/ConfirmationModal.vue
Normal file
67
resources/js/Jetstream/ConfirmationModal.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import Modal from './Modal.vue';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '2xl',
|
||||
},
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:show="show"
|
||||
:max-width="maxWidth"
|
||||
:closeable="closeable"
|
||||
@close="close"
|
||||
>
|
||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg
|
||||
class="h-6 w-6 text-red-600"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 class="text-lg">
|
||||
<slot name="title" />
|
||||
</h3>
|
||||
|
||||
<div class="mt-2">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-end px-6 py-4 bg-gray-100 text-right">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
117
resources/js/Jetstream/ConfirmsPassword.vue
Normal file
117
resources/js/Jetstream/ConfirmsPassword.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick } from 'vue';
|
||||
import DialogModal from './DialogModal.vue';
|
||||
import InputError from './InputError.vue';
|
||||
import PrimaryButton from './PrimaryButton.vue';
|
||||
import SecondaryButton from './SecondaryButton.vue';
|
||||
import TextInput from './TextInput.vue';
|
||||
|
||||
const emit = defineEmits(['confirmed']);
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Confirm Password',
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: 'For your security, please confirm your password to continue.',
|
||||
},
|
||||
button: {
|
||||
type: String,
|
||||
default: 'Confirm',
|
||||
},
|
||||
});
|
||||
|
||||
const confirmingPassword = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
password: '',
|
||||
error: '',
|
||||
processing: false,
|
||||
});
|
||||
|
||||
const passwordInput = ref(null);
|
||||
|
||||
const startConfirmingPassword = () => {
|
||||
axios.get(route('password.confirmation')).then(response => {
|
||||
if (response.data.confirmed) {
|
||||
emit('confirmed');
|
||||
} else {
|
||||
confirmingPassword.value = true;
|
||||
|
||||
setTimeout(() => passwordInput.value.focus(), 250);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const confirmPassword = () => {
|
||||
form.processing = true;
|
||||
|
||||
axios.post(route('password.confirm'), {
|
||||
password: form.password,
|
||||
}).then(() => {
|
||||
form.processing = false;
|
||||
|
||||
closeModal();
|
||||
nextTick().then(() => emit('confirmed'));
|
||||
|
||||
}).catch(error => {
|
||||
form.processing = false;
|
||||
form.error = error.response.data.errors.password[0];
|
||||
passwordInput.value.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
confirmingPassword.value = false;
|
||||
form.password = '';
|
||||
form.error = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<span @click="startConfirmingPassword">
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
<DialogModal :show="confirmingPassword" @close="closeModal">
|
||||
<template #title>
|
||||
{{ title }}
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
{{ content }}
|
||||
|
||||
<div class="mt-4">
|
||||
<TextInput
|
||||
ref="passwordInput"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
class="mt-1 block w-3/4"
|
||||
placeholder="Password"
|
||||
@keyup.enter="confirmPassword"
|
||||
/>
|
||||
|
||||
<InputError :message="form.error" class="mt-2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<SecondaryButton @click="closeModal">
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
|
||||
<PrimaryButton
|
||||
class="ml-3"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
@click="confirmPassword"
|
||||
>
|
||||
{{ button }}
|
||||
</PrimaryButton>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</span>
|
||||
</template>
|
14
resources/js/Jetstream/DangerButton.vue
Normal file
14
resources/js/Jetstream/DangerButton.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :type="type" class="inline-flex items-center justify-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 focus:outline-none focus:border-red-700 focus:ring focus:ring-red-200 active:bg-red-600 disabled:opacity-25 transition">
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
47
resources/js/Jetstream/DialogModal.vue
Normal file
47
resources/js/Jetstream/DialogModal.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
import Modal from './Modal.vue';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '2xl',
|
||||
},
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:show="show"
|
||||
:max-width="maxWidth"
|
||||
:closeable="closeable"
|
||||
@close="close"
|
||||
>
|
||||
<div class="px-6 py-4">
|
||||
<div class="text-lg">
|
||||
<slot name="title" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-end px-6 py-4 bg-gray-100 text-right">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
79
resources/js/Jetstream/Dropdown.vue
Normal file
79
resources/js/Jetstream/Dropdown.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
align: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '48',
|
||||
},
|
||||
contentClasses: {
|
||||
type: Array,
|
||||
default: () => ['py-1', 'bg-white'],
|
||||
},
|
||||
});
|
||||
|
||||
let open = ref(false);
|
||||
|
||||
const closeOnEscape = (e) => {
|
||||
if (open.value && e.key === 'Escape') {
|
||||
open.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', closeOnEscape));
|
||||
onUnmounted(() => document.removeEventListener('keydown', closeOnEscape));
|
||||
|
||||
const widthClass = computed(() => {
|
||||
return {
|
||||
'48': 'w-48',
|
||||
}[props.width.toString()];
|
||||
});
|
||||
|
||||
const alignmentClasses = computed(() => {
|
||||
if (props.align === 'left') {
|
||||
return 'origin-top-left left-0';
|
||||
}
|
||||
|
||||
if (props.align === 'right') {
|
||||
return 'origin-top-right right-0';
|
||||
}
|
||||
|
||||
return 'origin-top';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div @click="open = ! open">
|
||||
<slot name="trigger" />
|
||||
</div>
|
||||
|
||||
<!-- Full Screen Dropdown Overlay -->
|
||||
<div v-show="open" class="fixed inset-0 z-40" @click="open = false" />
|
||||
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95"
|
||||
>
|
||||
<div
|
||||
v-show="open"
|
||||
class="absolute z-50 mt-2 rounded-md shadow-lg"
|
||||
:class="[widthClass, alignmentClasses]"
|
||||
style="display: none;"
|
||||
@click="open = false"
|
||||
>
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5" :class="contentClasses">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
24
resources/js/Jetstream/DropdownLink.vue
Normal file
24
resources/js/Jetstream/DropdownLink.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
defineProps({
|
||||
href: String,
|
||||
as: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button v-if="as == 'button'" type="submit" class="block w-full px-4 py-2 text-sm leading-5 text-gray-700 text-left hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition">
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
<a v-else-if="as =='a'" :href="href" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition">
|
||||
<slot />
|
||||
</a>
|
||||
|
||||
<Link v-else :href="href" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition">
|
||||
<slot />
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
38
resources/js/Jetstream/FormSection.vue
Normal file
38
resources/js/Jetstream/FormSection.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { computed, useSlots } from 'vue';
|
||||
import SectionTitle from './SectionTitle.vue';
|
||||
|
||||
defineEmits(['submitted']);
|
||||
|
||||
const hasActions = computed(() => !! useSlots().actions);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||
<SectionTitle>
|
||||
<template #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<slot name="description" />
|
||||
</template>
|
||||
</SectionTitle>
|
||||
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
<form @submit.prevent="$emit('submitted')">
|
||||
<div
|
||||
class="px-4 py-5 bg-white sm:p-6 shadow"
|
||||
:class="hasActions ? 'sm:rounded-tl-md sm:rounded-tr-md' : 'sm:rounded-md'"
|
||||
>
|
||||
<div class="grid grid-cols-6 gap-6">
|
||||
<slot name="form" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasActions" class="flex items-center justify-end px-4 py-3 bg-gray-50 text-right sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
13
resources/js/Jetstream/InputError.vue
Normal file
13
resources/js/Jetstream/InputError.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
message: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-show="message">
|
||||
<p class="text-sm text-red-600">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
12
resources/js/Jetstream/InputLabel.vue
Normal file
12
resources/js/Jetstream/InputLabel.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
value: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="block font-medium text-sm text-gray-700">
|
||||
<span v-if="value">{{ value }}</span>
|
||||
<span v-else><slot /></span>
|
||||
</label>
|
||||
</template>
|
91
resources/js/Jetstream/Modal.vue
Normal file
91
resources/js/Jetstream/Modal.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '2xl',
|
||||
},
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
watch(() => props.show, () => {
|
||||
if (props.show) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = null;
|
||||
}
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
if (props.closeable) {
|
||||
emit('close');
|
||||
}
|
||||
};
|
||||
|
||||
const closeOnEscape = (e) => {
|
||||
if (e.key === 'Escape' && props.show) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', closeOnEscape));
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', closeOnEscape);
|
||||
document.body.style.overflow = null;
|
||||
});
|
||||
|
||||
const maxWidthClass = computed(() => {
|
||||
return {
|
||||
'sm': 'sm:max-w-sm',
|
||||
'md': 'sm:max-w-md',
|
||||
'lg': 'sm:max-w-lg',
|
||||
'xl': 'sm:max-w-xl',
|
||||
'2xl': 'sm:max-w-2xl',
|
||||
}[props.maxWidth];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<transition leave-active-class="duration-200">
|
||||
<div v-show="show" class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50" scroll-region>
|
||||
<transition
|
||||
enter-active-class="ease-out duration-300"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="ease-in duration-200"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-show="show" class="fixed inset-0 transform transition-all" @click="close">
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75" />
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition
|
||||
enter-active-class="ease-out duration-300"
|
||||
enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enter-to-class="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave-active-class="ease-in duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div v-show="show" class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full sm:mx-auto" :class="maxWidthClass">
|
||||
<slot v-if="show" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</teleport>
|
||||
</template>
|
21
resources/js/Jetstream/NavLink.vue
Normal file
21
resources/js/Jetstream/NavLink.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
const props = defineProps({
|
||||
href: String,
|
||||
active: Boolean,
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
return props.active
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link :href="href" :class="classes">
|
||||
<slot />
|
||||
</Link>
|
||||
</template>
|
14
resources/js/Jetstream/PrimaryButton.vue
Normal file
14
resources/js/Jetstream/PrimaryButton.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'submit',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :type="type" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring focus:ring-gray-300 disabled:opacity-25 transition">
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
28
resources/js/Jetstream/ResponsiveNavLink.vue
Normal file
28
resources/js/Jetstream/ResponsiveNavLink.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
const props = defineProps({
|
||||
active: Boolean,
|
||||
href: String,
|
||||
as: String,
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
return props.active
|
||||
? 'block pl-3 pr-4 py-2 border-l-4 border-indigo-400 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition'
|
||||
: 'block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button v-if="as == 'button'" :class="classes" class="w-full text-left">
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
<Link v-else :href="href" :class="classes">
|
||||
<slot />
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
14
resources/js/Jetstream/SecondaryButton.vue
Normal file
14
resources/js/Jetstream/SecondaryButton.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :type="type" class="inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:ring focus:ring-blue-200 active:text-gray-800 active:bg-gray-50 disabled:opacity-25 transition">
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
7
resources/js/Jetstream/SectionBorder.vue
Normal file
7
resources/js/Jetstream/SectionBorder.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="hidden sm:block">
|
||||
<div class="py-8">
|
||||
<div class="border-t border-gray-200" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
17
resources/js/Jetstream/SectionTitle.vue
Normal file
17
resources/js/Jetstream/SectionTitle.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="md:col-span-1 flex justify-between">
|
||||
<div class="px-4 sm:px-0">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
<slot name="title" />
|
||||
</h3>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
<slot name="description" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="px-4 sm:px-0">
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
28
resources/js/Jetstream/TextInput.vue
Normal file
28
resources/js/Jetstream/TextInput.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
modelValue: String,
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
const input = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
if (input.value.hasAttribute('autofocus')) {
|
||||
input.value.focus();
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ focus: () => input.value.focus() });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
ref="input"
|
||||
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
|
||||
:value="modelValue"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
>
|
||||
</template>
|
138
resources/js/Jetstream/Welcome.vue
Normal file
138
resources/js/Jetstream/Welcome.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<script setup>
|
||||
import ApplicationLogo from '@/Components/ApplicationLogo.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="p-6 sm:px-20 bg-white border-b border-gray-200">
|
||||
<div>
|
||||
<ApplicationLogo class="block h-12 w-auto" />
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-2xl">
|
||||
Welcome to your Jetstream application!
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-gray-500">
|
||||
Laravel Jetstream provides a beautiful, robust starting point for your next Laravel application. Laravel is designed
|
||||
to help you build your application using a development environment that is simple, powerful, and enjoyable. We believe
|
||||
you should love expressing your creativity through programming, so we have spent time carefully crafting the Laravel
|
||||
ecosystem to be a breath of fresh air. We hope you love it.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-200 bg-opacity-25 grid grid-cols-1 md:grid-cols-2">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
class="w-8 h-8 text-gray-400"
|
||||
><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /></svg>
|
||||
<div class="ml-4 text-lg text-gray-600 leading-7 font-semibold">
|
||||
<a href="https://laravel.com/docs">Documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
Laravel has wonderful documentation covering every aspect of the framework. Whether you're new to the framework or have previous experience, we recommend reading all of the documentation from beginning to end.
|
||||
</div>
|
||||
|
||||
<a href="https://laravel.com/docs">
|
||||
<div class="mt-3 flex items-center text-sm font-semibold text-indigo-700">
|
||||
<div>Explore the documentation</div>
|
||||
|
||||
<div class="ml-1 text-indigo-500">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t border-gray-200 md:border-t-0 md:border-l">
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
class="w-8 h-8 text-gray-400"
|
||||
><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" /><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
<div class="ml-4 text-lg text-gray-600 leading-7 font-semibold">
|
||||
<a href="https://laracasts.com">Laracasts</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
|
||||
</div>
|
||||
|
||||
<a href="https://laracasts.com">
|
||||
<div class="mt-3 flex items-center text-sm font-semibold text-indigo-700">
|
||||
<div>Start watching Laracasts</div>
|
||||
|
||||
<div class="ml-1 text-indigo-500">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t border-gray-200">
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
class="w-8 h-8 text-gray-400"
|
||||
><path d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
<div class="ml-4 text-lg text-gray-600 leading-7 font-semibold">
|
||||
<a href="https://tailwindcss.com/">Tailwind</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
Laravel Jetstream is built with Tailwind, an amazing utility first CSS framework that doesn't get in your way. You'll be amazed how easily you can build and maintain fresh, modern designs with this wonderful framework at your fingertips.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t border-gray-200 md:border-l">
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
class="w-8 h-8 text-gray-400"
|
||||
><path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||
<div class="ml-4 text-lg text-gray-600 leading-7 font-semibold">
|
||||
Authentication
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
Authentication and registration views are included with Laravel Jetstream, as well as support for user email verification and resetting forgotten passwords. So, you're free to get started what matters most: building your application.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
Reference in New Issue
Block a user