Последняя версия с сервера прошлого разработчика
This commit is contained in:
178
resources/js/Pages/Auth/Feedback.vue
Executable file
178
resources/js/Pages/Auth/Feedback.vue
Executable file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<meta-head title="Написать нам">
|
||||
<meta name="description" content="Написать нам">
|
||||
</meta-head>
|
||||
|
||||
<!-- <header-auth /> -->
|
||||
<!-- min-h-[calc(100vh-64px)] -->
|
||||
<div class="min-h-screen place-items-center grid md:pt-3 bg-center bg-cover"
|
||||
style="background-image: url('/image/auth-bg.jpg');"
|
||||
>
|
||||
<div class="w-full max-w-3xl mx-auto ">
|
||||
<div class="md:grid grid-cols-12 ">
|
||||
<div class="col-span-12 bg-indigo-200">
|
||||
<div class="py-12 lg:py-20 px-10">
|
||||
<inertia-link
|
||||
:href="route('dashboard')"
|
||||
class="inline-flex gap-1 items-center text-gray-light"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18"
|
||||
height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Вернуться
|
||||
</inertia-link>
|
||||
<h1 class="mt-5 font-medium text-xl md:text-2xl lg:text-4xl text-white text-center">
|
||||
Написать нам
|
||||
</h1>
|
||||
<div v-if="$page.props.flash.status">
|
||||
<p class="text-xl text-orange text-center mt-3">
|
||||
{{ $page.props.flash.status }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="auth-form mt-7">
|
||||
<form class="space-y-6" @submit.prevent="submit">
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
|
||||
>
|
||||
</path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.email"
|
||||
autocapitalize="off"
|
||||
required
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="email" placeholder="E-mail"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.email" class="form-error text-sm text-red">
|
||||
{{ form.errors.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="feather feather-edit-2"
|
||||
><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.title"
|
||||
autocapitalize="off"
|
||||
required
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="text" placeholder="Тема"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.title" class="form-error text-sm text-red">
|
||||
{{ form.errors.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<textarea
|
||||
v-model="form.body"
|
||||
autocapitalize="off"
|
||||
required
|
||||
rows="5"
|
||||
class="w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
placeholder="Сообщение"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="form.errors.body" class="form-error text-sm text-red">
|
||||
{{ form.errors.body }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Отправить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<div class="-mx-2 -my-2 flex flex-wrap justify-between">
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/about">О компании</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/offer">Оферта</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-payment">Условия оплаты</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-service">Условия предоставления услуг</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/cookies">Cookies</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/privacy-policy">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
LoadingButton,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const form = Inertia.form({
|
||||
title: null,
|
||||
body: null,
|
||||
email: null,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('common.write-to-us'), {
|
||||
onSuccess: () => form.reset(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
139
resources/js/Pages/Auth/ForgotPassword.vue
Executable file
139
resources/js/Pages/Auth/ForgotPassword.vue
Executable file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<meta-head title="Восстановить пароль">
|
||||
<meta name="description" content="Восстановить пароль">
|
||||
</meta-head>
|
||||
|
||||
<!-- <header-auth /> -->
|
||||
<!-- min-h-[calc(100vh-64px)] -->
|
||||
<div class="min-h-screen place-items-center grid md:pt-3 bg-center bg-cover"
|
||||
style="background-image: url('/image/auth-bg.jpg');"
|
||||
>
|
||||
<div class="w-full max-w-3xl mx-auto ">
|
||||
<div class="md:grid grid-cols-12 ">
|
||||
<div class="col-span-12 bg-indigo-200">
|
||||
<div class="py-12 lg:py-20 px-10">
|
||||
<inertia-link
|
||||
:href="route('login')"
|
||||
class="inline-flex gap-1 items-center text-gray-light"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18"
|
||||
height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Вернуться
|
||||
</inertia-link>
|
||||
<h1 class="mt-5 font-medium text-xl md:text-2xl lg:text-4xl text-white text-center">
|
||||
Восстановить пароль
|
||||
</h1>
|
||||
<div v-if="$page.props.flash.status">
|
||||
<p class="text-xl text-orange text-center mt-3">
|
||||
{{ $page.props.flash.status }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="auth-form mt-7">
|
||||
<form class="space-y-6" @submit.prevent="submit">
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
|
||||
>
|
||||
</path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.email"
|
||||
autocapitalize="off"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="email" placeholder="E-mail"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.email" class="form-error text-sm text-red">
|
||||
{{ form.errors.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Отправить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<div class="-mx-2 -my-2 flex flex-wrap justify-between">
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/about">О компании</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/offer">Оферта</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-payment">Условия оплаты</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-service">Условия предоставления услуг</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/cookies">Cookies</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/privacy-policy">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
// import HeaderAuth from '@/Shared/LayoutParts/HeaderAuth.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
// HeaderAuth,
|
||||
LoadingButton,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const form = Inertia.form({
|
||||
email: null,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('password.email'), {
|
||||
onSuccess: () => form.reset('email'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
196
resources/js/Pages/Auth/Login.vue
Executable file
196
resources/js/Pages/Auth/Login.vue
Executable file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<meta-head title="Авторизация">
|
||||
<meta name="description" content="Авторизация">
|
||||
</meta-head>
|
||||
|
||||
<!-- <header-auth /> -->
|
||||
|
||||
<div class="min-h-screen place-items-center grid md:pt-3 bg-center bg-cover"
|
||||
style="background-image: url('/image/auth-bg.jpg');"
|
||||
>
|
||||
<div class="w-full max-w-7xl mx-auto ">
|
||||
<div class="md:grid grid-cols-12 ">
|
||||
<div class="col-span-7 bg-indigo-200">
|
||||
<div class="p-4 lg:p-28">
|
||||
<inertia-link
|
||||
:href="route('dashboard')"
|
||||
class="inline-flex gap-1 items-center text-gray-light"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18"
|
||||
height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Вернуться
|
||||
</inertia-link>
|
||||
<h1 class="font-medium text-xl md:text-2xl lg:text-4xl text-white text-center">
|
||||
Войти
|
||||
</h1>
|
||||
<div v-if="$page.props.flash.status">
|
||||
<p class="text-lg md:text-xl text-orange text-center mt-3">
|
||||
{{ $page.props.flash.status }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="auth-form mt-7">
|
||||
<form class="space-y-6" @submit.prevent="submit">
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
|
||||
>
|
||||
</path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.email"
|
||||
autocapitalize="off"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="email" placeholder="E-mail"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.email" class="form-error text-sm text-red">
|
||||
{{ form.errors.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<rect x="3" y="11"
|
||||
width="18" height="11"
|
||||
rx="2" ry="2"
|
||||
></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.password"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="password" placeholder="Пароль"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.password" class="form-error text-sm text-red">
|
||||
{{ form.errors.password }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="remember" v-model="form.remember"
|
||||
type="checkbox" class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="remember" class="select-none ml-3 text-gray">Запомни меня</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-1">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Войти
|
||||
</loading-button>
|
||||
|
||||
<inertia-link :href="route('password.request')" class="hover:underline text-sm text-gray-light">
|
||||
Восстановить пароль
|
||||
</inertia-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-5 bg-center bg-cover" style="background-image: url('image/orange-auth.jpg');">
|
||||
<div class="py-4 lg:py-28 px-4">
|
||||
<div class="font-medium text-xl md:text-2xl lg:text-4xl !leading-relaxed text-white text-center">
|
||||
Заработай на своем
|
||||
творчестве первый миллион
|
||||
</div>
|
||||
<div class="font-medium text-xl md:text-2xl lg:text-4xl !leading-relaxed text-white text-center">
|
||||
Присоединяйся к teeaseer
|
||||
прямо сейчас!
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4 md:mt-10">
|
||||
<inertia-link
|
||||
:href="route('register')"
|
||||
class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white border border-white bg-transparent focus:outline-none"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<div class="-mx-2 -my-2 flex flex-wrap justify-between">
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/about">О компании</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/offer">Оферта</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-payment">Условия оплаты</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-service">Условия предоставления услуг</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/cookies">Cookies</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/privacy-policy">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
// import HeaderAuth from '@/Shared/LayoutParts/HeaderAuth.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
// HeaderAuth,
|
||||
LoadingButton,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const form = Inertia.form({
|
||||
email: null,
|
||||
password: null,
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('login.store'))
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
219
resources/js/Pages/Auth/Register.vue
Executable file
219
resources/js/Pages/Auth/Register.vue
Executable file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<meta-head title="Авторизация">
|
||||
<meta name="description" content="Авторизация">
|
||||
</meta-head>
|
||||
|
||||
<!-- <header-auth /> -->
|
||||
|
||||
<div class="min-h-screen place-items-center grid md:pt-3 bg-center bg-cover"
|
||||
style="background-image: url('/image/auth-bg.jpg');"
|
||||
>
|
||||
<div class="w-full max-w-7xl mx-auto ">
|
||||
<div class="md:grid grid-cols-12 ">
|
||||
<div class="col-span-7 bg-indigo-200">
|
||||
<div class="p-4 lg:p-28">
|
||||
<inertia-link
|
||||
:href="route('dashboard')"
|
||||
class="inline-flex gap-1 items-center text-gray-light"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18"
|
||||
height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Вернуться
|
||||
</inertia-link>
|
||||
<h1 class="font-medium text-xl md:text-2xl lg:text-4xl text-white text-center">
|
||||
Регистрация
|
||||
</h1>
|
||||
|
||||
<div class="auth-form mt-7">
|
||||
<form class="space-y-6" @submit.prevent="submit">
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7"
|
||||
r="4"
|
||||
></circle></svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.first_name"
|
||||
autocapitalize="off"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="text" placeholder="Имя"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.email" class="form-error text-sm text-red">
|
||||
{{ form.errors.first_name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
|
||||
>
|
||||
</path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.email"
|
||||
autocapitalize="off"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="email" placeholder="E-mail"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.email" class="form-error text-sm text-red">
|
||||
{{ form.errors.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<rect x="3" y="11"
|
||||
width="18" height="11"
|
||||
rx="2" ry="2"
|
||||
></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.password"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="password" placeholder="Пароль"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.password" class="form-error text-sm text-red">
|
||||
{{ form.errors.password }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input id="check" v-model="form.check"
|
||||
type="checkbox" class="mt-1 h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="check" class="select-none ml-3 text-gray"> Нажимая на кнопку я даю согласие на
|
||||
<a class="underline" href="/docs/personal_data.pdf"
|
||||
target="_blank" rel="noopener noreferrer nofollow"
|
||||
>обработку персональных данных</a> и соглашаюсь
|
||||
<a class="underline" href="/docs/privacy_policy.pdf"
|
||||
target="_blank" rel="noopener noreferrer nofollow"
|
||||
>с условиями политики конфеденциальности</a>.</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-5 bg-center bg-cover" style="background-image: url('image/orange-auth.jpg');">
|
||||
<div class="py-4 lg:py-28 px-4 flex flex-col h-full justify-center">
|
||||
<div class="font-medium text-xl md:text-2xl lg:text-4xl !leading-relaxed text-white text-center">
|
||||
Уже есть учетная запись?
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4 md:mt-10">
|
||||
<inertia-link
|
||||
:href="route('login')"
|
||||
class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white border border-white bg-transparent focus:outline-none"
|
||||
>
|
||||
Войти
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<div class="-mx-2 -my-2 flex flex-wrap justify-between">
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/about">О компании</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/offer">Оферта</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-payment">Условия оплаты</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-service">Условия предоставления услуг</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/cookies">Cookies</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/privacy-policy">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
// import HeaderAuth from '@/Shared/LayoutParts/HeaderAuth.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
// HeaderAuth,
|
||||
LoadingButton,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const form = Inertia.form({
|
||||
first_name: null,
|
||||
email: null,
|
||||
password: null,
|
||||
check: false,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
if(form.check){
|
||||
form.post(route('register.store'))
|
||||
}
|
||||
// else{
|
||||
// alert('Нужно дать согласие на обработку персональных данных')
|
||||
// }
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
203
resources/js/Pages/Auth/ResetPassword.vue
Executable file
203
resources/js/Pages/Auth/ResetPassword.vue
Executable file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<meta-head title="Восстановить пароль">
|
||||
<meta name="description" content="Восстановить пароль">
|
||||
</meta-head>
|
||||
|
||||
<!-- <header-auth /> -->
|
||||
|
||||
<div class="min-h-screen place-items-center grid md:pt-3 bg-center bg-cover"
|
||||
style="background-image: url('/image/auth-bg.jpg');"
|
||||
>
|
||||
<div class="w-full max-w-3xl mx-auto ">
|
||||
<div class="md:grid grid-cols-12 ">
|
||||
<div class="col-span-12 bg-indigo-200">
|
||||
<div class="py-12 lg:py-20 px-10">
|
||||
<inertia-link
|
||||
:href="route('dashboard')"
|
||||
class="inline-flex gap-1 items-center text-gray-light"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18"
|
||||
height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Вернуться
|
||||
</inertia-link>
|
||||
<h1 class="mt-5 font-medium text-xl md:text-2xl lg:text-4xl text-white text-center">
|
||||
Восстановить пароль
|
||||
</h1>
|
||||
<div v-if="$page.props.flash.status">
|
||||
<p class="text-lg md:text-xl text-orange text-center mt-3">
|
||||
{{ $page.props.flash.status }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="auth-form mt-7">
|
||||
<form class="space-y-6" @submit.prevent="submit">
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
|
||||
>
|
||||
</path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.email"
|
||||
autocapitalize="off"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="email" placeholder="E-mail"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.email" class="form-error text-sm text-red">
|
||||
{{ form.errors.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<rect x="3" y="11"
|
||||
width="18" height="11"
|
||||
rx="2" ry="2"
|
||||
></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.password"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="password" placeholder="Пароль"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.password" class="form-error text-sm text-red">
|
||||
{{ form.errors.password }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-5 text-gray flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="flex-shrink-0"
|
||||
>
|
||||
<rect x="3" y="11"
|
||||
width="18" height="11"
|
||||
rx="2" ry="2"
|
||||
></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.password_confirmation"
|
||||
class="w-full pl-14 h-12 md:h-16 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border-transparent bg-indigo-100 rounded-md placeholder-gray"
|
||||
type="password" placeholder="Повторите пароль"
|
||||
>
|
||||
</div>
|
||||
<div v-if="form.errors.password_confirmation" class="form-error text-sm text-red">
|
||||
{{ form.errors.password_confirmation }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Отправить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<div class="-mx-2 -my-2 flex flex-wrap justify-between">
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/about">О компании</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/offer">Оферта</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-payment">Условия оплаты</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-service">Условия предоставления услуг</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/cookies">Cookies</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/privacy-policy">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
// import HeaderAuth from '@/Shared/LayoutParts/HeaderAuth.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
// HeaderAuth,
|
||||
LoadingButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
token: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const form = Inertia.form({
|
||||
email: null,
|
||||
password: null,
|
||||
password_confirmation: null,
|
||||
token: props.token
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('password.store'))
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
62
resources/js/Pages/Auth/VerifyEmail.vue
Executable file
62
resources/js/Pages/Auth/VerifyEmail.vue
Executable file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<meta-head title="Авторизация">
|
||||
<meta name="description" content="Авторизация">
|
||||
</meta-head>
|
||||
|
||||
<!-- <header-auth /> -->
|
||||
|
||||
<div class="min-h-screen place-items-center grid md:pt-3 bg-center bg-cover"
|
||||
style="background-image: url('/image/auth-bg.jpg');"
|
||||
>
|
||||
<div class="w-full md:w-auto max-w-7xl mx-auto ">
|
||||
<div class="md:grid grid-cols-12 ">
|
||||
<div class="col-span-12 bg-center bg-cover" style="background-image: url('image/orange-auth.jpg');">
|
||||
<div class="py-4 lg:py-28 px-10">
|
||||
<div class="font-medium text-xl md:text-2xl lg:text-4xl !leading-relaxed text-white text-center">
|
||||
Пожалуйста подтвердите свой адрес электронной почты
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<div class="-mx-2 -my-2 flex flex-wrap justify-between">
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/about">О компании</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/offer">Оферта</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-payment">Условия оплаты</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/terms-service">Условия предоставления услуг</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/cookies">Cookies</a>
|
||||
</div>
|
||||
<div class="mx-2 my-2">
|
||||
<a href="/company/privacy-policy">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
// import HeaderAuth from '@/Shared/LayoutParts/HeaderAuth.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
// HeaderAuth,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
130
resources/js/Pages/Complaint/Index.vue
Executable file
130
resources/js/Pages/Complaint/Index.vue
Executable file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<meta-head title="Пожаловаться на контент"></meta-head>
|
||||
|
||||
<modal-feed
|
||||
:modal-feed="modalFeed"
|
||||
:is_exist_menu="false"
|
||||
:open="show"
|
||||
@close-modal="closeModal"
|
||||
/>
|
||||
|
||||
<div class="mt-16 md:container !max-w-5xl mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<form class=" bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Пожаловаться на контент
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 grid grid-cols-2">
|
||||
<div class="space-y-4">
|
||||
<div v-for="reason in reasons" :key="reason.id"
|
||||
class="flex"
|
||||
>
|
||||
<input :id="`reason_${reason.id}`" v-model="form.reason"
|
||||
:value="reason.id" name="reason"
|
||||
type="radio" class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label :for="`reason_${reason.id}`" class="select-none ml-3 text-gray leading-none">{{ reason.name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<feed-preview class="ml-auto w-20 h-20 lg:w-48 lg:h-48 object-cover cursor-pointer"
|
||||
:type="feed.type"
|
||||
:source="feed.entity.preview"
|
||||
@click="openModal(feed)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Отправить
|
||||
</loading-button>
|
||||
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { toRefs } from 'vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import ModalFeed from '@/Shared/Overlay/ModalFeed.vue'
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
LoadingButton,
|
||||
ModalFeed,
|
||||
FeedPreview,
|
||||
LinkBack,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feed: Object,
|
||||
user: Object,
|
||||
reasons: Array,
|
||||
},
|
||||
setup(props) {
|
||||
const { feed } = toRefs(props)
|
||||
const form = useForm({
|
||||
reason: null,
|
||||
feed: feed.value.id,
|
||||
})
|
||||
const submit = () => {
|
||||
form.post(route('complaint.store'))
|
||||
form.reason = null
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
modalFeed: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
authUser() {
|
||||
return usePage().props.value.auth.user
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
openModal(feed) {
|
||||
this.show = true
|
||||
this.modalFeed = feed
|
||||
},
|
||||
closeModal() {
|
||||
this.show = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
100
resources/js/Pages/Complaint/IndexComment.vue
Executable file
100
resources/js/Pages/Complaint/IndexComment.vue
Executable file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<meta-head title="Пожаловаться на комментарий"></meta-head>
|
||||
|
||||
<div class="mt-16 md:container !max-w-5xl mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<form class=" bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-sm md:text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Пожаловаться на комментарий
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 md:grid grid-cols-2">
|
||||
<div class="space-y-4">
|
||||
<div v-for="reason in reasons" :key="reason.id"
|
||||
class="flex"
|
||||
>
|
||||
<input :id="`reason_${reason.id}`" v-model="form.reason"
|
||||
:value="reason.id" name="reason"
|
||||
type="radio" class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label :for="`reason_${reason.id}`" class="select-none ml-3 text-gray leading-none">{{ reason.name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 md:mt-0">
|
||||
<inertia-link :href="route('profile.user', user.username)" class=" flex items-center mr-3">
|
||||
<user-avatar
|
||||
:user="user"
|
||||
size="small"
|
||||
class="text-xs w-12 h-12"
|
||||
/>
|
||||
<span class="text-gray-light ml-3">{{ user.name }}</span>
|
||||
</inertia-link>
|
||||
<h3 class="mt-3 font-medium text-gray-light">
|
||||
Текст комментария:
|
||||
</h3>
|
||||
<div class="mt-3 text-lg text-white">
|
||||
{{ comment.body }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Отправить
|
||||
</loading-button>
|
||||
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { toRefs } from 'vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
LoadingButton,
|
||||
LinkBack,
|
||||
UserAvatar
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
comment: Object,
|
||||
user: Object,
|
||||
reasons: Array,
|
||||
},
|
||||
setup(props) {
|
||||
const { comment } = toRefs(props)
|
||||
const form = useForm({
|
||||
reason: null,
|
||||
comment: comment.value.id,
|
||||
})
|
||||
const submit = () => {
|
||||
form.post(route('complaint.store.comment'))
|
||||
form.reason = null
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
206
resources/js/Pages/Dashboard/Index.vue
Executable file
206
resources/js/Pages/Dashboard/Index.vue
Executable file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<meta-head title="Новинки"></meta-head>
|
||||
|
||||
<div class="mb-6 py-10 md:py-14 xl:py-20 banner relative bg-center bg-no-repeat bg-cover"
|
||||
style="background-image: url('/image/bg-home.jpg');"
|
||||
>
|
||||
<div class="min-h-[24rem] flex justify-center items-center text-center">
|
||||
<div class="max-w-4xl text-gray px-3">
|
||||
<p class="text-md md:text-2xl">
|
||||
Социальная сеть для творческих людей
|
||||
</p>
|
||||
<h1
|
||||
class="mt-4 md:mt-8 text-xl md:text-2xl lg:text-3xl xl:text-5xl text-white font-semibold xl:leading-relaxed"
|
||||
>
|
||||
Зарабатывай на
|
||||
своем творчестве вместе с teeaseer
|
||||
</h1>
|
||||
<div v-if="!$page.props.auth.user" class="mt-4 md:flex flex-wrap -mx-3 justify-center">
|
||||
<inertia-link
|
||||
:href="route('login')"
|
||||
class="min-w-[260px] mx-3 my-1 px-6 md:px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white border border-white bg-transparent focus:outline-none"
|
||||
>
|
||||
Войти
|
||||
</inertia-link>
|
||||
|
||||
|
||||
<inertia-link
|
||||
:href="route('register')"
|
||||
class="min-w-[260px] mx-3 my-1 px-6 md:px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white border border-white bg-transparent focus:outline-none"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</inertia-link>
|
||||
<inertia-link
|
||||
:href="route('common.write-to-us.get')"
|
||||
class="min-w-[260px] mx-3 my-1 px-6 md:px-12 py-3 transition shadow-none hover:shadow-classic2 inline-flex items-center justify-center text-base rounded-full text-white border border-white bg-transparent focus:outline-none"
|
||||
>
|
||||
Написать нам
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 text-gray mx-auto max-w-2xl">
|
||||
<ul class="flex justify-center flex-wrap gap-3 text-xs px-3">
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/company.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>О компании</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/offer_authors.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Оферта для авторов</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/offer_paid_subscription.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Оферта на платную подписку</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/security_policy.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Политика безопасности</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/privacy_policy.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Политика конфиденциальности</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/terms_use.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Пользовательское соглашение</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/personal_data.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Согласие на обработку персональных данных</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 buttons-filter-line">
|
||||
<div class=" relative mb-5">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center z-[1]">
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-light" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 4a7 7 0 100 14 7 7 0 000-14zm-9 7a9 9 0 1118 0 9 9 0 01-18 0z"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.943 15.943a1 1 0 011.414 0l4.35 4.35a1 1 0 01-1.414 1.414l-4.35-4.35a1 1 0 010-1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.search"
|
||||
class="relative w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light !pl-10 h-14" placeholder="Поиск"
|
||||
type="search"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<inertia-link :href="route('dashboard')"
|
||||
:class="[active_filter == 'new' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
Новинки
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link :href="route('dashboard')"
|
||||
:data="{ filter: 'hot' }"
|
||||
:class="[active_filter == 'hot' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center shadow-classic rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M14.422 6.243c-3.57-2.172-1.895-5.238-1.824-5.365A.586.586 0 0012.09 0c-1.837 0-3.276.522-4.276 1.552-1.71 1.76-1.63 4.498-1.597 5.667.004.13.007.242.007.325 0 .868.14 1.67.263 2.377.079.456.147.85.16 1.159.012.331-.048.407-.05.41-.01.01-.081.049-.276.049a.657.657 0 01-.53-.235c-.565-.634-.604-2.365-.496-3.307a.586.586 0 00-.582-.654c-1.521 0-2.658 2.488-2.658 4.712 0 1.046.21 2.07.625 3.045a8.081 8.081 0 001.7 2.527C5.894 19.157 7.89 20 10 20c2.119 0 4.114-.83 5.618-2.34a7.892 7.892 0 002.327-5.605c0-2.692-2.107-4.95-3.523-5.812zM10 18.828c-3.671 0-6.773-3.101-6.773-6.773 0-.893.213-1.83.585-2.571.086-.173.175-.322.263-.447.01.967.163 2.284.841 3.046.366.41.852.627 1.405.627.518 0 .912-.156 1.172-.466.486-.578.337-1.436.148-2.524-.115-.66-.245-1.409-.245-2.176 0-.1-.003-.22-.007-.359-.032-1.107-.099-3.412 1.265-4.817.613-.63 1.467-1.014 2.55-1.145-.123.406-.228.92-.232 1.497-.01 1.27.476 3.085 2.841 4.524 1.145.696 2.96 2.618 2.96 4.811A6.78 6.78 0 0110 18.828z"
|
||||
/>
|
||||
</svg>
|
||||
Популярные
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 my-6">
|
||||
<button class="button-default text-gray text-lg" @click="resetFilter">
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<feed
|
||||
:next-cursor="nextCursor"
|
||||
:feeds="feeds"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
import throttle from 'lodash/throttle'
|
||||
|
||||
import Feed from '@/Shared/Feed/Feed.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
Feed
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feeds: Array,
|
||||
nextCursor: String,
|
||||
active_filter: String,
|
||||
searchFilters: Object,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
search: this.searchFilters.search,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
Inertia.get(route('dashboard'), pickBy(this.form), {
|
||||
preserveScroll: true, preserveState: true
|
||||
})
|
||||
}, 500),
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
resetFilter() {
|
||||
Inertia.get(
|
||||
route('dashboard'),
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
113
resources/js/Pages/Feed/Index.vue
Executable file
113
resources/js/Pages/Feed/Index.vue
Executable file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<meta-head title="Новости"></meta-head>
|
||||
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<div class="items-center grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-4 2xl:gap-12">
|
||||
<dropdown-menu>
|
||||
<MenuItems class="origin-top-left absolute left-0 mt-2 w-64 bg-indigo-300 shadow-lg max-h-60 rounded-md text-base ring-1 ring-indigo-200 overflow-auto focus:outline-none">
|
||||
<MenuItem>
|
||||
<inertia-link :href="route('videos.create')" class="group flex items-center px-4 py-2 text-base hover:bg-indigo-200 text-gray-light">
|
||||
<VideoCameraIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
|
||||
Загрузить видео
|
||||
</inertia-link>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<inertia-link :href="route('images.create')" class="group flex items-center px-4 py-2 text-base hover:bg-indigo-200 text-gray-light">
|
||||
<PhotographIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
|
||||
Загрузить изображение
|
||||
</inertia-link>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<inertia-link :href="route('musics.create')" class="group flex items-center px-4 py-2 text-base hover:bg-indigo-200 text-gray-light">
|
||||
<MusicNoteIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
|
||||
Загрузить музыку
|
||||
</inertia-link>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</dropdown-menu>
|
||||
<inertia-link
|
||||
:href="route('feeds.layoutsidebar.image')"
|
||||
:class="[active_button == 'image' ? 'bg-orange' : 'bg-indigo-200 hover:bg-orange', 'transition inline-flex items-center px-3 py-3 lg:px-7 lg:py-5 justify-center shadow-classic text-sm rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M4.167 3.333a.833.833 0 00-.834.834v11.666c0 .46.373.834.834.834h11.666c.46 0 .834-.373.834-.834V4.167a.833.833 0 00-.834-.834H4.167zm-2.5.834a2.5 2.5 0 012.5-2.5h11.666a2.5 2.5 0 012.5 2.5v11.666a2.5 2.5 0 01-2.5 2.5H4.167a2.5 2.5 0 01-2.5-2.5V4.167z"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M7.083 6.667a.417.417 0 100 .833.417.417 0 000-.833zM5 7.083a2.083 2.083 0 114.167 0 2.083 2.083 0 01-4.167 0zM12.744 7.744a.833.833 0 011.179 0l4.166 4.167a.834.834 0 01-1.178 1.178l-3.578-3.577-8.577 8.577a.833.833 0 01-1.179-1.178l9.167-9.167z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="truncate">Изображения</span>
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link
|
||||
:href="route('feeds.layoutsidebar.video')"
|
||||
:class="[active_button == 'video' ? 'bg-orange' : 'bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-7 lg:py-5 justify-center shadow-classic text-sm rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
><path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M3.483 2.5a.983.983 0 00-.983.983v13.034c0 .543.44.983.983.983h13.034c.543 0 .983-.44.983-.983V3.483a.983.983 0 00-.983-.983H3.483zm-2.65.983a2.65 2.65 0 012.65-2.65h13.034a2.65 2.65 0 012.65 2.65v13.034a2.65 2.65 0 01-2.65 2.65H3.483a2.65 2.65 0 01-2.65-2.65V3.483z"
|
||||
/><path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M5.833.833c.46 0 .834.373.834.834v16.666a.833.833 0 11-1.667 0V1.667c0-.46.373-.834.833-.834zM14.167.833c.46 0 .833.373.833.834v16.666a.833.833 0 11-1.667 0V1.667c0-.46.373-.834.834-.834z"
|
||||
/><path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M.833 10c0-.46.373-.833.834-.833h16.666a.833.833 0 010 1.666H1.667A.833.833 0 01.833 10zM.833 5.833c0-.46.373-.833.834-.833h4.166a.833.833 0 010 1.667H1.667a.833.833 0 01-.834-.834zM.833 14.167c0-.46.373-.834.834-.834h4.166a.833.833 0 010 1.667H1.667a.833.833 0 01-.834-.833zM13.333 14.167c0-.46.373-.834.834-.834h4.166a.833.833 0 010 1.667h-4.166a.833.833 0 01-.834-.833zM13.333 5.833c0-.46.373-.833.834-.833h4.166a.833.833 0 010 1.667h-4.166a.833.833 0 01-.834-.834z"
|
||||
/></svg>
|
||||
<span class="truncate">Видео</span>
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link
|
||||
:href="route('feeds.layoutsidebar.music')"
|
||||
:class="[active_button == 'music' ? 'bg-orange' : 'bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-7 lg:py-5 justify-center shadow-classic text-sm rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
><path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M12.399 12.399a2.5 2.5 0 011.768-.732H17.5c.46 0 .833.373.833.833v1.667a2.5 2.5 0 01-2.5 2.5h-1.666a2.5 2.5 0 01-1.768-4.268zm1.768.934a.833.833 0 000 1.667h1.666a.833.833 0 00.834-.833v-.834h-2.5zm-11.768.733a2.5 2.5 0 011.768-.733H7.5c.46 0 .833.373.833.834v1.666a2.5 2.5 0 01-2.5 2.5H4.167a2.5 2.5 0 01-1.768-4.267zM4.167 15a.833.833 0 100 1.667h1.666a.833.833 0 00.834-.834V15h-2.5z"
|
||||
/><path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M18.039 1.864c.187.158.294.391.294.636v10a.833.833 0 01-1.666 0V3.484L8.333 4.873v9.294a.833.833 0 01-1.666 0v-10c0-.408.294-.755.696-.822l10-1.667a.833.833 0 01.676.186z"
|
||||
/></svg>
|
||||
<span class="truncate">Музыка</span>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feeds-user-list mt-5 container mx-auto px-2 md:px-6 2xl:px-28 space-y-4 lg:space-y-8">
|
||||
<feed-list :feeds="feeds" :next-cursor="nextCursor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
PhotographIcon,
|
||||
VideoCameraIcon,
|
||||
MusicNoteIcon,
|
||||
} from '@heroicons/vue/solid'
|
||||
import { MenuItem, MenuItems } from '@headlessui/vue'
|
||||
|
||||
import FeedList from '@/Shared/FeedList/FeedList.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import DropdownMenu from '@/Shared/Form/DropdownMenu.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedList,
|
||||
MetaHead,
|
||||
DropdownMenu,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
PhotographIcon,
|
||||
MusicNoteIcon,
|
||||
VideoCameraIcon
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: String,
|
||||
feeds: Array,
|
||||
active_button: String,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
171
resources/js/Pages/Image/Create.vue
Executable file
171
resources/js/Pages/Image/Create.vue
Executable file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<meta-head title="Загрузить изображение"></meta-head>
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<form class=" bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Загрузка изображения
|
||||
</h1>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Название"
|
||||
/>
|
||||
<warning-text />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Описание"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<file-input-multiple
|
||||
v-model="form.photos"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.photos"
|
||||
label="Выбрать изображения"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-gray-light">
|
||||
<TagInput v-model="form.tags" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип контента
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-1" v-model="form.is_paid"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-1" class="select-none ml-3 text-gray">Бесплатный</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-2" v-model="form.is_paid"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-2" class="select-none ml-3 text-gray">Эксклюзивный</label>
|
||||
</div>
|
||||
<div v-if="authUser.private" class="flex items-center">
|
||||
<input id="feed-paid-3" v-model="form.is_paid"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-3" class="select-none ml-3 text-gray">Доступен по личной подписке</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="is_adult_feed" v-model="form.is_adult"
|
||||
type="checkbox"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="is_adult_feed" class="select-none ml-3 text-gray">Контент для взрослых</label>
|
||||
</div> -->
|
||||
|
||||
|
||||
<div v-if="form.is_paid == 1" class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
type="number" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Цена"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<file-input-multiple
|
||||
v-model="form.photos_paid"
|
||||
:error="form.errors.photos_paid"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
label="Выбрать изображения"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Создать
|
||||
</loading-button>
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import FileInputMultiple from '@/Shared/Form/FileInputMultiple.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
import TagInput from '@/Shared/Form/TagInput.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import WarningText from '@/Shared/Misc/WarningText.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
TextInput,
|
||||
FileInputMultiple,
|
||||
LoadingButton,
|
||||
TextareaInput,
|
||||
LinkBack,
|
||||
TagInput,
|
||||
WarningText,
|
||||
},
|
||||
layout: Layout,
|
||||
|
||||
setup() {
|
||||
// let isFeedPaid = ref(0)
|
||||
// let openPaidBlock = ref(0)
|
||||
|
||||
const form = useForm({
|
||||
title: null,
|
||||
body: null,
|
||||
price: null,
|
||||
photos: null,
|
||||
photos_paid: null,
|
||||
tags: [],
|
||||
is_paid: 0,
|
||||
})
|
||||
|
||||
// watch(isFeedPaid, (value) => {
|
||||
// openPaidBlock.value = value
|
||||
// form.is_paid = value
|
||||
// })
|
||||
|
||||
const authUser = computed(() => usePage().props.value.auth.user)
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('images.store'))
|
||||
}
|
||||
|
||||
return { form, submit, authUser }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
313
resources/js/Pages/Image/Edit.vue
Executable file
313
resources/js/Pages/Image/Edit.vue
Executable file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<meta-head title="Обновить изображение"></meta-head>
|
||||
|
||||
<modal-warning
|
||||
:feed_id="feed.id"
|
||||
:open="modalShow"
|
||||
@action="deleteActionModal"
|
||||
/>
|
||||
|
||||
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<div v-if="feed.status != 1" class="bg-indigo-200 shadow-classic rounded-md p-5 mb-10">
|
||||
<p class="text-lg font-medium text-gray">
|
||||
Контент запрещен к публикации, причина:
|
||||
</p>
|
||||
<p v-if="feed.status_note " class="text-gray-light">
|
||||
<ul>
|
||||
<li v-for="textBreak in textsBreak" :key="textBreak">
|
||||
{{ textBreak }}
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p v-else class="text-gray-light">
|
||||
модерация созданного\обновленного контента
|
||||
</p>
|
||||
<p v-if="feed.status == 3" class="pt-3 text-green">
|
||||
Обновления отправлены, ожидайте!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Редактирование изображения
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Название"
|
||||
/>
|
||||
<warning-text />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Описание"
|
||||
/>
|
||||
</div>
|
||||
<div class="border border-indigo-300 flex flex-col p-4 rounded-md">
|
||||
<div>
|
||||
<file-input-multiple
|
||||
v-model="form.photos"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.photos"
|
||||
label="Загрузить новые изображения"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="mt-3 mb-2 text-lg text-gray-light select-none">
|
||||
Загруженные изображения:
|
||||
</h2>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<created-media-item
|
||||
v-for="mediaCommon in mediasCommon" :key="mediaCommon.id"
|
||||
:media="mediaCommon"
|
||||
@addRemoveId="stateToggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-gray-light">
|
||||
<TagInput v-model="form.tags" />
|
||||
</div>
|
||||
<div v-if="isFreeFeed">
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип контента
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-1" v-model="form.is_paid"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-1" class="select-none ml-3 text-gray">Бесплатный</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-2" v-model="form.is_paid"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-2" class="select-none ml-3 text-gray">Эксклюзивный</label>
|
||||
</div>
|
||||
<div v-if="authUser.private" class="flex items-center">
|
||||
<input id="feed-paid-3" v-model="form.is_paid"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-3" class="select-none ml-3 text-gray">Доступен по личной подписке</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="is_adult_feed" v-model="form.is_adult"
|
||||
type="checkbox"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="is_adult_feed" class="select-none ml-3 text-gray">Контент для взрослых</label>
|
||||
</div> -->
|
||||
<div v-if="form.is_paid == 1" class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
type="number" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Цена"
|
||||
/>
|
||||
</div>
|
||||
<div class="border border-indigo-300 flex flex-col p-4 rounded-md space-y-3">
|
||||
<div v-if="isFreeFeed">
|
||||
<file-input-multiple
|
||||
v-model="form.photos_paid"
|
||||
:error="form.errors.photos_paid"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
label="Выбрать изображения"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mediasPaid.length">
|
||||
<h2 class="mb-2 text-lg text-gray-light select-none">
|
||||
Загруженные платные изображения:
|
||||
</h2>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<created-media-item
|
||||
v-for="mediaPaid in mediasPaid" :key="mediaPaid.id"
|
||||
:edit="false"
|
||||
:media="mediaPaid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Обновить
|
||||
</loading-button>
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
<button type="button" class="mx-3 my-1 default text-lg text-pink"
|
||||
@click="openModal"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find'
|
||||
import { toRefs, watch, ref, computed } from 'vue'
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import FileInputMultiple from '@/Shared/Form/FileInputMultiple.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
import TagInput from '@/Shared/Form/TagInput.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import WarningText from '@/Shared/Misc/WarningText.vue'
|
||||
import CreatedMediaItem from '@/Shared/Edit/CreatedMediaItem.vue'
|
||||
import ModalWarning from '@/Shared/Overlay/ModalWarning.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
CreatedMediaItem,
|
||||
TextInput,
|
||||
FileInputMultiple,
|
||||
LoadingButton,
|
||||
WarningText,
|
||||
TextareaInput,
|
||||
LinkBack,
|
||||
TagInput,
|
||||
ModalWarning,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feed: Object,
|
||||
tags: Array,
|
||||
mediasCommon: Array,
|
||||
mediasPaid: Array,
|
||||
mediasCount: Number,
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { feed, tags, mediasCount } = toRefs(props)
|
||||
const modalShow = ref(false)
|
||||
|
||||
const textsBreak = computed(() => feed.value.status_note.split('\n'))
|
||||
|
||||
// let isFeedPaid, openPaidBlock
|
||||
|
||||
let form_fields = {
|
||||
id: feed.value.id,
|
||||
title: feed.value.title,
|
||||
body: feed.value.body,
|
||||
price: feed.value.price,
|
||||
tags: tags.value,
|
||||
is_paid: feed.value.is_paid,
|
||||
|
||||
|
||||
photos: null,
|
||||
photos_paid: null,
|
||||
|
||||
removedItems: [],
|
||||
totalItems: mediasCount,
|
||||
}
|
||||
if (feed.value.is_paid) {
|
||||
// isFeedPaid = ref(1)
|
||||
// openPaidBlock = ref(1)
|
||||
delete form_fields.photos_paid
|
||||
}
|
||||
// else {
|
||||
// isFeedPaid = ref(0)
|
||||
// openPaidBlock = ref(0)
|
||||
// }
|
||||
|
||||
const form = useForm(form_fields)
|
||||
|
||||
// watch(isFeedPaid, (value) => {
|
||||
// openPaidBlock.value = value
|
||||
// form.is_paid = value
|
||||
// })
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('images.update', feed.value.id), {
|
||||
onSuccess: () => {
|
||||
form.reset('photos')
|
||||
form.reset('photos_paid')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const stateToggle = (id) => {
|
||||
let existID = find(form.removedItems, (itemID) => itemID === id)
|
||||
if (existID) {
|
||||
const index = form.removedItems.indexOf(existID)
|
||||
if (index > -1) {
|
||||
form.removedItems.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
form.removedItems.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteActionModal = (remove) => {
|
||||
if (remove) {
|
||||
Inertia.delete(route('feed.destroy', feed.value.id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
}
|
||||
modalShow.value = false
|
||||
}
|
||||
|
||||
const openModal = () => modalShow.value = true
|
||||
|
||||
const authUser = computed(() => usePage().props.value.auth.user)
|
||||
const isFreeFeed = computed(() => feed.value.is_paid === 0)
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
stateToggle,
|
||||
authUser,
|
||||
// isFeedPaid,
|
||||
// openPaidBlock,
|
||||
isFreeFeed,
|
||||
modalShow,
|
||||
deleteActionModal,
|
||||
openModal,
|
||||
textsBreak
|
||||
}
|
||||
},
|
||||
|
||||
updated(){
|
||||
if (this.feed.is_paid) {
|
||||
delete this.form.photos_paid
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
151
resources/js/Pages/Image/Index.vue
Executable file
151
resources/js/Pages/Image/Index.vue
Executable file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<meta-head title="Изображения"></meta-head>
|
||||
|
||||
<div class="mb-6 py-3 banner relative bg-center bg-no-repeat bg-cover"
|
||||
style="background-image: url('/image/bg-home.jpg');"
|
||||
>
|
||||
<div class="h-52 flex justify-center items-center text-center">
|
||||
<div class="max-w-4xl text-gray px-3">
|
||||
<h1 class="text-xl md:text-2xl lg:text-3xl xl:text-5xl text-white font-semibold xl:leading-relaxed">
|
||||
Изображения
|
||||
</h1>
|
||||
<h2 v-if="tag" class="text-lg md:text-3xl text-gray-light">
|
||||
#{{ tag.name }}
|
||||
</h2>
|
||||
|
||||
<inertia-link :href="route('images.create')"
|
||||
class="mt-8 inline-flex tracking-wide items-center px-8 md:px-12 py-3 border border-white text-sm lg:text-lg text-white rounded-full bg-transparent hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
>
|
||||
Загрузить
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 buttons-filter-line">
|
||||
<div class=" relative mb-5">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center z-[1]">
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-light" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 4a7 7 0 100 14 7 7 0 000-14zm-9 7a9 9 0 1118 0 9 9 0 01-18 0z"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.943 15.943a1 1 0 011.414 0l4.35 4.35a1 1 0 01-1.414 1.414l-4.35-4.35a1 1 0 010-1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.search"
|
||||
class="relative w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light !pl-10 h-14" placeholder="Поиск"
|
||||
type="search"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<inertia-link :href="local_route"
|
||||
:class="[active_filter == 'new' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
Новинки
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link :href="local_route"
|
||||
:data="{ filter: 'hot' }"
|
||||
:class="[active_filter == 'hot' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center shadow-classic rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M14.422 6.243c-3.57-2.172-1.895-5.238-1.824-5.365A.586.586 0 0012.09 0c-1.837 0-3.276.522-4.276 1.552-1.71 1.76-1.63 4.498-1.597 5.667.004.13.007.242.007.325 0 .868.14 1.67.263 2.377.079.456.147.85.16 1.159.012.331-.048.407-.05.41-.01.01-.081.049-.276.049a.657.657 0 01-.53-.235c-.565-.634-.604-2.365-.496-3.307a.586.586 0 00-.582-.654c-1.521 0-2.658 2.488-2.658 4.712 0 1.046.21 2.07.625 3.045a8.081 8.081 0 001.7 2.527C5.894 19.157 7.89 20 10 20c2.119 0 4.114-.83 5.618-2.34a7.892 7.892 0 002.327-5.605c0-2.692-2.107-4.95-3.523-5.812zM10 18.828c-3.671 0-6.773-3.101-6.773-6.773 0-.893.213-1.83.585-2.571.086-.173.175-.322.263-.447.01.967.163 2.284.841 3.046.366.41.852.627 1.405.627.518 0 .912-.156 1.172-.466.486-.578.337-1.436.148-2.524-.115-.66-.245-1.409-.245-2.176 0-.1-.003-.22-.007-.359-.032-1.107-.099-3.412 1.265-4.817.613-.63 1.467-1.014 2.55-1.145-.123.406-.228.92-.232 1.497-.01 1.27.476 3.085 2.841 4.524 1.145.696 2.96 2.618 2.96 4.811A6.78 6.78 0 0110 18.828z"
|
||||
/>
|
||||
</svg>
|
||||
Популярные
|
||||
</inertia-link>
|
||||
</div>
|
||||
|
||||
<div v-if="tag" class="mt-4 grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<feed-tags-buttons :slug="tag.slug" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 my-6">
|
||||
<button class="button-default text-gray text-lg" @click="resetFilter">
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div v-if="feeds.length" class="grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<feed
|
||||
:next-cursor="nextCursor"
|
||||
:feeds="feeds"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="font-bold text-xl lg:text-3xl text-gray text-center">
|
||||
Записи не найдены
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import throttle from 'lodash/throttle'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
|
||||
import Feed from '@/Shared/Feed/Feed.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import FeedTagsButtons from '@/Shared/Misc/FeedTagsButtons.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
FeedTagsButtons,
|
||||
Feed,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feeds: Array,
|
||||
local_route: String,
|
||||
active_filter: String,
|
||||
tag: { type: [Object, Boolean], default: false },
|
||||
searchFilters: Object,
|
||||
nextCursor: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
search: this.searchFilters.search,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
Inertia.get(this.local_route, pickBy(this.form), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
}, 500),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetFilter() {
|
||||
Inertia.get(
|
||||
this.local_route,
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
78
resources/js/Pages/Image/Show.vue
Executable file
78
resources/js/Pages/Image/Show.vue
Executable file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
|
||||
<Head>
|
||||
<title>{{feed.entity.title}}</title>
|
||||
<meta name="description" :content="feed.entity.body">
|
||||
<meta property="og:url" :content="route(`${feed.entity.type}.show`, feed.entity.slug)">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" :content="feed.entity.title">
|
||||
<meta property="og:description" :content="feed.entity.body">
|
||||
<meta property="og:image" :content="feed.entity.preview">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- <meta name="twitter:title" :content="feed.entity.title">
|
||||
<meta name="twitter:description" :content="feed.entity.body">
|
||||
<meta name="twitter:image" :content="feed.entity.preview">
|
||||
<meta name="twitter:card" content="summary_large_image"> -->
|
||||
|
||||
</Head>
|
||||
|
||||
<header-auth v-if="!$page.props.auth.user" />
|
||||
|
||||
<modal-share :entity='feed.entity' ref="shareModalNode" />
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 md:grid grid-cols-12 ">
|
||||
<modal-feed-media
|
||||
:type='feed.type'
|
||||
:feed_id='feed.id'
|
||||
:title='feed.entity.title'
|
||||
:preview='feed.entity.preview'
|
||||
:medias='feed.entity.collection_medias'
|
||||
class="border-l border-r md:border-r-0 border-t border-b border-indigo-100"
|
||||
/>
|
||||
|
||||
<modal-feed-body
|
||||
@openShare='showShareModal'
|
||||
:user='user'
|
||||
:feed_id='feed.id'
|
||||
:entity='feed.entity'
|
||||
class="border-l border-r md:border-t border-b border-indigo-100"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Head } from "@inertiajs/inertia-vue3";
|
||||
import Layout from "@/Shared/Layout.vue";
|
||||
import ModalFeedMedia from "@/Shared/Overlay/ModalFeedMedia.vue";
|
||||
import ModalFeedBody from "@/Shared/Overlay/ModalFeedBody.vue";
|
||||
import ModalShare from "@/Shared/Overlay/ModalShare.vue";
|
||||
import HeaderAuth from "@/Shared/LayoutParts/HeaderAuth.vue";
|
||||
|
||||
export default {
|
||||
layout: Layout,
|
||||
components: {
|
||||
Head,
|
||||
ModalShare,
|
||||
ModalFeedMedia,
|
||||
ModalFeedBody,
|
||||
HeaderAuth,
|
||||
},
|
||||
props: {
|
||||
feed: Object,
|
||||
user: Object,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
is_exist_menu: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
showShareModal() {
|
||||
this.$refs.shareModalNode.openModal();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
263
resources/js/Pages/Messenger/Index.vue
Executable file
263
resources/js/Pages/Messenger/Index.vue
Executable file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<meta-head title="Мессенджер"></meta-head>
|
||||
<div class="flex sm:grid grid-cols-12 h-full overflow-hidden">
|
||||
<!-- flex-shrink-0 -->
|
||||
<div ref="userSidebar" class="h-full sm:h-auto transition-transform z-50 absolute transform-gpu -translate-x-full w-64 sm:relative sm:transform-none sm:w-auto col-span-4 bg-indigo-200 border-l border-r border-indigo-300 py-7">
|
||||
<div class="mb-7 px-2 xl:px-8">
|
||||
<div class="relative">
|
||||
<text-input v-model="inputSearchRoom"
|
||||
class="pr-12 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light w-full" placeholder="Поиск"
|
||||
/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="text-gray-light absolute right-3 top-2"
|
||||
><circle cx="11" cy="11"
|
||||
r="8"
|
||||
></circle><line x1="21" y1="21"
|
||||
x2="16.65" y2="16.65"
|
||||
></line></svg>
|
||||
</div>
|
||||
<div class="flex flex-col lg:flex-row lg:space-x-6 mt-2">
|
||||
<div class="flex items-center">
|
||||
<input id="room-search-1" v-model="typeSearch"
|
||||
:value="1" type="radio"
|
||||
class="text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent cursor-pointer"
|
||||
>
|
||||
<label for="room-search-1" class="cursor-pointer select-none ml-2 text-gray-light text-sm">по сообщениям</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="room-search-2" v-model="typeSearch"
|
||||
:value="2" type="radio"
|
||||
class="text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent cursor-pointer"
|
||||
>
|
||||
<label for="room-search-2" class="cursor-pointer select-none ml-2 text-gray-light text-sm">по пользователям</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button class="default mb-2 text-orange text-sm mr-1" @click="openFriendList">
|
||||
открыть список друзей
|
||||
</button>
|
||||
</div>
|
||||
<div data-simplebar class="messsage-height overflow-auto">
|
||||
<div class="divide-y divide-indigo-300 border-t border-b border-indigo-300">
|
||||
<board-room-list v-for="room in userRooms" :key="room.id"
|
||||
class="pt-5 pb-5"
|
||||
:class="{ 'bg-indigo-300': room.classActive }"
|
||||
:room="room"
|
||||
@select-room="changeRoom"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 w-full sm:flex-none sm:w-auto col-span-8 bg-indigo-200 overflow-hidden relative">
|
||||
<button class="default text-center left-2 top-3 absolute z-40 flex sm:hidden items-center text-white text-xs" @click="openSidebar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="feather feather-menu"
|
||||
><line x1="3" y1="12"
|
||||
x2="21" y2="12"
|
||||
></line><line x1="3" y1="6"
|
||||
x2="21" y2="6"
|
||||
></line><line x1="3" y1="18"
|
||||
x2="21" y2="18"
|
||||
></line></svg>
|
||||
<span class="ml-1">открыть список пользователей</span>
|
||||
</button>
|
||||
<board-message :room-id="selectRoom" :user="selectUser"
|
||||
:frozen="selectFrozenRoom" :search="searchOnlyMessage"
|
||||
@disable="interactDisable" @last-message="updateRoom"
|
||||
@change-room="touchRoom" @leave-room="leaveRoom"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, toRef, watch } from 'vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { usePage } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import debounce from 'lodash/debounce'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import BoardRoomList from '@/Shared/Messanger/BoardRoomList.vue'
|
||||
import BoardMessage from '@/Shared/Messanger/BoardMessage.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
TextInput,
|
||||
BoardRoomList,
|
||||
BoardMessage,
|
||||
},
|
||||
layout: Layout,
|
||||
|
||||
props: {
|
||||
rooms: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
roomSearch: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props){
|
||||
var inputSearchRoom = ref(props.roomSearch)
|
||||
const typeSearch = ref(1)
|
||||
const searchOnlyMessage = ref('')
|
||||
var userRooms = toRef(props, 'rooms')
|
||||
|
||||
var selectUser = ref(null)
|
||||
var selectRoom = ref(0)
|
||||
var selectFrozenRoom = ref(false)
|
||||
|
||||
var userSidebar = ref(null)
|
||||
var canChangeRoom = ref(true)
|
||||
|
||||
watch(inputSearchRoom, debounce((search) => {
|
||||
selectUser.value = null
|
||||
selectRoom.value = 0
|
||||
if(typeSearch.value === 1){
|
||||
searchOnlyMessage.value = search
|
||||
}
|
||||
Inertia.get(route('messenger.index'), pickBy({ search: search, type: search ? typeSearch.value : null }), { preserveState: true })
|
||||
}, 500))
|
||||
|
||||
watch(typeSearch, () => {
|
||||
inputSearchRoom.value = ''
|
||||
})
|
||||
|
||||
const checkSelectedActiveUser = () => {
|
||||
const activeRoom = userRooms.value.find(userRoom => userRoom.classActive === true)
|
||||
if(activeRoom){
|
||||
selectUser.value = activeRoom.correspond
|
||||
selectRoom.value = activeRoom.id
|
||||
selectFrozenRoom.value = activeRoom.is_freeze
|
||||
}
|
||||
}
|
||||
checkSelectedActiveUser()
|
||||
|
||||
function openFriendList() {
|
||||
selectUser.value = null
|
||||
selectRoom.value = 0
|
||||
userRooms.value.forEach(item => item.classActive = false)
|
||||
}
|
||||
|
||||
const touchRoom = (id) => {
|
||||
const activeRoom = userRooms.value.find(userRoom => userRoom.id === id)
|
||||
if(activeRoom){
|
||||
activeRoom.classActive = true
|
||||
selectUser.value = activeRoom.correspond
|
||||
selectRoom.value = activeRoom.id
|
||||
selectFrozenRoom.value = activeRoom.is_freeze
|
||||
}
|
||||
}
|
||||
|
||||
const leaveRoom = () => {
|
||||
selectRoom.value = 0
|
||||
selectUser.value = null
|
||||
selectFrozenRoom.value = false
|
||||
}
|
||||
|
||||
|
||||
const changeRoom = (room) => {
|
||||
closeSidebar()
|
||||
if(canChangeRoom.value){
|
||||
selectRoom.value = room.id
|
||||
selectUser.value = room.correspond
|
||||
selectFrozenRoom.value = room.is_freeze
|
||||
const lastActiveRoom = userRooms.value.find(userRoom => userRoom.classActive === true)
|
||||
if(lastActiveRoom){
|
||||
lastActiveRoom.classActive = false
|
||||
}
|
||||
const nowActiveRoom = userRooms.value.find(userRoom => userRoom.id === room.id)
|
||||
nowActiveRoom.classActive = true
|
||||
|
||||
if(!nowActiveRoom.is_my_message && !nowActiveRoom.is_reading){
|
||||
nowActiveRoom.is_reading = true
|
||||
usePage().props.value.message_reading_count--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let openSidebar = () => {
|
||||
if (window.matchMedia('(max-width: 640px)').matches) {
|
||||
userSidebar.value.classList.remove('-translate-x-full')
|
||||
userSidebar.value.classList.add('-translate-x-0')
|
||||
userSidebar.value.classList.add('shadow-classic')
|
||||
}
|
||||
}
|
||||
|
||||
let closeSidebar = () => {
|
||||
if (window.matchMedia('(max-width: 640px)').matches) {
|
||||
userSidebar.value.classList.add('-translate-x-full')
|
||||
userSidebar.value.classList.remove('-translate-x-0')
|
||||
userSidebar.value.classList.remove('shadow-classic')
|
||||
}
|
||||
}
|
||||
|
||||
let interactDisable = (type) => {
|
||||
if(type === true){
|
||||
canChangeRoom.value = false
|
||||
}else{
|
||||
canChangeRoom.value = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let updateRoom = (message) => {
|
||||
const upRoom = userRooms.value.find(userRoom => userRoom.id === message.chat_room_id)
|
||||
upRoom.message = message.message
|
||||
upRoom.is_my_message = true
|
||||
upRoom.is_reading = false
|
||||
upRoom.updated_at = message.updated_at_room
|
||||
upRoom.updated_at_human = message.updated_at_room_human
|
||||
|
||||
userRooms.value.sort(function(a, b) {
|
||||
if (a.updated_at > b.updated_at) {
|
||||
return -1
|
||||
}
|
||||
if (a.updated_at < b.updated_at) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
if (window.matchMedia('(max-width: 640px)').matches) {
|
||||
onClickOutside(userSidebar, () => {
|
||||
closeSidebar()
|
||||
})
|
||||
}
|
||||
return {
|
||||
changeRoom,
|
||||
selectUser,
|
||||
userRooms,
|
||||
selectRoom,
|
||||
openSidebar,
|
||||
userSidebar,
|
||||
closeSidebar,
|
||||
interactDisable,
|
||||
updateRoom,
|
||||
inputSearchRoom,
|
||||
typeSearch,
|
||||
searchOnlyMessage,
|
||||
selectFrozenRoom,
|
||||
touchRoom,
|
||||
leaveRoom,
|
||||
openFriendList,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
243
resources/js/Pages/Music/Create.vue
Executable file
243
resources/js/Pages/Music/Create.vue
Executable file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<meta-head title="Загрузить музыку"></meta-head>
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<form class=" bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Загрузка музыки
|
||||
</h1>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Название"
|
||||
/>
|
||||
<warning-text />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Описание"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<file-input
|
||||
v-model="form.preview"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.preview"
|
||||
label="Загрузить превью"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<file-input-multiple-decode
|
||||
v-model="form.musics"
|
||||
accept=".mp3"
|
||||
:error="form.errors.musics"
|
||||
label="Выбрать музыку"
|
||||
@fileTime="saveTimeFile"
|
||||
@loadFileStart="incrementLoadFile"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-gray-light">
|
||||
<TagInput v-model="form.tags" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип контента
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-1" v-model="form.is_paid"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-1" class="select-none ml-3 text-gray">Бесплатный</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-2" v-model="form.is_paid"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-2" class="select-none ml-3 text-gray">Эксклюзивный</label>
|
||||
</div>
|
||||
<div v-if="authUser.private" class="flex items-center">
|
||||
<input id="feed-paid-3" v-model="form.is_paid"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-3" class="select-none ml-3 text-gray">Доступен по личной подписке</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="is_adult_feed" v-model="form.is_adult"
|
||||
type="checkbox"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="is_adult_feed" class="select-none ml-3 text-gray">Контент для взрослых</label>
|
||||
</div> -->
|
||||
|
||||
<div v-if="form.is_paid == 1" class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
type="number" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Цена"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<file-input-multiple-decode
|
||||
v-model="form.musics_paid"
|
||||
accept=".mp3"
|
||||
:error="form.errors.musics_paid"
|
||||
label="Выбрать музыку"
|
||||
@fileTime="savePaidTimeFile"
|
||||
@loadFileStart="incrementLoadFile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<div v-if="!is_disabled_create">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Создать
|
||||
</loading-button>
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button type="button" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none ease-in-out duration-150 cursor-not-allowed"
|
||||
disabled
|
||||
>
|
||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
><circle class="opacity-25" cx="12"
|
||||
cy="12" r="10"
|
||||
stroke="currentColor" stroke-width="4"
|
||||
></circle><path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path></svg>
|
||||
Обработка
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import FileInputMultipleDecode from '@/Shared/Form/FileInputMultipleDecode.vue'
|
||||
import FileInput from '@/Shared/Form/FileInput.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import TagInput from '@/Shared/Form/TagInput.vue'
|
||||
import WarningText from '@/Shared/Misc/WarningText.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
TextInput,
|
||||
FileInput,
|
||||
FileInputMultipleDecode,
|
||||
LoadingButton,
|
||||
TextareaInput,
|
||||
TagInput,
|
||||
WarningText,
|
||||
LinkBack,
|
||||
},
|
||||
layout: Layout,
|
||||
|
||||
setup() {
|
||||
let is_disabled_create = ref(0)
|
||||
let countFiles = ref(0)
|
||||
let countFilesLoaded = ref(0)
|
||||
|
||||
// let isFeedPaid = ref(0)
|
||||
// let openPaidBlock = ref(0)
|
||||
|
||||
const form = useForm({
|
||||
title: null,
|
||||
body: null,
|
||||
preview: null,
|
||||
musics: null,
|
||||
musics_paid: null,
|
||||
price: null,
|
||||
is_paid: 0,
|
||||
times: [],
|
||||
times_paid: [],
|
||||
tags: [],
|
||||
})
|
||||
|
||||
// watch(isFeedPaid, (value) => {
|
||||
// openPaidBlock.value = value
|
||||
// form.is_paid = value
|
||||
// })
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('musics.store'))
|
||||
}
|
||||
|
||||
const saveTimeFile = (time) => {
|
||||
form.times.push(time)
|
||||
countFilesLoaded.value++
|
||||
if (countFilesLoaded.value === countFiles.value) {
|
||||
is_disabled_create.value = 0
|
||||
}
|
||||
}
|
||||
const savePaidTimeFile = (time) => {
|
||||
form.times_paid.push(time)
|
||||
countFilesLoaded.value++
|
||||
if (countFilesLoaded.value === countFiles.value) {
|
||||
is_disabled_create.value = 0
|
||||
}
|
||||
}
|
||||
const incrementLoadFile = () => {
|
||||
countFiles.value++
|
||||
is_disabled_create.value = 1
|
||||
}
|
||||
const authUser = computed(() => usePage().props.value.auth.user)
|
||||
|
||||
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
saveTimeFile,
|
||||
savePaidTimeFile,
|
||||
incrementLoadFile,
|
||||
is_disabled_create,
|
||||
authUser,
|
||||
// isFeedPaid,
|
||||
// openPaidBlock,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
416
resources/js/Pages/Music/Edit.vue
Executable file
416
resources/js/Pages/Music/Edit.vue
Executable file
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<meta-head title="Обновить музыку"></meta-head>
|
||||
|
||||
<modal-warning
|
||||
:feed_id="feed.id"
|
||||
:open="modalShow"
|
||||
@action="deleteActionModal"
|
||||
/>
|
||||
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<div v-if="feed.status != 1" class="bg-indigo-200 shadow-classic rounded-md p-5 mb-10">
|
||||
<p class="text-lg font-medium text-gray">
|
||||
Контент запрещен к публикации, причина:
|
||||
</p>
|
||||
<p v-if="feed.status_note " class="text-gray-light">
|
||||
<ul>
|
||||
<li v-for="textBreak in textsBreak" :key="textBreak">
|
||||
{{ textBreak }}
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p v-else class="text-gray-light">
|
||||
модерация созданного\обновленного контента
|
||||
</p>
|
||||
<p v-if="feed.status == 3" class="pt-3 text-green">
|
||||
Обновления отправлены, ожидайте!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<form class="bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Редактирование музыки
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Название"
|
||||
/>
|
||||
<warning-text />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Описание"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<file-input
|
||||
v-model="form.preview"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.preview"
|
||||
:label="textPreview()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div v-if="mediaPreview" class="border border-indigo-300 p-4 rounded-md">
|
||||
<button type="button"
|
||||
class="flex-shrink-0 px-6 py-2 bg-pink focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:outline-none focus:border-transparent rounded-sm text-sm text-white" @click="removePreview(mediaPreview.id)"
|
||||
>
|
||||
Удалить старое превью
|
||||
</button>
|
||||
<div class="mt-4 flex-shrink-0 self-start overflow-hidden">
|
||||
<img class="object-contain h-48" :src="mediaPreview.url"
|
||||
alt=""
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-indigo-300 flex flex-col p-4 rounded-md">
|
||||
<file-input-multiple-decode
|
||||
v-model="form.musics"
|
||||
accept=".mp3"
|
||||
:error="form.errors.musics"
|
||||
label="Загрузить новую музыку"
|
||||
@fileTime="saveTimeFile"
|
||||
@loadFileStart="incrementLoadFile"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<h2 class="mt-3 mb-2 text-lg text-gray-light select-none">
|
||||
Загруженная музыка:
|
||||
</h2>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<created-media-item
|
||||
v-for="mediaCommon in mediasCommon"
|
||||
:key="mediaCommon.id" :type="feed.type"
|
||||
:media="mediaCommon"
|
||||
@addRemoveId="stateToggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-gray-light">
|
||||
<TagInput v-model="form.tags" />
|
||||
</div>
|
||||
<div v-if="isFreeFeed">
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип контента
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-1" v-model="form.is_paid"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-1" class="select-none ml-3 text-gray">Бесплатный</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-2" v-model="form.is_paid"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-2" class="select-none ml-3 text-gray">Эксклюзивный</label>
|
||||
</div>
|
||||
<div v-if="authUser.private" class="flex items-center">
|
||||
<input id="feed-paid-3" v-model="form.is_paid"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-3" class="select-none ml-3 text-gray">Доступен по личной подписке</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="is_adult_feed" v-model="form.is_adult"
|
||||
type="checkbox"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="is_adult_feed" class="select-none ml-3 text-gray">Контент для взрослых</label>
|
||||
</div> -->
|
||||
<div v-if="form.is_paid == 1" class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
type="number" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Цена"
|
||||
/>
|
||||
</div>
|
||||
<div class="border border-indigo-300 flex flex-col p-4 rounded-md space-y-3">
|
||||
<div v-if="isFreeFeed">
|
||||
<file-input-multiple-decode
|
||||
v-model="form.musics_paid"
|
||||
accept=".mp3"
|
||||
:error="form.errors.musics_paid"
|
||||
label="Выбрать музыку"
|
||||
@fileTime="savePaidTimeFile"
|
||||
@loadFileStart="incrementLoadFile"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mediasPaid.length">
|
||||
<h2 class="mb-2 text-lg text-gray-light select-none">
|
||||
Загруженная платная музыка:
|
||||
</h2>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<created-media-item
|
||||
v-for="mediaPaid in mediasPaid" :key="mediaPaid.id"
|
||||
:edit="false"
|
||||
:type="feed.type"
|
||||
:media="mediaPaid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<div v-if="!is_disabled_create">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Обновить
|
||||
</loading-button>
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
<button type="button" class="mx-3 my-1 default text-lg text-pink"
|
||||
@click="openModal"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button type="button" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none ease-in-out duration-150 cursor-not-allowed"
|
||||
disabled
|
||||
>
|
||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
><circle class="opacity-25" cx="12"
|
||||
cy="12" r="10"
|
||||
stroke="currentColor" stroke-width="4"
|
||||
></circle><path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path></svg>
|
||||
Обработка
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toRefs, watch, ref, computed } from 'vue'
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { useStore } from 'vuex'
|
||||
import find from 'lodash/find'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import FileInput from '@/Shared/Form/FileInput.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import FileInputMultipleDecode from '@/Shared/Form/FileInputMultipleDecode.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
import TagInput from '@/Shared/Form/TagInput.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import WarningText from '@/Shared/Misc/WarningText.vue'
|
||||
import CreatedMediaItem from '@/Shared/Edit/CreatedMediaItem.vue'
|
||||
import ModalWarning from '@/Shared/Overlay/ModalWarning.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
CreatedMediaItem,
|
||||
TextInput,
|
||||
FileInput,
|
||||
FileInputMultipleDecode,
|
||||
LoadingButton,
|
||||
WarningText,
|
||||
TextareaInput,
|
||||
LinkBack,
|
||||
TagInput,
|
||||
ModalWarning,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feed: Object,
|
||||
tags: Array,
|
||||
mediasCommon: Array,
|
||||
mediasPaid: Array,
|
||||
mediasCount: Number,
|
||||
mediaPreview: Object,
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const { feed, tags, mediasCount, mediaPreview } = toRefs(props)
|
||||
|
||||
const textsBreak = computed(() => feed.value.status_note.split('\n'))
|
||||
|
||||
const modalShow = ref(false)
|
||||
|
||||
let is_disabled_create = ref(0)
|
||||
let countFiles = ref(0)
|
||||
let countFilesLoaded = ref(0)
|
||||
|
||||
// let isFeedPaid, openPaidBlock
|
||||
|
||||
let form_fields = {
|
||||
id: feed.value.id,
|
||||
title: feed.value.title,
|
||||
body: feed.value.body,
|
||||
price: feed.value.price,
|
||||
tags: tags.value,
|
||||
is_paid: feed.value.is_paid,
|
||||
|
||||
|
||||
preview: null,
|
||||
musics: null,
|
||||
musics_paid: null,
|
||||
|
||||
times: [],
|
||||
times_paid: [],
|
||||
removedItems: [],
|
||||
totalItems: mediasCount,
|
||||
}
|
||||
|
||||
if (feed.value.is_paid) {
|
||||
// isFeedPaid = ref(1)
|
||||
// openPaidBlock = ref(1)
|
||||
delete form_fields.musics_paid
|
||||
}
|
||||
// else {
|
||||
// isFeedPaid = ref(0)
|
||||
// openPaidBlock = ref(0)
|
||||
// }
|
||||
|
||||
const form = useForm(form_fields)
|
||||
|
||||
// watch(isFeedPaid, (value) => {
|
||||
// openPaidBlock.value = value
|
||||
// form.is_paid = value
|
||||
// })
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('musics.update', feed.value.id), {
|
||||
onSuccess: () => {
|
||||
form.reset('musics')
|
||||
form.reset('musics_paid')
|
||||
form.reset('preview')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const stateToggle = (id) => {
|
||||
let existID = find(form.removedItems, (itemID) => itemID === id)
|
||||
if (existID) {
|
||||
const index = form.removedItems.indexOf(existID)
|
||||
if (index > -1) {
|
||||
form.removedItems.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
form.removedItems.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
const resetPlaylist = () => {
|
||||
store.dispatch('defaultPlaylistReset')
|
||||
}
|
||||
|
||||
const deleteActionModal = (remove) => {
|
||||
if (remove) {
|
||||
resetPlaylist()
|
||||
Inertia.delete(route('feed.destroy', feed.value.id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
}
|
||||
modalShow.value = false
|
||||
}
|
||||
|
||||
const openModal = () => (modalShow.value = true)
|
||||
|
||||
const authUser = computed(() => usePage().props.value.auth.user)
|
||||
|
||||
const isFreeFeed = computed(() => feed.value.is_paid === 0)
|
||||
const textPreview = () => {
|
||||
if (mediaPreview.value) {
|
||||
return 'Обновить превью'
|
||||
}
|
||||
return 'Загрузить превью'
|
||||
}
|
||||
|
||||
const saveTimeFile = (time) => {
|
||||
form.times.push(time)
|
||||
countFilesLoaded.value++
|
||||
if (countFilesLoaded.value === countFiles.value) {
|
||||
is_disabled_create.value = 0
|
||||
}
|
||||
}
|
||||
const savePaidTimeFile = (time) => {
|
||||
form.times_paid.push(time)
|
||||
countFilesLoaded.value++
|
||||
if (countFilesLoaded.value === countFiles.value) {
|
||||
is_disabled_create.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
const incrementLoadFile = () => {
|
||||
countFiles.value++
|
||||
is_disabled_create.value = 1
|
||||
}
|
||||
|
||||
const removePreview = (id) => {
|
||||
Inertia.delete(route('feed.preview.destroy', id))
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
saveTimeFile,
|
||||
savePaidTimeFile,
|
||||
incrementLoadFile,
|
||||
stateToggle,
|
||||
authUser,
|
||||
// isFeedPaid,
|
||||
// openPaidBlock,
|
||||
isFreeFeed,
|
||||
modalShow,
|
||||
deleteActionModal,
|
||||
openModal,
|
||||
textPreview,
|
||||
is_disabled_create,
|
||||
removePreview,
|
||||
textsBreak
|
||||
}
|
||||
},
|
||||
|
||||
updated() {
|
||||
if (this.feed.is_paid) {
|
||||
delete this.form.musics_paid
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
139
resources/js/Pages/Music/Feed.vue
Executable file
139
resources/js/Pages/Music/Feed.vue
Executable file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<meta-head title="Музыка"></meta-head>
|
||||
|
||||
<div class="mb-6 py-3 banner relative bg-center bg-no-repeat bg-cover"
|
||||
style="background-image: url('/image/bg-home.jpg');">
|
||||
<div class="h-52 flex justify-center items-center text-center">
|
||||
<div class="max-w-4xl text-gray px-3">
|
||||
<h1
|
||||
class=" text-xl md:text-2xl lg:text-3xl xl:text-5xl text-white font-semibold xl:leading-relaxed">
|
||||
Музыка</h1>
|
||||
|
||||
<h2 v-if="tag" class="text-lg md:text-3xl text-gray-light">
|
||||
#{{tag.name}}
|
||||
</h2>
|
||||
|
||||
<inertia-link :href="route('musics.create')"
|
||||
class="mt-8 inline-flex tracking-wide items-center px-8 md:px-12 py-3 border border-white text-sm lg:text-lg text-white rounded-full bg-transparent hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2">
|
||||
Загрузить
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 buttons-filter-line">
|
||||
|
||||
<div class=" relative mb-5">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center z-[1]">
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-light" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" fill="currentColor">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 4a7 7 0 100 14 7 7 0 000-14zm-9 7a9 9 0 1118 0 9 9 0 01-18 0z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.943 15.943a1 1 0 011.414 0l4.35 4.35a1 1 0 01-1.414 1.414l-4.35-4.35a1 1 0 010-1.414z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.search"
|
||||
class="relative w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light !pl-10 h-14" placeholder="Поиск"
|
||||
type="search">
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
|
||||
<inertia-link :href="local_route"
|
||||
:class="[active_filter == 'new' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']">
|
||||
Новинки
|
||||
</inertia-link >
|
||||
|
||||
<inertia-link :href="local_route"
|
||||
:data="{ filter: 'hot' }"
|
||||
:class="[active_filter == 'hot' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center shadow-classic rounded-md text-white focus:outline-none']">
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M14.422 6.243c-3.57-2.172-1.895-5.238-1.824-5.365A.586.586 0 0012.09 0c-1.837 0-3.276.522-4.276 1.552-1.71 1.76-1.63 4.498-1.597 5.667.004.13.007.242.007.325 0 .868.14 1.67.263 2.377.079.456.147.85.16 1.159.012.331-.048.407-.05.41-.01.01-.081.049-.276.049a.657.657 0 01-.53-.235c-.565-.634-.604-2.365-.496-3.307a.586.586 0 00-.582-.654c-1.521 0-2.658 2.488-2.658 4.712 0 1.046.21 2.07.625 3.045a8.081 8.081 0 001.7 2.527C5.894 19.157 7.89 20 10 20c2.119 0 4.114-.83 5.618-2.34a7.892 7.892 0 002.327-5.605c0-2.692-2.107-4.95-3.523-5.812zM10 18.828c-3.671 0-6.773-3.101-6.773-6.773 0-.893.213-1.83.585-2.571.086-.173.175-.322.263-.447.01.967.163 2.284.841 3.046.366.41.852.627 1.405.627.518 0 .912-.156 1.172-.466.486-.578.337-1.436.148-2.524-.115-.66-.245-1.409-.245-2.176 0-.1-.003-.22-.007-.359-.032-1.107-.099-3.412 1.265-4.817.613-.63 1.467-1.014 2.55-1.145-.123.406-.228.92-.232 1.497-.01 1.27.476 3.085 2.841 4.524 1.145.696 2.96 2.618 2.96 4.811A6.78 6.78 0 0110 18.828z" />
|
||||
</svg>
|
||||
Популярные
|
||||
</inertia-link >
|
||||
</div>
|
||||
|
||||
<div v-if="tag" class="mt-4 grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<feed-tags-buttons :slug='tag.slug' />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 my-6">
|
||||
<button @click="resetFilter" class="button-default text-gray text-lg">Очистить фильтры</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div v-if="feeds.length" class="grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<feed
|
||||
:nextCursor="nextCursor"
|
||||
:feeds="feeds"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="font-bold text-xl lg:text-3xl text-gray text-center">Записи не найдены</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from "@inertiajs/inertia";
|
||||
import throttle from "lodash/throttle";
|
||||
import pickBy from "lodash/pickBy";
|
||||
|
||||
import Feed from "@/Shared/Feed/Feed.vue";
|
||||
import Layout from "@/Shared/Layout.vue";
|
||||
import MetaHead from "@/Shared/MetaHead.vue";
|
||||
import FeedTagsButtons from "@/Shared/Misc/FeedTagsButtons.vue";
|
||||
|
||||
export default {
|
||||
layout: Layout,
|
||||
components: {
|
||||
MetaHead,
|
||||
FeedTagsButtons,
|
||||
Feed,
|
||||
},
|
||||
props: {
|
||||
feeds: Array,
|
||||
local_route: String,
|
||||
active_filter: String,
|
||||
tag: { type: [Object, Boolean], default: false },
|
||||
searchFilters: Object,
|
||||
nextCursor: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
search: this.searchFilters.search,
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
Inertia.get(this.local_route, pickBy(this.form), {
|
||||
preserveScroll: true, preserveState: true
|
||||
});
|
||||
}, 500),
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
resetFilter() {
|
||||
Inertia.get(
|
||||
this.local_route,
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
78
resources/js/Pages/Music/Show.vue
Executable file
78
resources/js/Pages/Music/Show.vue
Executable file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
|
||||
<Head>
|
||||
<title>{{feed.entity.title}}</title>
|
||||
<meta name="description" :content="feed.entity.body">
|
||||
<meta property="og:url" :content="route(`${feed.entity.type}.show`, feed.entity.slug)">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" :content="feed.entity.title">
|
||||
<meta property="og:description" :content="feed.entity.body">
|
||||
<meta property="og:image" :content="feed.entity.preview">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- <meta name="twitter:title" :content="feed.entity.title">
|
||||
<meta name="twitter:description" :content="feed.entity.body">
|
||||
<meta name="twitter:image" :content="feed.entity.preview">
|
||||
<meta name="twitter:card" content="summary_large_image"> -->
|
||||
|
||||
</Head>
|
||||
|
||||
<header-auth v-if="!$page.props.auth.user" />
|
||||
|
||||
<modal-share :entity='feed.entity' ref="shareModalNode" />
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 md:grid grid-cols-12 ">
|
||||
<modal-feed-media
|
||||
:type='feed.type'
|
||||
:feed_id='feed.id'
|
||||
:title='feed.entity.title'
|
||||
:preview='feed.entity.preview'
|
||||
:medias='feed.entity.collection_medias'
|
||||
class="border-l border-r md:border-r-0 border-t border-b border-indigo-100"
|
||||
/>
|
||||
|
||||
<modal-feed-body
|
||||
@openShare='showShareModal'
|
||||
:user='user'
|
||||
:feed_id='feed.id'
|
||||
:entity='feed.entity'
|
||||
class="border-r md:border-t border-b border-indigo-100"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Head } from "@inertiajs/inertia-vue3";
|
||||
import Layout from "@/Shared/Layout.vue";
|
||||
import HeaderAuth from "@/Shared/LayoutParts/HeaderAuth.vue";
|
||||
import ModalFeedMedia from "@/Shared/Overlay/ModalFeedMedia.vue";
|
||||
import ModalFeedBody from "@/Shared/Overlay/ModalFeedBody.vue";
|
||||
import ModalShare from "@/Shared/Overlay/ModalShare.vue";
|
||||
|
||||
export default {
|
||||
layout: Layout,
|
||||
components: {
|
||||
Head,
|
||||
ModalShare,
|
||||
ModalFeedMedia,
|
||||
ModalFeedBody,
|
||||
HeaderAuth
|
||||
},
|
||||
props: {
|
||||
feed: Object,
|
||||
user: Object,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
is_exist_menu: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
showShareModal() {
|
||||
this.$refs.shareModalNode.openModal();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
116
resources/js/Pages/Profile/Index.vue
Executable file
116
resources/js/Pages/Profile/Index.vue
Executable file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<meta-head title="Профиль юзера"></meta-head>
|
||||
|
||||
<profile-header :is_leader="is_leader" :user="user"
|
||||
:counts="counts" :limit-leader="limitLeader"
|
||||
:auth-user-active-subscription="authUserActiveSubscription"
|
||||
:package-completed="packageCompleted"
|
||||
/>
|
||||
|
||||
<profile-menu :user="user" />
|
||||
|
||||
<div v-if="packet?.id && !packageCompleted && !user.is_auth_user && user.private" class="mt-12 xl:container xl:mx-auto ">
|
||||
<div class="p-5 text-lg text-white flex items-center gap-10 bg-indigo-200 mx-3">
|
||||
<div>
|
||||
<h2>Купить подписку на пользователя</h2>
|
||||
<div>Цена: <span>{{ packet.price }}</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button"
|
||||
class="p-5 text-lg bg-orange rounded-lg twext-white"
|
||||
@click="createSubsPacket"
|
||||
>
|
||||
Оплатить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div v-if="testPeriod" class="mt-12 xl:container xl:mx-auto px-2 md:px-3 ">
|
||||
<div class="bg-pink p-10 text-center text-white text-xl rounded-xl">
|
||||
<p>Вам предоставлен тестовый доступ в течении часа!</p>
|
||||
<p>
|
||||
после ознакомления, Вам нужно будет оплатить подписку, <inertia-link class="underline" :href="route('setting.tarif')">
|
||||
по данной ссылке
|
||||
</inertia-link> : (или перейти в настройки -> тарифы)
|
||||
</p>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div v-if="feeds.length" class="mt-12 xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<feed
|
||||
:feeds="feeds"
|
||||
:next-cursor="nextCursor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-12 xl:container xl:mx-auto px-2 md:px-3 ">
|
||||
<div class="font-bold text-xl lg:text-3xl text-gray text-center">
|
||||
Записи не найдены
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import ProfileHeader from '@/Shared/Partials/ProfileHeader.vue'
|
||||
import ProfileMenu from '@/Shared/Partials/ProfileMenu.vue'
|
||||
import Feed from '@/Shared/Feed/Feed.vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Feed,
|
||||
MetaHead,
|
||||
ProfileHeader,
|
||||
ProfileMenu,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
packet: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
feeds: {
|
||||
type: Array,
|
||||
default: () => {}
|
||||
},
|
||||
counts: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
nextCursor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
limitLeader: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
packageCompleted: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
authUserActiveSubscription: Boolean,
|
||||
close_account: Boolean,
|
||||
is_leader: Boolean,
|
||||
|
||||
},
|
||||
methods: {
|
||||
createSubsPacket()
|
||||
{
|
||||
// asdasd
|
||||
Inertia.post(
|
||||
route('user.package.subs'),
|
||||
{ packet_id: this.packet.id },
|
||||
// { preserveScroll: true, preserveState: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
232
resources/js/Pages/Profile/Readers.vue
Executable file
232
resources/js/Pages/Profile/Readers.vue
Executable file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<meta-head title="Читаемые пользователи"></meta-head>
|
||||
|
||||
<profile-header :is_leader="is_leader" :user="user"
|
||||
:counts="counts" :limit-leader="limitLeader"
|
||||
:auth-user-active-subscription="authUserActiveSubscription"
|
||||
:package-completed="packageCompleted"
|
||||
/>
|
||||
<profile-menu :user="user" />
|
||||
|
||||
|
||||
<div v-if="user.is_auth_user || (user.private === false || packageCompleted)" class="mt-12 xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="cards-block rounded-md bg-indigo-200 shadow-classic p-2 lg:p-5">
|
||||
<div class="">
|
||||
<search-filter v-model="form.search" class="w-full max-w-3xl mr-4"
|
||||
@reset="reset"
|
||||
>
|
||||
<div class="flex flex-col space-y-6">
|
||||
<div class="flex items-center">
|
||||
<input id="user-leader-1" v-model="form.leader"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-leader-1" class="select-none ml-3 text-gray text-xs md:text-base">По лидерам</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="user-leader-2" v-model="form.leader"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-leader-2" class="select-none ml-3 text-gray text-xs md:text-base">Не по лидерам</label>
|
||||
</div>
|
||||
</div>
|
||||
</search-filter>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-show="readers.length" class="mt-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 grid gap-2 lg:gap-4 grid-cards">
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="reader in userLists" :key="reader.id"
|
||||
:ref="el => { if (el && reader.id === lastElementID) lastNodeLement = el }" class="group mb-5 user-card relative"
|
||||
>
|
||||
<div v-if="user.is_auth_user" class="absolute inset-x-0 top-4 z-10 flex justify-center">
|
||||
<toggle
|
||||
v-if="authUserActiveSubscription"
|
||||
:user_id="reader.id"
|
||||
:enabled="reader.is_leader"
|
||||
:disabled="limitLeader === true && reader.is_leader === false ? true : false"
|
||||
textin="Сделать лидером"
|
||||
textout="Убрать лидера" @clicked="leader"
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 bottom-4 z-10 flex justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<inertia-link :href="route('profile.user', reader.username)" class="block flex-shrink-0">
|
||||
<user-avatar :user="reader" size="small"
|
||||
class="border border-white shadow-classic h-20 w-20 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', reader.username)" class="mt-2 block text-white text-sm text-center">
|
||||
<p>{{ reader.name }}</p>
|
||||
<p class="text-xs">
|
||||
{{ reader.username }}
|
||||
</p>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gradient-profile relative overflow-hidden">
|
||||
<user-banner class="w-full h-72 bg-indigo-300" :user="reader"
|
||||
size="banner"
|
||||
/>
|
||||
</div>
|
||||
<!-- myPaidListSubscribeTo[reader.id] -->
|
||||
<div v-if="user.is_auth_user && (reader.private === false ? true : false) " class="transition-opacity sm:opacity-0 group-hover:opacity-100 absolute w-full text-center">
|
||||
<button class="leading-none focus:outline-none hover:underline text-sm text-orange-dark" @click="susbscribe(reader.id)">
|
||||
Отписаться
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="readers.length == 0">
|
||||
<p class="mt-4 text-center md:text-2xl text-gray-light">
|
||||
Пользователи не найдены
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-12 xl:container xl:mx-auto px-2 md:px-3 text-gray-light text-lg">
|
||||
Аккаунт закрыт
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import filter from 'lodash/filter'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
import throttle from 'lodash/throttle'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import ProfileHeader from '@/Shared/Partials/ProfileHeader.vue'
|
||||
import ProfileMenu from '@/Shared/Partials/ProfileMenu.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import UserBanner from '@/Shared/Misc/UserBanner.vue'
|
||||
import Toggle from '@/Shared/Form/Toggle.vue'
|
||||
import SearchFilter from '@/Shared/Form/SearchFilter.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
Toggle,
|
||||
UserAvatar,
|
||||
UserBanner,
|
||||
ProfileHeader,
|
||||
ProfileMenu,
|
||||
SearchFilter,
|
||||
InfinityScroll,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: String,
|
||||
user: Object,
|
||||
myPaidListSubscribeTo: Object,
|
||||
readers: Array,
|
||||
counts: Object,
|
||||
filters: Object,
|
||||
is_leader: Boolean,
|
||||
authUserActiveSubscription: Boolean,
|
||||
close_account: Boolean,
|
||||
limitLeader: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
packageCompleted: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
userLists: [],
|
||||
form: {
|
||||
search: this.filters.search,
|
||||
leader: this.filters.leader,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastElementID() {
|
||||
return this.userLists[this.userLists.length - 1]?.id
|
||||
},
|
||||
// limitChoiceLeader() {
|
||||
// return this.userLists.filter(item => item.is_leader === true).length === 2
|
||||
// },
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateRequest()
|
||||
},
|
||||
watch: {
|
||||
readers() {
|
||||
this.updateRequest()
|
||||
},
|
||||
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
const url = route('profile.readers', this.user.username)
|
||||
Inertia.get(url, pickBy(this.form), {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
})
|
||||
}, 300),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.userLists.push(list)
|
||||
}
|
||||
},
|
||||
|
||||
updateRequest() {
|
||||
this.userLists = this.readers
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.form = mapValues(this.form, () => null)
|
||||
},
|
||||
|
||||
susbscribe(user_id) {
|
||||
Inertia.post(
|
||||
route('users.subs', user_id),
|
||||
{},
|
||||
{ preserveScroll: true, preserveState: true }
|
||||
)
|
||||
},
|
||||
|
||||
leader(user_id) {
|
||||
let current_user = filter(this.readers, function (x) {
|
||||
return x.id === user_id
|
||||
})
|
||||
if (current_user) {
|
||||
current_user = current_user[0]
|
||||
Inertia.post(
|
||||
route('users.leader', user_id),
|
||||
{ vote: current_user.is_leader },
|
||||
{ preserveScroll: true, preserveState: true }
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
204
resources/js/Pages/Profile/Subs.vue
Executable file
204
resources/js/Pages/Profile/Subs.vue
Executable file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<meta-head title="Подписчики"></meta-head>
|
||||
|
||||
<profile-header :is_leader="is_leader" :user="user"
|
||||
:counts="counts" :limit-leader="limitLeader"
|
||||
:auth-user-active-subscription="authUserActiveSubscription"
|
||||
:package-completed="packageCompleted"
|
||||
/>
|
||||
<profile-menu :user="user" />
|
||||
|
||||
|
||||
<div v-if="user.is_auth_user || (user.private === false || packageCompleted)" class="mt-12 xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="cards-block rounded-md bg-indigo-200 shadow-classic p-2 lg:p-5">
|
||||
<div class="">
|
||||
<search-filter v-model="form.search" class="w-full max-w-3xl mr-4"
|
||||
@reset="reset"
|
||||
>
|
||||
<div class="flex flex-col space-y-6">
|
||||
<div class="flex items-center">
|
||||
<input id="user-sub-1" v-model="form.sub"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-sub-1" class="select-none ml-3 text-gray text-xs md:text-base">По подписчикам</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="user-sub-2" v-model="form.sub"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-sub-2" class="select-none ml-3 text-gray text-xs md:text-base">Не по подписчикам</label>
|
||||
</div>
|
||||
</div>
|
||||
</search-filter>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-show="subscribers.length" class="mt-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 grid gap-2 lg:gap-4 grid-cards">
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="subscriber in userLists" :key="subscriber.id"
|
||||
:ref="el => { if (el && subscriber.id === lastElementID) lastNodeLement = el }" class=" user-card relative"
|
||||
>
|
||||
<div v-if="user.is_auth_user && !subscriber.private" class="absolute inset-x-0 top-4 z-10 flex justify-center">
|
||||
<toggle
|
||||
:user_id="subscriber.id"
|
||||
:enabled="subscriber.is_sub"
|
||||
textin="Подписаться"
|
||||
textout="Отписаться" @clicked="susbscribe"
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 bottom-4 z-10 flex justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<inertia-link :href="route('profile.user', subscriber.username)" class="block flex-shrink-0">
|
||||
<user-avatar :user="subscriber" size="small"
|
||||
class="border border-white shadow-classic h-20 w-20 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', subscriber.username)" class="mt-2 block text-white text-sm text-center">
|
||||
<p>{{ subscriber.name }}</p>
|
||||
<p class="text-xs">
|
||||
{{ subscriber.username }}
|
||||
</p>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gradient-profile relative overflow-hidden">
|
||||
<user-banner class="w-full h-72 bg-indigo-300" :user="subscriber"
|
||||
size="banner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="subscribers.length == 0">
|
||||
<p class="mt-4 text-center md:text-2xl text-gray-light">
|
||||
Пользователи не найдены
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-12 xl:container xl:mx-auto px-2 md:px-3 text-gray-light text-lg">
|
||||
Аккаунт закрыт
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
import throttle from 'lodash/throttle'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import ProfileHeader from '@/Shared/Partials/ProfileHeader.vue'
|
||||
import ProfileMenu from '@/Shared/Partials/ProfileMenu.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import UserBanner from '@/Shared/Misc/UserBanner.vue'
|
||||
import Toggle from '@/Shared/Form/Toggle.vue'
|
||||
import SearchFilter from '@/Shared/Form/SearchFilter.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
Toggle,
|
||||
UserAvatar,
|
||||
UserBanner,
|
||||
ProfileHeader,
|
||||
ProfileMenu,
|
||||
SearchFilter,
|
||||
InfinityScroll,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: String,
|
||||
user: Object,
|
||||
subscribers: Array,
|
||||
counts: Object,
|
||||
filters: Object,
|
||||
is_leader: Boolean,
|
||||
close_account: Boolean,
|
||||
authUserActiveSubscription: Boolean,
|
||||
limitLeader: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
packageCompleted: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userLists: [],
|
||||
form: {
|
||||
search: this.filters.search,
|
||||
sub: this.filters.sub,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastElementID() {
|
||||
return this.userLists[this.userLists.length - 1]?.id
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateRequest()
|
||||
},
|
||||
|
||||
watch: {
|
||||
subscribers() {
|
||||
this.updateRequest()
|
||||
},
|
||||
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
const url = route('profile.subs', this.user.username)
|
||||
Inertia.get(url, pickBy(this.form), {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
})
|
||||
}, 300),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.userLists.push(list)
|
||||
}
|
||||
},
|
||||
|
||||
updateRequest() {
|
||||
this.userLists = this.subscribers
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.form = mapValues(this.form, () => null)
|
||||
},
|
||||
susbscribe(user_id) {
|
||||
Inertia.post(
|
||||
route('users.subs', user_id),
|
||||
{},
|
||||
{ preserveScroll: true, preserveState: true }
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
79
resources/js/Pages/Settings/SettingsDocuments.vue
Executable file
79
resources/js/Pages/Settings/SettingsDocuments.vue
Executable file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<meta-head title="Написать нам"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div class="mx-4 2xl:mx-28 my-8">
|
||||
<div class="mt-10">
|
||||
<ul class="flex flex-col text-white gap-3 text-base px-3">
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/company.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>О компании</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/offer_authors.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Оферта для авторов</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/offer_paid_subscription.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Оферта на платную подписку</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/security_policy.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Политика безопасности</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/privacy_policy.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Политика конфиденциальности</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/terms_use.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Пользовательское соглашение</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/personal_data.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Согласие на обработку персональных данных</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {},
|
||||
setup() {}
|
||||
}
|
||||
</script>
|
||||
40
resources/js/Pages/Settings/SettingsLikes.vue
Executable file
40
resources/js/Pages/Settings/SettingsLikes.vue
Executable file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<meta-head title="Понравилось"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div v-if="feeds.length" class="m-4 lg:m-8 grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-4 gap-2 lg:gap-4">
|
||||
<feed :feeds="feeds" :next-cursor="nextCursor" />
|
||||
</div>
|
||||
<div v-else class="m-4 lg:m-8 text-lg text-gray-light">
|
||||
Данных нет
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import Feed from '@/Shared/Feed/Feed.vue'
|
||||
export default {
|
||||
components: {
|
||||
Feed,
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feeds: Array,
|
||||
nextCursor: String,
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
196
resources/js/Pages/Settings/SettingsMoney.vue
Executable file
196
resources/js/Pages/Settings/SettingsMoney.vue
Executable file
@@ -0,0 +1,196 @@
|
||||
|
||||
|
||||
<template>
|
||||
<meta-head title="Доход"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
<div class="col-span-4">
|
||||
<div class="border-b border-indigo-300">
|
||||
<div class="py-5 px-4 2xl:px-28">
|
||||
<a rel="noopener noreferrer nofollow" target="_blank"
|
||||
href="https://yoomoney.ru/pay/page?id=526623"
|
||||
>
|
||||
<svg class="h-10 mb-5" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1440 320"
|
||||
><defs></defs><path fill="#fff" d="M1265.12,160.92c0-39.25-29.7-71-71.65-71-41.12,0-70.4,30.53-70.4,72.48,0,42.16,30.11,72.9,73.31,72.9,27.41,0,50.46-12.26,64.79-37.18l-29.07-13.5c-6.85,12.46-21.81,20.56-35.72,20.56-17.65,0-38-13.29-39.25-31.77h106.95A77.88,77.88,0,0,0,1265.12,160.92Zm-107.37-12c2.5-18.69,15.37-30.94,35.93-30.94,20.35,0,32.81,12,34.47,30.94Z" /><path fill="#fff" d="M864.79,89.89c-42.16,0-72.06,30.33-72.06,73.11,0,42.36,29.9,72.27,72.48,72.27,42.78,0,72.68-29.91,72.68-71.65C937.89,120.63,907.16,89.89,864.79,89.89Zm.42,113.6c-21.19,0-34.27-15.78-34.27-40.29,0-24.92,13.29-41.95,34.27-41.95,21.18,0,34.47,16.82,34.47,41.95C899.68,187.92,886.6,203.49,865.21,203.49Z" /><path fill="#fff" d="M714.67,89.93c-17.27,0-30.28,5.9-43.29,20.53h-4.27c-8.74-13.21-23-20.53-39-20.53A50.44,50.44,0,0,0,587,110.46h-4.27l-.21-.2V92.78H546.78V232.22H585.4V160.46c0-23.17,9.76-36.79,26.63-36.79,15.65,0,25.81,11.59,25.81,29.27v79.28h38.82V160.46c0-23,9.76-36.79,26.84-36.79,15.44,0,25.4,11.59,25.4,29.48v79.07h38.83V148.47C767.73,112.09,748.21,89.93,714.67,89.93Z" /><path fill="#fff" d="M1351,183.86h-2.76c0-2.48-16.84-46.64-27.88-74l-6.9-17.11h-38.91l55.2,139.93L1308,286h38.64l79.49-193.2H1387.2l-6.35,14.63-21.53,53.27C1353.25,175.58,1350.76,182.48,1351,183.86Z" /><path fill="#fff" d="M1045,89.93c-17.28,0-32.12,7.52-42.08,20.94h-4.07l-.4-.2V92.78H962.69V232.22h38.62V160.87c0-22.76,11-37,29.07-37,16.87,0,29.27,12.6,29.27,32.52v75.82h38.62V148.07C1098.27,113.92,1075.91,89.93,1045,89.93Z" /><path fill="#8b3ffd" d="M284.49,10C201.05,10,134,77.5,134,160c0,83.18,67.71,150,150.47,150S435,242.5,435,160,367.25,10,284.49,10Zm0,205.91c-30.78,0-56.08-25.23-56.08-55.91s25.3-55.91,56.08-55.91,56.09,25.23,56.09,55.91C339.89,190.68,315.27,215.91,284.49,215.91Z" /><path fill="#8b3ffd" d="M134,53.58V271.76H80.68L12.28,53.58Z" /></svg>
|
||||
</a>
|
||||
|
||||
<form method="GET" class="flex"
|
||||
action="/actions-to-payments"
|
||||
>
|
||||
<input v-model="sum" type="number"
|
||||
name="sum"
|
||||
placeholder="Введите сумму"
|
||||
class="w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
/>
|
||||
<button type="submit" class="ml-5 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-pink focus:outline-none">
|
||||
Пополнить
|
||||
</button>
|
||||
</form>
|
||||
<p v-show="sum" class="text-white mt-1">
|
||||
ИТОГО + 3.5% комиссия сервиса yoomoney = {{ amount }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between px-4 2xl:px-28 my-4 lg:my-8">
|
||||
<div class="flex flex-col text-white">
|
||||
<span class="text-lg">Баланс:</span>
|
||||
<span class="text-3xl">{{ $page.props.balance }}</span>
|
||||
</div>
|
||||
<inertia-link :href="route('setting.payouts')" type="button"
|
||||
class="my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
>
|
||||
Вывести
|
||||
</inertia-link>
|
||||
</div>
|
||||
<div class="text-white flex justify-end gap-3 flex-wrap pb-2 px-4 2xl:px-28">
|
||||
<div class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16"
|
||||
height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="feather feather-check-square"
|
||||
><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>
|
||||
Ознакомлен:
|
||||
</div>
|
||||
<p>
|
||||
<a class="underline" rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
href="https://yoomoney.ru/pay/page?id=526623"
|
||||
>Yoomoney</a>
|
||||
</p>
|
||||
<inertia-link class="underline" :href="route('setting.tarif')">
|
||||
Тарифы
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="pointLists.length" class="my-4 lg:my-8">
|
||||
<div class="py-4 px-4 2xl:px-28 md:text-lg text-white">
|
||||
История операций:
|
||||
<!-- <button class="text-xs" @click="testPaid">
|
||||
Пополнить
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="divide-y divide-indigo-300 ">
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="point in pointLists" :key="point.id"
|
||||
:ref="el => { if (el && point.id === lastElementID) lastNodeLement = el }"
|
||||
class="py-4 px-4 2xl:px-28 lg:items-center flex flex-col lg:grid gap-2 lg:gap-5 grid-cols-12"
|
||||
>
|
||||
<div class="col-span-5 flex items-center">
|
||||
<div class="flex-shrink-0 mr-3 lg:mr-8">
|
||||
<svg :class="[point.direction == 2 ? 'text-white rotate-180' : point.direction == 0 ? 'text-green rotate-90' : 'text-red -rotate-90', 'transform w-8 h-8']">
|
||||
<use xlink:href="#arrow-up-circle"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<p class="truncate text-base lg:text-lg font-semibold text-gray">
|
||||
{{ point.date }}
|
||||
</p>
|
||||
<p class="truncate lg:mt-1 text-base text-gray-light">
|
||||
{{ point.time }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 text-left lg:text-center text-white xl:text-xl font-semibold">
|
||||
{{ point.point }}
|
||||
</div>
|
||||
<div class="col-span-5 text-left lg:text-right text-gray-light xl:text-lg">
|
||||
{{ point.type }}
|
||||
|
||||
<div v-if="point.direction == 2">
|
||||
<button class="text-green underline" @click="checkPaid(point.id)">
|
||||
Проверить оплату
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="py-4 px-4 2xl:px-28 md:text-lg text-gray-light">
|
||||
Нет данных
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
InfinityScroll,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: String,
|
||||
points: Array,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
pointLists: [],
|
||||
sum: null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
amount(){
|
||||
return ((Math.abs(this.sum) * 0.035) + Math.abs(this.sum)).toFixed(1)
|
||||
},
|
||||
lastElementID() {
|
||||
return this.pointLists[this.pointLists.length - 1]?.id
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
points(points){
|
||||
this.pointLists = points
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.pointLists = this.points
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.pointLists.push(list)
|
||||
}
|
||||
},
|
||||
checkPaid(id) {
|
||||
Inertia.post(route('payouts.checkPointPay', id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
105
resources/js/Pages/Settings/SettingsNotify.vue
Executable file
105
resources/js/Pages/Settings/SettingsNotify.vue
Executable file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<meta-head title="Оповещения"></meta-head>
|
||||
|
||||
<modal-feed
|
||||
:modal-feed="modalFeed"
|
||||
:is_exist_menu="false"
|
||||
:open="show"
|
||||
@close-modal="closeModal"
|
||||
/>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div v-if="notifications.length" data-simplebar
|
||||
class="max-h-[500px] overflow-auto my-4 lg:my-8"
|
||||
>
|
||||
<div class="divide-y divide-indigo-300">
|
||||
<div v-for="notification in notifications" :key="notification.id"
|
||||
class="py-4 px-4 2xl:px-28 lg:items-center flex flex-col lg:grid gap-2 lg:gap-5 grid-cols-12 md:space-x-4"
|
||||
>
|
||||
<div class="col-span-4 flex items-center">
|
||||
<inertia-link :href="route('profile.user', notification.user.username)" class="flex-shrink-0 block mr-5">
|
||||
<user-avatar :user="notification.user" size="small"
|
||||
class="w-14 h-14 md:w-20 md:h-20 text-xl"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', notification.user.username)" class="flex flex-col">
|
||||
<p class="truncate text-base lg:text-lg font-semibold text-orange">
|
||||
{{ notification.user.name }}
|
||||
</p>
|
||||
<p class="truncate lg:mt-1 text-base text-gray-light">
|
||||
{{ notification.created_at }}
|
||||
</p>
|
||||
</inertia-link>
|
||||
</div>
|
||||
<div class="col-span-6 text-white xl:text-xl">
|
||||
<notify-text
|
||||
:type="notification.type"
|
||||
:content="notification.data"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="notification.feed" class="col-span-2 flex lg:justify-end">
|
||||
<feed-preview class="w-20 h-20 object-cover cursor-pointer"
|
||||
:type="notification.feed.type"
|
||||
:source="notification.feed.entity.preview"
|
||||
@click="openModal(notification.feed)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="m-4 lg:m-8 text-lg text-gray-light">
|
||||
Оповещений нет
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import ModalFeed from '@/Shared/Overlay/ModalFeed.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
import NotifyText from '@/Shared/Notification/NotifyText.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
ModalFeed,
|
||||
UserAvatar,
|
||||
FeedPreview,
|
||||
NotifyText,
|
||||
},
|
||||
layout: Layout,
|
||||
|
||||
props: {
|
||||
notifications: Array,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
modalFeed: {},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
openModal(feed) {
|
||||
this.show = true
|
||||
this.modalFeed = feed
|
||||
},
|
||||
closeModal() {
|
||||
this.show = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
174
resources/js/Pages/Settings/SettingsPacket.vue
Executable file
174
resources/js/Pages/Settings/SettingsPacket.vue
Executable file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<meta-head title="Настроить оплату за подписку"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div class="mx-4 2xl:mx-20 my-8">
|
||||
<form v-if="user.private" class="border-b border-indigo-400 pb-10 mt-10 grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-7 gap-5 lg:gap-10 items-start"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<div class="2xl:col-span-3 flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Цена подписки"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div class="lg:col-span-2 2xl:col-span-1 2xl:mt-8">
|
||||
<loading-button :loading="form.processing" class="transition shadow-none hover:shadow-classic2 inline-flex items-center px-3 py-2.5 w-full justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Обновить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
|
||||
<div v-if="buyers.length || subscriptions.length" class="grid md:grid-cols-2 gap-3">
|
||||
<div v-if="buyers.length">
|
||||
<h2 class="mt-5 text-base text-gray-light font-medium">
|
||||
Ваши платные подписчики
|
||||
</h2>
|
||||
<div class="mt-3 space-y-4">
|
||||
<div v-for="buyer in buyers" :key="buyer.id"
|
||||
class="bg-indigo-300 flex flex-col p-4 rounded-sm"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<inertia-link :href="route('profile.user', buyer.user.username)" class="flex-shrink-0 block mr-2 md:mr-4">
|
||||
<user-avatar :user="buyer.user" size="small"
|
||||
class="w-10 h-10 md:w-14 md:h-14 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', buyer.user.username)" class="flex flex-col">
|
||||
<span class="hover:underline text-sm md:text-base block font-medium text-white">{{ buyer.user.first_name }} {{ buyer.user.last_name }}</span>
|
||||
</inertia-link>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="list in buyer.lists" :key="list.time_end">
|
||||
<span :class="[list.active ? 'text-green' : 'text-orange']" class="text-xs">
|
||||
Дата окончания подписки: {{ list.time_end }}{{ list.active ? ' - активно' : '' }}. Цена {{ list.price }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="subscriptions.length">
|
||||
<h2 class="mt-5 text-base text-gray-light font-medium">
|
||||
Ваши платные подписки
|
||||
</h2>
|
||||
<div class="mt-3 space-y-4">
|
||||
<div v-for="subscription in subscriptions" :key="subscription.id"
|
||||
class="bg-indigo-300 flex flex-col p-4 rounded-sm"
|
||||
>
|
||||
<toggle
|
||||
:user_id="subscription.user.id"
|
||||
:enabled="subscription.autosubscription"
|
||||
textin="Автоматическое списание отключено"
|
||||
textout="Автоматическое списание включено" @clicked="makeAutoSubsUser"
|
||||
/>
|
||||
<div class="mt-3 flex items-center gap-1">
|
||||
<inertia-link :href="route('profile.user', subscription.user.username)" class="flex-shrink-0 block mr-2 md:mr-4">
|
||||
<user-avatar :user="subscription.user" size="small"
|
||||
class="w-10 h-10 md:w-14 md:h-14 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', subscription.user.username)" class="flex flex-col">
|
||||
<span class="hover:underline text-sm md:text-base block font-medium text-white">{{ subscription.user.first_name }} {{ subscription.user.last_name }}</span>
|
||||
</inertia-link>
|
||||
|
||||
<div v-if="subscription.active === false" class="ml-auto text-gray-light text-xs">
|
||||
<button class="hover:underline" @click="removePaidUser(subscription.user.id)">
|
||||
Отписаться от пользователя
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="list in subscription.lists" :key="list.time_end">
|
||||
<span :class="[list.active ? 'text-green' : 'text-orange']" class="text-xs">
|
||||
Дата окончания подписки: {{ list.time_end }}{{ list.active ? ' - активно' : '' }}. Цена {{ list.price }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-10 text-gray-light text-lg">
|
||||
Данных нет!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { toRefs } from 'vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import Toggle from '@/Shared/Form/Toggle.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
LoadingButton,
|
||||
TextInput,
|
||||
UserAvatar,
|
||||
Toggle
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
user: Object,
|
||||
packet: Object,
|
||||
buyers: Array,
|
||||
subscriptions: Array,
|
||||
},
|
||||
setup(props) {
|
||||
const { user, packet } = toRefs(props)
|
||||
const form = useForm({
|
||||
id: packet.value?.id,
|
||||
price: packet.value?.price,
|
||||
user: user.value.id,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('user.package.update'))
|
||||
}
|
||||
|
||||
const removePaidUser = (id) => {
|
||||
Inertia.post(route('users.removePaidSubs', id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
}
|
||||
const makeAutoSubsUser = (id) => {
|
||||
Inertia.post(route('users.settingsPaidSubsUser', id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return { form, submit, removePaidUser, makeAutoSubsUser }
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
198
resources/js/Pages/Settings/SettingsPayouts.vue
Executable file
198
resources/js/Pages/Settings/SettingsPayouts.vue
Executable file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<meta-head title="Выплаты"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
<div class="col-span-4">
|
||||
<div class="border-b border-indigo-300">
|
||||
<div v-if="isVerified" class="py-5 px-4 2xl:px-28">
|
||||
<div class="flex">
|
||||
<input v-model="amount" type="number"
|
||||
placeholder="Введите сумму"
|
||||
class="w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
/>
|
||||
<button class="ml-5 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-pink focus:outline-none" @click="pay">
|
||||
Вывести
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="py-5 px-4 2xl:px-28">
|
||||
<div class="text-white text-lg">
|
||||
Заполните номер телефона в <inertia-link :href="route('setting.index')" class="underline">
|
||||
профиле
|
||||
</inertia-link> и пройдите <inertia-link :href="route('setting.verification')" class="underline">
|
||||
верификацию
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="py-5 px-4 2xl:px-28">
|
||||
<div class="flex">
|
||||
<input v-model="bank_info.number" type="number"
|
||||
placeholder="Введите номер карты"
|
||||
class="w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Фамилия"
|
||||
/>
|
||||
<button class="ml-5 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-pink focus:outline-none" @click="updateRequisites">
|
||||
{{ bank_info?.id ? 'Обновить' : 'Создать' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-red text-sm">
|
||||
Банковские реквизиты
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="flex justify-between px-4 2xl:px-28 my-4 lg:my-8">
|
||||
<div class="flex flex-col text-white">
|
||||
<span class="text-lg">Баланс:</span>
|
||||
<span class="text-3xl">{{ $page.props.balance }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="withdrawalsLists.length" class="my-4 lg:my-8">
|
||||
<div class="py-4 px-4 2xl:px-28 md:text-lg text-white">
|
||||
История операций:
|
||||
</div>
|
||||
<div class="divide-y divide-indigo-300 ">
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="withdrawal in withdrawalsLists" :key="withdrawal.id"
|
||||
:ref="el => { if (el && withdrawal.id === lastElementID) lastNodeLement = el }"
|
||||
class="py-4 px-4 2xl:px-28 lg:items-center flex flex-col lg:grid gap-2 lg:gap-5 grid-cols-12"
|
||||
>
|
||||
<div class="col-span-4 flex items-center">
|
||||
<div class="flex-shrink-0 mr-3 lg:mr-8">
|
||||
<svg class="transform w-8 h-8 rotate-90" :class="{
|
||||
'text-gray-light': withdrawal.status === 'pending',
|
||||
'text-green': withdrawal.status === 'success',
|
||||
'text-red': withdrawal.status === 'cancel',
|
||||
}"
|
||||
>
|
||||
<use xlink:href="#arrow-up-circle"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<p class="truncate text-base lg:text-lg font-semibold text-gray">
|
||||
{{ withdrawal.datePart }}
|
||||
</p>
|
||||
<p class="truncate lg:mt-1 text-base text-gray-light">
|
||||
{{ withdrawal.timePart }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 text-left lg:text-center text-white xl:text-xl font-semibold">
|
||||
{{ withdrawal.amount }}
|
||||
</div>
|
||||
<div class="col-span-6 text-left lg:text-right text-gray-light xl:text-lg flex flex-col">
|
||||
<span v-html="withdrawal.history_payment_details"></span>
|
||||
<span class="mt-2 text-orange">{{ withdrawal.meta }}</span>
|
||||
<span v-show="withdrawal.description" class="mt-2">{{ withdrawal.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="py-4 px-4 2xl:px-28 md:text-lg text-gray-light">
|
||||
Нет данных
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { ref } from 'vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
InfinityScroll,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isVerified: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
withdrawals: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// requisites: {
|
||||
// type: Object,
|
||||
// default: () => {}
|
||||
// }
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
withdrawalsLists: [],
|
||||
bank_info: {},
|
||||
amount: null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastElementID() {
|
||||
return this.withdrawalsLists[this.withdrawalsLists.length - 1]?.id
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
withdrawals(withdrawals){
|
||||
this.withdrawalsLists = withdrawals
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.withdrawalsLists = this.withdrawals
|
||||
// this.bank_info = this.requisites
|
||||
},
|
||||
|
||||
methods: {
|
||||
// updateRequisites() {
|
||||
// if(!this.bank_info?.number) return
|
||||
// Inertia.post(route('requisites.bank'), {
|
||||
// number: this.bank_info.number,
|
||||
// })
|
||||
// },
|
||||
pay() {
|
||||
if(!this.amount) return
|
||||
const that = this
|
||||
Inertia.post(route('payouts.start'), {
|
||||
amount: that.amount,
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
that.amount = null
|
||||
}
|
||||
})
|
||||
},
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.withdrawalsLists.push(list)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
358
resources/js/Pages/Settings/SettingsProfile.vue
Executable file
358
resources/js/Pages/Settings/SettingsProfile.vue
Executable file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<meta-head title="Мои настройки"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div class="m-4 lg:m-8">
|
||||
<div class="flex flex-col xl:flex-row">
|
||||
<div class="xl:mr-24">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 mr-5">
|
||||
<user-avatar :user="user" class="w-20 h-20 text-lg" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<p class="text-base lg:text-xl font-semibold text-gray">
|
||||
{{ user.name }}
|
||||
</p>
|
||||
<p class="lg:mt-1 text-base text-gray-light">
|
||||
@{{ user.username }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<input ref="fileavatar" class="hidden"
|
||||
accept="image/png, image/jpeg, image/jpg" type="file"
|
||||
@change="previewFiles"
|
||||
>
|
||||
<input ref="filebanner" class="hidden"
|
||||
accept="image/png, image/jpeg, image/jpg" type="file"
|
||||
@change="previewFilesBanner"
|
||||
>
|
||||
<div>
|
||||
<div v-if="$page.props.errors.avatar" class="text-red lg:text-lg">
|
||||
{{ $page.props.errors.avatar }}
|
||||
</div>
|
||||
<button v-if="!user.photo_path" class="hover:underline mt-2 text-left lg:mt-5 default lg:text-lg text-orange"
|
||||
@click="changeAvatar"
|
||||
>
|
||||
Изменить фото профиля
|
||||
</button>
|
||||
<button v-else class="hover:underline mt-2 text-left lg:mt-5 default lg:text-lg text-red"
|
||||
@click="removeAvatar"
|
||||
>
|
||||
Удалить фото
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 xl:mt-0">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 mr-5">
|
||||
<user-banner class="w-60 lg:w-96 h-20 rounded-xl bg-indigo-300" :user="user"
|
||||
size="banner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="$page.props.errors.banner" class="text-red lg:text-lg">
|
||||
{{ $page.props.errors.banner }}
|
||||
</div>
|
||||
<button v-if="!user.banner_path" class="hover:underline mt-2 text-left lg:mt-5 default lg:text-lg text-orange"
|
||||
@click="changeBanner"
|
||||
>
|
||||
Изменить баннер профиля
|
||||
</button>
|
||||
<button v-else class="hover:underline mt-2 text-left lg:mt-5 default lg:text-lg text-red"
|
||||
@click="removeBanner"
|
||||
>
|
||||
Удалить баннер
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="border-b border-indigo-400 pb-10 mt-10 grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-7 gap-5 lg:gap-10 items-start" @submit.prevent="submitPassword">
|
||||
<div class="2xl:col-span-3 flex flex-col">
|
||||
<text-input v-model="passwordForm.old_password" :error="passwordForm.errors.old_password"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Старый пароль"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div class="2xl:col-span-3 flex flex-col">
|
||||
<text-input v-model="passwordForm.new_password" :error="passwordForm.errors.new_password"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Новый пароль"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div class="lg:col-span-2 2xl:col-span-1 2xl:mt-8">
|
||||
<loading-button :loading="form.processing" class="transition shadow-none hover:shadow-classic2 inline-flex items-center px-3 py-2.5 w-full justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Обновить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="mt-10 grid gap-4 lg:gap-10 grid-cols-1 lg:grid-cols-2" @submit.prevent="submit">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.first_name" :error="form.errors.first_name"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Имя"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.last_name" :error="form.errors.last_name"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Фамилия"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.username" :error="form.errors.username"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light" label="Тег"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.date_of_birth" :error="form.errors.date_of_birth"
|
||||
type="date" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Дата рождения"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.email" :error="form.errors.email"
|
||||
type="email" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Почта"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.phone" :error="form.errors.phone"
|
||||
type="tel" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Телефон"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.inn" :error="form.errors.inn"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="ИНН"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.checking_account" :error="form.errors.checking_account"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Расчетный счет"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.bik" :error="form.errors.bik"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Бик банка"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Пол
|
||||
</div>
|
||||
<div class="flex space-x-6">
|
||||
<div class="flex items-center">
|
||||
<input id="user-sex-1" v-model="form.sex"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-sex-1" class="select-none ml-3 text-gray">Женский</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="user-sex-2" v-model="form.sex"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-sex-2" class="select-none ml-3 text-gray">Мужской</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип
|
||||
</div>
|
||||
<div class="flex space-x-6">
|
||||
<div class="flex items-center">
|
||||
<input id="user-type-1" v-model="form.type"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-type-1" class="select-none ml-3 text-gray">Физ. лицо</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="user-type-2" v-model="form.type"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-type-2" class="select-none ml-3 text-gray">Самозанятый</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="user-type-3" v-model="form.type"
|
||||
value="3" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-type-3" class="select-none ml-3 text-gray">Юридическое лицо</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="user-type-4" v-model="form.type"
|
||||
value="4" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-type-4" class="select-none ml-3 text-gray">ИП</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.about" label="О себе"
|
||||
:error="form.errors.about" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md"
|
||||
cols="30" rows="2"
|
||||
></textarea-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<!-- <div class="text-gray-light text-lg mb-2">
|
||||
Ограничить доступ
|
||||
</div> -->
|
||||
<div class="flex items-center">
|
||||
<input id="user-private" v-model="form.private"
|
||||
type="checkbox" class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-private" class="select-none ml-3 text-gray">Включить эксклюзивный акаунт, доступ к контенту только по подписке</label>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="user-commercial" v-model="form.allow_adult_content"
|
||||
type="checkbox" class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="user-commercial" class="select-none ml-3 text-gray">Разрешить контент для взрослых</label>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap -my-1 -mx-3">
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Сохранить
|
||||
</loading-button>
|
||||
<!-- <button type="button"
|
||||
class="mx-3 my-1 default text-lg text-orange"
|
||||
>
|
||||
Удалить аккаунт
|
||||
</button> -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import UserBanner from '@/Shared/Misc/UserBanner.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
UserAvatar,
|
||||
UserBanner,
|
||||
TextInput,
|
||||
TextareaInput,
|
||||
LoadingButton,
|
||||
SettingsMenu,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
user: Object,
|
||||
},
|
||||
setup(props) {
|
||||
const { user } = toRefs(props)
|
||||
const form = useForm({
|
||||
first_name: user.value.first_name,
|
||||
last_name: user.value.last_name,
|
||||
username: user.value.username,
|
||||
email: user.value.email,
|
||||
phone: user.value.phone,
|
||||
sex: user.value.sex,
|
||||
type: user.value.type,
|
||||
date_of_birth: user.value.date_of_birth,
|
||||
about: user.value.about,
|
||||
private: user.value.private,
|
||||
inn: user.value.inn,
|
||||
checking_account: user.value.checking_account,
|
||||
bik: user.value.bik,
|
||||
password: null,
|
||||
})
|
||||
|
||||
const passwordForm = useForm({
|
||||
old_password: null,
|
||||
new_password: null,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.put(route('users.update', user.value.id))
|
||||
}
|
||||
const submitPassword = () => {
|
||||
passwordForm.put(route('users.update.password', user.value.id), {
|
||||
onSuccess: () => passwordForm.reset(),
|
||||
})
|
||||
}
|
||||
|
||||
return { form, passwordForm, submit, submitPassword }
|
||||
},
|
||||
methods: {
|
||||
previewFilesBanner(event){
|
||||
const file = event.target.files[0]
|
||||
Inertia.post(route('image.banner'), {
|
||||
_method: 'put',
|
||||
banner: file,
|
||||
})
|
||||
},
|
||||
previewFiles(event){
|
||||
const file = event.target.files[0]
|
||||
Inertia.post(route('image.avatar'), {
|
||||
_method: 'put',
|
||||
avatar: file,
|
||||
})
|
||||
},
|
||||
changeBanner(){
|
||||
this.$refs.filebanner.click()
|
||||
},
|
||||
removeBanner(){
|
||||
Inertia.post(route('image.banner.remove'), {
|
||||
_method: 'delete',
|
||||
})
|
||||
},
|
||||
changeAvatar(){
|
||||
this.$refs.fileavatar.click()
|
||||
},
|
||||
removeAvatar(){
|
||||
Inertia.post(route('image.avatar.remove'), {
|
||||
_method: 'delete',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
96
resources/js/Pages/Settings/SettingsPurchases.vue
Executable file
96
resources/js/Pages/Settings/SettingsPurchases.vue
Executable file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<meta-head title="Покупки"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div v-if="feedLists.length" class="m-4 lg:m-8 grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-4 gap-2 lg:gap-4">
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="feed in feedLists" :key="feed.id"
|
||||
:ref="el => { if (el && feed.id === lastElementID) lastNodeLement = el }"
|
||||
>
|
||||
<inertia-link :href="route('setting.show.purchases', feed.id)" class="block contain relative overflow-hidden">
|
||||
<feed-header-misc :count="1" :type="feed.type" />
|
||||
<div>
|
||||
<feed-preview class="w-full h-36 md:h-72 object-cover" :type="feed.type"
|
||||
:source="feed.preview"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-gray-light text-sm">
|
||||
Цена: {{ feed.price }}
|
||||
</p>
|
||||
<p class="mt-2 text-gray-light text-sm">
|
||||
Дата покупки: {{ feed.purchase_date }}
|
||||
</p>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</div>
|
||||
<div v-else class="m-4 lg:m-8 text-lg text-gray-light">
|
||||
Вы еще не совершали покупки
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import FeedHeaderMisc from '@/Shared/Feed/HeaderMisc.vue'
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
FeedHeaderMisc,
|
||||
FeedPreview,
|
||||
InfinityScroll,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: String,
|
||||
feeds: Array,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
feedLists: [],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastElementID() {
|
||||
return this.feedLists[this.feedLists.length - 1]?.id
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.feedLists = this.feeds
|
||||
},
|
||||
|
||||
methods: {
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.feedLists.push(list)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
106
resources/js/Pages/Settings/SettingsPurchasesFile.vue
Executable file
106
resources/js/Pages/Settings/SettingsPurchasesFile.vue
Executable file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<meta-head title="Скачать медиа контент"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4 p-5">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<inertia-link :href="route('setting.purchases')" class="block hover:underline">
|
||||
Вернуться
|
||||
</inertia-link>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Скачать медиа контент
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="my-8 flex items-center justify-between">
|
||||
<inertia-link :href="route('profile.user', seller.username)" class="flex items-center">
|
||||
<div class="flex-shrink-0 block mr-2 md:mr-4">
|
||||
<user-avatar :user="seller" size="small"
|
||||
class="w-10 h-10 md:w-16 md:h-16 text-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm md:text-base block font-medium text-white">{{ seller.name }}</span>
|
||||
<span class="text-xs text-gray-light">продавец</span>
|
||||
</div>
|
||||
</inertia-link>
|
||||
|
||||
<div class="text-right">
|
||||
<p class="mt-2 text-gray-light text-sm">
|
||||
Цена: {{ purchase.price }}
|
||||
</p>
|
||||
<p class="mt-2 text-gray-light text-sm">
|
||||
Дата покупки: {{ purchase.purchase_date }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div v-if="purchase.title" class="text-lg text-gray font-semibold">
|
||||
{{ purchase.title }}
|
||||
</div>
|
||||
<div v-if="purchase.body" class="text-lg mt-4 text-gray-light"
|
||||
v-html="purchase.body"
|
||||
></div>
|
||||
|
||||
<div class="mt-4">
|
||||
<component
|
||||
:is="currentTypeNode"
|
||||
:purchase="purchase"
|
||||
></component>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 flex -mx-3 -my-1 flex-wrap">
|
||||
<a :href="route('download.purchases', purchase.id)" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none">
|
||||
Скачать архив
|
||||
</a>
|
||||
<inertia-link :href="route('setting.purchases')"
|
||||
class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none"
|
||||
>
|
||||
Вернуться
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import PurchaseImages from '@/Shared/Purchase/PurchaseImages.vue'
|
||||
import PurchaseMusics from '@/Shared/Purchase/PurchaseMusics.vue'
|
||||
import PurchaseVideos from '@/Shared/Purchase/PurchaseVideos.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
PurchaseImages,
|
||||
PurchaseMusics,
|
||||
PurchaseVideos,
|
||||
SettingsMenu,
|
||||
UserAvatar,
|
||||
},
|
||||
layout: Layout,
|
||||
|
||||
props: {
|
||||
purchase: Object,
|
||||
seller: Object,
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentTypeNode() {
|
||||
return 'purchase-' + this.purchase.type
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
115
resources/js/Pages/Settings/SettingsTarif.vue
Executable file
115
resources/js/Pages/Settings/SettingsTarif.vue
Executable file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<meta-head title="Тарифы"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div class="mx-4 2xl:mx-28 my-8">
|
||||
<h2 class="text-xl lg:text-2xl xl:text-4xl text-white font-semibold">
|
||||
Выберите тариф, который подходит именно вам
|
||||
</h2>
|
||||
<p class="mt-4 lg:text-xl xl:text-2xl text-white">
|
||||
Более 1 млн фотографий, видео и музыки в постоянном доступе для вас!
|
||||
</p>
|
||||
<p v-if="lastSubscription" class="mt-4 xl:text-xl text-green">
|
||||
Дата окончания подписки:
|
||||
<span class="block md:inline-block">{{ lastSubscription.endDateTime }} ({{ lastSubscription.package.name }})</span>
|
||||
<span v-if="!is_active_sub" class="text-pink"> (Подписка закончилась)</span>
|
||||
</p>
|
||||
<div class="rounded-xl bg-indigo-300 px-3 py-4 mt-5">
|
||||
<toggle
|
||||
:user_id="user_id"
|
||||
:enabled="user_autosubscription"
|
||||
textin="Автоматическое списание отключено"
|
||||
textout="Автоматическое списание включно" @clicked="makeAutoSubs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-for="plan in plans" :key="plan.id"
|
||||
:class="[is_active_sub && lastSubscription?.package.id === plan.id ? 'border-l-4 lg:border-l-8 border-orange' : '' ,'mt-7 p-7 xl:mt-14 xl:p-14 shadow-classic rounded-md bg-indigo-100']"
|
||||
>
|
||||
<div class="flex flex-col lg:grid grid-cols-6 gap-3 items-center">
|
||||
<div class="col-start-1 col-end-4 flex flex-col items-center lg:items-start text-center lg:text-left">
|
||||
<span class="text-xl md:text-2xl lg:text-3xl xl:text-4xl text-white font-medium">{{ plan.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-start-5 col-end-7 flex flex-col items-center">
|
||||
<span class="text-3xl lg:text-4xl xl:text-6xl text-white font-bold">{{ plan.price }}</span>
|
||||
<button :disabled="offerCheck == false" type="button"
|
||||
:class="[is_active_sub && lastSubscription?.package.id === plan.id ? 'bg-white border border-green text-green' : 'text-white bg-green' , 'mt-4 my-1 transition shadow-none inline-flex items-center px-8 py-3 justify-center text-base rounded-3xl focus:outline-none disabled:bg-gray-light disabled:cursor-not-allowed']"
|
||||
@click="subsPlan(plan.id)"
|
||||
>
|
||||
<span v-if="is_active_sub && lastSubscription?.package.id === plan.id">
|
||||
активен
|
||||
</span>
|
||||
<span v-else>
|
||||
купить тариф
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex items-center">
|
||||
<input id="offer_check" v-model="offerCheck"
|
||||
type="checkbox" class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="offer_check" class="select-none ml-3 text-gray text-lg md:text-xl">Согласен с условиями
|
||||
<a href="/docs/offer_paid_subscription.pdf" class="underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>оферты</a></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
import Toggle from '@/Shared/Form/Toggle.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
Toggle,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
plans: Array,
|
||||
is_active_sub: Boolean,
|
||||
user_autosubscription: Boolean,
|
||||
user_id: Number,
|
||||
lastSubscription: Object,
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
offerCheck: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
makeAutoSubs(id){
|
||||
Inertia.patch(route('users.settingsAutoSubs', id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
},
|
||||
subsPlan(id) {
|
||||
Inertia.post(route('users.plan', id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
178
resources/js/Pages/Settings/SettingsVerification.vue
Executable file
178
resources/js/Pages/Settings/SettingsVerification.vue
Executable file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<meta-head title="Верификация"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div class="mx-4 2xl:mx-28 my-8">
|
||||
<h2 class="text-xl lg:text-2xl xl:text-3xl text-white font-semibold">
|
||||
Верификация: паспорт
|
||||
</h2>
|
||||
<div>
|
||||
<div v-if="isPassportVerified">
|
||||
<div class="mt-5 text-green font-semibold text-lg">
|
||||
Аккаунт успешно верифицирован
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<form class="flex flex-col" @submit.prevent="submit">
|
||||
<file-input
|
||||
v-model="form.docs"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.docs"
|
||||
label="Скан 1ой страницы паспорта(.png,.jpg)"
|
||||
/>
|
||||
|
||||
<div class="mt-3 flex flex-wrap">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<loading-button :loading="form.processing" class=" transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-1.5 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Загрузить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="docUuid">
|
||||
<p class="mt-4 text-sm text-white">
|
||||
<a :href="route('users.get.document', {'uuid': docUuid}) " class="underline">
|
||||
<span>Ваш паспорт</span>
|
||||
</a>
|
||||
</p>
|
||||
<p class="text-sm text-white">
|
||||
(Ожидайте верификацию, для замены загрузите скан заново)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <div class="my-5">
|
||||
<hr>
|
||||
</div> -->
|
||||
|
||||
<!-- <h2 class="text-xl lg:text-2xl xl:text-3xl text-white font-semibold">
|
||||
Верификация: телефон
|
||||
</h2>
|
||||
<div v-if="isPhoneVerify" class="mt-5 text-green font-semibold text-lg">
|
||||
Ваш номер успешно верифицирован!
|
||||
</div> -->
|
||||
<!-- <div v-else class="mt-2">
|
||||
<p class=" text-white text-lg">
|
||||
На номер телефона {{ phone }}, будет совершен звонок, нужно ввести 4-х значный код
|
||||
</p>
|
||||
<div v-if="isToken" class="mt-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="token"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Введите код"
|
||||
/>
|
||||
<div>
|
||||
<button type="button"
|
||||
class="text-white bg-orange hover:shadow-classic2 mt-4 my-1 transition shadow-none inline-flex items-center px-8 py-2 justify-center text-base rounded-md"
|
||||
@click="sendVerifyCode"
|
||||
>
|
||||
Отправить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button v-else type="button"
|
||||
class="text-white bg-pink hover:shadow-pink mt-5 my-1 transition shadow-none inline-flex items-center px-8 py-2 justify-center text-base rounded-md"
|
||||
@click="sendCode"
|
||||
>
|
||||
Отправить код
|
||||
</button>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import FileInput from '@/Shared/Form/FileInput.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
FileInput,
|
||||
LoadingButton,
|
||||
TextInput
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
docUuid: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
phone: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isToken: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isPhoneVerify: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isPassportVerified: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const form = useForm({
|
||||
docs: null,
|
||||
})
|
||||
const token = ref('')
|
||||
const submit = () => {
|
||||
if(!form.docs) return
|
||||
form.post(route('users.document'), {
|
||||
onSuccess: () => form.reset(),
|
||||
})
|
||||
}
|
||||
|
||||
const sendCode = () => {
|
||||
Inertia.post(route('users.phone.verify.request'))
|
||||
}
|
||||
const sendVerifyCode = () => {
|
||||
Inertia.post(route('users.phone.verify.token'), {
|
||||
token: token.value,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
sendCode,
|
||||
sendVerifyCode,
|
||||
token,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
90
resources/js/Pages/Settings/SettingsWriteToUs.vue
Executable file
90
resources/js/Pages/Settings/SettingsWriteToUs.vue
Executable file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<meta-head title="Написать нам"></meta-head>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div class="mt-16 shadow-classic rounded-md bg-indigo-200">
|
||||
<div class="flex flex-col md:grid grid-cols-6 lg:grid-cols-5">
|
||||
<settings-menu />
|
||||
|
||||
<div class="col-span-4">
|
||||
<div class="mx-4 2xl:mx-28 my-8">
|
||||
<form class="flex flex-col gap-4" @submit.prevent="submit">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Тема"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Сообщение"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<loading-button :loading="form.processing" class="my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Отправить
|
||||
</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- <div class="mt-10">
|
||||
<div class="flex flex-col text-white">
|
||||
<a href="/company/about" class="hover:underline">О компании</a>
|
||||
<a href="/company/offer" class="hover:underline">Оферта</a>
|
||||
<a href="/company/terms-payment" class="hover:underline">Условия оплаты</a>
|
||||
<a href="/company/terms-service" class="hover:underline">Условия предоставления услуг</a>
|
||||
<a href="/company/cookies" class="hover:underline">Cookies</a>
|
||||
<a href="/company/privacy-policy" class="hover:underline">Privacy Policy</a>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import SettingsMenu from '@/Shared/LayoutParts/SettingsMenu.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
SettingsMenu,
|
||||
LoadingButton,
|
||||
TextInput,
|
||||
TextareaInput,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
|
||||
},
|
||||
setup(props) {
|
||||
const form = useForm({
|
||||
title: null,
|
||||
body: null,
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('common.write-to-us'), {
|
||||
onSuccess: () => form.reset(),
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
144
resources/js/Pages/Tag/Feed.vue
Executable file
144
resources/js/Pages/Tag/Feed.vue
Executable file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<meta-head title="Тег"></meta-head>
|
||||
|
||||
<div class="mb-6 py-3 banner relative bg-center bg-no-repeat bg-cover"
|
||||
style="background-image: url('/image/bg-home.jpg');"
|
||||
>
|
||||
<div class="h-52 flex justify-center items-center text-center">
|
||||
<div class="max-w-4xl text-gray px-3">
|
||||
<h1 class="text-xl md:text-2xl lg:text-3xl xl:text-5xl text-white font-semibold xl:leading-relaxed">
|
||||
#{{ tag.name }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 buttons-filter-line">
|
||||
<div class=" relative mb-5">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center z-[1]">
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-light" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 4a7 7 0 100 14 7 7 0 000-14zm-9 7a9 9 0 1118 0 9 9 0 01-18 0z"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.943 15.943a1 1 0 011.414 0l4.35 4.35a1 1 0 01-1.414 1.414l-4.35-4.35a1 1 0 010-1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.search"
|
||||
class="relative w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light !pl-10 h-14" placeholder="Поиск"
|
||||
type="search"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<inertia-link :href="local_route"
|
||||
:class="[active_filter == 'new' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
Новинки
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link :href="local_route"
|
||||
:data="{ filter: 'hot' }"
|
||||
:class="[active_filter == 'hot' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center shadow-classic rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M14.422 6.243c-3.57-2.172-1.895-5.238-1.824-5.365A.586.586 0 0012.09 0c-1.837 0-3.276.522-4.276 1.552-1.71 1.76-1.63 4.498-1.597 5.667.004.13.007.242.007.325 0 .868.14 1.67.263 2.377.079.456.147.85.16 1.159.012.331-.048.407-.05.41-.01.01-.081.049-.276.049a.657.657 0 01-.53-.235c-.565-.634-.604-2.365-.496-3.307a.586.586 0 00-.582-.654c-1.521 0-2.658 2.488-2.658 4.712 0 1.046.21 2.07.625 3.045a8.081 8.081 0 001.7 2.527C5.894 19.157 7.89 20 10 20c2.119 0 4.114-.83 5.618-2.34a7.892 7.892 0 002.327-5.605c0-2.692-2.107-4.95-3.523-5.812zM10 18.828c-3.671 0-6.773-3.101-6.773-6.773 0-.893.213-1.83.585-2.571.086-.173.175-.322.263-.447.01.967.163 2.284.841 3.046.366.41.852.627 1.405.627.518 0 .912-.156 1.172-.466.486-.578.337-1.436.148-2.524-.115-.66-.245-1.409-.245-2.176 0-.1-.003-.22-.007-.359-.032-1.107-.099-3.412 1.265-4.817.613-.63 1.467-1.014 2.55-1.145-.123.406-.228.92-.232 1.497-.01 1.27.476 3.085 2.841 4.524 1.145.696 2.96 2.618 2.96 4.811A6.78 6.78 0 0110 18.828z"
|
||||
/>
|
||||
</svg>
|
||||
Популярные
|
||||
</inertia-link>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<feed-tags-buttons :slug="tag.slug" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 my-6">
|
||||
<button class="button-default text-gray text-lg" @click="resetFilter">
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div v-if="feeds.length" class="grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<feed
|
||||
:next-cursor="nextCursor"
|
||||
:feeds="feeds"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="font-bold text-xl lg:text-3xl text-gray text-center">
|
||||
Записи не найдены
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import throttle from 'lodash/throttle'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
import Feed from '@/Shared/Feed/Feed.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import FeedTagsButtons from '@/Shared/Misc/FeedTagsButtons.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
FeedTagsButtons,
|
||||
Feed,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feeds: Array,
|
||||
local_route: String,
|
||||
active_filter: String,
|
||||
tag: Object,
|
||||
searchFilters: Object,
|
||||
nextCursor: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
search: this.searchFilters.search,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
Inertia.get(this.local_route, pickBy(this.form), {
|
||||
preserveScroll: true, preserveState: true
|
||||
})
|
||||
}, 500),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetFilter() {
|
||||
Inertia.get(
|
||||
route('feed.tags', this.tag.slug),
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
175
resources/js/Pages/User/Index.vue
Executable file
175
resources/js/Pages/User/Index.vue
Executable file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<meta-head title="Пользователи"></meta-head>
|
||||
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<div class="mb-5">
|
||||
<search-filter v-model="form.search" class="w-full max-w-3xl mr-4"
|
||||
@reset="reset"
|
||||
>
|
||||
<label class="text-xs md:text-base block text-gray mb-2">Пол:</label>
|
||||
<div class="flex space-x-6">
|
||||
<div class="flex items-center">
|
||||
<input id="user-sex-1" v-model="form.sex"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent cursor-pointer"
|
||||
>
|
||||
<label for="user-sex-1" class="cursor-pointer select-none ml-3 text-gray text-xs md:text-base">Женский</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="user-sex-2" v-model="form.sex"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent cursor-pointer"
|
||||
>
|
||||
<label for="user-sex-2" class="cursor-pointer select-none ml-3 text-gray text-xs md:text-base">Мужской</label>
|
||||
</div>
|
||||
</div>
|
||||
</search-filter>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-show="users.length" class="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div
|
||||
v-for="user in userLists"
|
||||
:key="user.id"
|
||||
:ref="el => { if (el && user.id === lastElementID) lastNodeLement = el }"
|
||||
class="user-card relative"
|
||||
>
|
||||
<div v-if="!user.private" class="absolute inset-x-0 top-4 z-10 flex justify-center">
|
||||
<toggle
|
||||
:user_id="user.id"
|
||||
:enabled="user.is_sub"
|
||||
textin="Подписаться"
|
||||
textout="Отписаться" @clicked="susbscribe"
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 bottom-4 z-10 flex justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<inertia-link :href="route('profile.user', user.username)" class="block flex-shrink-0">
|
||||
<user-avatar :user="user" size="small"
|
||||
class="border border-white shadow-classic h-20 w-20 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', user.username)" class="mt-2 block text-white text-sm text-center">
|
||||
<p class="">
|
||||
{{ user.name }}
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
{{ user.username }}
|
||||
</p>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gradient-profile relative overflow-hidden">
|
||||
<user-banner class="h-72 bg-indigo-300" :user="user"
|
||||
size="banner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</div>
|
||||
|
||||
<div v-show="users.length == 0">
|
||||
<p class="text-center md:text-2xl text-gray-light">
|
||||
Пользователи не найдены
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import throttle from 'lodash/throttle'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
import mapValues from 'lodash/mapValues'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import Toggle from '@/Shared/Form/Toggle.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import UserBanner from '@/Shared/Misc/UserBanner.vue'
|
||||
import SearchFilter from '@/Shared/Form/SearchFilter.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
UserAvatar,
|
||||
UserBanner,
|
||||
Toggle,
|
||||
InfinityScroll,
|
||||
SearchFilter,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
nextCursor: String,
|
||||
users: Array,
|
||||
filters: Object,
|
||||
per_page: Number,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userLists: [],
|
||||
form: {
|
||||
search: this.filters.search,
|
||||
sex: this.filters.sex,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lastElementID() {
|
||||
return this.userLists[this.userLists.length - 1]?.id
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
users() {
|
||||
this.updateRequest()
|
||||
},
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
Inertia.get(this.route('users.index'), pickBy(this.form), {
|
||||
preserveState: true,
|
||||
})
|
||||
}, 500),
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateRequest()
|
||||
},
|
||||
|
||||
methods: {
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.userLists.push(list)
|
||||
}
|
||||
},
|
||||
|
||||
susbscribe(user_id) {
|
||||
Inertia.post(
|
||||
route('users.subs', user_id),
|
||||
{},
|
||||
{ preserveScroll: true, preserveState: true }
|
||||
)
|
||||
},
|
||||
updateRequest() {
|
||||
this.userLists = this.users
|
||||
},
|
||||
reset() {
|
||||
this.form = mapValues(this.form, () => null)
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
180
resources/js/Pages/Video/Create.vue
Executable file
180
resources/js/Pages/Video/Create.vue
Executable file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<meta-head title="Загрузить видео"></meta-head>
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
<form class=" bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Загрузка видео
|
||||
</h1>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Название"
|
||||
/>
|
||||
<warning-text />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Описание"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<file-input
|
||||
v-model="form.preview"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.preview"
|
||||
label="Загрузить превью"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<file-input-multiple
|
||||
v-model="form.videos"
|
||||
accept=".mp4"
|
||||
:error="form.errors.videos"
|
||||
label="Выбрать видео"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-gray-light">
|
||||
<TagInput v-model="form.tags" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип контента
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-1" v-model="form.is_paid"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-1" class="select-none ml-3 text-gray">Бесплатный</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-2" v-model="form.is_paid"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-2" class="select-none ml-3 text-gray">Эксклюзивный</label>
|
||||
</div>
|
||||
<div v-if="authUser.private" class="flex items-center">
|
||||
<input id="feed-paid-3" v-model="form.is_paid"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-3" class="select-none ml-3 text-gray">Доступен по личной подписке</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="is_adult_feed" v-model="form.is_adult"
|
||||
type="checkbox"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="is_adult_feed" class="select-none ml-3 text-gray">Контент для взрослых</label>
|
||||
</div> -->
|
||||
<div v-if="form.is_paid == 1" class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
type="number" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Цена"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<file-input-multiple
|
||||
v-model="form.videos_paid"
|
||||
accept=".mp4"
|
||||
:error="form.errors.videos_paid"
|
||||
label="Выбрать видео"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Создать
|
||||
</loading-button>
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import FileInputMultiple from '@/Shared/Form/FileInputMultiple.vue'
|
||||
import FileInput from '@/Shared/Form/FileInput.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import TagInput from '@/Shared/Form/TagInput.vue'
|
||||
import WarningText from '@/Shared/Misc/WarningText.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
TextInput,
|
||||
FileInput,
|
||||
FileInputMultiple,
|
||||
LoadingButton,
|
||||
TextareaInput,
|
||||
LinkBack,
|
||||
TagInput,
|
||||
WarningText,
|
||||
},
|
||||
layout: Layout,
|
||||
|
||||
setup() {
|
||||
// let isFeedPaid = ref(0)
|
||||
// let openPaidBlock = ref(0)
|
||||
|
||||
const form = useForm({
|
||||
title: null,
|
||||
body: null,
|
||||
preview: null,
|
||||
videos: null,
|
||||
price: null,
|
||||
videos_paid: null,
|
||||
tags: [],
|
||||
is_paid: 0,
|
||||
})
|
||||
|
||||
// watch(isFeedPaid, (value) => {
|
||||
// openPaidBlock.value = value
|
||||
// form.is_paid = value
|
||||
// })
|
||||
|
||||
const authUser = computed(() => usePage().props.value.auth.user)
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('videos.store'))
|
||||
}
|
||||
|
||||
return { form, submit, authUser }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
356
resources/js/Pages/Video/Edit.vue
Executable file
356
resources/js/Pages/Video/Edit.vue
Executable file
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<meta-head title="Обновить видео"></meta-head>
|
||||
|
||||
<modal-warning
|
||||
:feed_id="feed.id"
|
||||
:open="modalShow"
|
||||
@action="deleteActionModal"
|
||||
/>
|
||||
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 buttons-filter-line">
|
||||
{{ feed.status }}
|
||||
<div v-if="feed.status != 1" class="bg-indigo-200 shadow-classic rounded-md p-5 mb-10">
|
||||
<p class="text-lg font-medium text-gray">
|
||||
Контент запрещен к публикации, причина:
|
||||
</p>
|
||||
<p v-if="feed.status_note " class="text-gray-light">
|
||||
<ul>
|
||||
<li v-for="textBreak in textsBreak" :key="textBreak">
|
||||
{{ textBreak }}
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p v-else class="text-gray-light">
|
||||
модерация созданного\обновленного контента
|
||||
</p>
|
||||
<p v-if="feed.status == 3" class="pt-3 text-green">
|
||||
Обновления отправлены, ожидайте!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<form class="bg-indigo-200 shadow-classic rounded-md p-5" @submit.prevent="submit">
|
||||
<div class="mb-4 flex items-center text-gray-light text-lg font-medium">
|
||||
<link-back class="default block hover:underline">
|
||||
Вернуться
|
||||
</link-back>
|
||||
<span class="px-3">/</span>
|
||||
<h1 class="text-gray">
|
||||
Редактирование видео
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.title" :error="form.errors.title"
|
||||
type="text" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Название"
|
||||
/>
|
||||
<warning-text />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea-input v-model="form.body" :error="form.errors.body"
|
||||
class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md" cols="30"
|
||||
rows="4" label="Описание"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<file-input
|
||||
v-model="form.preview"
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
:error="form.errors.preview"
|
||||
:label="textPreview()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="mediaPreview" class="border border-indigo-300 p-4 rounded-md">
|
||||
<button type="button"
|
||||
class="flex-shrink-0 px-6 py-2 bg-pink focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:outline-none focus:border-transparent rounded-sm text-sm text-white" @click="removePreview(mediaPreview.id)"
|
||||
>
|
||||
Удалить старое превью
|
||||
</button>
|
||||
<div class="mt-4 flex-shrink-0 self-start overflow-hidden">
|
||||
<img class="object-contain h-48" :src="mediaPreview.url"
|
||||
alt=""
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="border border-indigo-300 flex flex-col p-4 rounded-md">
|
||||
<file-input-multiple
|
||||
v-model="form.videos"
|
||||
accept=".mp4"
|
||||
:error="form.errors.videos"
|
||||
label="Загрузить новое видео"
|
||||
/>
|
||||
<div>
|
||||
<h2 class="mt-3 mb-2 text-lg text-gray-light select-none">
|
||||
Загруженные видео:
|
||||
</h2>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<created-media-item
|
||||
v-for="mediaCommon in mediasCommon"
|
||||
:key="mediaCommon.id" :type="feed.type"
|
||||
:media="mediaCommon"
|
||||
@addRemoveId="stateToggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-gray-light">
|
||||
<TagInput v-model="form.tags" />
|
||||
</div>
|
||||
<div v-if="isFreeFeed">
|
||||
<div class="text-gray-light text-lg mb-2">
|
||||
Тип контента
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-1" v-model="form.is_paid"
|
||||
value="0" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-1" class="select-none ml-3 text-gray">Бесплатный</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="feed-paid-2" v-model="form.is_paid"
|
||||
value="1" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-2" class="select-none ml-3 text-gray">Эксклюзивный</label>
|
||||
</div>
|
||||
<div v-if="authUser.private" class="flex items-center">
|
||||
<input id="feed-paid-3" v-model="form.is_paid"
|
||||
value="2" type="radio"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="feed-paid-3" class="select-none ml-3 text-gray">Доступен по личной подписке</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<input id="is_adult_feed" v-model="form.is_adult"
|
||||
type="checkbox"
|
||||
class="h-5 w-5 text-orange border-gray-light focus:ring-transparent focus:ring-offset-transparent"
|
||||
>
|
||||
<label for="is_adult_feed" class="select-none ml-3 text-gray">Контент для взрослых</label>
|
||||
</div> -->
|
||||
<div v-if="form.is_paid == 1" class="space-y-5">
|
||||
<div class="flex flex-col">
|
||||
<text-input v-model="form.price" :error="form.errors.price"
|
||||
type="number" class="focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light"
|
||||
label="Цена"
|
||||
/>
|
||||
</div>
|
||||
<div class="border border-indigo-300 flex flex-col p-4 rounded-md space-y-3">
|
||||
<div v-if="isFreeFeed">
|
||||
<file-input-multiple
|
||||
v-model="form.videos_paid"
|
||||
:error="form.errors.videos_paid"
|
||||
accept=".mp4"
|
||||
label="Выбрать видео"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mediasPaid.length">
|
||||
<h2 class="mb-2 text-lg text-gray-light select-none">
|
||||
Загруженные платные видео:
|
||||
</h2>
|
||||
<div class="grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<created-media-item
|
||||
v-for="mediaPaid in mediasPaid" :key="mediaPaid.id"
|
||||
:edit="false"
|
||||
:type="feed.type"
|
||||
:media="mediaPaid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-wrap -my-1 -mx-3">
|
||||
<progress
|
||||
v-if="form.progress"
|
||||
class="mx-3 my-1 w-full"
|
||||
:value="form.progress.percentage"
|
||||
max="100"
|
||||
>
|
||||
{{ form.progress.percentage }}%
|
||||
</progress>
|
||||
|
||||
<loading-button :loading="form.processing" class="mx-3 my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-orange focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Обновить
|
||||
</loading-button>
|
||||
<link-back class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
|
||||
Отменить
|
||||
</link-back>
|
||||
<button type="button" class="mx-3 my-1 default text-lg text-pink"
|
||||
@click="openModal"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find'
|
||||
import { toRefs, watch, ref, computed } from 'vue'
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import FileInput from '@/Shared/Form/FileInput.vue'
|
||||
import TextInput from '@/Shared/Form/TextInput.vue'
|
||||
import FileInputMultiple from '@/Shared/Form/FileInputMultiple.vue'
|
||||
import TextareaInput from '@/Shared/Form/TextareaInput.vue'
|
||||
import TagInput from '@/Shared/Form/TagInput.vue'
|
||||
import LoadingButton from '@/Shared/Form/LoadingButton.vue'
|
||||
import LinkBack from '@/Shared/Misc/LinkBack.vue'
|
||||
import WarningText from '@/Shared/Misc/WarningText.vue'
|
||||
import CreatedMediaItem from '@/Shared/Edit/CreatedMediaItem.vue'
|
||||
import ModalWarning from '@/Shared/Overlay/ModalWarning.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
CreatedMediaItem,
|
||||
TextInput,
|
||||
FileInput,
|
||||
FileInputMultiple,
|
||||
LoadingButton,
|
||||
WarningText,
|
||||
TextareaInput,
|
||||
LinkBack,
|
||||
TagInput,
|
||||
ModalWarning,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feed: Object,
|
||||
tags: Array,
|
||||
mediasCommon: Array,
|
||||
mediasPaid: Array,
|
||||
mediasCount: Number,
|
||||
mediaPreview: Object,
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { feed, tags, mediasCount, mediaPreview } = toRefs(props)
|
||||
|
||||
const textsBreak = computed(() => feed.value.status_note.split('\n'))
|
||||
|
||||
const modalShow = ref(false)
|
||||
|
||||
// let isFeedPaid, openPaidBlock
|
||||
|
||||
let form_fields = {
|
||||
id: feed.value.id,
|
||||
title: feed.value.title,
|
||||
body: feed.value.body,
|
||||
price: feed.value.price,
|
||||
tags: tags.value,
|
||||
is_paid: feed.value.is_paid,
|
||||
|
||||
|
||||
preview: null,
|
||||
videos: null,
|
||||
videos_paid: null,
|
||||
|
||||
removedItems: [],
|
||||
totalItems: mediasCount,
|
||||
}
|
||||
if (feed.value.is_paid) {
|
||||
// isFeedPaid = ref(1)
|
||||
// openPaidBlock = ref(1)
|
||||
delete form_fields.videos_paid
|
||||
}
|
||||
// else {
|
||||
// isFeedPaid = ref(0)
|
||||
// openPaidBlock = ref(0)
|
||||
// }
|
||||
|
||||
const form = useForm(form_fields)
|
||||
|
||||
// watch(isFeedPaid, (value) => {
|
||||
// openPaidBlock.value = value
|
||||
// form.is_paid = value
|
||||
// })
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('videos.update', feed.value.id), {
|
||||
onSuccess: () => {
|
||||
form.reset('videos')
|
||||
form.reset('videos_paid')
|
||||
form.reset('preview')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const stateToggle = (id) => {
|
||||
let existID = find(form.removedItems, (itemID) => itemID === id)
|
||||
if (existID) {
|
||||
const index = form.removedItems.indexOf(existID)
|
||||
if (index > -1) {
|
||||
form.removedItems.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
form.removedItems.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteActionModal = (remove) => {
|
||||
if (remove) {
|
||||
Inertia.delete(route('feed.destroy', feed.value.id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
}
|
||||
modalShow.value = false
|
||||
}
|
||||
|
||||
const openModal = () => (modalShow.value = true)
|
||||
|
||||
const isFreeFeed = computed(() => feed.value.is_paid === 0)
|
||||
const authUser = computed(() => usePage().props.value.auth.user)
|
||||
const textPreview = () => {
|
||||
if (mediaPreview) {
|
||||
return 'Обновить превью'
|
||||
}
|
||||
return 'Загрузить превью'
|
||||
}
|
||||
|
||||
const removePreview = (id) => {
|
||||
Inertia.delete(route('feed.preview.destroy', id))
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
submit,
|
||||
stateToggle,
|
||||
authUser,
|
||||
// isFeedPaid,
|
||||
// openPaidBlock,
|
||||
isFreeFeed,
|
||||
modalShow,
|
||||
deleteActionModal,
|
||||
openModal,
|
||||
textPreview,
|
||||
removePreview,
|
||||
textsBreak
|
||||
}
|
||||
},
|
||||
|
||||
updated() {
|
||||
if (this.feed.is_paid) {
|
||||
delete this.form.videos_paid
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
152
resources/js/Pages/Video/Feed.vue
Executable file
152
resources/js/Pages/Video/Feed.vue
Executable file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<meta-head title="Видео"></meta-head>
|
||||
<div class="mb-6 py-3 banner relative bg-center bg-no-repeat bg-cover"
|
||||
style="background-image: url('/image/bg-home.jpg');"
|
||||
>
|
||||
<div class="h-52 flex justify-center items-center text-center">
|
||||
<div class="max-w-4xl text-gray px-3">
|
||||
<h1
|
||||
class=" text-xl md:text-2xl lg:text-3xl xl:text-5xl text-white font-semibold xl:leading-relaxed"
|
||||
>
|
||||
Видео
|
||||
</h1>
|
||||
|
||||
<h2 v-if="tag" class="text-lg md:text-3xl text-gray-light">
|
||||
#{{ tag.name }}
|
||||
</h2>
|
||||
|
||||
<inertia-link :href="route('videos.create')"
|
||||
class="mt-8 inline-flex tracking-wide items-center px-8 md:px-12 py-3 border border-white text-sm lg:text-lg text-white rounded-full bg-transparent hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
>
|
||||
Загрузить
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 buttons-filter-line">
|
||||
<div class="relative mb-5">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center z-[1]">
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-light" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 4a7 7 0 100 14 7 7 0 000-14zm-9 7a9 9 0 1118 0 9 9 0 01-18 0z"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.943 15.943a1 1 0 011.414 0l4.35 4.35a1 1 0 01-1.414 1.414l-4.35-4.35a1 1 0 010-1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.search"
|
||||
class="relative w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md placeholder-gray-light !pl-10 h-14" placeholder="Поиск"
|
||||
type="search"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<inertia-link :href="local_route"
|
||||
:class="[active_filter == 'new' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
Новинки
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link :href="local_route"
|
||||
:data="{ filter: 'hot' }"
|
||||
:class="[active_filter == 'hot' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center shadow-classic rounded-md text-white focus:outline-none']"
|
||||
>
|
||||
<svg class="-ml-1 mr-2 h-4 w-4 md:h-5 md:w-5 flex-shrink-0" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M14.422 6.243c-3.57-2.172-1.895-5.238-1.824-5.365A.586.586 0 0012.09 0c-1.837 0-3.276.522-4.276 1.552-1.71 1.76-1.63 4.498-1.597 5.667.004.13.007.242.007.325 0 .868.14 1.67.263 2.377.079.456.147.85.16 1.159.012.331-.048.407-.05.41-.01.01-.081.049-.276.049a.657.657 0 01-.53-.235c-.565-.634-.604-2.365-.496-3.307a.586.586 0 00-.582-.654c-1.521 0-2.658 2.488-2.658 4.712 0 1.046.21 2.07.625 3.045a8.081 8.081 0 001.7 2.527C5.894 19.157 7.89 20 10 20c2.119 0 4.114-.83 5.618-2.34a7.892 7.892 0 002.327-5.605c0-2.692-2.107-4.95-3.523-5.812zM10 18.828c-3.671 0-6.773-3.101-6.773-6.773 0-.893.213-1.83.585-2.571.086-.173.175-.322.263-.447.01.967.163 2.284.841 3.046.366.41.852.627 1.405.627.518 0 .912-.156 1.172-.466.486-.578.337-1.436.148-2.524-.115-.66-.245-1.409-.245-2.176 0-.1-.003-.22-.007-.359-.032-1.107-.099-3.412 1.265-4.817.613-.63 1.467-1.014 2.55-1.145-.123.406-.228.92-.232 1.497-.01 1.27.476 3.085 2.841 4.524 1.145.696 2.96 2.618 2.96 4.811A6.78 6.78 0 0110 18.828z"
|
||||
/>
|
||||
</svg>
|
||||
Популярные
|
||||
</inertia-link>
|
||||
</div>
|
||||
|
||||
<div v-if="tag" class="mt-4 grid gap-2 md:gap-5 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<feed-tags-buttons :slug="tag.slug" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3 my-6">
|
||||
<button class="button-default text-gray text-lg" @click="resetFilter">
|
||||
Очистить фильтры
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="xl:container xl:mx-auto px-2 md:px-3">
|
||||
<div v-if="feeds.length" class="grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 cards-block rounded-md bg-indigo-200 shadow-classic grid gap-2 lg:gap-4 grid-cards p-2 lg:p-5">
|
||||
<feed
|
||||
:next-cursor="nextCursor"
|
||||
:feeds="feeds"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="font-bold text-xl lg:text-3xl text-gray text-center">
|
||||
Записи не найдены
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import throttle from 'lodash/throttle'
|
||||
import pickBy from 'lodash/pickBy'
|
||||
|
||||
import Feed from '@/Shared/Feed/Feed.vue'
|
||||
import Layout from '@/Shared/Layout.vue'
|
||||
import MetaHead from '@/Shared/MetaHead.vue'
|
||||
import FeedTagsButtons from '@/Shared/Misc/FeedTagsButtons.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaHead,
|
||||
FeedTagsButtons,
|
||||
Feed,
|
||||
},
|
||||
layout: Layout,
|
||||
props: {
|
||||
feeds: Array,
|
||||
local_route: String,
|
||||
active_filter: String,
|
||||
tag: { type: [Object, Boolean], default: false },
|
||||
searchFilters: Object,
|
||||
nextCursor: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
search: this.searchFilters.search,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler: throttle(function () {
|
||||
Inertia.get(this.local_route, pickBy(this.form), {
|
||||
preserveScroll: true, preserveState: true
|
||||
})
|
||||
}, 500),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetFilter() {
|
||||
Inertia.get(
|
||||
this.local_route,
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
79
resources/js/Pages/Video/Show.vue
Executable file
79
resources/js/Pages/Video/Show.vue
Executable file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
|
||||
<Head>
|
||||
<title>{{feed.entity.title}}</title>
|
||||
<meta name="description" :content="feed.entity.body">
|
||||
<meta property="og:url" :content="route(`${feed.entity.type}.show`, feed.entity.slug)">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" :content="feed.entity.title">
|
||||
<meta property="og:description" :content="feed.entity.body">
|
||||
<meta property="og:image" :content="feed.entity.preview">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- <meta name="twitter:title" :content="feed.entity.title">
|
||||
<meta name="twitter:description" :content="feed.entity.body">
|
||||
<meta name="twitter:image" :content="feed.entity.preview">
|
||||
<meta name="twitter:card" content="summary_large_image"> -->
|
||||
|
||||
</Head>
|
||||
|
||||
<header-auth v-if="!$page.props.auth.user" />
|
||||
|
||||
<modal-share :entity='feed.entity' ref="shareModalNode" />
|
||||
<div class="mt-16 container mx-auto px-2 md:px-6 2xl:px-28 md:grid grid-cols-12 ">
|
||||
<modal-feed-media
|
||||
:type='feed.type'
|
||||
:feed_id='feed.id'
|
||||
:title='feed.entity.title'
|
||||
:preview='feed.entity.preview'
|
||||
:medias='feed.entity.collection_medias'
|
||||
class="border-l border-r md:border-r-0 border-t border-b border-indigo-100"
|
||||
/>
|
||||
|
||||
<modal-feed-body
|
||||
@openShare='showShareModal'
|
||||
:user='user'
|
||||
:feed_id='feed.id'
|
||||
:entity='feed.entity'
|
||||
class="border-l border-r md:border-t border-b border-indigo-100"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Head } from "@inertiajs/inertia-vue3";
|
||||
|
||||
import Layout from "@/Shared/Layout.vue";
|
||||
import ModalFeedMedia from "@/Shared/Overlay/ModalFeedMedia.vue";
|
||||
import ModalFeedBody from "@/Shared/Overlay/ModalFeedBody.vue";
|
||||
import ModalShare from "@/Shared/Overlay/ModalShare.vue";
|
||||
import HeaderAuth from "@/Shared/LayoutParts/HeaderAuth.vue";
|
||||
|
||||
export default {
|
||||
layout: Layout,
|
||||
components: {
|
||||
Head,
|
||||
ModalShare,
|
||||
ModalFeedMedia,
|
||||
ModalFeedBody,
|
||||
HeaderAuth,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
is_exist_menu: false
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
feed: Object,
|
||||
user: Object,
|
||||
},
|
||||
methods: {
|
||||
showShareModal() {
|
||||
this.$refs.shareModalNode.openModal();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
315
resources/js/Shared/AudioPlayer.vue
Executable file
315
resources/js/Shared/AudioPlayer.vue
Executable file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="col-span-7 md:col-span-5 lg:col-span-3 player-widget contain-content">
|
||||
<div
|
||||
ref="topbar"
|
||||
class="
|
||||
player-line
|
||||
rounded-md
|
||||
transition-colors
|
||||
bg-transparent
|
||||
hover:bg-orange-min
|
||||
flex
|
||||
items-center
|
||||
space-x-3
|
||||
p-2
|
||||
cursor-pointer
|
||||
"
|
||||
@click="openFull"
|
||||
>
|
||||
<button class="focus:outline-none text-gray-600 hover:text-gray-800" @click.stop="skipTrack('prev')">
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polygon points="19 20 9 12 19 4 19 20"></polygon>
|
||||
<line x1="5" y1="19"
|
||||
x2="5" y2="5"
|
||||
></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
flex
|
||||
h-8
|
||||
items-center
|
||||
justify-center
|
||||
w-8
|
||||
transition-colors
|
||||
text-gray-600
|
||||
hover:text-gray-800
|
||||
"
|
||||
@click.stop="toggleAudio"
|
||||
>
|
||||
<PlayIcon v-show="!playing" class="w-8 h-8" />
|
||||
<StopIcon v-show="playing" class="w-8 h-8" />
|
||||
</button>
|
||||
<button class="focus:outline-none text-gray-600 hover:text-gray-800" @click.stop="skipTrack('next')">
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polygon points="5 4 15 12 5 20 5 4"></polygon>
|
||||
<line x1="19" y1="5"
|
||||
x2="19" y2="19"
|
||||
></line>
|
||||
</svg>
|
||||
</button>
|
||||
<div v-show="count_playlist" class="text-base truncate select-none">
|
||||
{{ currentSong.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<teleport to="body">
|
||||
<div v-if="open_drop">
|
||||
<div
|
||||
style="
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 9998;
|
||||
background: black;
|
||||
opacity: 0.2;
|
||||
"
|
||||
@click="open_drop = false"
|
||||
/>
|
||||
<div
|
||||
ref="dropdown"
|
||||
class="z-[9999] player-widget-list w-full lg:w-[48rem] "
|
||||
>
|
||||
<div class="shadow-classic rounded-md bg-indigo-200">
|
||||
<div
|
||||
v-show="count_playlist"
|
||||
class="overflow-hidden bg-orange relative h-2 mt-2 mb-2"
|
||||
@click.prevent="updateSeek"
|
||||
>
|
||||
<div
|
||||
:style="{ transform: `translate3d(${playerProgress}, 0, 0)` }"
|
||||
class="will-change-transform absolute transition-transform h-full bg-white flex items-center justify-end w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
p-2
|
||||
lg:p-4
|
||||
rounded-md
|
||||
items-center
|
||||
grid grid-cols-1
|
||||
md:grid-cols-10
|
||||
cursor-pointer
|
||||
"
|
||||
>
|
||||
<div class="col-span-7 flex items-center space-x-3">
|
||||
<button class="focus:outline-none text-gray" @click.stop="skipTrack('prev')">
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polygon points="19 20 9 12 19 4 19 20"></polygon>
|
||||
<line x1="5" y1="19"
|
||||
x2="5" y2="5"
|
||||
></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
flex
|
||||
h-8
|
||||
items-center
|
||||
justify-center
|
||||
w-8
|
||||
transition-colors
|
||||
text-gray
|
||||
"
|
||||
@click.stop="toggleAudio"
|
||||
>
|
||||
<PlayIcon v-show="!playing" class="w-8 h-8" />
|
||||
<StopIcon v-show="playing" class="w-8 h-8" />
|
||||
</button>
|
||||
<button class="focus:outline-none text-gray" @click.stop="skipTrack('next')">
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polygon points="5 4 15 12 5 20 5 4"></polygon>
|
||||
<line x1="19" y1="5"
|
||||
x2="19" y2="19"
|
||||
></line>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="text-gray-light text-base truncate select-none">
|
||||
{{ currentSong.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="count_playlist" class="col-span-1 text-center text-gray-light text-xs select-none">
|
||||
{{ seek }}
|
||||
</div>
|
||||
<div v-show="count_playlist" class="col-span-2 mt-2 md:mt-0 md:ml-auto flex items-center">
|
||||
<input v-model="volume" class="w-full custom-input-range"
|
||||
type="range" max="1"
|
||||
step="0.1" @click.stop=""
|
||||
@input="updateVolume(volume)"
|
||||
/>
|
||||
<span class="text-gray-light ml-1.5 text-xs">{{ volume * 100 + "%" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="choices-tabs">
|
||||
<div class="border-b border-indigo-300">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<a v-for="tab in tabs" :key="tab.key"
|
||||
href="#" :class="[
|
||||
current_tab === tab.key ? 'border-orange text-white' : 'border-transparent text-gray-light hover:text-white',
|
||||
'w-1/2 lg:w-1/4 py-3 lg:py-4 px-1 text-center text-sm border-b-2',
|
||||
]"
|
||||
@click.prevent="changeTab(tab.key)"
|
||||
>{{ tab.name }}</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<keep-alive>
|
||||
<component :is="currentTabComponent"></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popper from 'popper.js'
|
||||
import { PlayIcon, StopIcon } from '@heroicons/vue/solid'
|
||||
import { mapActions, mapGetters, mapState } from 'vuex'
|
||||
|
||||
import AudioTabNow from '@/Shared/Tabs/AudioTabNow.vue'
|
||||
import AudioTabFavorite from '@/Shared/Tabs/AudioTabFavorite.vue'
|
||||
import AudioTabLoaded from '@/Shared/Tabs/AudioTabLoaded.vue'
|
||||
|
||||
export default {
|
||||
name: 'Player',
|
||||
components: {
|
||||
AudioTabNow,
|
||||
AudioTabLoaded,
|
||||
AudioTabFavorite,
|
||||
PlayIcon,
|
||||
StopIcon
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: [
|
||||
{key: 'now', name: 'Текущая'},
|
||||
{key: 'loaded', name: 'Загруженная'},
|
||||
{key: 'favorite', name: 'Понравившийся'},
|
||||
],
|
||||
open_drop: false,
|
||||
popper: null,
|
||||
volume: 0.5,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentTabComponent() {
|
||||
return 'audio-tab-' + this.current_tab
|
||||
},
|
||||
...mapGetters(['playing', 'count_playlist', 'volume_step']),
|
||||
...mapState({
|
||||
current_tab: (state) => state.player.tab,
|
||||
seek: (state) => state.player.seek,
|
||||
duration: (state) => state.player.duration,
|
||||
playerProgress: (state) => state.player.playerProgress,
|
||||
currentSong: (state) => state.player.currentSong,
|
||||
}),
|
||||
},
|
||||
|
||||
watch: {
|
||||
open_drop(open_drop) {
|
||||
if (open_drop) {
|
||||
this.$nextTick(() => {
|
||||
if (window.matchMedia('(min-width: 1024px)').matches) {
|
||||
this.popper = new Popper(this.$refs.topbar, this.$refs.dropdown, {
|
||||
placement: 'bottom-start',
|
||||
modifiers: {
|
||||
preventOverflow: { boundariesElement: 'scrollParent' },
|
||||
},
|
||||
})
|
||||
}else {
|
||||
this.popper = new Popper(this.$refs.topbar, this.$refs.dropdown, {
|
||||
placement: 'bottom-end',
|
||||
modifiers: {
|
||||
preventOverflow: {
|
||||
boundariesElement: 'scrollParent',
|
||||
padding: 0,
|
||||
},
|
||||
offset: {
|
||||
enabled: true,
|
||||
offset: '0, 10'
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
} else if (this.popper) {
|
||||
setTimeout(() => this.popper.destroy(), 100)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadExsitPlaylist()
|
||||
this.changeVolume(this.volume_step)
|
||||
this.volume = this.volume_step
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
...mapActions([
|
||||
'toggleAudio',
|
||||
'updateSeek',
|
||||
'skipTrack',
|
||||
'changeVolume',
|
||||
'tabName',
|
||||
'loadExsitPlaylist',
|
||||
]),
|
||||
|
||||
openFull() {
|
||||
this.open_drop = !this.open_drop
|
||||
},
|
||||
|
||||
updateVolume(volume) {
|
||||
this.changeVolume(volume)
|
||||
},
|
||||
|
||||
changeTab(name){
|
||||
this.tabName(name)
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
68
resources/js/Shared/Edit/CreatedMediaItem.vue
Executable file
68
resources/js/Shared/Edit/CreatedMediaItem.vue
Executable file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<div v-if="type === 'images'" class="bg-indigo-100 overflow-hidden">
|
||||
<img :class="{ 'blur-sm': classActive }" class="object-contain h-48 w-full" :src="media.url" alt="">
|
||||
</div>
|
||||
<div v-if="type === 'videos'" class="overflow-hidden">
|
||||
<video :class="{ 'blur-sm': classActive }" class="object-contain h-48 w-full" :src="media.url" controls></video>
|
||||
</div>
|
||||
<div v-if="type === 'musics'" class="overflow-hidden">
|
||||
<div :class="{ 'blur-sm': classActive }" class="border border-indigo-100 flex flex-col items-center p-4">
|
||||
<MusicNoteIcon class="flex-shrink-0 w-12 h-12 text-indigo-300" />
|
||||
<div class="text-base text-gray font-medium">{{media.name}} / {{media.time}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="edit"
|
||||
:class="[classActive ? 'bg-green' : 'bg-pink']"
|
||||
class="block text-sm text-white w-full p-1"
|
||||
type="button" @click="stateToggle(media.id)">
|
||||
<span v-show="!classActive">Удалить</span>
|
||||
<span v-show="classActive">Восстановить</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MusicNoteIcon } from "@heroicons/vue/solid";
|
||||
export default {
|
||||
components: {
|
||||
MusicNoteIcon,
|
||||
},
|
||||
props: {
|
||||
media: Object,
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "images",
|
||||
},
|
||||
},
|
||||
emits: ["addRemoveId"],
|
||||
data() {
|
||||
return {
|
||||
activeItemId: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
stateToggle(id) {
|
||||
if (this.activeItemId === 0) {
|
||||
this.activeItemId = id;
|
||||
} else {
|
||||
this.activeItemId = 0;
|
||||
}
|
||||
this.$emit("addRemoveId", id);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
classActive() {
|
||||
return this.media.id === this.activeItemId;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
24
resources/js/Shared/Feed/Adults.vue
Executable file
24
resources/js/Shared/Feed/Adults.vue
Executable file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div
|
||||
class="card-block contain cursor-pointer flex group overflow-hidden relative"
|
||||
>
|
||||
<div class="absolute inset-0 flex items-center justify-center z-10 text-white text-lg w-full h-36 md:h-72 object-cover">
|
||||
18 + Контент
|
||||
</div>
|
||||
<div class="blur-2xl flex flex-grow bg-indigo-300 w-full h-36 md:h-72 object-cover">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
97
resources/js/Shared/Feed/Feed.vue
Executable file
97
resources/js/Shared/Feed/Feed.vue
Executable file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<modal-feed
|
||||
:modal-feed="modalFeed"
|
||||
:open="show"
|
||||
@close-modal="closeModal"
|
||||
@destroyFeed="destroyFeed"
|
||||
/>
|
||||
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="feed in feedLists" :key="feed.id"
|
||||
:ref="el => { if (el && feed.id === lastElementID) lastNodeLement = el }"
|
||||
>
|
||||
<feed-node :feed="feed" @open-modal="openModal" />
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import findIndex from 'lodash/findIndex'
|
||||
|
||||
import FeedNode from '@/Shared/Feed/FeedNode.vue'
|
||||
import ModalFeed from '@/Shared/Overlay/ModalFeed.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedNode,
|
||||
ModalFeed,
|
||||
InfinityScroll,
|
||||
},
|
||||
|
||||
props: {
|
||||
feeds: Array,
|
||||
nextCursor: { type: String, default: '' },
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
let feedLists = ref(props.feeds)
|
||||
|
||||
const show = ref(false)
|
||||
const modalFeed = ref({})
|
||||
|
||||
const containerRef = ref(null)
|
||||
|
||||
const lastElementID = computed(
|
||||
() => feedLists.value[feedLists.value.length - 1]?.id
|
||||
)
|
||||
|
||||
watch(props, (props) => {
|
||||
feedLists.value = props.feeds
|
||||
})
|
||||
|
||||
const putFromPagination = (lists) => {
|
||||
for (let list of lists) {
|
||||
feedLists.value.push(list)
|
||||
}
|
||||
}
|
||||
|
||||
const destroyFeed = () => {
|
||||
Inertia.delete(route('feed.destroy', modalFeed.value.id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
const index = findIndex(
|
||||
feedLists.value,
|
||||
(item) => item.id === modalFeed.value.id
|
||||
)
|
||||
feedLists.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const openModal = (feed) => {
|
||||
show.value = true
|
||||
modalFeed.value = feed
|
||||
}
|
||||
const closeModal = () => {
|
||||
show.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
openModal,
|
||||
closeModal,
|
||||
destroyFeed,
|
||||
lastElementID,
|
||||
show,
|
||||
modalFeed,
|
||||
feedLists,
|
||||
putFromPagination,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
95
resources/js/Shared/Feed/FeedNode.vue
Executable file
95
resources/js/Shared/Feed/FeedNode.vue
Executable file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<component
|
||||
:is="currentTypeNode"
|
||||
:class="{'need-approved': feed.entity?.status == 0}"
|
||||
:feed_id="feed.id"
|
||||
:user="feed.user"
|
||||
:entity="feed.entity"
|
||||
@click.prevent="openModal(feed)"
|
||||
@like-feed="likeFeed"
|
||||
></component>
|
||||
<p v-if="feed.entity?.price" class="mt-2 text-gray-light text-sm">
|
||||
Цена: {{ feed.entity.price }}
|
||||
</p>
|
||||
<p v-if="feed.entity?.purchase_date" class="mt-2 text-gray-light text-sm">
|
||||
Дата покупки: {{ feed.entity.purchase_date }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { usePage } from '@inertiajs/inertia-vue3'
|
||||
import axios from 'axios'
|
||||
import FeedImages from '@/Shared/Feed/Images.vue'
|
||||
import FeedVideos from '@/Shared/Feed/Videos.vue'
|
||||
import FeedMusics from '@/Shared/Feed/Musics.vue'
|
||||
import FeedAdults from '@/Shared/Feed/Adults.vue'
|
||||
import FeedProhibited from '@/Shared/Feed/Prohibited.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedImages,
|
||||
FeedVideos,
|
||||
FeedMusics,
|
||||
FeedAdults,
|
||||
FeedProhibited,
|
||||
},
|
||||
props: {
|
||||
feed: Object,
|
||||
},
|
||||
emits: ['openModal'],
|
||||
computed: {
|
||||
authUser() {
|
||||
return usePage().props.value.auth.user
|
||||
},
|
||||
currentTypeNode() {
|
||||
return 'feed-' + this.feed.type.toLowerCase()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal(feed) {
|
||||
|
||||
if(feed.entity){
|
||||
this.$emit('openModal', feed)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
likeFeed() {
|
||||
axios
|
||||
.post(route('feed.like', this.feed.id))
|
||||
.then(() => {
|
||||
if (this.feed.entity.liked) {
|
||||
this.feed.entity.liked = false
|
||||
this.feed.entity.likes--
|
||||
} else {
|
||||
this.feed.entity.liked = true
|
||||
this.feed.entity.likes++
|
||||
}
|
||||
})
|
||||
|
||||
// Inertia.post(
|
||||
// route('feed.like', this.feed.id),
|
||||
// {},
|
||||
// {
|
||||
// preserveScroll: true,
|
||||
// preserveState: true,
|
||||
// }
|
||||
// )
|
||||
// if (this.feed.entity.liked) {
|
||||
// this.feed.entity.liked = false
|
||||
// this.feed.entity.likes--
|
||||
// } else {
|
||||
// this.feed.entity.liked = true
|
||||
// this.feed.entity.likes++
|
||||
// }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.need-approved{
|
||||
outline: 5px solid #FF9800;
|
||||
}
|
||||
</style>
|
||||
32
resources/js/Shared/Feed/FeedPreview.vue
Executable file
32
resources/js/Shared/Feed/FeedPreview.vue
Executable file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<img :class="{'object-top sm:object-center': existSource}" :src="setImage()"
|
||||
alt=""
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
source: String,
|
||||
type: String,
|
||||
},
|
||||
computed: {
|
||||
existSource () {
|
||||
return !this.source
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setImage() {
|
||||
if (this.source) {
|
||||
return this.source
|
||||
}
|
||||
// if(this.type == 'music'){
|
||||
// return '/image/modalimg1.jpg'
|
||||
// }
|
||||
// return '/image/card4.jpg'
|
||||
return '/image/default-placeholder.jpg'
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
26
resources/js/Shared/Feed/FooterBanned.vue
Executable file
26
resources/js/Shared/Feed/FooterBanned.vue
Executable file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
transition-opacity
|
||||
ease-out
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
bg-indigo-300 bg-opacity-75
|
||||
z-10
|
||||
absolute
|
||||
inset-x-0
|
||||
bottom-0
|
||||
p-2
|
||||
md:p-3
|
||||
"
|
||||
>
|
||||
<footer class="misc-info flex space-x-4">
|
||||
<span class="text-white text-xs lg:text-base font-medium">КОНТЕНТ НА МОДЕРАЦИИ</span>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
68
resources/js/Shared/Feed/FooterMisc.vue
Executable file
68
resources/js/Shared/Feed/FooterMisc.vue
Executable file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
transition-opacity
|
||||
ease-out
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
opacity-0
|
||||
group-hover:opacity-100
|
||||
bg-indigo-300 bg-opacity-75
|
||||
z-10
|
||||
absolute
|
||||
inset-x-0
|
||||
bottom-0
|
||||
p-2
|
||||
md:p-3
|
||||
"
|
||||
>
|
||||
<div class="misc-info flex space-x-4 items-center">
|
||||
<template v-if="ads == false">
|
||||
<like-count :likes="likes" :liked="is_like"
|
||||
@likeFeed="callLike"
|
||||
/>
|
||||
<comment-count :comments="comments" />
|
||||
</template>
|
||||
<span v-else class="text-gray-light">Реклама</span>
|
||||
<view-count :count="count" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import LikeCount from '@/Shared/Misc/LikeCount.vue'
|
||||
import ViewCount from '@/Shared/Misc/ViewCount.vue'
|
||||
import CommentCount from '@/Shared/Misc/CommentCount.vue'
|
||||
export default {
|
||||
components: {
|
||||
LikeCount,
|
||||
CommentCount,
|
||||
ViewCount,
|
||||
},
|
||||
props: {
|
||||
likes: Number,
|
||||
comments: Number,
|
||||
is_like: Boolean,
|
||||
ads: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
emits: ['likeFeed'],
|
||||
methods: {
|
||||
callLike() {
|
||||
if (this.$page.props.auth.user) {
|
||||
this.$emit('likeFeed')
|
||||
}else{
|
||||
Inertia.get(route('login'))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
24
resources/js/Shared/Feed/HeaderMisc.vue
Executable file
24
resources/js/Shared/Feed/HeaderMisc.vue
Executable file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="absolute z-10 top-3 right-3">
|
||||
<div class="flex items-center text-white">
|
||||
<span v-if="count > 1" class="mr-1 text-sm">
|
||||
{{ count }}
|
||||
</span>
|
||||
<svg class="drop-shadow-custom w-5 h-5">
|
||||
<use v-if="type == 'images'" xlink:href="#imagefeed"></use>
|
||||
<use v-if="type == 'videos'" xlink:href="#filmmark"></use>
|
||||
<use v-if="type == 'musics'" xlink:href="#musicmark"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: { },
|
||||
props: {
|
||||
count: Number,
|
||||
type: String,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
53
resources/js/Shared/Feed/Images.vue
Executable file
53
resources/js/Shared/Feed/Images.vue
Executable file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div
|
||||
class="card-block contain group cursor-pointer relative overflow-hidden"
|
||||
>
|
||||
<feed-header-misc
|
||||
:count="entity.collection_medias.length"
|
||||
type="images"
|
||||
/>
|
||||
|
||||
|
||||
<feed-footer-misc
|
||||
v-if="entity.status == 1"
|
||||
:is_like="entity.liked"
|
||||
:likes="entity.likes"
|
||||
:comments="entity.comments"
|
||||
:count="entity.views_count"
|
||||
:ads="entity.is_ads"
|
||||
@like-feed="likeFeed"
|
||||
/>
|
||||
<feed-footer-banned v-else />
|
||||
|
||||
|
||||
|
||||
<div class="relative overflow-hidden">
|
||||
<img
|
||||
class="w-full h-36 md:h-72 object-cover"
|
||||
:src="entity.preview"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import FeedFooterMisc from '@/Shared/Feed/FooterMisc.vue'
|
||||
import FeedHeaderMisc from '@/Shared/Feed/HeaderMisc.vue'
|
||||
import FeedFooterBanned from '@/Shared/Feed/FooterBanned.vue'
|
||||
export default {
|
||||
components: { FeedFooterMisc, FeedHeaderMisc, FeedFooterBanned },
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
emits: ['likeFeed'],
|
||||
methods: {
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
126
resources/js/Shared/Feed/Musics.vue
Executable file
126
resources/js/Shared/Feed/Musics.vue
Executable file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div
|
||||
class="card-block contain group cursor-pointer relative overflow-hidden"
|
||||
>
|
||||
<feed-header-misc
|
||||
:count="entity.collection_medias.length"
|
||||
type="musics"
|
||||
/>
|
||||
|
||||
<div :class="[playlist_id === feed_id ? '' : 'transition-opacity ease-out opacity-0 group-hover:opacity-100' , ' absolute inset-0 z-10 flex items-center justify-center']">
|
||||
<div class="w-full grid grid-cols-7 items-center p-1 md:p-3 bg-indigo-300 bg-opacity-75">
|
||||
<div class="col-span-1 flex mr-3 text-white" @click.stop>
|
||||
<div class="inline-block transition-colors hover:text-orange" @click.prevent="startPlay(first_entity)">
|
||||
<button :class="[ currentSong?.id === first_entity.id ? '' : 'hidden', 'default' ]">
|
||||
<svg :class="[ playing ? 'hidden' : 'block', 'w-4 h-4 md:w-6 md:h-6' ]">
|
||||
<use xlink:href="#play"></use>
|
||||
</svg>
|
||||
<svg :class="[ playing ? 'block' : 'hidden', 'w-4 h-4 md:w-6 md:h-6' ]">
|
||||
<use xlink:href="#pause"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="[ currentSong?.id !== first_entity.id ? '' : 'hidden', 'default' ]">
|
||||
<svg class="w-4 h-4 md:w-6 md:h-6">
|
||||
<use xlink:href="#play"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="playlist_id === feed_id" class="col-span-6 flex flex-col text-white">
|
||||
<span class="text-xs md:text-base text-gray font-semibold truncate">{{ currentSong.name }}</span>
|
||||
<span class="text-xs md:text-sm text-gray-light truncate">{{ seek }}</span>
|
||||
</div>
|
||||
<div v-show="playlist_id !== feed_id" class="col-span-6 flex flex-col text-white">
|
||||
<span class="text-xs md:text-base text-gray font-semibold truncate">{{ first_entity.name }}</span>
|
||||
<span class="text-xs md:text-sm text-gray-light truncate">{{ first_entity.time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<feed-footer-misc
|
||||
v-if="entity.status == 1"
|
||||
:is_like="entity.liked"
|
||||
:likes="entity.likes"
|
||||
:comments="entity.comments"
|
||||
:count="entity.views_count"
|
||||
:ads="entity.is_ads"
|
||||
@like-feed="likeFeed"
|
||||
/>
|
||||
<feed-footer-banned v-else />
|
||||
|
||||
<div class="relative overflow-hidden">
|
||||
<feed-preview class="w-full h-36 md:h-72 object-cover" type="music"
|
||||
:source="entity.preview"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState, mapGetters } from 'vuex'
|
||||
|
||||
import FeedFooterMisc from '@/Shared/Feed/FooterMisc.vue'
|
||||
import FeedHeaderMisc from '@/Shared/Feed/HeaderMisc.vue'
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
import FeedFooterBanned from '@/Shared/Feed/FooterBanned.vue'
|
||||
import axios from 'axios'
|
||||
export default {
|
||||
components: { FeedFooterMisc, FeedHeaderMisc, FeedPreview, FeedFooterBanned },
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
emits: ['likeFeed'],
|
||||
computed: {
|
||||
first_entity() {
|
||||
const media = this.entity.collection_medias
|
||||
if (
|
||||
this.playlist_id === this.feed_id &&
|
||||
typeof media[this.indexPlaylist] !== 'undefined'
|
||||
) {
|
||||
return media[this.indexPlaylist]
|
||||
}
|
||||
return media[0]
|
||||
},
|
||||
...mapGetters(['playing']),
|
||||
...mapState({
|
||||
seek: (state) => state.player.seek,
|
||||
indexPlaylist: (state) => state.player.index,
|
||||
playlist_id: (state) => state.player.playlist_id,
|
||||
currentSong: (state) => state.player.currentSong,
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['toggleAudio', 'newCurrentPlaylist']),
|
||||
startPlay(music) {
|
||||
this.addViewShow()
|
||||
if (this.currentSong?.id === music.id) {
|
||||
this.toggleAudio()
|
||||
return
|
||||
}
|
||||
this.newCurrentPlaylist([
|
||||
this.first_entity,
|
||||
this.entity.collection_medias,
|
||||
this.feed_id,
|
||||
])
|
||||
|
||||
},
|
||||
|
||||
addViewShow() {
|
||||
axios
|
||||
.post(route('add.view.feed', this.feed_id))
|
||||
.then(({ data }) => {
|
||||
data && this.entity.views_count++
|
||||
})
|
||||
},
|
||||
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
31
resources/js/Shared/Feed/Prohibited.vue
Executable file
31
resources/js/Shared/Feed/Prohibited.vue
Executable file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div
|
||||
class="card-block contain cursor-pointer flex group overflow-hidden relative"
|
||||
>
|
||||
<div class="absolute inset-0 flex items-center justify-center z-10 text-white text-sm sm:text-lg w-full h-36 md:h-72 object-cover">
|
||||
<div class="text-center p-2">
|
||||
<p>Контент доступен по подписке!</p>
|
||||
<p>
|
||||
Купить: <inertia-link :href="route('profile.user', user.username)" class="font-semibold underline inline-block mr-2">
|
||||
{{ user.username }}
|
||||
</inertia-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="blur-2xl flex flex-grow bg-indigo-300 w-full h-36 md:h-72 object-cover">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
47
resources/js/Shared/Feed/Videos.vue
Executable file
47
resources/js/Shared/Feed/Videos.vue
Executable file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div
|
||||
class="card-block contain group cursor-pointer relative overflow-hidden"
|
||||
>
|
||||
<feed-header-misc
|
||||
:count="entity.collection_medias.length"
|
||||
type="videos"
|
||||
/>
|
||||
<feed-footer-misc
|
||||
v-if="entity.status == 1"
|
||||
:is_like="entity.liked"
|
||||
:likes="entity.likes"
|
||||
:comments="entity.comments"
|
||||
:count="entity.views_count"
|
||||
:ads="entity.is_ads"
|
||||
@like-feed="likeFeed"
|
||||
/>
|
||||
<feed-footer-banned v-else />
|
||||
|
||||
<div class="relative overflow-hidden">
|
||||
<feed-preview class="w-full h-36 md:h-72 object-cover" :source="entity.preview" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import FeedFooterMisc from '@/Shared/Feed/FooterMisc.vue'
|
||||
import FeedHeaderMisc from '@/Shared/Feed/HeaderMisc.vue'
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
import FeedFooterBanned from '@/Shared/Feed/FooterBanned.vue'
|
||||
|
||||
export default {
|
||||
components: { FeedFooterMisc, FeedHeaderMisc, FeedPreview, FeedFooterBanned },
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
emits: ['likeFeed'],
|
||||
methods: {
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
23
resources/js/Shared/FeedList/Adults.vue
Executable file
23
resources/js/Shared/FeedList/Adults.vue
Executable file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="shadow-classic rounded-md bg-indigo-200 p-3 md:px-5 md:py-7 relative">
|
||||
<div class="absolute inset-0 flex items-center justify-center z-10 text-white text-lg w-full object-cover font-medium">
|
||||
18 + Контент
|
||||
</div>
|
||||
<div class="blur-2xl flex flex-grow bg-indigo-300 w-full h-36 object-cover">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
47
resources/js/Shared/FeedList/FeedFooter.vue
Executable file
47
resources/js/Shared/FeedList/FeedFooter.vue
Executable file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="mt-3 md:mt-6 feed-footer">
|
||||
<div class="flex justify-between">
|
||||
<div v-if="ads"></div>
|
||||
<div v-else class="misc-info flex space-x-4">
|
||||
<like-count :likes="likes" :liked="liked"
|
||||
@likeFeed="likeFeed"
|
||||
/>
|
||||
<comment-count :comments="comments" />
|
||||
<share-count />
|
||||
</div>
|
||||
<view-count :count="count" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LikeCount from '@/Shared/Misc/LikeCount.vue'
|
||||
import ViewCount from '@/Shared/Misc/ViewCount.vue'
|
||||
import CommentCount from '@/Shared/Misc/CommentCount.vue'
|
||||
import ShareCount from '@/Shared/Misc/ShareCount.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LikeCount,
|
||||
CommentCount,
|
||||
ShareCount,
|
||||
ViewCount,
|
||||
},
|
||||
props: {
|
||||
comments: Number,
|
||||
likes: Number,
|
||||
liked: Boolean,
|
||||
ads: Boolean,
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
emits:['likeFeed'],
|
||||
methods:{
|
||||
likeFeed(){
|
||||
this.$emit('likeFeed')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
80
resources/js/Shared/FeedList/FeedHeader.vue
Executable file
80
resources/js/Shared/FeedList/FeedHeader.vue
Executable file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="feed-header flex items-center justify-between" @click.stop="">
|
||||
<div v-if="entity.is_ads" class="flex items-center gap-3">
|
||||
<user-avatar :user="user" size="small"
|
||||
class="w-10 h-10 md:w-14 md:h-14 text-lg"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm md:text-base block font-medium text-white">{{ user.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center">
|
||||
<inertia-link :href="route('profile.user', user.username)" class="flex-shrink-0 block mr-2 md:mr-4">
|
||||
<user-avatar :user="user" size="small"
|
||||
class="w-10 h-10 md:w-14 md:h-14 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('profile.user', user.username)" class="flex flex-col">
|
||||
<span class="hover:underline text-sm md:text-base block font-medium text-white">{{ user.name }}</span>
|
||||
<span class="hover:underline text-xs text-gray-light">{{ created_at }}</span>
|
||||
</inertia-link>
|
||||
</div>
|
||||
<div v-if="entity.is_ads == false" class="flex-shrink-0 text-white">
|
||||
<dropdown-menu-point>
|
||||
<MenuItems class="origin-top-right absolute right-0 mt-2 w-64 bg-indigo-300 shadow-lg max-h-60 rounded-md text-base ring-1 ring-indigo-200 overflow-auto focus:outline-none">
|
||||
<MenuItem v-if="user.id === $page.props.auth.user.id">
|
||||
<inertia-link :href="route(`${entity.type}.edit`, entity.slug)" class="w-full group flex items-center px-4 py-2 text-base hover:bg-indigo-100 text-gray-light">
|
||||
<PencilAltIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
|
||||
Редактировать
|
||||
</inertia-link>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem v-if="user.id === $page.props.auth.user.id">
|
||||
<button class="w-full group flex items-center px-4 py-2 text-base hover:bg-indigo-100 text-gray-light" @click="onRemoveFeed()">
|
||||
<MinusCircleIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
|
||||
Удалить
|
||||
</button>
|
||||
</MenuItem>
|
||||
<MenuItem v-if="user.id !== $page.props.auth.user.id">
|
||||
<inertia-link :href="route('complaint.reason', feed_id)" class="group flex items-center px-4 py-2 text-base hover:bg-indigo-100 text-gray-light">
|
||||
<ExclamationIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
|
||||
Пожаловаться
|
||||
</inertia-link>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</dropdown-menu-point>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MenuItem, MenuItems } from '@headlessui/vue'
|
||||
import { MinusCircleIcon, ExclamationIcon, PencilAltIcon } from '@heroicons/vue/solid'
|
||||
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import DropdownMenuPoint from '@/Shared/Form/DropdownMenuPoint.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserAvatar,
|
||||
DropdownMenuPoint,
|
||||
MinusCircleIcon,
|
||||
ExclamationIcon,
|
||||
PencilAltIcon,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
},
|
||||
props: {
|
||||
user: Object,
|
||||
created_at: String,
|
||||
feed_id: Number,
|
||||
entity: Object,
|
||||
},
|
||||
emits: ['onRemoveFeed'],
|
||||
methods: {
|
||||
onRemoveFeed() {
|
||||
this.$emit('onRemoveFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
115
resources/js/Shared/FeedList/FeedList.vue
Executable file
115
resources/js/Shared/FeedList/FeedList.vue
Executable file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<modal-feed
|
||||
:modal-feed="modalFeed"
|
||||
:open="show"
|
||||
@close-modal="closeModal"
|
||||
@destroyFeed="destroyFeed"
|
||||
/>
|
||||
|
||||
<modal-warning
|
||||
:feed_id="modalFeedId"
|
||||
:open="warningShow"
|
||||
@action="deleteActionModal"
|
||||
/>
|
||||
|
||||
<InfinityScroll :node-element="lastNodeLement" :next-cursor="nextCursor"
|
||||
@fromPagination="putFromPagination"
|
||||
>
|
||||
<div v-for="feed in feedLists" :key="feed.id"
|
||||
:ref="el => { if (el && feed.id === lastElementID) lastNodeLement = el }"
|
||||
>
|
||||
<feed-list-node :feed="feed" @onRemoveFeed="onRemoveFeed"
|
||||
@open-modal="openModal"
|
||||
/>
|
||||
</div>
|
||||
</InfinityScroll>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import filter from 'lodash/filter'
|
||||
|
||||
import FeedListNode from '@/Shared/FeedList/FeedListNode.vue'
|
||||
import ModalFeed from '@/Shared/Overlay/ModalFeed.vue'
|
||||
import ModalWarning from '@/Shared/Overlay/ModalWarning.vue'
|
||||
import InfinityScroll from '@/Shared/Misc/InfinityScroll.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedListNode,
|
||||
ModalFeed,
|
||||
ModalWarning,
|
||||
InfinityScroll,
|
||||
},
|
||||
|
||||
props: {
|
||||
feeds: Array,
|
||||
nextCursor: { type: String, default: '' },
|
||||
},
|
||||
|
||||
setup() {
|
||||
const containerRef = ref(null)
|
||||
return {
|
||||
lastNodeLement: containerRef,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
modalFeedId: 0,
|
||||
warningShow: false,
|
||||
feedLists: [],
|
||||
modalFeed: {},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastElementID() {
|
||||
return this.feedLists[this.feedLists.length - 1]?.id
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.feedLists = this.feeds
|
||||
},
|
||||
|
||||
methods: {
|
||||
putFromPagination(lists) {
|
||||
for (let list of lists) {
|
||||
this.feedLists.push(list)
|
||||
}
|
||||
},
|
||||
|
||||
deleteActionModal(removeFeed) {
|
||||
if (removeFeed) {
|
||||
this.destroyFeed(this.modalFeedId)
|
||||
}
|
||||
this.warningShow = false
|
||||
},
|
||||
|
||||
onRemoveFeed(id) {
|
||||
this.warningShow = true
|
||||
this.modalFeedId = id
|
||||
},
|
||||
destroyFeed(id) {
|
||||
Inertia.delete(route('feed.destroy', id), {
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
})
|
||||
|
||||
this.feedLists = filter(this.feedLists, function (x) {
|
||||
return x.id !== id
|
||||
})
|
||||
},
|
||||
|
||||
openModal(feed) {
|
||||
this.show = true
|
||||
this.modalFeed = feed
|
||||
},
|
||||
closeModal() {
|
||||
this.show = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
86
resources/js/Shared/FeedList/FeedListNode.vue
Executable file
86
resources/js/Shared/FeedList/FeedListNode.vue
Executable file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<component
|
||||
:is="currentTypeNode"
|
||||
:feed_id="feed.id"
|
||||
:user="feed.user"
|
||||
:entity="feed.entity"
|
||||
@click.prevent="openModal(feed)"
|
||||
@onRemoveFeed="onRemoveFeed"
|
||||
@like-feed="likeFeed"
|
||||
></component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import { usePage } from '@inertiajs/inertia-vue3'
|
||||
import axios from 'axios'
|
||||
import FeedImages from '@/Shared/FeedList/Images.vue'
|
||||
import FeedVideos from '@/Shared/FeedList/Videos.vue'
|
||||
import FeedMusics from '@/Shared/FeedList/Musics.vue'
|
||||
import FeedAdults from '@/Shared/FeedList/Adults.vue'
|
||||
import FeedProhibited from '@/Shared/FeedList/Prohibited.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedImages,
|
||||
FeedVideos,
|
||||
FeedMusics,
|
||||
FeedAdults,
|
||||
FeedProhibited,
|
||||
},
|
||||
props: {
|
||||
feed: Object,
|
||||
},
|
||||
emits: ['openModal', 'onRemoveFeed'],
|
||||
computed: {
|
||||
authUser() {
|
||||
return usePage().props.value.auth.user
|
||||
},
|
||||
currentTypeNode() {
|
||||
return 'feed-' + this.feed.type.toLowerCase()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onRemoveFeed(id) {
|
||||
this.$emit('onRemoveFeed', id)
|
||||
},
|
||||
openModal(feed) {
|
||||
if(feed.entity){
|
||||
this.$emit('openModal', feed)
|
||||
}
|
||||
},
|
||||
|
||||
likeFeed() {
|
||||
axios
|
||||
.post(route('feed.like', this.feed.id))
|
||||
.then(() => {
|
||||
if (this.feed.entity.liked) {
|
||||
this.feed.entity.liked = false
|
||||
this.feed.entity.likes--
|
||||
} else {
|
||||
this.feed.entity.liked = true
|
||||
this.feed.entity.likes++
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Inertia.post(
|
||||
// route('feed.like', this.feed.id),
|
||||
// {},
|
||||
// {
|
||||
// preserveScroll: true,
|
||||
// preserveState: true,
|
||||
// }
|
||||
// )
|
||||
// if (this.feed.entity.liked) {
|
||||
// this.feed.entity.liked = false
|
||||
// this.feed.entity.likes--
|
||||
// } else {
|
||||
// this.feed.entity.liked = true
|
||||
// this.feed.entity.likes++
|
||||
// }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
109
resources/js/Shared/FeedList/FeedMusicBody.vue
Executable file
109
resources/js/Shared/FeedList/FeedMusicBody.vue
Executable file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="flex items-center border-b border-indigo-100">
|
||||
<div class="flex-shrink-0 mr-5 w-28 md:w-56">
|
||||
<feed-preview class="h-28 w-28 md:w-56 md:h-56 object-cover" type="music"
|
||||
:source="preview"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-base md:text-xl font-semibold text-gray">{{ title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-simplebar class="max-h-72 overflow-auto">
|
||||
<div class="divide-y divide-indigo-100" @click.stop="">
|
||||
<div
|
||||
v-for="media in medias"
|
||||
:key="media.id" :class="[currentSong?.id === media.id ?
|
||||
'bg-indigo-300 bg-opacity-25' :
|
||||
'hover:bg-indigo-300 hover:bg-opacity-25',
|
||||
'p-4 flex items-center space-x-4']"
|
||||
@click.prevent="startPlay(media)"
|
||||
>
|
||||
<div class="flex">
|
||||
<toggle-play-button :media_id="media.id" />
|
||||
</div>
|
||||
<div class="flex-1 text-sm text-gray-light">
|
||||
{{ media.name }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-light">
|
||||
<div v-show="currentSong?.id === media.id">
|
||||
{{ seek }}
|
||||
</div>
|
||||
<div v-show="currentSong?.id !== media.id">
|
||||
{{ media.time }}
|
||||
</div>
|
||||
</div>
|
||||
<music-add-count v-if="$page.props.auth.user" :media_id="media.id"
|
||||
:liked="media.liked" @addAudio="likeAudio"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState, mapGetters } from 'vuex'
|
||||
import find from 'lodash/find'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
import TogglePlayButton from '@/Shared/Misc/TogglePlayButton.vue'
|
||||
import MusicAddCount from '@/Shared/Misc/MusicAddCount.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedPreview,
|
||||
TogglePlayButton,
|
||||
MusicAddCount,
|
||||
},
|
||||
props: {
|
||||
medias: Array,
|
||||
feed_id: Number,
|
||||
title: String,
|
||||
preview: String,
|
||||
},
|
||||
emits: ['trackClick'],
|
||||
computed: {
|
||||
...mapGetters(['playing']),
|
||||
...mapState({
|
||||
playlist_id: (state) => state.player.playlist_id,
|
||||
seek: (state) => state.player.seek,
|
||||
currentSong: (state) => state.player.currentSong,
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {
|
||||
likeAudio(media_id) {
|
||||
Inertia.post(
|
||||
route('feed.audio.like', media_id),
|
||||
{},
|
||||
{
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
}
|
||||
)
|
||||
let media = find(this.medias, (item) => item.id === media_id)
|
||||
if(media){
|
||||
media.liked = !media.liked
|
||||
}
|
||||
},
|
||||
...mapActions(['toggleAudio', 'newCurrentPlaylist', 'skipToIndexByMusic']),
|
||||
startPlay(music) {
|
||||
this.$emit('trackClick')
|
||||
if (this.currentSong?.id === music.id) {
|
||||
this.toggleAudio()
|
||||
return
|
||||
}
|
||||
// if (this.playlist_id === this.feed_id) {
|
||||
// this.skipToIndexByMusic(music);
|
||||
// return;
|
||||
// }
|
||||
this.newCurrentPlaylist([music, this.medias, this.feed_id])
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
104
resources/js/Shared/FeedList/FeedPaidBlock.vue
Executable file
104
resources/js/Shared/FeedList/FeedPaidBlock.vue
Executable file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div v-if="disabled === 0 && is_paid === 1 && $page.props.auth.user.id !== user_id" class="mt-3 rounded-md bg-indigo-300 p-4"
|
||||
@click.stop=""
|
||||
>
|
||||
<div v-if="bought == 0" class="flex items-center justify-center">
|
||||
<div class="mr-5 mb-2 md:mb-0 font-bold text-center md:text-left w-full md:w-auto">
|
||||
Цена: {{ price }}
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="my-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-1 md:py-3 justify-center text-sm md:text-base rounded-md text-white bg-pink focus:outline-none"
|
||||
@click="purchaseFeed"
|
||||
>
|
||||
Купить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-wrap items-center justify-center">
|
||||
<div class="paid-feed-msg mr-5 mb-2 md:mb-0 font-bold text-center md:text-left w-full md:w-auto">
|
||||
Данный товар вами куплен
|
||||
</div>
|
||||
|
||||
<button v-if="disable_btn == 0 && paid_open == 0" type="button"
|
||||
class="my-1 mx-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-1 md:py-3 justify-center text-sm md:text-base rounded-md text-white bg-orange focus:outline-none" @click="replaceFeed"
|
||||
>
|
||||
Показать
|
||||
</button>
|
||||
|
||||
|
||||
<inertia-link :href="route('setting.show.purchases', feed_id)" class="my-1 mx-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-1 md:py-3 justify-center text-sm md:text-base rounded-md text-white bg-pink focus:outline-none">
|
||||
Скачать
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="is_paid === 1 && $page.props.auth.user.id === user_id" class="p-4 text-center"
|
||||
@click.stop=""
|
||||
>
|
||||
<button v-if="paid_open === 0" type="button"
|
||||
class="my-1 mx-1 transition shadow-none hover:shadow-classic2 inline-flex items-center px-8 py-1 md:py-2 justify-center text-sm rounded-md text-white bg-orange focus:outline-none" @click="replaceFeed"
|
||||
>
|
||||
Показать эксклюзивный контент
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
user_id: Number,
|
||||
is_paid: Number,
|
||||
feed_id: Number,
|
||||
price: String,
|
||||
paid_open: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
emits: ['onReplaceFeed'],
|
||||
data() {
|
||||
return {
|
||||
bought: 0,
|
||||
disable_btn: 0,
|
||||
disabled: 1,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.is_paid === 1) {
|
||||
axios
|
||||
.post(route('feeds.purchase_check', this.feed_id))
|
||||
.then(({ data }) => {
|
||||
if (data === 1) {
|
||||
this.bought = 1
|
||||
}
|
||||
this.disabled = 0
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
purchaseFeed() {
|
||||
axios.post(route('feed.purchase', this.feed_id)).then(({ data }) => {
|
||||
if (data.error == 1) {
|
||||
alert(data.msg)
|
||||
} else {
|
||||
this.$emit('onReplaceFeed', data)
|
||||
this.disable_btn = 1
|
||||
this.bought = 1
|
||||
}
|
||||
})
|
||||
},
|
||||
replaceFeed() {
|
||||
axios.post(route('feeds.replace', this.feed_id)).then(({ data }) => {
|
||||
if (data.collection) {
|
||||
this.$emit('onReplaceFeed', data)
|
||||
this.disable_btn = 1
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
101
resources/js/Shared/FeedList/Images.vue
Executable file
101
resources/js/Shared/FeedList/Images.vue
Executable file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="shadow-classic rounded-md bg-indigo-200 p-3 md:px-5 md:py-7">
|
||||
<feed-header
|
||||
:entity="entity"
|
||||
:feed_id="feed_id"
|
||||
:created_at="entity.created_at_humans"
|
||||
:user="user"
|
||||
@onRemoveFeed="onRemoveFeed"
|
||||
/>
|
||||
|
||||
<div class="mt-3 md:mt-6 feed-body">
|
||||
<div class="mb-3 md:mb-6 text-gray text-sm md:text-base">
|
||||
<div @click.stop="">
|
||||
<div v-if="entity.is_ads" class="prose-content"
|
||||
v-html="entity.body"
|
||||
>
|
||||
</div>
|
||||
<template v-else>
|
||||
<feed-tags
|
||||
v-if="entity.tags.length"
|
||||
class="mb-1"
|
||||
:tags="entity.tags"
|
||||
/>
|
||||
{{ entity.body }}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<feed-paid-block
|
||||
:is_paid="entity.is_paid"
|
||||
:user_id="user.id"
|
||||
:price="entity.price"
|
||||
:feed_id="feed_id"
|
||||
:paid_open="entity.paid_open"
|
||||
@onReplaceFeed="onReplaceFeed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:class="[{
|
||||
'grid-cols-2': countMediaContent > 1
|
||||
}]"
|
||||
class="grid gap-1 md:gap-3 md:grid-cols-[repeat(auto-fit,minmax(280px,1fr))]"
|
||||
>
|
||||
<div v-for="media in entity.collection_medias" :key="media.id">
|
||||
<img class="w-full h-full object-cover" :src="media.url"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<feed-footer
|
||||
:likes="entity.likes"
|
||||
:liked="entity.liked"
|
||||
:comments="entity.comments"
|
||||
:count="entity.views_count"
|
||||
:ads="entity.is_ads"
|
||||
@likeFeed="likeFeed"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FeedHeader from '@/Shared/FeedList/FeedHeader.vue'
|
||||
import FeedFooter from '@/Shared/FeedList/FeedFooter.vue'
|
||||
import FeedPaidBlock from '@/Shared/FeedList/FeedPaidBlock.vue'
|
||||
import FeedTags from '@/Shared/Misc/FeedTags.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedHeader,
|
||||
FeedFooter,
|
||||
FeedPaidBlock,
|
||||
FeedTags,
|
||||
},
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
emits: ['likeFeed', 'onRemoveFeed'],
|
||||
computed: {
|
||||
countMediaContent() {
|
||||
return this.entity.collection_medias.length
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onReplaceFeed(data) {
|
||||
this.entity.collection_medias = data.collection
|
||||
this.entity.preview = data.preview
|
||||
this.entity.paid_open = 1
|
||||
},
|
||||
onRemoveFeed() {
|
||||
this.$emit('onRemoveFeed', this.feed_id)
|
||||
},
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
90
resources/js/Shared/FeedList/Musics.vue
Executable file
90
resources/js/Shared/FeedList/Musics.vue
Executable file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="shadow-classic rounded-md bg-indigo-200 p-3 md:px-5 md:py-7">
|
||||
<feed-header :entity="entity" :feed_id="feed_id"
|
||||
:created_at="entity.created_at_humans" :user="user"
|
||||
@onRemoveFeed="onRemoveFeed"
|
||||
/>
|
||||
|
||||
<div class="mt-3 md:mt-6 feed-body">
|
||||
<div class="mb-3 md:mb-6 text-gray text-sm md:text-base" @click.stop="">
|
||||
<div>
|
||||
<feed-tags v-if="entity.tags.length" class="mb-1"
|
||||
:tags="entity.tags"
|
||||
/>
|
||||
{{ entity.body }}
|
||||
</div>
|
||||
<feed-paid-block
|
||||
:is_paid="entity.is_paid"
|
||||
:user_id="user.id"
|
||||
:price="entity.price"
|
||||
:feed_id="feed_id"
|
||||
:paid_open="entity.paid_open"
|
||||
@onReplaceFeed="onReplaceFeed"
|
||||
/>
|
||||
</div>
|
||||
<feed-music-body
|
||||
:feed_id="feed_id"
|
||||
:title="entity.title"
|
||||
:preview="entity.preview"
|
||||
:medias="entity.collection_medias"
|
||||
@trackClick="addViewShow"
|
||||
/>
|
||||
</div>
|
||||
<feed-footer
|
||||
:likes="entity.likes"
|
||||
:liked="entity.liked"
|
||||
:comments="entity.comments"
|
||||
:count="entity.views_count"
|
||||
:ads="entity.is_ads"
|
||||
@likeFeed="likeFeed"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FeedHeader from '@/Shared/FeedList/FeedHeader.vue'
|
||||
import FeedFooter from '@/Shared/FeedList/FeedFooter.vue'
|
||||
import FeedMusicBody from '@/Shared/FeedList/FeedMusicBody.vue'
|
||||
import FeedPaidBlock from '@/Shared/FeedList/FeedPaidBlock.vue'
|
||||
import FeedTags from '@/Shared/Misc/FeedTags.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedHeader,
|
||||
FeedFooter,
|
||||
FeedMusicBody,
|
||||
FeedPaidBlock,
|
||||
FeedTags,
|
||||
},
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
emits: ['likeFeed', 'onRemoveFeed'],
|
||||
|
||||
methods: {
|
||||
addViewShow() {
|
||||
axios
|
||||
.post(route('add.view.feed', this.feed_id))
|
||||
.then(({ data }) => {
|
||||
data && this.entity.views_count++
|
||||
})
|
||||
},
|
||||
onReplaceFeed(data) {
|
||||
this.entity.collection_medias = data.collection
|
||||
this.entity.preview = data.preview
|
||||
this.entity.paid_open = 1
|
||||
},
|
||||
|
||||
onRemoveFeed() {
|
||||
this.$emit('onRemoveFeed', this.feed_id)
|
||||
},
|
||||
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
29
resources/js/Shared/FeedList/Prohibited.vue
Executable file
29
resources/js/Shared/FeedList/Prohibited.vue
Executable file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="shadow-classic rounded-md bg-indigo-200 p-3 md:px-5 md:py-7 relative">
|
||||
<div class="absolute inset-0 flex items-center justify-center z-10 text-white text-sm sm:text-lg w-full object-cover font-medium">
|
||||
<div class="text-center p-2">
|
||||
<p>Контент доступен по подписке!</p>
|
||||
<p>
|
||||
Купить: <inertia-link :href="route('profile.user', user.username)" class="font-semibold underline inline-block mr-2">
|
||||
{{ user.username }}
|
||||
</inertia-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="blur-2xl flex flex-grow bg-indigo-300 w-full h-36 object-cover"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
118
resources/js/Shared/FeedList/Videos.vue
Executable file
118
resources/js/Shared/FeedList/Videos.vue
Executable file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="shadow-classic rounded-md bg-indigo-200 p-3 md:px-5 md:py-7">
|
||||
<feed-header :entity="entity" :feed_id="feed_id"
|
||||
:created_at="entity.created_at_humans" :user="user"
|
||||
@onRemoveFeed="onRemoveFeed"
|
||||
/>
|
||||
<div class="mt-3 md:mt-6 feed-body" @click.stop="">
|
||||
<div class="mb-3 md:mb-6 text-gray text-sm md:text-base">
|
||||
<div @click.stop="">
|
||||
<div class="flex items-start border-b border-indigo-100">
|
||||
<div class="flex-shrink-0 mr-5 w-28 md:w-56">
|
||||
<feed-preview class="h-28 w-28 md:w-56 md:h-56 object-cover" type="music"
|
||||
:source="entity.preview"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-base md:text-xl font-semibold text-gray pb-1">
|
||||
{{ entity.title }}
|
||||
</div>
|
||||
<div v-if="entity.is_ads" class="prose-content"
|
||||
v-html="entity.body"
|
||||
>
|
||||
</div>
|
||||
<template v-else>
|
||||
<feed-tags v-if="entity.tags.length" class="mb-1"
|
||||
:tags="entity.tags"
|
||||
/>
|
||||
<div class="pb-2">
|
||||
{{ entity.body }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<feed-paid-block
|
||||
:is_paid="entity.is_paid"
|
||||
:user_id="user.id"
|
||||
:price="entity.price"
|
||||
:feed_id="feed_id"
|
||||
:paid_open="entity.paid_open"
|
||||
@onReplaceFeed="onReplaceFeed"
|
||||
/>
|
||||
</div>
|
||||
<div :class="[{
|
||||
'grid-cols-2': countMediaContent > 1
|
||||
}]" class="grid gap-1 md:gap-3 md:grid-cols-[repeat(auto-fit,minmax(280px,1fr))]"
|
||||
@click="addViewShow"
|
||||
>
|
||||
<div v-for="media in entity.collection_medias" :key="media.id"
|
||||
class="aspect-w-16 aspect-h-9"
|
||||
>
|
||||
<video :src="media.url" :poster="entity.preview"
|
||||
controls
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<feed-footer
|
||||
:likes="entity.likes"
|
||||
:liked="entity.liked"
|
||||
:comments="entity.comments"
|
||||
:count="entity.views_count"
|
||||
:ads="entity.is_ads"
|
||||
@likeFeed="likeFeed"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FeedHeader from '@/Shared/FeedList/FeedHeader.vue'
|
||||
import FeedFooter from '@/Shared/FeedList/FeedFooter.vue'
|
||||
import FeedPaidBlock from '@/Shared/FeedList/FeedPaidBlock.vue'
|
||||
import FeedPreview from '@/Shared/Feed/FeedPreview.vue'
|
||||
import FeedTags from '@/Shared/Misc/FeedTags.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FeedHeader,
|
||||
FeedFooter,
|
||||
FeedPaidBlock,
|
||||
FeedTags,
|
||||
FeedPreview,
|
||||
},
|
||||
props: {
|
||||
entity: Object,
|
||||
user: Object,
|
||||
feed_id: Number,
|
||||
},
|
||||
emits: ['likeFeed', 'onRemoveFeed'],
|
||||
computed: {
|
||||
countMediaContent() {
|
||||
return this.entity.collection_medias.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addViewShow(){
|
||||
axios
|
||||
.post(route('add.view.feed', this.feed_id))
|
||||
.then(({ data }) => {
|
||||
data && this.entity.views_count++
|
||||
})
|
||||
},
|
||||
onReplaceFeed(data) {
|
||||
this.entity.collection_medias = data.collection
|
||||
this.entity.preview = data.preview
|
||||
this.entity.paid_open = 1
|
||||
},
|
||||
onRemoveFeed() {
|
||||
this.$emit('onRemoveFeed', this.feed_id)
|
||||
},
|
||||
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
71
resources/js/Shared/Form/Dropdown.vue
Executable file
71
resources/js/Shared/Form/Dropdown.vue
Executable file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<button type="button" @click="show = true">
|
||||
<slot />
|
||||
<teleport to="body">
|
||||
<div v-if="show">
|
||||
<div class="bg-indigo-200 bg-opacity-25" style="position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 99998;" @click="show = false" />
|
||||
<div ref="dropdown" style="position: absolute; z-index: 99999;" @click.stop="show = autoClose ? false : true">
|
||||
<slot name="dropdown" />
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popper from 'popper.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-end',
|
||||
},
|
||||
offset: {
|
||||
type: String,
|
||||
default: '0, 10',
|
||||
},
|
||||
boundary: {
|
||||
type: String,
|
||||
default: 'scrollParent',
|
||||
},
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(show) {
|
||||
if (show) {
|
||||
this.$nextTick(() => {
|
||||
this.popper = new Popper(this.$el, this.$refs.dropdown, {
|
||||
placement: this.placement,
|
||||
modifiers: {
|
||||
preventOverflow: { boundariesElement: this.boundary, padding: 0},
|
||||
offset: {
|
||||
enabled: true,
|
||||
offset: this.offset
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
})
|
||||
} else if (this.popper) {
|
||||
setTimeout(() => this.popper.destroy(), 100)
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 27) {
|
||||
this.show = false
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
26
resources/js/Shared/Form/DropdownMenu.vue
Executable file
26
resources/js/Shared/Form/DropdownMenu.vue
Executable file
@@ -0,0 +1,26 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<Menu as="div" class="relative inline-block text-left z-50">
|
||||
<div>
|
||||
<MenuButton class="transition inline-flex items-center justify-center shadow-classic2 rounded-full bg-orange text-white focus:outline-none w-12 h-12">
|
||||
<svg class="h-4 w-4 md:h-5 md:w-5 flex-shrink-0" 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" ><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
</MenuButton>
|
||||
</div>
|
||||
<transition enter-active-class="transition ease-out duration-100" 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">
|
||||
<slot />
|
||||
</transition>
|
||||
</Menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
27
resources/js/Shared/Form/DropdownMenuPoint.vue
Executable file
27
resources/js/Shared/Form/DropdownMenuPoint.vue
Executable file
@@ -0,0 +1,27 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<Menu as="div" v-slot="{ open }" class="relative inline-block text-left z-50">
|
||||
<div>
|
||||
<MenuButton class="transition inline-flex items-center justify-center focus:outline-none ">
|
||||
<svg :class="[open ? 'rotate-90' : 'rotate-180' ,'transform-gpu transition-transform transform w-6 h-6']">
|
||||
<use xlink:href="#more-vertical"></use>
|
||||
</svg>
|
||||
</MenuButton>
|
||||
</div>
|
||||
|
||||
<transition enter-active-class="transition ease-out duration-100" 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">
|
||||
<slot />
|
||||
</transition>
|
||||
</Menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Menu, MenuButton } from '@headlessui/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Menu,
|
||||
MenuButton,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
69
resources/js/Shared/Form/FileInput.vue
Executable file
69
resources/js/Shared/Form/FileInput.vue
Executable file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div>
|
||||
<label v-if="label" class="cursor-pointer text-gray-light text-lg mb-2"
|
||||
@click="browse"
|
||||
>{{ label }}:</label>
|
||||
<div :class="{ error: error }">
|
||||
<input ref="file" type="file"
|
||||
:accept="accept" class="hidden"
|
||||
@change="change"
|
||||
>
|
||||
<div v-if="!modelValue" class="py-2">
|
||||
<button type="button" class="px-6 py-2 bg-indigo-300 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:outline-none focus:border-transparent rounded-sm text-sm text-white"
|
||||
@click="browse"
|
||||
>
|
||||
Выбрать файл
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex flex-col justify-center max-w-2xl mt-3 p-2 border rounded-md">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between p-1">
|
||||
<div class="md:w-5/6 flex-1 flex flex-col pr-1 text-gray">
|
||||
<span class="truncate">{{ modelValue.name }}</span>
|
||||
<span class="text-xs text-gray-light">({{ filesize(modelValue.size) }})</span>
|
||||
</div>
|
||||
<button type="button" class="md:w-1/6 px-1 py-1 bg-indigo-300 hover:bg-indigo-100 rounded-sm text-xs font-medium text-white"
|
||||
@click="remove"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="error" class="text-red text-sm">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: File,
|
||||
label: String,
|
||||
accept: String,
|
||||
error: String,
|
||||
},
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
if (!value) {
|
||||
this.$refs.file.value = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
filesize(size) {
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024))
|
||||
return (size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||
},
|
||||
browse() {
|
||||
this.$refs.file.click()
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('update:modelValue', e.target.files[0])
|
||||
},
|
||||
remove() {
|
||||
this.$emit('update:modelValue', null)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
104
resources/js/Shared/Form/FileInputMultiple.vue
Executable file
104
resources/js/Shared/Form/FileInputMultiple.vue
Executable file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<label v-if="label" class="cursor-pointer text-gray-light text-lg mb-2"
|
||||
@click="browse"
|
||||
>{{ label }}:</label>
|
||||
<div :class="{ error: error }">
|
||||
<input
|
||||
ref="file"
|
||||
type="file"
|
||||
:accept="accept"
|
||||
multiple
|
||||
class="hidden"
|
||||
@change="change"
|
||||
/>
|
||||
<div v-if="!modelValue" class="py-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-2 bg-indigo-300 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:outline-none focus:border-transparent rounded-sm text-sm text-white"
|
||||
@click="browse"
|
||||
>
|
||||
Выбрать файлы
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex flex-col justify-center max-w-2xl mt-3 p-2 border rounded-md">
|
||||
<div
|
||||
v-for="(file, index) in modelValue"
|
||||
:key="index"
|
||||
class="flex flex-col md:flex-row md:items-start md:justify-between p-1"
|
||||
>
|
||||
<div class="md:w-5/6 text-sm md:text-base flex-1 pr-2 text-gray flex flex-col">
|
||||
<span class="truncate">{{ file.name }}</span>
|
||||
<span class="text-xs text-gray-light">({{ filesize(file.size) }})</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="md:w-1/6 px-1 py-1 bg-indigo-300 hover:bg-indigo-100 rounded-sm text-xs font-medium text-white"
|
||||
@click="remove(index)"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="error" class="text-red text-sm">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: FileList,
|
||||
label: String,
|
||||
accept: String,
|
||||
error: String,
|
||||
},
|
||||
emits:['update:modelValue'],
|
||||
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
if (!value) {
|
||||
this.$refs.file.value = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
filesize(size) {
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024))
|
||||
return (
|
||||
(size / Math.pow(1024, i)).toFixed(2) * 1 +
|
||||
' ' +
|
||||
['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||
)
|
||||
},
|
||||
browse() {
|
||||
this.$refs.file.click()
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('update:modelValue', e.target.files)
|
||||
},
|
||||
|
||||
remove(file_index) {
|
||||
const dt = new DataTransfer()
|
||||
let files = Array.from(this.$refs.file.files)
|
||||
|
||||
files.map(function (file, index) {
|
||||
if (index !== file_index) {
|
||||
dt.items.add(file)
|
||||
}
|
||||
})
|
||||
if (dt.files.length) {
|
||||
this.$refs.file.files = dt.files
|
||||
this.$emit('update:modelValue', dt.files)
|
||||
} else {
|
||||
this.$refs.file.files = null
|
||||
this.$emit('update:modelValue', null)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
120
resources/js/Shared/Form/FileInputMultipleDecode.vue
Executable file
120
resources/js/Shared/Form/FileInputMultipleDecode.vue
Executable file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div>
|
||||
<label v-if="label" @click="browse" class="cursor-pointer text-gray-light text-lg mb-2">{{ label }}:</label>
|
||||
<div :class="{ error: error }">
|
||||
<input
|
||||
ref="file"
|
||||
type="file"
|
||||
:accept="accept"
|
||||
multiple
|
||||
class="hidden"
|
||||
@change="change"
|
||||
/>
|
||||
<div v-if="!modelValue" class="py-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-2 bg-indigo-300 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:outline-none focus:border-transparent rounded-sm text-sm text-white"
|
||||
@click="browse"
|
||||
>
|
||||
Выбрать файлы
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex flex-col justify-center max-w-lg mt-3 p-2 border rounded-md">
|
||||
<div
|
||||
v-for="(file, index) in modelValue"
|
||||
:key="index"
|
||||
class="flex items-center justify-between p-1"
|
||||
>
|
||||
<div class="flex-1 pr-1 text-gray">
|
||||
{{ file.name }}
|
||||
<span class="text-xs text-gray-light"
|
||||
>({{ filesize(file.size) }})</span
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-1 bg-indigo-300 hover:bg-indigo-100 rounded-sm text-xs font-medium text-white"
|
||||
@click="remove(index)"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="error" class="text-red text-sm">{{ error }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import helper from "@/includes/helper";
|
||||
export default {
|
||||
props: {
|
||||
modelValue: FileList,
|
||||
label: String,
|
||||
accept: String,
|
||||
error: String,
|
||||
},
|
||||
emits: ["fileTime", "loadFileStart", "update:modelValue"],
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
if (!value) {
|
||||
this.$refs.file.value = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
filesize(size) {
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024));
|
||||
return (
|
||||
(size / Math.pow(1024, i)).toFixed(2) * 1 +
|
||||
" " +
|
||||
["B", "kB", "MB", "GB", "TB"][i]
|
||||
);
|
||||
},
|
||||
browse() {
|
||||
this.$refs.file.click();
|
||||
},
|
||||
change(e) {
|
||||
const files = Array.from(e.target.files);
|
||||
const that = this;
|
||||
files.map(function (file) {
|
||||
that.$emit("loadFileStart");
|
||||
var audioCtx = new (AudioContext || webkitAudioContext)();
|
||||
var readerAudio = new FileReader();
|
||||
readerAudio.readAsArrayBuffer(file);
|
||||
readerAudio.onload = function (ev) {
|
||||
audioCtx.decodeAudioData(ev.target.result).then(function (buffer) {
|
||||
that.$emit(
|
||||
"fileTime",
|
||||
file.name + "," + that.formatTimeSong(buffer.duration)
|
||||
);
|
||||
});
|
||||
};
|
||||
});
|
||||
this.$emit("update:modelValue", e.target.files);
|
||||
},
|
||||
|
||||
formatTimeSong(value) {
|
||||
return helper.formatTime(value);
|
||||
},
|
||||
|
||||
remove(file_index) {
|
||||
const dt = new DataTransfer();
|
||||
let files = Array.from(this.$refs.file.files);
|
||||
|
||||
files.map(function (file, index) {
|
||||
if (index !== file_index) {
|
||||
dt.items.add(file);
|
||||
}
|
||||
});
|
||||
if (dt.files.length) {
|
||||
this.$refs.file.files = dt.files;
|
||||
this.$emit("update:modelValue", dt.files);
|
||||
} else {
|
||||
this.$refs.file.files = null;
|
||||
this.$emit("update:modelValue", null);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
14
resources/js/Shared/Form/LoadingButton.vue
Executable file
14
resources/js/Shared/Form/LoadingButton.vue
Executable file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<button :disabled="loading">
|
||||
<div v-if="loading" class="btn-spinner mr-2" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
loading: Boolean,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
57
resources/js/Shared/Form/SearchFilter.vue
Executable file
57
resources/js/Shared/Form/SearchFilter.vue
Executable file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<div class="flex w-full rounded">
|
||||
<dropdown :auto-close="false" class="px-4 md:px-6 border border-indigo-300 rounded-l bg-indigo-200 hover:bg-indigo-100 focus:z-10 focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent" placement="bottom-start">
|
||||
<div class="flex text-gray items-baseline">
|
||||
<span class="text-base hidden md:inline">Фильтр</span>
|
||||
<svg class="w-2 h-2 fill-current md:ml-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 961.243 599.998">
|
||||
<path d="M239.998 239.999L0 0h961.243L721.246 240c-131.999 132-240.28 240-240.624 239.999-.345-.001-108.625-108.001-240.624-240z" />
|
||||
</svg>
|
||||
</div>
|
||||
<template v-slot:dropdown>
|
||||
<div class="mt-2 px-4 py-6 w-screen shadow-xl bg-indigo-300 rounded-md" :style="{ maxWidth: `${maxWidth}px` }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
|
||||
<div class="flex-1 relative">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center z-[1]">
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-light" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" fill="currentColor">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M11 4a7 7 0 100 14 7 7 0 000-14zm-9 7a9 9 0 1118 0 9 9 0 01-18 0z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.943 15.943a1 1 0 011.414 0l4.35 4.35a1 1 0 01-1.414 1.414l-4.35-4.35a1 1 0 010-1.414z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
:value="modelValue"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
autocomplete="off"
|
||||
name="search"
|
||||
class="relative w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-r placeholder-gray-light !pl-10 h-14" placeholder="Поиск"
|
||||
type="search">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ml-3 text-sm text-gray focus:text-orange" type="button" @click="$emit('reset')">Сбросить</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from "@/Shared/Form/Dropdown.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dropdown,
|
||||
},
|
||||
emits: ["update:modelValue", "reset"],
|
||||
props: {
|
||||
modelValue: String,
|
||||
maxWidth: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
190
resources/js/Shared/Form/TagInput.vue
Executable file
190
resources/js/Shared/Form/TagInput.vue
Executable file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div class="relative" :class="{ 'with-count': showCount }">
|
||||
<input
|
||||
v-model="newTag"
|
||||
type="text"
|
||||
:list="id"
|
||||
placeholder="Теги"
|
||||
class="w-full focus:ring-4 focus:ring-offset-1 focus:ring-orange focus:ring-opacity-20 focus:ring-offset-orange focus:border-transparent text-gray border border-indigo-300 bg-indigo-200 rounded-md"
|
||||
autocomplete="off"
|
||||
:style="{ 'padding-left': `${paddingLeft}px` }"
|
||||
@keydown.prevent.enter="addTag(newTag)"
|
||||
@blur.prevent="addTag(newTag)"
|
||||
/>
|
||||
|
||||
<datalist v-if="options" :id="id">
|
||||
<option v-for="option in availableOptions" :key="option"
|
||||
:value="option"
|
||||
>
|
||||
{{ option }}
|
||||
</option>
|
||||
</datalist>
|
||||
|
||||
<ul ref="tagsUl" class="tags">
|
||||
<li
|
||||
v-for="(tag, index) in tags"
|
||||
:key="tag"
|
||||
class=""
|
||||
:class="{ 'duplicate-shake': tag === duplicate }"
|
||||
>
|
||||
<span class="inline-flex rounded-full items-center py-0.5 pl-2.5 pr-1 text-sm font-medium bg-orange text-indigo-300">
|
||||
{{ tag }}
|
||||
<button type="button" class="flex-shrink-0 ml-0.5 h-4 w-4 rounded-full inline-flex items-center justify-center text-indigo-300 hover:bg-indigo-200 hover:text-white focus:outline-none focus:bg-indigo-200 focus:text-white"
|
||||
@click="removeTag(index)"
|
||||
>
|
||||
<span class="sr-only">Удалить</span>
|
||||
<svg class="h-2 w-2" stroke="currentColor"
|
||||
fill="none" viewBox="0 0 8 8"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-width="1.5"
|
||||
d="M1 1l6 6m0-6L1 7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="showCount" class="count">
|
||||
<span>{{ tags.length }}</span> tags
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { toRef, ref, watch, nextTick, onMounted, computed } from 'vue'
|
||||
export default {
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
modelValue: { type: Array, default: () => [] },
|
||||
options: { type: [Array, Boolean], default: false },
|
||||
allowCustom: { type: Boolean, default: true },
|
||||
showCount: { type: Boolean, default: false },
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// Tags
|
||||
const tags = toRef(props, 'modelValue')
|
||||
|
||||
const newTag = ref('')
|
||||
const id = Math.random().toString(36).substring(7)
|
||||
|
||||
const addTag = (tag) => {
|
||||
if (!tag) return // prevent empty tag
|
||||
// only allow predefined tags when allowCustom is false
|
||||
if (!props.allowCustom && !props.options.includes(tag)) return
|
||||
// return early if duplicate
|
||||
if (tags.value.includes(tag)) {
|
||||
handleDuplicate(tag)
|
||||
return
|
||||
}
|
||||
tags.value.push(tag)
|
||||
newTag.value = '' // reset newTag
|
||||
}
|
||||
const removeTag = (index) => {
|
||||
tags.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// handling duplicates
|
||||
const duplicate = ref(null)
|
||||
const handleDuplicate = (tag) => {
|
||||
duplicate.value = tag
|
||||
setTimeout(() => (duplicate.value = null), 1000)
|
||||
newTag.value = ''
|
||||
}
|
||||
|
||||
// positioning and handling tag change
|
||||
const paddingLeft = ref(10)
|
||||
const tagsUl = ref(null)
|
||||
const onTagsChange = () => {
|
||||
// position cursor
|
||||
const extraCushion = 15
|
||||
paddingLeft.value = tagsUl.value.clientWidth + extraCushion
|
||||
// scroll to end of tags
|
||||
tagsUl.value.scrollTo(tagsUl.value.scrollWidth, 0)
|
||||
// emit value on tags change
|
||||
emit('update:modelValue', tags.value)
|
||||
}
|
||||
watch(tags, () => nextTick(onTagsChange), { deep: true })
|
||||
onMounted(onTagsChange)
|
||||
|
||||
// options
|
||||
const availableOptions = computed(() => {
|
||||
if (!props.options) return false
|
||||
return props.options.filter((option) => !tags.value.includes(option))
|
||||
})
|
||||
|
||||
return {
|
||||
tags,
|
||||
newTag,
|
||||
addTag,
|
||||
removeTag,
|
||||
paddingLeft,
|
||||
tagsUl,
|
||||
availableOptions,
|
||||
id,
|
||||
duplicate,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 10px;
|
||||
max-width: 75%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%,
|
||||
90% {
|
||||
transform: scale(0.9) translate3d(-1px, 0, 0);
|
||||
}
|
||||
|
||||
20%,
|
||||
80% {
|
||||
transform: scale(0.9) translate3d(2px, 0, 0);
|
||||
}
|
||||
|
||||
30%,
|
||||
50%,
|
||||
70% {
|
||||
transform: scale(0.9) translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%,
|
||||
60% {
|
||||
transform: scale(0.9) translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
.duplicate-shake {
|
||||
animation: shake 1s;
|
||||
}
|
||||
.count {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 10px;
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.count span {
|
||||
background: #eee;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.with-count input {
|
||||
padding-right: 60px;
|
||||
}
|
||||
.with-count ul {
|
||||
max-width: 60%;
|
||||
}
|
||||
</style>
|
||||
45
resources/js/Shared/Form/TextInput.vue
Executable file
45
resources/js/Shared/Form/TextInput.vue
Executable file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<label v-if="label" class="text-gray-light text-lg mb-2"
|
||||
:for="id"
|
||||
>{{ label }}:</label>
|
||||
<input :id="id" ref="input"
|
||||
v-bind="$attrs" :class="{ error: error }"
|
||||
:type="type" :value="modelValue"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
>
|
||||
<div v-if="error" class="text-red text-sm">
|
||||
{{ error }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default() {
|
||||
return `select-input-${Math.random() * 1000}`
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
modelValue: [String, Number],
|
||||
label: String,
|
||||
error: String,
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
select() {
|
||||
this.$refs.input.select()
|
||||
},
|
||||
setSelectionRange(start, end) {
|
||||
this.$refs.input.setSelectionRange(start, end)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
30
resources/js/Shared/Form/TextareaInput.vue
Executable file
30
resources/js/Shared/Form/TextareaInput.vue
Executable file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<label class="text-gray-light text-lg mb-2" v-if="label" :for="id">{{ label }}:</label>
|
||||
<textarea :id="id" ref="input" v-bind="$attrs" :class="{ error: error }" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
|
||||
<div v-if="error" class="text-red text-sm">{{ error }}</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default() {
|
||||
return `select-input-${Math.random() * 1000}`;
|
||||
},
|
||||
},
|
||||
modelValue: String,
|
||||
label: String,
|
||||
error: String,
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
select() {
|
||||
this.$refs.input.select()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
50
resources/js/Shared/Form/Toggle.vue
Executable file
50
resources/js/Shared/Form/Toggle.vue
Executable file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<SwitchGroup as="div" :class="{'pointer-events-none': disabled}"
|
||||
class="flex items-center"
|
||||
@click="clicked"
|
||||
>
|
||||
<Switch v-model="enabled" :class="[enabled ? 'bg-orange' : 'bg-indigo-200', 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500']">
|
||||
<!-- <span class="sr-only">Use setting</span> -->
|
||||
<span aria-hidden="true" :class="[enabled ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200']" />
|
||||
</Switch>
|
||||
<SwitchLabel as="span" class="ml-3">
|
||||
<span v-show="!enabled" class="text-sm text-white">{{ textin }}</span>
|
||||
<span v-show="enabled" class="text-sm text-white">{{ textout }}</span>
|
||||
</SwitchLabel>
|
||||
</SwitchGroup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Switch,
|
||||
SwitchGroup,
|
||||
SwitchLabel,
|
||||
},
|
||||
props: {
|
||||
textin: String,
|
||||
textout: String,
|
||||
user_id: Number,
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
emits: ['clicked', 'prohibited'],
|
||||
methods: {
|
||||
clicked(){
|
||||
if(this.disabled === false){
|
||||
this.$emit('clicked', this.user_id)
|
||||
}else{
|
||||
this.$emit('prohibited')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
149
resources/js/Shared/Layout.vue
Executable file
149
resources/js/Shared/Layout.vue
Executable file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<header-bar v-if="$page.props.auth.user" />
|
||||
|
||||
<div class="min-h-screen-header flex overflow-hidden">
|
||||
<sidebar v-if="$page.props.auth.user" />
|
||||
|
||||
<div class="flex-1 flex flex-col overflow-hidden contain">
|
||||
<div class="bg-indigo-300 flex-1 flex items-stretch overflow-hidden">
|
||||
<!-- pb-10 -->
|
||||
<main class="flex-1 overflow-y-auto relative">
|
||||
<button v-if="$page.props.sidebar_layout" class="default text-center right-2 top-3 absolute z-40 flex lg:hidden items-center text-white text-xs"
|
||||
@click="openSidebar"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="feather feather-menu"
|
||||
><line x1="3" y1="12"
|
||||
x2="21" y2="12"
|
||||
></line><line x1="3" y1="6"
|
||||
x2="21" y2="6"
|
||||
></line><line x1="3" y1="18"
|
||||
x2="21" y2="18"
|
||||
></line></svg>
|
||||
<span class="ml-1">Лидеры</span>
|
||||
</button>
|
||||
|
||||
<section class="min-w-0 flex-1 h-full flex flex-col overflow-hidden lg:order-last">
|
||||
<flash-messages />
|
||||
<slot />
|
||||
|
||||
<footer :class="{'px-2 md:px-6 2xl:px-28': $page.props.sidebar_layout}" class="bg-indigo-300 mt-auto">
|
||||
<div class="container mx-auto p-2 text-sm text-gray-light">
|
||||
<ul class="flex justify-center flex-wrap gap-3 text-xs px-3">
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/company.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>О компании</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/offer_authors.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Оферта для авторов</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/offer_paid_subscription.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Оферта на платную подписку</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/security_policy.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Политика безопасности</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/privacy_policy.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Политика конфиденциальности</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/terms_use.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Пользовательское соглашение</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="hover:underline"
|
||||
href="/docs/personal_data.pdf"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>Согласие на обработку персональных данных</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
</main>
|
||||
<section v-if="$page.props.sidebar_layout" ref="userSidebar"
|
||||
class="lg:mt-16 lg:w-[19rem] xl:w-[26rem] 2xl:w-[32rem] overflow-y-auto lg:block lg:pr-5 2xl:pr-28 transition-transform z-50 absolute top-0 right-0 transform-gpu translate-x-full lg:relative lg:transform-none"
|
||||
>
|
||||
<sidebar-secondary />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FlashMessages from '@/Shared/Misc/FlashMessages.vue'
|
||||
import HeaderBar from '@/Shared/LayoutParts/HeaderBar.vue'
|
||||
import Sidebar from '@/Shared/LayoutParts/Sidebar.vue'
|
||||
import SidebarSecondary from '@/Shared/LayoutParts/SidebarSecondary.vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FlashMessages,
|
||||
HeaderBar,
|
||||
Sidebar,
|
||||
SidebarSecondary,
|
||||
},
|
||||
setup(){
|
||||
|
||||
const userSidebar = ref(null)
|
||||
|
||||
|
||||
let openSidebar = () => {
|
||||
if (window.matchMedia('(max-width: 1024px)').matches) {
|
||||
userSidebar.value.classList.remove('translate-x-full')
|
||||
userSidebar.value.classList.add('translate-x-0')
|
||||
userSidebar.value.classList.add('shadow-classic')
|
||||
}
|
||||
}
|
||||
|
||||
let closeSidebar = () => {
|
||||
if (window.matchMedia('(max-width: 1024px)').matches) {
|
||||
userSidebar.value.classList.add('translate-x-full')
|
||||
userSidebar.value.classList.remove('translate-x-0')
|
||||
userSidebar.value.classList.remove('shadow-classic')
|
||||
}
|
||||
}
|
||||
|
||||
if (window.matchMedia('(max-width: 1024px)').matches) {
|
||||
onClickOutside(userSidebar, () => {
|
||||
closeSidebar()
|
||||
})
|
||||
}
|
||||
return {
|
||||
openSidebar,
|
||||
closeSidebar,
|
||||
userSidebar
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.prose-content a{
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
19
resources/js/Shared/LayoutParts/HeaderAuth.vue
Executable file
19
resources/js/Shared/LayoutParts/HeaderAuth.vue
Executable file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="header-simple relative z-10 flex items-center justify-center p-3 bg-orange shadow-md">
|
||||
<inertia-link class="block" href="/">
|
||||
<img class="block h-12" :src="setLogo()"
|
||||
alt=""
|
||||
>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
setLogo() {
|
||||
return '/image/logotype.svg'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
250
resources/js/Shared/LayoutParts/HeaderBar.vue
Executable file
250
resources/js/Shared/LayoutParts/HeaderBar.vue
Executable file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<header class="main-header-wrp w-full sticky top-0 z-[100]">
|
||||
<div class="relative z-10 bg-orange shadow-xl">
|
||||
<!-- contain-content -->
|
||||
<div
|
||||
class="
|
||||
|
||||
grid grid-cols-12
|
||||
items-center
|
||||
pt-2.5
|
||||
pb-2.5
|
||||
px-4
|
||||
sm:px-6
|
||||
md:space-x-5
|
||||
lg:space-x-10
|
||||
"
|
||||
>
|
||||
<inertia-link :href="route('dashboard')" class="col-span-1 block flex-shrink-0">
|
||||
<div class="block md:hidden font-bold text-indigo-300 text-2xl">
|
||||
T
|
||||
</div>
|
||||
<img class="hidden md:block" :src="setLogo()"
|
||||
alt=""
|
||||
/>
|
||||
</inertia-link>
|
||||
|
||||
<audio-player />
|
||||
<search-header ref="searchHeader" />
|
||||
|
||||
<div
|
||||
class="
|
||||
col-span-4
|
||||
md:col-span-2
|
||||
lg:col-span-4
|
||||
flex
|
||||
items-center
|
||||
justify-end
|
||||
space-x-2
|
||||
sm:ml-6
|
||||
sm:space-x-6
|
||||
"
|
||||
>
|
||||
<inertia-link :href="route('setting.money')" class="hidden lg:block default text-sm text-white">
|
||||
Баланс: {{ $page.props.balance }}
|
||||
</inertia-link>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
flex
|
||||
md:hidden
|
||||
p-1
|
||||
rounded-full
|
||||
items-center
|
||||
justify-center
|
||||
text-white
|
||||
focus:outline-none
|
||||
"
|
||||
@click="showSearch"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 md:h-6 md:w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="11" cy="11"
|
||||
r="8"
|
||||
></circle>
|
||||
<line x1="21" y1="21"
|
||||
x2="16.65" y2="16.65"
|
||||
></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="relative">
|
||||
<div
|
||||
v-if="$page.props.is_notify"
|
||||
class="
|
||||
animate-ping
|
||||
absolute
|
||||
top-0
|
||||
right-1
|
||||
w-2
|
||||
h-2
|
||||
md:w-3
|
||||
md:h-3
|
||||
bg-pink
|
||||
rounded-full
|
||||
"
|
||||
></div>
|
||||
<inertia-link
|
||||
:href="route('setting.notify')"
|
||||
class="
|
||||
flex
|
||||
p-1
|
||||
rounded-full
|
||||
items-center
|
||||
justify-center
|
||||
text-white
|
||||
focus:outline-none
|
||||
"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 md:h-6 md:w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
||||
</svg>
|
||||
</inertia-link>
|
||||
</div>
|
||||
|
||||
<dropdown class="flex md:hidden user-menu h-12 w-12 rounded-full items-center justify-center focus:outline-none" placement="bottom-start">
|
||||
<svg class="h-6 w-6 text-white" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
> <line x1="3" y1="12"
|
||||
x2="21" y2="12"
|
||||
></line> <line x1="3" y1="6"
|
||||
x2="21" y2="6"
|
||||
></line> <line x1="3" y1="18"
|
||||
x2="21" y2="18"
|
||||
></line> </svg>
|
||||
<template #dropdown>
|
||||
<div class="mobile-user-menu z-50 bg-orange-dark rounded-b-lg">
|
||||
<div
|
||||
class="absolute z-50 right-0 top-[-16px] mr-[20px] sm:mr-[20px]"
|
||||
>
|
||||
</div>
|
||||
<ul class="p-6 text-white space-y-6">
|
||||
<li>
|
||||
<inertia-link :href="route('setting.money')">
|
||||
Баланс: {{ $page.props.balance }}
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('profile.user', $page.props.auth.user.username)">
|
||||
Профиль
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('feeds.layoutsidebar')">
|
||||
Новости
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('messenger.index')">
|
||||
<!-- <span class="text-xs rounded-full mr-1"></span> -->
|
||||
Сообщения
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('images.index')">
|
||||
Изображения
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('videos.index')">
|
||||
Видео
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('musics.index')">
|
||||
Музыка
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('users.index')">
|
||||
Пользователи
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link :href="route('setting.index')">
|
||||
Настройки
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li>
|
||||
<inertia-link as="button" :href="route('logout')"
|
||||
method="delete"
|
||||
>
|
||||
Выйти
|
||||
</inertia-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
|
||||
|
||||
<div class="hidden md:block relative flex-shrink-0">
|
||||
<inertia-link
|
||||
:href="route('profile.user', $page.props.auth.user.username)"
|
||||
class="
|
||||
bg-white
|
||||
rounded-full
|
||||
flex
|
||||
text-sm
|
||||
focus:outline-none
|
||||
focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500
|
||||
"
|
||||
>
|
||||
<user-avatar :user="$page.props.auth.user" size="small"
|
||||
class="w-9 h-9 md:h-12 md:w-12 text-base"
|
||||
/>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '@/Shared/Form/Dropdown.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import AudioPlayer from '@/Shared/AudioPlayer.vue'
|
||||
import SearchHeader from '@/Shared/Search/SearchHeader.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AudioPlayer,
|
||||
UserAvatar,
|
||||
Dropdown,
|
||||
SearchHeader,
|
||||
},
|
||||
|
||||
methods: {
|
||||
showSearch(){
|
||||
this.$refs.searchHeader.openModal()
|
||||
},
|
||||
setLogo() {
|
||||
return '/image/logotype.svg'
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
107
resources/js/Shared/LayoutParts/SettingsMenu.vue
Executable file
107
resources/js/Shared/LayoutParts/SettingsMenu.vue
Executable file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="md:py-7 col-span-2 lg:col-span-1 border-r border-indigo-300">
|
||||
<ul class="flex md:block text-base lg:text-xl xl:text-2xl text-gray-light overflow-x-auto">
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsProfile' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.index')">
|
||||
Профиль
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.url.startsWith('/settings/purchases') ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.purchases')">
|
||||
Покупки
|
||||
</inertia-link>
|
||||
</li>
|
||||
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsLikes' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.likes')">
|
||||
Понравилось
|
||||
</inertia-link>
|
||||
</li>
|
||||
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsMoney' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.money')">
|
||||
Доход
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsTarif' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.tarif')">
|
||||
Тарифы
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsNotify' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.notify')">
|
||||
Оповещения
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsPayouts' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.payouts')">
|
||||
Выплаты
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsPacket' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.packet')">
|
||||
Платные подписки
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsVerification' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.verification')">
|
||||
Верификация
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsDocuments' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.documents')">
|
||||
Документы
|
||||
</inertia-link>
|
||||
</li>
|
||||
<li
|
||||
:class="[$page.component === 'Settings/SettingsWriteToUs' ?
|
||||
'md:border-l-4 md:border-b-0 border-b-2 border-pink text-white' :
|
||||
'hover:text-white' ,'px-6 py-4']"
|
||||
>
|
||||
<inertia-link :href="route('setting.write-to-us')">
|
||||
Написать нам
|
||||
</inertia-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
148
resources/js/Shared/LayoutParts/Sidebar.vue
Executable file
148
resources/js/Shared/LayoutParts/Sidebar.vue
Executable file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="main-sidebar-wrp hidden w-28 bg-indigo-200 overflow-y-auto md:block">
|
||||
<div class="w-full py-6 flex flex-col items-center">
|
||||
<div class="flex-1 w-full px-2 space-y-1">
|
||||
<inertia-link :class="$page.url.startsWith('/profile') ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('profile.user', $page.props.auth.user.username)">
|
||||
<svg :class="$page.url.startsWith('/profile') ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
<span class="mt-2">Профиль</span>
|
||||
</inertia-link>
|
||||
|
||||
|
||||
|
||||
<inertia-link :href="route('setting.money')"
|
||||
class="lg:hidden text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs"
|
||||
>
|
||||
<svg class="text-green group-hover:text-white h-6 w-6" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 440 440" fill="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
><path d="M232.522 242.428c63.913 0 115.91-54.382 115.91-121.227C348.432 54.37 296.435 0 232.522 0H120.568v282.428h-29v30h29V440h30V312.428h101.955v-30H150.568v-40h81.954zM150.568 30h81.955c47.371 0 85.91 40.912 85.91 91.201 0 50.303-38.539 91.227-85.91 91.227h-81.955V30z" /></svg>
|
||||
|
||||
<span class="mt-2">{{ $page.props.balance }}</span>
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link class="relative" :class="$page.component === 'Messenger/Index' ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' "
|
||||
:href="route('messenger.index')"
|
||||
>
|
||||
<div v-if="$page.props.message_reading_count" class="absolute top-1 right-1 w-2 h-2 md:w-5 md:h-5 bg-pink rounded-full text-xs flex justify-center items-center text-white">
|
||||
{{ $page.props.message_reading_count }}
|
||||
</div>
|
||||
<svg :class="$page.component === 'Messenger/Index' ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " 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 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
|
||||
<span class="mt-2">Сообщения</span>
|
||||
</inertia-link>
|
||||
|
||||
|
||||
<inertia-link :class="$page.component === 'Feed/Index' ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('feeds.layoutsidebar')">
|
||||
<svg :class="$page.component === 'Feed/Index' ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="mt-2">Новости</span>
|
||||
</inertia-link>
|
||||
|
||||
|
||||
<inertia-link :class="$page.component === 'Image/Index' ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('images.index')">
|
||||
<svg :class="$page.component === 'Image/Index' ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
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>
|
||||
<span class="mt-2">Изображения</span>
|
||||
</inertia-link>
|
||||
|
||||
|
||||
<inertia-link :class="$page.component === 'Video/Feed' ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('videos.index')">
|
||||
<svg :class="$page.component === 'Video/Feed' ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="mt-2">Видео</span>
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link :class="$page.component === 'Music/Feed' ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('musics.index')">
|
||||
<svg :class="$page.component === 'Music/Feed' ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"
|
||||
/>
|
||||
</svg>
|
||||
<span class="mt-2">Музыка</span>
|
||||
</inertia-link>
|
||||
|
||||
|
||||
<inertia-link :class="$page.component === 'User/Index' ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('users.index')">
|
||||
<svg :class="$page.component === 'User/Index' ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="mt-2">Пользователи</span>
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link :class="$page.component.startsWith('Settings') ? 'text-white group w-full p-3 rounded-md flex flex-col items-center text-xs font-medium' : 'text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs' " :href="route('setting.index')">
|
||||
<svg :class="$page.component.startsWith('Settings') ? 'text-white h-6 w-6' : 'text-indigo-300 group-hover:text-white h-6 w-6' " xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
/><path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="mt-2">Настройки</span>
|
||||
</inertia-link>
|
||||
|
||||
<inertia-link
|
||||
as="button"
|
||||
:href="route('logout')" method="delete"
|
||||
class="relative text-gray-light hover:bg-orange hover:text-white group w-full p-3 rounded-md flex flex-col items-center text-xs"
|
||||
>
|
||||
<svg class="text-indigo-300 group-hover:text-white h-6 w-6" 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 stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/></svg>
|
||||
<span class="mt-2">Выйти</span>
|
||||
</inertia-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
45
resources/js/Shared/LayoutParts/SidebarSecondary.vue
Executable file
45
resources/js/Shared/LayoutParts/SidebarSecondary.vue
Executable file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="bg-indigo-200 shadow-classic rounded-md p-5">
|
||||
<span class="block text-white font-medium">Лидеры</span>
|
||||
<div v-if="leaderUsers.length" class="mt-5 space-y-5">
|
||||
<div v-for="leaderUser in leaderUsers" :key="leaderUser.id"
|
||||
class="flex items-center"
|
||||
>
|
||||
<div class="flex-shrink-0 mr-2 md:mr-4">
|
||||
<inertia-link :href="route('profile.user', leaderUser.username)" class="flex-shrink-0 block mr-2 md:mr-4">
|
||||
<user-avatar :user="leaderUser" size="small"
|
||||
class="w-10 h-10 md:w-14 md:h-14 text-lg"
|
||||
/>
|
||||
</inertia-link>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<inertia-link :href="route('profile.user', leaderUser.username)" class="hover:underline text-sm md:text-base block text-white">
|
||||
{{ leaderUser.name }}
|
||||
</inertia-link>
|
||||
<span class="text-xs text-gray-light">Кол. голосов: {{ leaderUser.countVote }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center mt-3 text-lg text-gray-light">
|
||||
Лидер не определен
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { usePage } from '@inertiajs/inertia-vue3'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserAvatar,
|
||||
},
|
||||
|
||||
computed: {
|
||||
leaderUsers() {
|
||||
return usePage().props.value.leaders
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
61
resources/js/Shared/Messanger/BoardFriendList.vue
Executable file
61
resources/js/Shared/Messanger/BoardFriendList.vue
Executable file
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { useInfinityScroll } from '@/includes/composables/useInfinityScroll'
|
||||
import MessangerModal from '@/Shared/Messanger/MessangerModal.vue'
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import UserBanner from '@/Shared/Misc/UserBanner.vue'
|
||||
import axios from 'axios'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const search = ref('')
|
||||
const refMessangerModal = ref()
|
||||
const { domNodeFn, cursor, loading, error, callAPI, data: userLists } = useInfinityScroll(async () => {
|
||||
const url = search.value ? `user-friend/?search=${search.value}&cursor=${cursor.value}` : `user-friend/?cursor=${cursor.value}`
|
||||
const res = await axios.get(url)
|
||||
return res.data
|
||||
})
|
||||
callAPI()
|
||||
watch(search, debounce(() => {
|
||||
callAPI()
|
||||
}, 500))
|
||||
|
||||
|
||||
function openModalMessanger(user) {
|
||||
refMessangerModal.value.openAction(user)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="userLists" class="mt-5 grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 cards-block rounded-md bg-indigo-200 grid gap-2 lg:gap-4 grid-cards ">
|
||||
<messanger-modal ref="refMessangerModal" />
|
||||
<div
|
||||
v-for="user in userLists"
|
||||
:ref="el => domNodeFn(el, user.id)"
|
||||
:key="user.id"
|
||||
class="user-card relative cursor-pointer"
|
||||
@click="openModalMessanger(user)"
|
||||
>
|
||||
<div class="absolute inset-x-0 bottom-4 z-10 flex justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<user-avatar :user="user" size="small"
|
||||
class="border border-white shadow-classic h-20 w-20 text-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-2 text-white text-sm text-center">
|
||||
<p class="">
|
||||
{{ user.name }}
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
{{ user.username }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gradient-profile relative overflow-hidden">
|
||||
<user-banner class="h-72 bg-indigo-300" :user="user"
|
||||
size="banner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
285
resources/js/Shared/Messanger/BoardMessage.vue
Executable file
285
resources/js/Shared/Messanger/BoardMessage.vue
Executable file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div v-if="user" class="flex flex-col h-full">
|
||||
<board-top :user="user">
|
||||
<template v-if="partnerIsBanned !== null" #menu>
|
||||
<button class="default text-gray text-sm underline" @click="bannRequest">
|
||||
{{ isBannedMessage }}
|
||||
</button>
|
||||
<!-- <button class="default mt-2 text-gray text-sm underline" @click="exitRoom">
|
||||
Покинуть беседу
|
||||
</button> -->
|
||||
</template>
|
||||
</board-top>
|
||||
<div class="px-2 sm:px-5 xl:px-10 flex bg-indigo-200 border-b border-indigo-300">
|
||||
<div class="flex-1 pt-5 pb-3">
|
||||
<textarea ref="textarea" v-model="textMessage"
|
||||
rows="3"
|
||||
autofocus
|
||||
name="comment" class="placeholder-gray bg-indigo-200 text-white max-h-56 block w-full py-3 border-0 resize-none focus:ring-0 text-sm sm:text-base"
|
||||
placeholder="Напишите сообщение ..."
|
||||
:disabled="frozen"
|
||||
@keyup.ctrl.enter="createMessage"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!frozen" class="mt-5 ml-3 flex-shrink-0 text-pink"
|
||||
@click="createMessage"
|
||||
>
|
||||
<button class="default">
|
||||
<svg class="w-8 h-8 flex-shrink-0">
|
||||
<use xlink:href="#arrow-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="py-5 lg:py-10 px-2 sm:px-5 xl:px-10 space-y-5 flex-1 bg-indigo-400">
|
||||
<div v-if="frozen" class="p-3 text-sm rounded-md bg-orange text-white text-center">
|
||||
Вы не можете отправить сообщение пользователю
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="p-3 text-sm rounded-md bg-pink text-white text-center">
|
||||
Ошибка загрузки данных
|
||||
</div>
|
||||
|
||||
<div v-for="(messageCollection, key) in messages" :key="key"
|
||||
class="space-y-5"
|
||||
>
|
||||
<div class="text-gray-light text-xs text-right">
|
||||
{{ key }}
|
||||
</div>
|
||||
|
||||
<board-message-item v-for="message in messageCollection"
|
||||
:key="message.id"
|
||||
:ref="el => domNodeFn(el, message.id)"
|
||||
:profile-id="user.id"
|
||||
:user-message="message"
|
||||
@remove="removeMessage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center">
|
||||
<svg class="animate-spin h-8 w-8 text-white" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12"
|
||||
cy="12" r="10"
|
||||
stroke="currentColor" stroke-width="4"
|
||||
></circle>
|
||||
<path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="p-10 text-center">
|
||||
<h2 class="text-lg xl:text-3xl font-bold text-indigo-100">
|
||||
Выберите пользователя
|
||||
|
||||
<board-friend-list />
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { useAutoresizeTextarea } from '@/includes/composables'
|
||||
import { useInfinityScroll } from '@/includes/composables/useInfinityScroll'
|
||||
import { ref, watch, toRef, computed } from 'vue'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
import axios from 'axios'
|
||||
|
||||
import BoardMessageItem from './BoardMessageItem.vue'
|
||||
import BoardTop from './BoardTop.vue'
|
||||
import BoardFriendList from './BoardFriendList.vue'
|
||||
|
||||
export default {
|
||||
components:{
|
||||
BoardTop,
|
||||
BoardMessageItem,
|
||||
BoardFriendList
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
roomId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
frozen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
search: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
emits: {
|
||||
'disable': null,
|
||||
'last-message': null,
|
||||
'change-room': null,
|
||||
'leave-room': null,
|
||||
},
|
||||
|
||||
setup(props, {emit}){
|
||||
const roomId = toRef(props, 'roomId')
|
||||
|
||||
const partnerIsBanned = ref(null)
|
||||
const textarea = ref()
|
||||
const textMessage = ref()
|
||||
useAutoresizeTextarea(textarea)
|
||||
|
||||
const { domNodeFn, cursor, loading, error, refetchApi, data: messages } = useInfinityScroll(async () => {
|
||||
const url = props.search ? `messenger/${roomId.value}?search=${props.search}&cursor=${cursor.value}` : `messenger/${roomId.value}?cursor=${cursor.value}`
|
||||
const res = await axios.get(url)
|
||||
return res.data
|
||||
})
|
||||
|
||||
const checkRequestBanned = () => {
|
||||
axios.post(route('messenger.banned.user'), {
|
||||
params: {
|
||||
user: props.user.id,
|
||||
}
|
||||
}).then(response => {
|
||||
partnerIsBanned.value = response.data.banned
|
||||
})
|
||||
}
|
||||
|
||||
watch(roomId, (id) => {
|
||||
if(id){
|
||||
refetchApi()
|
||||
checkRequestBanned()
|
||||
partnerIsBanned.value = null
|
||||
}
|
||||
if(textarea.value){
|
||||
textarea.value.focus()
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(loading, (loading) => {
|
||||
emit('disable', loading)
|
||||
})
|
||||
|
||||
const createMessage = () => {
|
||||
if(!textMessage.value.trim()){
|
||||
alert('Введите сообщение')
|
||||
return
|
||||
}
|
||||
axios.post('messenger', {
|
||||
params: {
|
||||
message: textMessage.value,
|
||||
room: roomId.value,
|
||||
}
|
||||
}).then(response => {
|
||||
if(!response.data){
|
||||
alert('Вы не можете отправить сообщение данному пользователю')
|
||||
return
|
||||
}
|
||||
textMessage.value = ''
|
||||
const k = response.data.keyGroupDate // keyGroupDate - ключ, eg 28.11.21
|
||||
if(messages.value[k]){
|
||||
messages.value[k].unshift(response.data)
|
||||
}else{
|
||||
messages.value = { [k]: [ response.data ], ...messages.value }
|
||||
}
|
||||
emit('last-message', response.data)
|
||||
})
|
||||
}
|
||||
|
||||
const bannRequest = () => {
|
||||
Inertia.post(route('messenger.banned.room'), { room: roomId.value },
|
||||
{
|
||||
preserveState: true,
|
||||
onSuccess: () => {
|
||||
checkRequestBanned()
|
||||
emit('change-room', roomId.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const removeMessage = (message) => {
|
||||
// console.log(id)
|
||||
removeMessageRequest(message)
|
||||
}
|
||||
|
||||
const removeMessageRequest = (message) => {
|
||||
|
||||
const group = messages.value[message.group]
|
||||
const count = group.length
|
||||
const index = messages.value[message.group].findIndex((item) => item.id === message.id)
|
||||
axios.delete(route('messenger.message.remove', {'id': message.id})).then(response => {
|
||||
if(count === 1){
|
||||
delete messages.value[message.group]
|
||||
}else{
|
||||
// delete messages.value[message.group][index]
|
||||
messages.value[message.group] = messages.value[message.group].filter((i) => i.id != messages.value[message.group][index].id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// const removeMessageRequest = (message) => {
|
||||
// // console.log(message)
|
||||
// // console.log(messages.value)
|
||||
// const group = messages.value[message.group]
|
||||
// const count = group.length
|
||||
// const index = messages.value[message.group].findIndex((item) => item.id === message.id)
|
||||
|
||||
// Inertia.delete(route('messenger.message.remove', {'id': message.id}),
|
||||
// {
|
||||
// preserveState: true,
|
||||
// onSuccess: () => {
|
||||
// if(count === 1){
|
||||
// delete messages.value[message.group]
|
||||
// }else{
|
||||
// delete messages.value[message.group][index]
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// })
|
||||
// }
|
||||
|
||||
// const exitRoom = () => {
|
||||
// Inertia.post(route('messenger.leave.room'), { room: roomId.value },
|
||||
// {
|
||||
// preserveState: true,
|
||||
// onSuccess: () => {
|
||||
// emit('leave-room')
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
|
||||
const isBannedMessage = computed(() => {
|
||||
if(partnerIsBanned.value === null){
|
||||
return false
|
||||
}
|
||||
if(partnerIsBanned.value){
|
||||
return 'Убрать из черного списка'
|
||||
}
|
||||
return 'Добавить в черный список'
|
||||
})
|
||||
|
||||
|
||||
|
||||
return {
|
||||
textarea,
|
||||
textMessage,
|
||||
loading,
|
||||
messages,
|
||||
createMessage,
|
||||
error,
|
||||
domNodeFn,
|
||||
bannRequest,
|
||||
isBannedMessage,
|
||||
partnerIsBanned,
|
||||
removeMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
63
resources/js/Shared/Messanger/BoardMessageItem.vue
Executable file
63
resources/js/Shared/Messanger/BoardMessageItem.vue
Executable file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<user-avatar :user="userMessage.user" size="small"
|
||||
class="flex-shrink-0 w-10 h-10 lg:w-12 lg:h-12 xl:w-14 xl:h-14 text-sm xl:text-lg"
|
||||
/>
|
||||
<div class="ml-5 flex bg-indigo-100 p-4 rounded shadow relative">
|
||||
<div v-if="isAuthorMessage" class="flex absolute top-px right-px">
|
||||
<button @click="remove">
|
||||
<svg class="text-red w-3.5 h-3.5" 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"
|
||||
><line x1="18" y1="6"
|
||||
x2="6" y2="18"
|
||||
></line><line x1="6" y1="6"
|
||||
x2="18" y2="18"
|
||||
></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-white break-all">
|
||||
<footer class="font-bold mb-2 text-gray-light flex items-center">
|
||||
{{ userMessage.user.first_name }}
|
||||
<span class="font-normal text-xs text-gray ml-4 lg:ml-8 flex-shrink-0">{{ userMessage.created }}</span>
|
||||
</footer>
|
||||
{{ userMessage.message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
export default {
|
||||
components:{
|
||||
UserAvatar,
|
||||
},
|
||||
props: {
|
||||
userMessage: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
profileId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
'remove': null
|
||||
},
|
||||
computed: {
|
||||
isAuthorMessage() {
|
||||
return this.profileId != this.userMessage.user.id
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
remove(){
|
||||
this.$emit('remove', this.userMessage)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
81
resources/js/Shared/Messanger/BoardRoomList.vue
Executable file
81
resources/js/Shared/Messanger/BoardRoomList.vue
Executable file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div :class="{
|
||||
'bg-purple': !room.is_my_message && !room.is_reading
|
||||
}" class="relative flex items-center px-2 xl:px-8 hover:bg-indigo-300 cursor-pointer transition-colors"
|
||||
@click="selectRoom(room)"
|
||||
>
|
||||
<div v-if="!room.is_reading && !room.is_my_message" class="absolute bottom-1 right-3 text-xxs text-white">
|
||||
<span>не прочитано</span>
|
||||
</div>
|
||||
<div v-if="room.is_my_message" class="absolute bottom-0 right-3">
|
||||
<div v-show="!room.is_reading" class="flex relative w-[30px] h-[24px] text-gray-light">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="absolute top-0 right-0"
|
||||
><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div v-show="room.is_reading" class="flex relative w-[30px] h-[24px] text-orange">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="absolute top-0 right-[5px]"
|
||||
><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="absolute top-0 right-0"
|
||||
><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-2 right-2 text-xxs lg:text-xs text-gray-light">
|
||||
{{ room.updated_at_human }}
|
||||
</div>
|
||||
<user-avatar :user="room.correspond" size="small"
|
||||
class="flex-shrink-0 w-10 h-10 lg:w-12 lg:h-12 xl:w-14 xl:h-14 text-sm xl:text-lg"
|
||||
/>
|
||||
<div class="ml-3 flex flex-col w-4/5 overflow-hidden">
|
||||
<span class="text-sm lg:text-base block text-white">{{ room.correspond.name }}</span>
|
||||
<span class="text-xs lg:text-sm truncate text-gray-light">
|
||||
<span v-if="room.is_my_message" class="text-orange">Вы:</span>
|
||||
{{ room.message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
export default {
|
||||
components: {
|
||||
UserAvatar,
|
||||
},
|
||||
props: {
|
||||
room: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
emits:{
|
||||
'select-room': null
|
||||
},
|
||||
|
||||
setup(props, {emit}){
|
||||
const selectRoom = (room) => {
|
||||
emit('select-room', room)
|
||||
}
|
||||
|
||||
return {
|
||||
selectRoom
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
56
resources/js/Shared/Messanger/BoardTop.vue
Executable file
56
resources/js/Shared/Messanger/BoardTop.vue
Executable file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gradient-profile relative border-b border-indigo-300">
|
||||
<user-banner class="h-24 xl:h-40 bg-indigo-200" :user="user"
|
||||
size="hero"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="-mt-14 xl:-mt-24 relative xl:container xl:mx-auto px-5 xl:px-10">
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<div class="flex-shrink-0 self-center md:self-start md:mr-6 2xl:mr-10">
|
||||
<user-avatar :user="user" size="medium"
|
||||
class="shadow-classic object-cover w-28 h-28 text-xl xl:w-48 xl:h-48 xl:text-3xl"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="h-12 xl:h-24 hidden md:block"></div>
|
||||
<div class="mt-2 ">
|
||||
<div class="-mx-2 -my-2 lg:-mx-4 lg:-my-4 flex flex-col md:flex-row flex-wrap xl:flex-nowrap justify-between">
|
||||
<div class="max-w-[300px] text-center md:text-left mx-2 my-2 lg:mx-4 lg:my-4 flex flex-shrink-0 flex-col self-center md:self-start">
|
||||
<inertia-link :href="route('profile.user', user.username)" class="text-lg xl:text-2xl block font-semibold text-white">
|
||||
{{ user.name }}
|
||||
</inertia-link>
|
||||
<h2 class="text-sm xl:text-base text-gray-light">
|
||||
@{{ user.username }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="mx-2 my-2 lg:mx-4 lg:my-4 flex flex-col items-end">
|
||||
<slot name="menu" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserAvatar from '@/Shared/Misc/UserAvatar.vue'
|
||||
import UserBanner from '@/Shared/Misc/UserBanner.vue'
|
||||
|
||||
export default {
|
||||
components:{
|
||||
UserAvatar,
|
||||
UserBanner
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
187
resources/js/Shared/Messanger/MessangerModal.vue
Executable file
187
resources/js/Shared/Messanger/MessangerModal.vue
Executable file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="open">
|
||||
<Dialog as="div" static
|
||||
class="fixed z-[1000] inset-0 overflow-y-auto" :open="open"
|
||||
@close="closeAction"
|
||||
>
|
||||
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-4 text-center sm:block sm:p-0">
|
||||
<TransitionChild as="template" enter="ease-out duration-300"
|
||||
enter-from="opacity-0" enter-to="opacity-100"
|
||||
leave="ease-in duration-200" leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<DialogOverlay class="fixed inset-0 bg-indigo-200 bg-opacity-75 transition-opacity" />
|
||||
</TransitionChild>
|
||||
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
<TransitionChild as="template" enter="ease-out duration-300"
|
||||
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div class="inline-block align-bottom bg-indigo-300 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle w-full sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-orange sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ChatIcon class="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<DialogTitle as="h3" class="text-lg leading-6 font-medium text-gray-light">
|
||||
Новое сообщение
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<div v-if="showDialogLink" class="ml-auto">
|
||||
<a href="#" class="underline text-gray text-xs"
|
||||
@click.prevent="linkTo"
|
||||
>перейти в чат</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<textarea ref="textarea" v-model="form.message"
|
||||
rows="3"
|
||||
name="comment" class="outline-none focus:ring-orange-dark focus:border-transparent focus:ring-offset-0 bg-indigo-200 text-white max-h-56 block w-full py-3 border-0 resize-none sm:text-sm"
|
||||
placeholder="Сообщение ..."
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row space-x-3 justify-end">
|
||||
<button type="button"
|
||||
class="my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-6 py-2 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none"
|
||||
@click="closeAction"
|
||||
>
|
||||
Отменить
|
||||
</button>
|
||||
<button type="button"
|
||||
class="my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-6 py-2 justify-center text-base rounded-md text-white bg-green focus:outline-none"
|
||||
@click="submit"
|
||||
>
|
||||
Отправить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Dialog,
|
||||
DialogOverlay,
|
||||
DialogTitle,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
} from '@headlessui/vue'
|
||||
import { ChatIcon } from '@heroicons/vue/outline'
|
||||
import { ref } from 'vue'
|
||||
import { useAutoresizeTextarea } from '@/includes/composables'
|
||||
import axios from 'axios'
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dialog,
|
||||
DialogOverlay,
|
||||
DialogTitle,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
ChatIcon,
|
||||
},
|
||||
|
||||
setup(props, {expose}) {
|
||||
|
||||
const form = useForm({
|
||||
message: null,
|
||||
chat_room: null,
|
||||
user_id: null,
|
||||
})
|
||||
|
||||
const textarea = ref()
|
||||
const user = ref()
|
||||
const open = ref(false)
|
||||
const chatCheck = ref(false)
|
||||
const showDialogLink = ref(false)
|
||||
|
||||
const openAction = (extUser) => {
|
||||
open.value = true
|
||||
user.value = extUser
|
||||
form.user_id = extUser.id
|
||||
if(!chatCheck.value){
|
||||
exsistChatRequest(user.value)
|
||||
}
|
||||
}
|
||||
const closeAction = () => {
|
||||
open.value = false
|
||||
}
|
||||
|
||||
const exsistChatRequest = (user) => {
|
||||
chatCheck.value = true
|
||||
axios.post('/messenger-check-room', {
|
||||
params: {
|
||||
user: user.id,
|
||||
}
|
||||
}).then(response => {
|
||||
if(response.data > 0){
|
||||
showDialogLink.value = 1
|
||||
form.chat_room = response.data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if(!form.message){
|
||||
alert('Введите сообщение')
|
||||
return
|
||||
}
|
||||
if(form.chat_room){
|
||||
createMessage()
|
||||
}else{
|
||||
createRoom()
|
||||
}
|
||||
}
|
||||
|
||||
const createMessage = () => {
|
||||
form.post(route('messenger.store.profile'), {
|
||||
onSuccess: () => {
|
||||
closeAction()
|
||||
form.message = ''
|
||||
linkTo()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const createRoom = () => {
|
||||
form.post(route('messenger.create.room'), {
|
||||
onSuccess: () => {
|
||||
closeAction()
|
||||
showDialogLink.value = 1
|
||||
form.message = ''
|
||||
linkTo()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const linkTo = () => {
|
||||
Inertia.visit(`/messenger?user=${user.value.username}`, { method: 'get' })
|
||||
}
|
||||
|
||||
useAutoresizeTextarea(textarea)
|
||||
|
||||
expose({
|
||||
openAction
|
||||
})
|
||||
|
||||
return {
|
||||
form,
|
||||
textarea,
|
||||
open,
|
||||
closeAction,
|
||||
user,
|
||||
showDialogLink,
|
||||
submit,
|
||||
linkTo,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
18
resources/js/Shared/MetaHead.vue
Executable file
18
resources/js/Shared/MetaHead.vue
Executable file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<Head :title="title ? `${title} - Тизер` : 'Тизер'">
|
||||
<slot />
|
||||
</Head>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Head } from "@inertiajs/inertia-vue3";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Head,
|
||||
},
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
20
resources/js/Shared/Misc/CommentCount.vue
Executable file
20
resources/js/Shared/Misc/CommentCount.vue
Executable file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="flex items-center text-gray-light">
|
||||
<svg class="w-5 h-5 flex-shrink-0">
|
||||
<use xlink:href="#message-circle"></use>
|
||||
</svg>
|
||||
<span v-show="comments" class="ml-2 text-sm">{{
|
||||
comments
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
comments: Number,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
19
resources/js/Shared/Misc/FeedTags.vue
Executable file
19
resources/js/Shared/Misc/FeedTags.vue
Executable file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="-mx-1">
|
||||
<inertia-link
|
||||
:href="route('feed.tags', tag.slug)"
|
||||
class="inline-flex rounded-full items-center mx-1 py-0.5 px-2.5 text-sm font-medium bg-orange text-indigo-300"
|
||||
v-for="tag in tags" :key="tag.id">
|
||||
{{tag.name}}
|
||||
</inertia-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
tags: Array,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
25
resources/js/Shared/Misc/FeedTagsButtons.vue
Executable file
25
resources/js/Shared/Misc/FeedTagsButtons.vue
Executable file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<inertia-link :href="route('feed.tags', slug)"
|
||||
:class="[$page.component === 'Tag/Feed' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']">
|
||||
Все
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('list.images.tag', slug)"
|
||||
:class="[$page.component === 'Image/Index' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']">
|
||||
Изображения
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('list.musics.tag', slug)"
|
||||
:class="[$page.component === 'Music/Feed' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']">
|
||||
Музыка
|
||||
</inertia-link>
|
||||
<inertia-link :href="route('list.videos.tag', slug)"
|
||||
:class="[$page.component === 'Video/Feed' ? 'bg-orange shadow-classic2' : 'shadow-classic bg-indigo-200 hover:bg-orange' , 'transition inline-flex items-center px-3 py-3 lg:px-6 lg:py-6 xl:px-10 text-sm lg:text-lg justify-center rounded-md text-white focus:outline-none']">
|
||||
Видео
|
||||
</inertia-link>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
slug: String,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
76
resources/js/Shared/Misc/FlashMessages.vue
Executable file
76
resources/js/Shared/Misc/FlashMessages.vue
Executable file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="absolute left-0 right-0 z-[100]">
|
||||
<div v-if="$page.props.flash.success && show" class="mx-auto mt-4 mb-4 flex items-center justify-between bg-green rounded max-w-3xl">
|
||||
<div class="flex items-center">
|
||||
<svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
><polygon points="0 11 2 9 7 14 18 3 20 5 7 18" /></svg>
|
||||
<div class="py-4 text-white text-sm font-medium">
|
||||
{{ $page.props.flash.success }}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="group mr-2 p-2"
|
||||
@click="show = false"
|
||||
>
|
||||
<svg class="block w-2 h-2 fill-green-800 group-hover:fill-white" xmlns="http://www.w3.org/2000/svg"
|
||||
width="235.908" height="235.908"
|
||||
viewBox="278.046 126.846 235.908 235.908"
|
||||
><path d="M506.784 134.017c-9.56-9.56-25.06-9.56-34.62 0L396 210.18l-76.164-76.164c-9.56-9.56-25.06-9.56-34.62 0-9.56 9.56-9.56 25.06 0 34.62L361.38 244.8l-76.164 76.165c-9.56 9.56-9.56 25.06 0 34.62 9.56 9.56 25.06 9.56 34.62 0L396 279.42l76.164 76.165c9.56 9.56 25.06 9.56 34.62 0 9.56-9.56 9.56-25.06 0-34.62L430.62 244.8l76.164-76.163c9.56-9.56 9.56-25.06 0-34.62z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="($page.props.flash.error || Object.keys($page.props.errors).length > 0) && show" class="mx-auto mt-4 mb-4 flex items-center justify-between bg-red rounded max-w-3xl">
|
||||
<div class="flex items-center">
|
||||
<svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm1.41-1.41A8 8 0 1 0 15.66 4.34 8 8 0 0 0 4.34 15.66zm9.9-8.49L11.41 10l2.83 2.83-1.41 1.41L10 11.41l-2.83 2.83-1.41-1.41L8.59 10 5.76 7.17l1.41-1.41L10 8.59l2.83-2.83 1.41 1.41z" /></svg>
|
||||
<div v-if="$page.props.flash.error" class="py-4 text-white text-sm font-medium">
|
||||
{{ $page.props.flash.error }}
|
||||
</div>
|
||||
<div v-else class="py-4 text-white text-sm font-medium">
|
||||
<span v-if="Object.keys($page.props.errors).length === 1">There is one form error.</span>
|
||||
<span v-else>There are {{ Object.keys($page.props.errors).length }} form errors.</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="group mr-2 p-2"
|
||||
@click="show = false"
|
||||
>
|
||||
<svg class="block w-2 h-2 fill-red-800 group-hover:fill-white" xmlns="http://www.w3.org/2000/svg"
|
||||
width="235.908" height="235.908"
|
||||
viewBox="278.046 126.846 235.908 235.908"
|
||||
><path d="M506.784 134.017c-9.56-9.56-25.06-9.56-34.62 0L396 210.18l-76.164-76.164c-9.56-9.56-25.06-9.56-34.62 0-9.56 9.56-9.56 25.06 0 34.62L361.38 244.8l-76.164 76.165c-9.56 9.56-9.56 25.06 0 34.62 9.56 9.56 25.06 9.56 34.62 0L396 279.42l76.164 76.165c9.56 9.56 25.06 9.56 34.62 0 9.56-9.56 9.56-25.06 0-34.62L430.62 244.8l76.164-76.163c9.56-9.56 9.56-25.06 0-34.62z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//import { ref, watch } from 'vue'
|
||||
|
||||
export default {
|
||||
|
||||
// setup() {
|
||||
// const show = ref(true)
|
||||
// watch('$page.props.flash', function(){
|
||||
// this.show = true
|
||||
// });
|
||||
|
||||
// return {
|
||||
// show,
|
||||
// };
|
||||
// },
|
||||
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$page.props.flash': {
|
||||
handler() {
|
||||
this.show = true
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
126
resources/js/Shared/Misc/InfinityScroll.vue
Executable file
126
resources/js/Shared/Misc/InfinityScroll.vue
Executable file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<slot v-bind="$attrs" />
|
||||
|
||||
<slot v-if="loading" name="loading"
|
||||
:text="loadText"
|
||||
>
|
||||
<div v-if="loading" class="col-span-full flex justify-center">
|
||||
<svg class="flex-shrink-0 animate-spin h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
> <circle class="opacity-25" cx="12"
|
||||
cy="12" r="10"
|
||||
stroke="currentColor" stroke-width="4"
|
||||
></circle><path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path></svg>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, onUpdated, onUnmounted, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { usePage } from '@inertiajs/inertia-vue3'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
nodeElement: Object,
|
||||
loadText: {
|
||||
type: String,
|
||||
default: 'Загрузка ...',
|
||||
},
|
||||
nextCursor: { type: String, default: '' },
|
||||
},
|
||||
|
||||
emits: ['fromPagination'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const cursor = ref(props.nextCursor)
|
||||
const loading = ref(false)
|
||||
|
||||
let installObserver = false
|
||||
let observer = null
|
||||
|
||||
// watch(props, (props) => {
|
||||
// observer?.disconnect();
|
||||
// createObserver();
|
||||
// });
|
||||
|
||||
const loadMore = () => {
|
||||
loading.value = true
|
||||
if (!cursor.value) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
axios
|
||||
.get(usePage().url.value, { params: { cursor: cursor.value } })
|
||||
.then(({ data }) => {
|
||||
if (data.collections.length) {
|
||||
cursor.value = data.next
|
||||
emit('fromPagination', data.collections)
|
||||
resolve(true)
|
||||
}
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const createObserver = () => {
|
||||
if (props.nodeElement) {
|
||||
const intersectionCallback = (entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
console.log('isIntersecting')
|
||||
console.log(props.nodeElement)
|
||||
removeObserver(entry.target)
|
||||
loadMore().then((exist) => {
|
||||
loading.value = false
|
||||
if (exist) {
|
||||
createObserver()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
observer = new IntersectionObserver(intersectionCallback, {})
|
||||
observer.observe(props.nodeElement)
|
||||
}
|
||||
}
|
||||
|
||||
const removeObserver = (target) => {
|
||||
observer.unobserve(target)
|
||||
observer?.disconnect()
|
||||
}
|
||||
|
||||
onUpdated(() => {
|
||||
if (!installObserver) {
|
||||
createObserver()
|
||||
installObserver = true
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
observer?.disconnect()
|
||||
})
|
||||
|
||||
// onMounted(() => {
|
||||
// window.addEventListener(
|
||||
// "scroll",
|
||||
// debounce((e) => {
|
||||
// console.log(props.nodeElement);
|
||||
// let pixelsFromBottom =
|
||||
// document.documentElement.offsetHeight -
|
||||
// document.documentElement.scrollTop -
|
||||
// window.innerHeight;
|
||||
// if (pixelsFromBottom < props.bottomLine && !loading.value) {
|
||||
// loading.value = true;
|
||||
// props.loadMore().finally(() => (loading.value = false));
|
||||
// }
|
||||
// }, 200)
|
||||
// );
|
||||
// });
|
||||
return {
|
||||
loading,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
24
resources/js/Shared/Misc/InfinityScrollRender.vue
Executable file
24
resources/js/Shared/Misc/InfinityScrollRender.vue
Executable file
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
export default {
|
||||
emits: ["fromPagination"],
|
||||
render() {
|
||||
const { lists, addTag, deleteTag } = this;
|
||||
return this.$slots.default({
|
||||
tags,
|
||||
addTag,
|
||||
deleteTag,
|
||||
});
|
||||
},
|
||||
props: {
|
||||
nodeElement: Object,
|
||||
loadText: {
|
||||
type: String,
|
||||
default: "Загрузка ...",
|
||||
},
|
||||
nextCursor: { type: String, default: "" },
|
||||
},
|
||||
setup(){
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
32
resources/js/Shared/Misc/LikeCount.vue
Executable file
32
resources/js/Shared/Misc/LikeCount.vue
Executable file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
liked ? 'text-red' : 'text-gray-light',
|
||||
'flex items-center',
|
||||
]"
|
||||
>
|
||||
<button class="default" @click.stop="likeFeed">
|
||||
<svg class="w-5 h-5 flex-shrink-0">
|
||||
<use xlink:href="#heart"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<span v-show="likes" class="ml-2 text-sm">{{
|
||||
likes
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
likes: Number,
|
||||
liked: Boolean,
|
||||
},
|
||||
emits: ['likeFeed'],
|
||||
methods: {
|
||||
likeFeed() {
|
||||
this.$emit('likeFeed')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
13
resources/js/Shared/Misc/LinkBack.vue
Executable file
13
resources/js/Shared/Misc/LinkBack.vue
Executable file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<button type="button" @click="back"><slot /></button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
back() {
|
||||
window.history.back();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
25
resources/js/Shared/Misc/MusicAddCount.vue
Executable file
25
resources/js/Shared/Misc/MusicAddCount.vue
Executable file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<button @click.stop="addAudio" type="button" class="default flex-shrink-0"
|
||||
:class="[
|
||||
liked ? 'text-red' : 'text-gray-light'
|
||||
]"
|
||||
>
|
||||
<svg class="w-4 h-4">
|
||||
<use xlink:href="#heart"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
emits: ["addAudio"],
|
||||
props: {
|
||||
media_id: Number,
|
||||
liked: Boolean,
|
||||
},
|
||||
methods: {
|
||||
addAudio() {
|
||||
this.$emit("addAudio", this.media_id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
20
resources/js/Shared/Misc/ShareCount.vue
Executable file
20
resources/js/Shared/Misc/ShareCount.vue
Executable file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="cursor-pointer flex items-center text-gray-light">
|
||||
<svg class="w-5 h-5 flex-shrink-0">
|
||||
<use xlink:href="#share"></use>
|
||||
</svg>
|
||||
<span v-show="shares" class="ml-2 text-sm">{{
|
||||
shares
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
shares: Number,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
32
resources/js/Shared/Misc/TogglePlayButton.vue
Executable file
32
resources/js/Shared/Misc/TogglePlayButton.vue
Executable file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<button :class="[ currentSong?.id === media_id ? '' : 'hidden', 'default' ]">
|
||||
<svg :class="[ playing ? 'hidden' : 'block', 'w-6 h-6' ]">
|
||||
<use xlink:href="#play"></use>
|
||||
</svg>
|
||||
<svg :class="[ playing ? 'block' : 'hidden', 'w-6 h-6' ]">
|
||||
<use xlink:href="#pause"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="[ currentSong?.id !== media_id ? '' : 'hidden', 'default' ]">
|
||||
<svg class="w-6 h-6">
|
||||
<use xlink:href="#play"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
media_id: Number,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["playing"]),
|
||||
...mapState({
|
||||
currentSong: (state) => state.player.currentSong,
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user