feat: userProfile
This commit is contained in:
parent
8f24453eff
commit
13a3de9709
16 changed files with 492 additions and 284 deletions
|
@ -26,7 +26,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
|||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
'pass' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,43 +13,48 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
// Use PHP's built-in list of timezones for robust validation
|
||||
$timezones = timezone_identifiers_list();
|
||||
|
||||
$rules = [
|
||||
'login' => ['required', 'string', 'max:255', Rule::unique('cc_subjs')->ignore($user->id)],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('cc_subjs')->ignore($user->id),
|
||||
],
|
||||
'email' => ['nullable', 'string', 'email', 'max:255', Rule::unique('cc_subjs')->ignore($user->id)],
|
||||
'first_name' => ['nullable', 'string', 'max:255'],
|
||||
'last_name' => ['nullable', 'string', 'max:255'],
|
||||
'cell_phone' => ['nullable', 'string', 'max:25'],
|
||||
'timezone' => ['nullable', 'string', Rule::in($timezones)],
|
||||
];
|
||||
|
||||
// Only add the 'type' validation rule if the user has the permission to change roles
|
||||
if (auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
if (isset($rules['type']) && auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
$rules['type'] = ['required', 'string', 'max:6', Rule::in(['admin', 'editor', 'dj'])];
|
||||
}
|
||||
|
||||
Validator::make($input, $rules)->validateWithBag('updateProfileInformation');
|
||||
|
||||
if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$data = [
|
||||
'login' => $input['login'],
|
||||
'email' => $input['email'],
|
||||
'first_name' => $input['first_name'],
|
||||
'last_name' => $input['last_name'],
|
||||
'cell_phone' => $input['cell_phone'],
|
||||
];
|
||||
|
||||
// Only update 'type' if the user has the permission
|
||||
if (auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
if (isset($rules['type']) && auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
$data['type'] = $input['type'];
|
||||
}
|
||||
|
||||
$user->forceFill($data)->save();
|
||||
}
|
||||
|
||||
// The timezone is handled by the mutator in the User model
|
||||
$user->timezone = $input['timezone'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,16 +67,22 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||
$data = [
|
||||
'login' => $input['login'],
|
||||
'email' => $input['email'],
|
||||
'first_name' => $input['first_name'],
|
||||
'last_name' => $input['last_name'],
|
||||
'cell_phone' => $input['cell_phone'],
|
||||
'email_verified_at' => null,
|
||||
];
|
||||
|
||||
// Only update 'type' if the user has the permission
|
||||
if (auth()->user()->hasPermissionTo('user.changeRole')) {
|
||||
// Corrected permission name to be consistent
|
||||
if (auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
$data['type'] = $input['type'];
|
||||
}
|
||||
|
||||
$user->forceFill($data)->save();
|
||||
|
||||
// The timezone is handled by the mutator in the User model
|
||||
$user->timezone = $input['timezone'];
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
|
@ -56,7 +56,10 @@ class UserController extends Controller
|
|||
|
||||
public function userProfile()
|
||||
{
|
||||
return response()->json(auth()->user());
|
||||
$user =auth()->user();
|
||||
$user->role = $user->roles()->value('name');
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user, UpdateUserProfileInformation $updater)
|
||||
|
@ -71,19 +74,20 @@ class UserController extends Controller
|
|||
}
|
||||
|
||||
try {
|
||||
(new UpdateUserProfileInformation())->update($user, $request->all());
|
||||
|
||||
$updater->update($user, $request->all());
|
||||
$user->load('preferences');
|
||||
|
||||
return response()->json($user);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error($e->getMessage());
|
||||
if ($e instanceof \Illuminate\Validation\ValidationException) {
|
||||
return response()->json(['message' => $e->getMessage(), 'errors' => $e->errors()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Failed to update user'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
try {
|
||||
|
|
|
@ -8,6 +8,8 @@ import { createPinia } from "pinia";
|
|||
import { createI18n } from "vue-i18n";
|
||||
|
||||
import App from "@/layouts/App.vue";
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
|
||||
const pinia = createPinia();
|
||||
const i18n = createI18n(vueI18n);
|
||||
const app = createApp(App);
|
||||
|
@ -16,4 +18,8 @@ app.use(pinia)
|
|||
.use(i18n)
|
||||
.use(router)
|
||||
.use(vuetify)
|
||||
.mount("#app");
|
||||
|
||||
const auth = useAuthStore();
|
||||
auth.fetchUser().finally(() => {
|
||||
app.mount("#app");
|
||||
});
|
2
resources/js/bootstrap.js
vendored
2
resources/js/bootstrap.js
vendored
|
@ -6,7 +6,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.withCredentials = true
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
|
|
|
@ -91,6 +91,7 @@ onDeactivated(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="showCreateEditMode || showInstanceCreateEditMode">
|
||||
<ShowForm v-if="showCreateEditMode" :showId="showSelected" @go-back="goBack"/>
|
||||
<ShowInstanceForm v-if="showInstanceCreateEditMode" :showInstance="selectedShowInstance"
|
||||
|
@ -114,6 +115,7 @@ onDeactivated(() => {
|
|||
@contextMenuDeleteShow="contextMenuDeleteShow"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,91 +1,233 @@
|
|||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/stores/auth.store.ts'; // Adjust the import path to your store
|
||||
import {useAuthStore} from '@/stores/auth.store.ts';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import {onBeforeMount, ref, reactive} from 'vue';
|
||||
import {useRouter} from "vue-router";
|
||||
import axios from "axios";
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const {userData} = storeToRefs(authStore);
|
||||
|
||||
const localUserData = reactive({...userData.value});
|
||||
|
||||
const emit = defineEmits([
|
||||
'userProfilePage'
|
||||
])
|
||||
]);
|
||||
|
||||
// Sample data for selection components
|
||||
const roles = ['Admin', 'User', 'Editor'];
|
||||
const timezones = [
|
||||
'UTC',
|
||||
'America/New_York',
|
||||
'America/Chicago',
|
||||
'America/Denver',
|
||||
'America/Los_Angeles',
|
||||
'Europe/London',
|
||||
'Europe/Berlin',
|
||||
'Asia/Tokyo',
|
||||
];
|
||||
let timezones = ref<string[]>([]);
|
||||
let roleList = ref<string[]>([]);
|
||||
|
||||
const form = ref(null);
|
||||
const form = ref<HTMLFormElement | null>(null);
|
||||
const passwordForm = ref<HTMLFormElement | null>(null);
|
||||
|
||||
const dialog = ref(false);
|
||||
const passwordData = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
||||
const formRules = {
|
||||
'emailRules': [(v: string) => !v || /.+@.+\..+/.test(v) || 'E-mail must be a valid format'],
|
||||
'cellphoneRules': [(v: string) => !v || /^[0-9-()]*$/.test(v) || 'Cellphone must be a valid number'],
|
||||
'passwordConfirmationRule': [
|
||||
(v: string) => !!v || 'Password confirmation is required',
|
||||
(v: string) => v === passwordData.newPassword || 'Passwords do not match'
|
||||
],
|
||||
'requiredRule': [(v: string) => !!v || 'This field is required'],
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
if (!form.value) return
|
||||
|
||||
const {valid} = await form.value.validate();
|
||||
if (!valid) return
|
||||
|
||||
authStore.userData = {...localUserData};
|
||||
await authStore.updateUser();
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
const openPasswordDialog = () => {
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
const closePasswordDialog = () => {
|
||||
dialog.value = false;
|
||||
passwordForm.value?.reset();
|
||||
passwordForm.value?.resetValidation();
|
||||
};
|
||||
|
||||
const resetPassword = async () => {
|
||||
console.log('aaaa')
|
||||
try {
|
||||
await axios.put('/api/user/password', passwordData)
|
||||
console.log('Password changed');
|
||||
closePasswordDialog()
|
||||
return
|
||||
} catch (e) {
|
||||
const errorMessage = e.response?.data?.error || 'An unexpected error occurred.';
|
||||
console.error('Error changing password:' + errorMessage);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await axios.get('/timezoneList').then(response => {
|
||||
timezones.value = response?.data
|
||||
})
|
||||
if (userData.value.role === 'admin') {
|
||||
await axios.get('/roleList').then(response => {
|
||||
roleList.value = response?.data
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-form ref="form">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="userData.login"
|
||||
v-model="localUserData.login"
|
||||
label="Login"
|
||||
required
|
||||
hint="Your public username."
|
||||
persistent-hint
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.email"
|
||||
:rules="formRules.emailRules"
|
||||
label="Email Address"
|
||||
hint="Used for notifications and account recovery."
|
||||
persistent-hint
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.firstName"
|
||||
label="First Name"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="userData.firstName"
|
||||
label="First Name"
|
||||
v-model="localUserData.lastName"
|
||||
label="Last Name"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="userData.lastName"
|
||||
label="Last Name"
|
||||
v-model="localUserData.cellPhone"
|
||||
:rules="formRules.cellphoneRules"
|
||||
label="Cell Phone"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-autocomplete
|
||||
v-model="userData.timezone"
|
||||
v-model="localUserData.timezone"
|
||||
:items="timezones"
|
||||
label="Timezone"
|
||||
hint="Sets the time for all events and schedules."
|
||||
persistent-hint
|
||||
></v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="userData.email"
|
||||
label="email"
|
||||
></v-text-field>
|
||||
<v-select
|
||||
v-model="localUserData.role"
|
||||
:items="roleList"
|
||||
label="Ruolo"
|
||||
:disabled="userData.role !== 'admin'"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="userData.cellPhone"
|
||||
label="cellPhone"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-col class="d-flex justify-end">
|
||||
<v-btn color="secondary" @click="goBack" class="mr-4">
|
||||
Back
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="saveUser">
|
||||
Salva
|
||||
Save Changes
|
||||
</v-btn>
|
||||
<v-btn color="error" @click="openPasswordDialog">
|
||||
Reset Password
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
<v-dialog v-model="dialog" persistent max-width="600px">
|
||||
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">Reset Your Password</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="text-subtitle-1 mb-4">
|
||||
Please be sure about the new password you are choosing. Password recovery via email is not implemented yet,
|
||||
so
|
||||
a forgotten password cannot be recovered.
|
||||
</p>
|
||||
</v-card-text>
|
||||
<v-form ref="passwordForm" @submit.prevent="resetPassword">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="passwordData.oldPassword"
|
||||
label="Old Password"
|
||||
type="password"
|
||||
:rules="formRules.requiredRule"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="passwordData.newPassword"
|
||||
label="New Password"
|
||||
type="password"
|
||||
:rules="formRules.requiredRule"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="passwordData.confirmPassword"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
:rules="formRules.passwordConfirmationRule"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="closePasswordDialog">
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn type="submit" color="blue-darken-1" variant="tonal">
|
||||
Confirm
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
|
@ -7,21 +7,9 @@ const router = useRouter();
|
|||
const auth = useAuthStore();
|
||||
const userName = auth.userData.login;
|
||||
|
||||
const emit = defineEmits([
|
||||
'userProfilePage'
|
||||
])
|
||||
|
||||
const userProfilePage = () => {
|
||||
emit('userProfilePage')
|
||||
}
|
||||
const logout = async () => {
|
||||
try {
|
||||
await axios.get('/logout');
|
||||
auth.resetUser();
|
||||
router.push('/login');
|
||||
} catch (error) {
|
||||
console.error('Error logging out:', error);
|
||||
}
|
||||
await auth.logout()
|
||||
router.push({ path: 'login' });
|
||||
};
|
||||
|
||||
</script>
|
||||
|
@ -30,7 +18,7 @@ const logout = async () => {
|
|||
<v-sheet
|
||||
:width="150"
|
||||
>
|
||||
<v-btn color="info" @click="userProfilePage">{{ userName }} {{ $t('header.userinfo.info') }}</v-btn>
|
||||
<v-btn color="info" :to="{ path: 'user-profile'}">{{ userName }} {{ $t('header.userinfo.info') }}</v-btn>
|
||||
<v-btn color="" @click="logout">{{ $t('header.userinfo.logout') }}</v-btn>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,74 @@
|
|||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import {createRouter, createWebHashHistory, createWebHistory, type RouteRecordRaw} from "vue-router";
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
import {useShowTypeStore} from '@/stores/showType.store';
|
||||
|
||||
const routes = [
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
name: 'Backoffice',
|
||||
component: () => import('../pages/Backoffice.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/components/content/Dashboard.vue')
|
||||
},
|
||||
{
|
||||
path: 'show',
|
||||
name: 'Trasmissioni',
|
||||
component: () => import('@/components/content/Show.vue'),
|
||||
meta: {showType: 'shows'}
|
||||
},
|
||||
{
|
||||
path: 'archive',
|
||||
name: 'Archivio',
|
||||
component: () => import('@/components/content/Archive.vue')},
|
||||
{
|
||||
path: 'playlist',
|
||||
name: 'Playlist',
|
||||
component: () => import('@/components/content/Playlist.vue'),
|
||||
meta: {showType: 'shows'}
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
name: 'Blocchi dinamici',
|
||||
component: () => import('@/components/content/SmartBlock.vue'),
|
||||
meta: {showType: 'shows'}
|
||||
},
|
||||
{
|
||||
path: 'podcast',
|
||||
name: 'Podcast',
|
||||
component: () => import('@/components/content/Podcast.vue')
|
||||
},
|
||||
{
|
||||
path: 'webstream',
|
||||
name: 'Webstream',
|
||||
component: () => import('@/components/content/Webstream.vue')
|
||||
},
|
||||
{
|
||||
path: 'spot',
|
||||
name: 'Spot',
|
||||
component: () => import('@/components/content/Show.vue'),
|
||||
meta: {showType: 'spots'}
|
||||
},
|
||||
{
|
||||
path: 'spot-playlist',
|
||||
name: 'Spot playlist',
|
||||
component: () => import('@/components/content/Playlist.vue'),
|
||||
meta: {showType: 'spots'}
|
||||
},
|
||||
{
|
||||
path: 'spot-blocks',
|
||||
name: 'Spot Blocchi dinamici',
|
||||
component: () => import('@/components/content/SmartBlock.vue'),
|
||||
meta: {showType: 'spots'}
|
||||
},
|
||||
{
|
||||
path: 'user-profile',
|
||||
name: 'UserProfile',
|
||||
component: () => import('@/components/content/UserProfile.vue')
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
|
@ -15,22 +78,35 @@ const routes = [
|
|||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
/**
|
||||
* Redirect to login page if unauthenticated
|
||||
* Navigation Guards
|
||||
*/
|
||||
router.beforeEach(async (to) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const publicPages = ['/login'];
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const auth = useAuthStore();
|
||||
|
||||
if (authRequired && !auth.userData.login) {
|
||||
return '/login';
|
||||
return next('/login');
|
||||
}
|
||||
|
||||
const showTypeStore = useShowTypeStore();
|
||||
switch (to.meta.showType) {
|
||||
case 'shows':
|
||||
showTypeStore.setAsShows();
|
||||
break;
|
||||
case 'spots':
|
||||
showTypeStore.setAsSpots();
|
||||
break;
|
||||
default:
|
||||
showTypeStore.clearType();
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -46,6 +46,7 @@ watch(currentPage, (newVal) => {
|
|||
|
||||
<template>
|
||||
<v-col>
|
||||
<router-view></router-view>
|
||||
<!-- <keep-alive>-->
|
||||
<Component :is="tabs[currentPage]" />
|
||||
<!-- </keep-alive>-->
|
||||
|
|
|
@ -3,14 +3,7 @@ import OnAir from "@/components/header/OnAir.vue";
|
|||
import Clock from "@/components/header/Clock.vue";
|
||||
import Timer from "@/components/header/Timer.vue";
|
||||
import UserInfo from "@/components/header/UserInfo.vue";
|
||||
|
||||
const emit = defineEmits([
|
||||
'userProfilePage'
|
||||
])
|
||||
|
||||
const userProfilePage = () => {
|
||||
emit('userProfilePage')
|
||||
}
|
||||
import {useRouter} from "vue-router";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -18,7 +11,7 @@ const userProfilePage = () => {
|
|||
<OnAir />
|
||||
<Clock />
|
||||
<Timer />
|
||||
<UserInfo @user-profile-page="userProfilePage" />
|
||||
<UserInfo />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,66 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const pages = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
component: '@components/content/Dashboard.vue',
|
||||
},
|
||||
{
|
||||
id: 'show',
|
||||
name: 'Trasmissioni',
|
||||
component: '@components/content/Show.vue',
|
||||
},
|
||||
{
|
||||
id: 'archive',
|
||||
name: 'Archivio',
|
||||
component: '@components/content/Archive.vue',
|
||||
},
|
||||
{
|
||||
id: 'playlist',
|
||||
name: 'Playlist',
|
||||
component: '@components/content/Playlist.vue',
|
||||
},
|
||||
{
|
||||
id: 'blocks',
|
||||
name: 'Blocchi dinamici',
|
||||
component: '@components/content/SmartBlock.vue',
|
||||
},
|
||||
{
|
||||
id: 'podcast',
|
||||
name: 'Podcast',
|
||||
component: '@components/content/Podcast.vue',
|
||||
},
|
||||
{
|
||||
id: 'webstream',
|
||||
name: 'Webstream',
|
||||
component: '@components/content/Webstream.vue',
|
||||
},
|
||||
{
|
||||
id: 'spot',
|
||||
name: 'Spot',
|
||||
component: '@components/content/Show.vue',
|
||||
},
|
||||
{
|
||||
id: 'spot-playlist',
|
||||
name: 'Spot playlist',
|
||||
component: '@components/content/Playlist.vue',
|
||||
},
|
||||
{
|
||||
id: 'spot-blocks',
|
||||
name: 'Spot Blocchi dinamici',
|
||||
component: '@components/content/SmartBlock.vue',
|
||||
},
|
||||
];
|
||||
import { useRouter } from 'vue-router';
|
||||
import {useAuthStore} from "@stores/auth.store.ts";
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
const backofficeRoutes = router.options.routes.find(route => route.name === 'Backoffice')?.children || [];
|
||||
const pages = backofficeRoutes.filter(route => {
|
||||
if (route.name === 'UserProfile') {
|
||||
return false;
|
||||
}
|
||||
if (auth.currentUser.role !== 'admin' && route.name === 'adminDashboard') {
|
||||
return false;
|
||||
}
|
||||
if (!['admin', 'editor'].includes(auth.currentUser.role) && route?.meta?.showType === 'spots') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-col>
|
||||
<v-btn
|
||||
v-for="page in pages"
|
||||
:key="page.id"
|
||||
:key="page.path"
|
||||
:to="{ name: page.name }"
|
||||
size="large"
|
||||
@click="$emit('showPage', page)"
|
||||
exact
|
||||
>{{ page.name }}</v-btn>
|
||||
</v-col>
|
||||
</template>
|
||||
|
|
|
@ -1,31 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
import Header from "@/layouts/partials/Header.vue";
|
||||
import Sidebar from "@/layouts/partials/Sidebar.vue";
|
||||
import Content from "@/layouts/partials/Content.vue";
|
||||
import {reactive} from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const page = reactive({
|
||||
id: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
})
|
||||
|
||||
let returnToPage = {id: '', name: ''}
|
||||
const router = useRouter();
|
||||
|
||||
const userProfilePage = () => {
|
||||
if(page.id == 'userProfile') {
|
||||
page.id = returnToPage.id
|
||||
page.name = returnToPage.name
|
||||
return
|
||||
}
|
||||
returnToPage = {...page}
|
||||
page.id = 'userProfile'
|
||||
page.name = 'userProfile'
|
||||
}
|
||||
|
||||
const changePage = (currentPage) => {
|
||||
page.id = currentPage.id;
|
||||
page.name = currentPage.name;
|
||||
router.push({ name: 'UserProfile' });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -33,17 +14,17 @@ const changePage = (currentPage) => {
|
|||
<div>
|
||||
<Header @user-profile-page="userProfilePage" />
|
||||
<v-row :fluid="true">
|
||||
<Sidebar
|
||||
@show-page="changePage"
|
||||
/>
|
||||
<Content
|
||||
:page="page"
|
||||
/>
|
||||
<Sidebar />
|
||||
<router-view class="routed-component"/>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.v-row {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import {onBeforeMount, reactive} from 'vue';
|
||||
import axios from "axios";
|
||||
import {useRouter} from "vue-router";
|
||||
import {Settings} from "luxon";
|
||||
import {onBeforeMount, reactive} from 'vue';
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
|
||||
axios.defaults.withCredentials = true
|
||||
|
||||
const data = reactive({
|
||||
'username': null,
|
||||
'password': null,
|
||||
|
@ -17,56 +14,29 @@ const auth = useAuthStore();
|
|||
const router = useRouter();
|
||||
|
||||
const onSubmit = async () => {
|
||||
let loginSuccessful = false
|
||||
if (!data.username || !data.password) return;
|
||||
|
||||
data.loading = true;
|
||||
|
||||
await axios.get('/sanctum/csrf-cookie').then(() => {
|
||||
axios.post('/login', {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
}).then(async (response) => {
|
||||
let timezone = await getTimezone();
|
||||
if(response.data.timezone) {
|
||||
timezone = response.data.timezone
|
||||
} else {
|
||||
response.data.timezone = timezone
|
||||
}
|
||||
|
||||
auth.loadUser(response.data);
|
||||
Settings.defaultZone = timezone;
|
||||
try {
|
||||
await router.push('/');
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
data.errors = error.response.data.errors
|
||||
const response = await auth.login(data.username, data.password);
|
||||
if (response?.error) {
|
||||
data.errors = response?.error
|
||||
data.loading = false;
|
||||
});
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const getTimezone = (): Promise<string> => {
|
||||
return axios.get("timezone").then((res) => {
|
||||
return res.data;
|
||||
}).catch(error => {
|
||||
return null;
|
||||
})
|
||||
data.loading = false;
|
||||
await router.push('/');
|
||||
}
|
||||
|
||||
const required = (v) => {
|
||||
return !!v || $t('login.errors.required');
|
||||
}
|
||||
|
||||
onBeforeMount(()=>{
|
||||
// TODO Create a route taht checks if the user is already logged in (laravel session cookie), if it's logged in
|
||||
// route.push /
|
||||
// In the BE make something like route.get('userIsAuthenticated')->return auth()->user()
|
||||
if(auth.isAuthenticated){
|
||||
router.push('/')
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import type {User} from "@models/User.ts";
|
||||
import axios, {type AxiosResponse} from "axios";
|
||||
import {camelToSnake} from "@/helpers/AxiosHelper.ts";
|
||||
import {camelToSnake, snakeToCamel} from "@/helpers/AxiosHelper.ts";
|
||||
import {Settings} from "luxon";
|
||||
|
||||
|
||||
export const baseUser = (): User => ({
|
||||
|
@ -15,7 +16,12 @@ export const baseUser = (): User => ({
|
|||
export const useAuthStore = defineStore('user', {
|
||||
state: () => ({
|
||||
userData: {} as User,
|
||||
isAuthenticated: false
|
||||
}),
|
||||
getters: {
|
||||
isLoggedIn: (state): boolean => state.isAuthenticated,
|
||||
currentUser: (state): User | null => state.userData,
|
||||
},
|
||||
actions: {
|
||||
loadUser(userData: User) {
|
||||
this.userData = {...userData};
|
||||
|
@ -37,7 +43,54 @@ export const useAuthStore = defineStore('user', {
|
|||
resetUser() {
|
||||
this.userData = {...baseUser()};
|
||||
},
|
||||
async fetchUser() {
|
||||
try {
|
||||
const { data } = await axios.get('/user/profile');
|
||||
this.userData = snakeToCamel(data);
|
||||
this.isAuthenticated = true;
|
||||
} catch (error) {
|
||||
this.userData = null;
|
||||
this.isAuthenticated = false;
|
||||
console.error("Not authenticated or failed to fetch user.", error);
|
||||
}
|
||||
},
|
||||
async login(username, password){
|
||||
await axios.get('/sanctum/csrf-cookie');
|
||||
await axios.post('/login', {
|
||||
username: username,
|
||||
password: password,
|
||||
}).then(async (response) => {
|
||||
let timezone = await axios.get("timezone").then((res) => {
|
||||
return res.data;
|
||||
}).catch(error => {
|
||||
return null;
|
||||
})
|
||||
if(response.data.timezone) {
|
||||
timezone = response.data.timezone
|
||||
} else {
|
||||
response.data.timezone = timezone
|
||||
}
|
||||
const userData = snakeToCamel(response.data)
|
||||
this.loadUser(userData);
|
||||
Settings.defaultZone = timezone;
|
||||
}).catch((error) => {
|
||||
return {
|
||||
"errors": error.response.data.errors,
|
||||
"loading": false,
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async logout(){
|
||||
try {
|
||||
await axios.get('/logout');
|
||||
} catch (error) {
|
||||
console.error("Error during logout, but clearing state anyway.", error)
|
||||
} finally {
|
||||
this.resetUser = null;
|
||||
this.isAuthenticated = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Updates the current user on the server.
|
||||
* @returns {Promise<any>} A promise that resolves with the response data from the server.
|
||||
|
@ -55,8 +108,6 @@ export const useAuthStore = defineStore('user', {
|
|||
return response.data;
|
||||
}).catch((error: Error) => {
|
||||
console.error("Error: " + error.message);
|
||||
|
||||
|
||||
});
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use App\Http\Controllers\MusicBrainzController;
|
|||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\WebstreamController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -30,6 +31,20 @@ use Illuminate\Support\Facades\Route;
|
|||
|
||||
Route::middleware('auth')->get('/user/profile', [UserController::class, 'userProfile']);
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
Route::middleware('auth')->get('/timezoneList',function() {
|
||||
return response()->json(DateTimeZone::listIdentifiers(DateTimeZone::ALL));
|
||||
});
|
||||
Route::middleware('auth')->get('/roleList',function() {
|
||||
if(!auth()->user()->hasRole('admin')){
|
||||
return response()->json(['message' => 'Unauthorized.'], 403);
|
||||
}
|
||||
$roles = Role::all()->pluck('name');
|
||||
return response()->json($roles);
|
||||
});
|
||||
|
||||
/**
|
||||
* Create routes without create method
|
||||
*/
|
||||
|
@ -79,6 +94,8 @@ Route::get('/rss_podcast_episodes', [PodcastController::class, 'getEpisodes']);
|
|||
Route::post('/musicbrainz/get_track_metadata', [MusicBrainzController::class, 'get_track_metadata'])->name('musicbrainz.get_track');
|
||||
Route::get('musicbrainz', [MusicBrainzController::class, 'test']);
|
||||
|
||||
Route::get('testCelery', [PodcastEpisodeController::class, 'testCelery']);
|
||||
|
||||
require __DIR__.'/auth.php';
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue