feat: Integrate official drive_provider, update user profile features & UI improvements
All checks were successful
CI / container-job (push) Successful in 41s
All checks were successful
CI / container-job (push) Successful in 41s
- adonisrc.ts: Load official drive_provider and unload custom driver_provider. - packages.json: Add @headlessui/vue dependency for tab components. - AvatarController.ts: Rewrite avatar generation logic to always return the same avatar per user. - auth/UserController.ts: Add profile and profileUpdate methods to support user profile editing. - Submitter/datasetController.ts & app/models/file.ts: Adapt code to use the official drive_provider. - app/models/user.ts: Introduce “isAdmin” getter. - config/drive.ts: Create new configuration for the official drive_provider. - providers/vinejs_provider.ts: Adapt allowedExtensions control to use provided options or database enabled extensions. - resource/js/app.ts: Load default Head and Link components. - resources/js/menu.ts: Add settings-profile.edit menu point. - resources/js/Components/action-message.vue: Add new component for improved user feedback after form submissions. - New avatar-input.vue component: Enable profile picture selection. - Components/CardBox.vue: Alter layout to optionally show HeaderIcon in title bar. - FormControl.vue: Define a readonly prop for textareas. - Improve overall UI with updates to NavBar.vue, UserAvatar.vue, UserAvatarCurrentUser.vue, and add v-model support to password-meter.vue. - Remove profile editing logic from AccountInfo.vue and introduce new profile components (show.vue, update-password-form.vue, update-profile-information.vue). - app.edge: Modify page (add @inertiaHead tag) for better meta management. - routes.ts: Add new routes for editing user profiles. - General npm updates.
This commit is contained in:
parent
a41b091214
commit
36cd7a757b
34 changed files with 1396 additions and 407 deletions
BIN
resources/js/Components/.FormField.vue.swo
Normal file
BIN
resources/js/Components/.FormField.vue.swo
Normal file
Binary file not shown.
|
@ -12,6 +12,10 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
showHeaderIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
headerIcon: {
|
||||
type: String,
|
||||
default: null,
|
||||
|
@ -63,7 +67,7 @@ const submit = (e) => {
|
|||
<BaseIcon v-if="icon" :path="icon" class="mr-3" />
|
||||
{{ title }}
|
||||
</div>
|
||||
<button class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring" @click="headerIconClick">
|
||||
<button v-if="showHeaderIcon" class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring" @click="headerIconClick">
|
||||
<BaseIcon :path="computedHeaderIcon" />
|
||||
</button>
|
||||
</header>
|
||||
|
|
|
@ -118,6 +118,9 @@ if (props.ctrlKFocus) {
|
|||
mainService.isFieldFocusRegistered = false;
|
||||
});
|
||||
}
|
||||
const focus = () => {
|
||||
inputEl?.value.focus();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -130,7 +133,7 @@ if (props.ctrlKFocus) {
|
|||
</option>
|
||||
</select>
|
||||
<textarea v-else-if="computedType === 'textarea'" :id="id" v-model="computedValue" :class="inputElClass"
|
||||
:name="name" :placeholder="placeholder" :required="required" />
|
||||
:name="name" :placeholder="placeholder" :required="required" :readonly="isReadOnly"/>
|
||||
<input v-else :id="id" ref="inputEl" v-model="computedValue" :name="name" :inputmode="inputmode"
|
||||
:autocomplete="autocomplete" :required="required" :placeholder="placeholder" :type="computedType"
|
||||
:class="inputElClass" :readonly="isReadOnly" />
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { computed, useSlots } from 'vue';
|
||||
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
|
@ -15,6 +15,10 @@ defineProps({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
// class: {
|
||||
// type: Object,
|
||||
// default: {},
|
||||
// },
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
|
@ -36,7 +40,7 @@ const wrapperClass = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-6 last:mb-0">
|
||||
<div :class="['last:mb-0', 'mb-6']">
|
||||
<!-- <label v-if="label" :for="labelFor" class="block font-bold mb-2">{{ label }}</label> -->
|
||||
<label v-if="label" :for="labelFor" class="font-bold h-6 mt-3 text-xs leading-8 uppercase">{{ label }}</label>
|
||||
<div v-bind:class="wrapperClass">
|
||||
|
|
|
@ -150,7 +150,7 @@ const showAbout = async () => {
|
|||
<!-- personal menu -->
|
||||
<NavBarMenu>
|
||||
<NavBarItemLabel v-bind:label="`hello ${user.login}`">
|
||||
<UserAvatarCurrentUser class="w-6 h-6 mr-3 inline-flex" />
|
||||
<UserAvatarCurrentUser :user="user" class="w-6 h-6 mr-3 inline-flex" />
|
||||
</NavBarItemLabel>
|
||||
<template #dropdown>
|
||||
<!-- <NavBarItem> -->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { checkStrength } from './logic/index';
|
||||
import { mdiFormTextboxPassword } from '@mdi/js';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
|
@ -7,17 +7,25 @@ import FormControl from '@/Components/FormControl.vue';
|
|||
|
||||
// Define props
|
||||
const props = defineProps<{
|
||||
password: string;
|
||||
modelValue: string;
|
||||
errors: Partial<Record<"new_password" | "old_password" | "confirm_password", string>>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:password', 'score']);
|
||||
const emit = defineEmits(['update:modelValue', 'score']);
|
||||
|
||||
// A local reactive variable for password input
|
||||
const localPassword = ref(props.password);
|
||||
// Watch localPassword and emit changes back to the parent
|
||||
watch(localPassword, (newValue) => {
|
||||
emit('update:password', newValue);
|
||||
// // A local reactive variable for password input
|
||||
// const localPassword = ref(props.modelValue);
|
||||
// // Watch localPassword and emit changes back to the parent
|
||||
// watch(localPassword, (newValue) => {
|
||||
// emit('update:modelValue', newValue);
|
||||
// });
|
||||
const localPassword = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
// const { score } = checkStrength(localPassword.value);
|
||||
// emit('score', score);
|
||||
},
|
||||
});
|
||||
|
||||
type PasswordMetrics = {
|
||||
|
@ -53,7 +61,7 @@ const passwordMetrics = computed<PasswordMetrics>(() => {
|
|||
|
||||
<template>
|
||||
<!-- Password input Form -->
|
||||
<FormField label="New password" help="Required. New password" :class="{ 'text-red-400': errors.new_password }">
|
||||
<FormField label="New password" help="Required. New password" :class="{'text-red-400': errors.new_password }">
|
||||
<FormControl v-model="localPassword" :icon="mdiFormTextboxPassword" name="new_password" type="password" required
|
||||
:error="errors.new_password">
|
||||
<!-- Secure Icon -->
|
||||
|
@ -72,6 +80,10 @@ const passwordMetrics = computed<PasswordMetrics>(() => {
|
|||
{{ errors.new_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
<!-- Score Display -->
|
||||
<div class="text-gray-700 text-sm">
|
||||
{{ passwordMetrics.score }} / 6 points max
|
||||
</div>
|
||||
</FormField>
|
||||
|
||||
<!-- Password Strength Bar -->
|
||||
|
@ -93,9 +105,9 @@ const passwordMetrics = computed<PasswordMetrics>(() => {
|
|||
</ul>
|
||||
</div>
|
||||
<!-- Score Display -->
|
||||
<div class="text-gray-700 text-sm">
|
||||
<!-- <div class="text-gray-700 text-sm">
|
||||
{{ passwordMetrics.score }} / 6 points max
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
|
|
@ -6,9 +6,9 @@ const props = defineProps({
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
avatar: {
|
||||
defaultUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false
|
||||
},
|
||||
api: {
|
||||
type: String,
|
||||
|
@ -16,58 +16,37 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const avatar = computed(
|
||||
// () => props.avatar ?? `https://avatars.dicebear.com/api/${props.api}/${props.username?.replace(/[^a-z0-9]+/i, '-')}.svg`
|
||||
|
||||
// () => props.avatar ?? `https://avatars.dicebear.com/api/initials/${props.username}.svg`,
|
||||
|
||||
() => {
|
||||
const initials = props.username
|
||||
.split(' ')
|
||||
.map((part) => part.charAt(0).toUpperCase())
|
||||
.join('');
|
||||
|
||||
return props.avatar ?? generateAvatarUrl(props.username);
|
||||
},
|
||||
);
|
||||
const avatar = computed(() => {
|
||||
return props.defaultUrl ?? generateAvatarUrl(props.username);
|
||||
});
|
||||
|
||||
const username = computed(() => props.username);
|
||||
|
||||
const darkenColor = (color) => {
|
||||
// Convert hex to RGB
|
||||
const r = parseInt(color.slice(0, 2), 16);
|
||||
const g = parseInt(color.slice(2, 4), 16);
|
||||
const b = parseInt(color.slice(4, 6), 16);
|
||||
|
||||
// Calculate darker color by reducing 20% of each RGB component
|
||||
const darkerR = Math.round(r * 0.6);
|
||||
const darkerG = Math.round(g * 0.6);
|
||||
const darkerB = Math.round(b * 0.6);
|
||||
|
||||
// Convert back to hex
|
||||
const darkerColor = ((darkerR << 16) + (darkerG << 8) + darkerB).toString(16);
|
||||
|
||||
return darkerColor.padStart(6, '0'); // Ensure it's 6 digits
|
||||
return darkerColor.padStart(6, '0');
|
||||
};
|
||||
|
||||
const getRandomColor = () => {
|
||||
return Math.floor(Math.random() * 16777215).toString(16);
|
||||
};
|
||||
|
||||
const adjustOpacity = (hexColor, opacity) => {
|
||||
// Remove # if present
|
||||
hexColor = hexColor.replace('#', '');
|
||||
// Convert hex to RGB
|
||||
// const r = parseInt(hexColor.slice(0, 2), 16);
|
||||
// const g = parseInt(hexColor.slice(2, 4), 16);
|
||||
// const b = parseInt(hexColor.slice(4, 6), 16);
|
||||
|
||||
// const r = parseInt(hexColor.slice(1, 3), 16);
|
||||
// const g = parseInt(hexColor.slice(3, 5), 16);
|
||||
// const b = parseInt(hexColor.slice(5, 7), 16);
|
||||
const [r, g, b] = hexColor.match(/\w\w/g).map(x => parseInt(x, 16));
|
||||
|
||||
return `rgba(${r},${g},${b},${opacity})`;
|
||||
const getColorFromName = (name) => {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
let color = '#';
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ('00' + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color.replace('#', '');
|
||||
};
|
||||
|
||||
const lightenColor = (hexColor, percent) => {
|
||||
|
@ -88,21 +67,12 @@ const lightenColor = (hexColor, percent) => {
|
|||
return lighterHex.padStart(6, '0');
|
||||
};
|
||||
|
||||
// backgroundColor = '7F9CF5',
|
||||
const generateAvatarUrl = (name) => {
|
||||
const initials = name
|
||||
.split(' ')
|
||||
.map((part) => part.charAt(0).toUpperCase())
|
||||
.join('');
|
||||
|
||||
const originalColor = getRandomColor();
|
||||
|
||||
const backgroundColor = lightenColor(originalColor, 60); // Lighten by 20%
|
||||
|
||||
const originalColor = getColorFromName(name);
|
||||
const backgroundColor = lightenColor(originalColor, 60);
|
||||
const textColor = darkenColor(originalColor);
|
||||
|
||||
// const avatarUrl = `https://ui-avatars.com/api/?name=${initials}&size=50&background=${backgroundColor}&color=${textColor}`;
|
||||
const avatarUrl = `/api/avatar?name=${name}&size=50&background=${backgroundColor}&textColor=${textColor}`;
|
||||
const avatarUrl = `/api/avatar?name=${name}&size=50`;
|
||||
return avatarUrl;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
// import { usePage } from '@inertiajs/vue3'
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
// import { computed } from 'vue';
|
||||
// import { usePage } from '@inertiajs/vue3';
|
||||
import UserAvatar from '@/Components/UserAvatar.vue';
|
||||
|
||||
const userName = computed(() => usePage().props.auth?.user.name);
|
||||
defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UserAvatar v-bind:username="'userName'" api="initials" />
|
||||
|
||||
<div v-if="user.avatar">
|
||||
<UserAvatar :default-url="user.avatar ? '/public' + user.avatar : ''"
|
||||
:username="user.first_name + ' ' + user.last_name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else>
|
||||
<UserAvatar :username="user.first_name + ' ' + user.last_name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</div>
|
||||
</template>
|
||||
<!-- <template v-else>
|
||||
<UserAvatar :username="user.first_name + ' ' + user.last_name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
|
||||
</template> -->
|
||||
|
|
30
resources/js/Components/action-message.vue
Normal file
30
resources/js/Components/action-message.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { colorsBgLight, colorsOutline } from '@/colors';
|
||||
|
||||
const props = defineProps({
|
||||
on: Boolean,
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
outline: Boolean,
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'info',
|
||||
},
|
||||
});
|
||||
const componentClass = computed(() => (props.outline ? colorsOutline[props.color] : colorsBgLight[props.color]));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div >
|
||||
<transition leave-active-class="transition ease-in duration-1000" leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0" :class="componentClass" class="px-3 py-2 last:mb-0 border rounded transition-colors duration-150 text-sm text-gray-600">
|
||||
<div v-show="on">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
79
resources/js/Components/avatar-input.vue
Normal file
79
resources/js/Components/avatar-input.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div class="relative inline-block overflow-hidden rounded-full">
|
||||
<input type="file" ref="avatarInput" @change="onChangeFile" class="hidden" accept="image/*">
|
||||
|
||||
<img :src="avatarUrl" alt="Avatar" class="h-full w-full object-cover">
|
||||
<div class="absolute top-0 h-full w-full bg-black bg-opacity-25 flex items-center justify-center">
|
||||
<button @click.prevent="browse"
|
||||
class="rounded-full hover:bg-white hover:bg-opacity-25 p-2 focus:outline-none text-white transition-colors duration-300">
|
||||
<IconRounded :icon="mdiCameraEnhanceOutline" class="bg-transparent h-6 w-6" />
|
||||
</button>
|
||||
<button v-if="file" @click.prevent="reset"
|
||||
class="rounded-full hover:bg-white hover:bg-opacity-25 p-2 focus:outline-none text-white transition-colors duration-300">
|
||||
<IconRounded :icon="mdiAlphaXCircleOutline " class="bg-transparent h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, defineEmits } from 'vue';
|
||||
import { mdiCameraEnhanceOutline, mdiAlphaXCircleOutline } from '@mdi/js';
|
||||
import IconRounded from './IconRounded.vue';
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
modelValue: File,
|
||||
defaultSrc: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
// vue data properties
|
||||
const file = ref<File | null>(null);
|
||||
const avatarUrl = ref<string>(props.defaultSrc);
|
||||
const avatarInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
|
||||
// const avatarUrl = computed({
|
||||
// get: () => props.modelValue ? props.modelValue : props.defaultSrc,
|
||||
// set: (value: string) => {
|
||||
// emit('update:modelValue', value);
|
||||
// },
|
||||
// });
|
||||
|
||||
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', file: File | null): void; (e: 'input', file: File | null): void }>();
|
||||
|
||||
// const avatarInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const browse = () => {
|
||||
avatarInput.value?.click();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
file.value = null;
|
||||
avatarUrl.value = props.defaultSrc;
|
||||
emit('input', file.value);
|
||||
};
|
||||
|
||||
const onChangeFile = (e: Event) => {
|
||||
// const target = (<HTMLInputElement>e.target)
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.files && target.files[0]) {
|
||||
file.value = target.files[0];
|
||||
}
|
||||
if (file.value) {
|
||||
emit('input', file.value);
|
||||
emit('update:modelValue', file.value);
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(file.value);
|
||||
reader.onload = (e) => {
|
||||
avatarUrl.value = e.target?.result as string;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
|
@ -1,43 +1,42 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { MainService } from '@/Stores/main'
|
||||
import { mdiCheckDecagram } from '@mdi/js'
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import UserAvatarCurrentUser from '@/Components/UserAvatarCurrentUser.vue'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue'
|
||||
import PillTag from '@/Components/PillTag.vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import { mdiCheckDecagram } from '@mdi/js';
|
||||
import BaseLevel from '@/Components/BaseLevel.vue';
|
||||
import UserAvatarCurrentUser from '@/Components/UserAvatarCurrentUser.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
|
||||
import PillTag from '@/Components/PillTag.vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import type { User } from '@/Dataset';
|
||||
import type { ComputedRef } from 'vue';
|
||||
|
||||
const mainService = MainService()
|
||||
const mainService = MainService();
|
||||
|
||||
const userName = computed(() => mainService.userName)
|
||||
const userName = computed(() => mainService.userName);
|
||||
|
||||
const userSwitchVal = ref([])
|
||||
const user: ComputedRef<User> = computed(() => {
|
||||
return usePage().props.authUser as User;
|
||||
});
|
||||
|
||||
const userSwitchVal = ref([]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox>
|
||||
<BaseLevel type="justify-around lg:justify-center">
|
||||
<UserAvatarCurrentUser class="lg:mx-12" />
|
||||
<UserAvatarCurrentUser :user="user" class="lg:mx-12" />
|
||||
<div class="space-y-3 text-center md:text-left lg:mx-12">
|
||||
<div class="flex justify-center md:block">
|
||||
<FormCheckRadioGroup
|
||||
v-model="userSwitchVal"
|
||||
name="sample-switch"
|
||||
type="switch"
|
||||
:options="{ one: 'Notifications' }"
|
||||
/>
|
||||
<FormCheckRadioGroup v-model="userSwitchVal" name="sample-switch" type="switch"
|
||||
:options="{ one: 'Notifications' }" />
|
||||
</div>
|
||||
<h1 class="text-2xl">
|
||||
Howdy, <b>{{ userName }}</b>!
|
||||
</h1>
|
||||
<p>Last login <b>12 mins ago</b> from <b>127.0.0.1</b></p>
|
||||
<div class="flex justify-center md:block">
|
||||
<PillTag
|
||||
text="Verified"
|
||||
type="info"
|
||||
:icon="mdiCheckDecagram"
|
||||
/>
|
||||
<PillTag text="Verified" type="info" :icon="mdiCheckDecagram" />
|
||||
</div>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { Head, usePage } from '@inertiajs/vue3';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline } from '@mdi/js';
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
mdiAsterisk,
|
||||
mdiFormTextboxPassword,
|
||||
mdiArrowLeftBoldOutline,
|
||||
mdiAlertBoxOutline,
|
||||
mdiAlertBoxOutline,
|
||||
} from '@mdi/js';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
|
@ -36,16 +36,16 @@ import PasswordMeter from '@/Components/SimplePasswordMeter/password-meter.vue';
|
|||
|
||||
const emit = defineEmits(['confirm', 'update:confirmation'])
|
||||
|
||||
const enabled = ref(false);
|
||||
const handleScore = (score: number) => {
|
||||
if (score >= 4){
|
||||
enabled.value = true;
|
||||
} else {
|
||||
enabled.value = false;
|
||||
}
|
||||
// strengthLabel.value = scoreLabel;
|
||||
// score.value = scoreValue;
|
||||
};
|
||||
// const enabled = ref(false);
|
||||
// const handleScore = (score: number) => {
|
||||
// if (score >= 4) {
|
||||
// enabled.value = true;
|
||||
// } else {
|
||||
// enabled.value = false;
|
||||
// }
|
||||
// // strengthLabel.value = scoreLabel;
|
||||
// // score.value = scoreValue;
|
||||
// };
|
||||
|
||||
defineProps({
|
||||
// user will be returned from controller action
|
||||
|
@ -82,20 +82,20 @@ defineProps({
|
|||
// };
|
||||
|
||||
|
||||
const passwordForm = useForm({
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
confirm_password: '',
|
||||
});
|
||||
const passwordSubmit = async () => {
|
||||
await passwordForm.post(stardust.route('account.password.store'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
// console.log(resp);
|
||||
passwordForm.reset();
|
||||
},
|
||||
});
|
||||
};
|
||||
// const passwordForm = useForm({
|
||||
// old_password: '',
|
||||
// new_password: '',
|
||||
// confirm_password: '',
|
||||
// });
|
||||
// const passwordSubmit = async () => {
|
||||
// await passwordForm.post(stardust.route('account.password.store'), {
|
||||
// preserveScroll: true,
|
||||
// onSuccess: () => {
|
||||
// // console.log(resp);
|
||||
// passwordForm.reset();
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
|
||||
const flash: Ref<any> = computed(() => {
|
||||
return usePage().props.flash;
|
||||
|
@ -139,40 +139,10 @@ const flash: Ref<any> = computed(() => {
|
|||
{{ $page.props.flash.message }}
|
||||
</NotificationBar> -->
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- <div class="grid grid-cols-1 lg:grid-cols-1 gap-6"> -->
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
|
||||
<!-- password form -->
|
||||
<!-- <CardBox title="Edit Profile" :icon="mdiAccountCircle" form @submit.prevent="profileForm.post(route('admin.account.info.store'))"> -->
|
||||
<!-- <CardBox title="Edit Profile" :icon="mdiAccountCircle" form @submit.prevent="profileSubmit()">
|
||||
<FormField label="Login" help="Required. Your login name" :class="{ 'text-red-400': errors.login }">
|
||||
<FormControl v-model="profileForm.login" v-bind:icon="mdiAccount" name="login" required :error="errors.login">
|
||||
<div class="text-red-400 text-sm" v-if="errors.login">
|
||||
{{ errors.login }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
<FormField label="Email" help="Required. Your e-mail" :class="{ 'text-red-400': errors.email }">
|
||||
<FormControl v-model="profileForm.email" :icon="mdiMail" type="email" name="email" required :error="errors.email">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email">
|
||||
{{ errors.email }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton color="info" type="submit" label="Submit" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox> -->
|
||||
|
||||
<!-- password form -->
|
||||
<!-- <CardBox title="Change Password" :icon="mdiLock" form @submit.prevent="passwordForm.post(route('admin.account.password.store'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => passwordForm.reset(),
|
||||
}) "> -->
|
||||
<CardBox id="passwordForm" title="Change Password" :icon="mdiLock" form @submit.prevent="passwordSubmit()">
|
||||
<!-- <CardBox id="passwordForm" title="Change Password" :icon="mdiLock" form
|
||||
@submit.prevent="passwordSubmit()">
|
||||
<FormValidationErrors v-bind:errors="errors" />
|
||||
|
||||
<FormField label="Current password" help="Required. Your current password"
|
||||
|
@ -186,22 +156,15 @@ const flash: Ref<any> = computed(() => {
|
|||
</FormField>
|
||||
<BaseDivider />
|
||||
|
||||
<!-- <FormField label="New password" help="Required. New password"
|
||||
:class="{ 'text-red-400': passwordForm.errors.new_password }">
|
||||
<FormControl v-model="passwordForm.new_password" :icon="mdiFormTextboxPassword" name="new_password"
|
||||
type="password" required :error="passwordForm.errors.new_password">
|
||||
<div class="text-red-400 text-sm" v-if="passwordForm.errors.new_password">
|
||||
{{ passwordForm.errors.new_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField> -->
|
||||
<PasswordMeter v-model:password="passwordForm.new_password" :errors="passwordForm.errors" @score="handleScore" />
|
||||
<PasswordMeter v-model="passwordForm.new_password" :errors="passwordForm.errors"
|
||||
@score="handleScore" />
|
||||
|
||||
|
||||
<FormField label="Confirm password" help="Required. New password one more time"
|
||||
:class="{ 'text-red-400': passwordForm.errors.confirm_password }">
|
||||
<FormControl v-model="passwordForm.confirm_password" :icon="mdiFormTextboxPassword"
|
||||
name="confirm_password" type="password" required :error="passwordForm.errors.confirm_password">
|
||||
name="confirm_password" type="password" required
|
||||
:error="passwordForm.errors.confirm_password">
|
||||
<div class="text-red-400 text-sm" v-if="passwordForm.errors.confirm_password">
|
||||
{{ passwordForm.errors.confirm_password }}
|
||||
</div>
|
||||
|
@ -219,16 +182,17 @@ const flash: Ref<any> = computed(() => {
|
|||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Change password" :disabled="passwordForm.processing == true || enabled == false" />
|
||||
<BaseButton type="submit" color="info" label="Change password"
|
||||
:disabled="passwordForm.processing == true || enabled == false" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</CardBox> -->
|
||||
|
||||
|
||||
|
||||
<PersonalTotpSettings :twoFactorEnabled="twoFactorEnabled" :backupState="backupState">
|
||||
</PersonalTotpSettings>
|
||||
<!-- <PersonalSettings :state="backupState"/> -->
|
||||
<PersonalTotpSettings :twoFactorEnabled="twoFactorEnabled" :backupState="backupState">
|
||||
</PersonalTotpSettings>
|
||||
<!-- <PersonalSettings :state="backupState"/> -->
|
||||
|
||||
<!-- <CardBox v-if="!props.twoFactorEnabled" title="Two-Factor Authentication" :icon="mdiInformation" form
|
||||
@submit.prevent="enableTwoFactorAuthentication()">
|
||||
|
@ -248,7 +212,7 @@ const flash: Ref<any> = computed(() => {
|
|||
</template>
|
||||
</CardBox> -->
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</SectionMain>
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
// import { containerMaxW } from '@/config.js'; // "xl:max-w-6xl xl:mx-auto"
|
||||
// import * as chartConfig from '@/Components/Charts/chart.config.js';
|
||||
import LineChart from '@/Components/Charts/LineChart.vue';
|
||||
import UserCard from '@/Components/unused/UserCard.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import CardBoxWidget from '@/Components/CardBoxWidget.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
|
@ -134,6 +135,8 @@ const datasets = computed(() => mainService.datasets);
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<UserCard />
|
||||
|
||||
<SectionBannerStarOnGitHub />
|
||||
|
||||
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview: Publications per month" />
|
||||
|
|
135
resources/js/Pages/profile/partials/update-password-form.vue
Normal file
135
resources/js/Pages/profile/partials/update-password-form.vue
Normal file
|
@ -0,0 +1,135 @@
|
|||
<script lang="ts" setup>
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import ActionMessage from '@/Components/action-message.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import { useForm, usePage } from '@inertiajs/vue3';
|
||||
import { ref, Ref, computed } from 'vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import { mdiLock } from '@mdi/js';
|
||||
import PasswordMeter from '@/Components/SimplePasswordMeter/password-meter.vue';
|
||||
// import BaseDivider from '@/Components/BaseDivider.vue';
|
||||
|
||||
// const errors: Ref<any> = computed(() => {
|
||||
// return usePage().props.errors;
|
||||
// });
|
||||
const flash: Ref<any> = computed(() => {
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
const newPasswordInput: Ref<typeof FormControl | null> = ref(null);
|
||||
const oldPasswordInput: Ref<typeof FormControl | null> = ref(null);
|
||||
|
||||
const enabled = ref(false);
|
||||
const handleScore = (score: number) => {
|
||||
if (score >= 4) {
|
||||
enabled.value = true;
|
||||
} else {
|
||||
enabled.value = false;
|
||||
}
|
||||
// strengthLabel.value = scoreLabel;
|
||||
// score.value = scoreValue;
|
||||
};
|
||||
|
||||
const form = useForm({
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
confirm_password: '',
|
||||
});
|
||||
const updatePassword = async () => {
|
||||
await form.put(stardust.route('settings.password.update'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
},
|
||||
onError: () => {
|
||||
if (form.errors.new_password) {
|
||||
form.reset('new_password', 'confirm_password');
|
||||
enabled.value = false;
|
||||
// newPasswordInput.value.focus();
|
||||
// newPasswordInput.value?.focus();
|
||||
}
|
||||
|
||||
if (form.errors.old_password) {
|
||||
form.reset('old_password');
|
||||
// oldPasswordInput.value?.focus();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <div class="p-7 text-gray-900 bg-white rounded-lg border border-gray-100 shadow dark:border-gray-600 dark:bg-secondary-dark dark:text-white"> -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <div class="mb-4">
|
||||
<h3 class="text-dark text-md">{{ 'Update Password' }}</h3>
|
||||
</div> -->
|
||||
|
||||
<CardBox id="passwordForm" title="Change Password" :icon="mdiLock" form :show-header-icon="false">
|
||||
|
||||
|
||||
|
||||
<FormField label="Current password" help="Required. Your current password"
|
||||
:class="{ 'text-red-400': form.errors.old_password }">
|
||||
<FormControl label="Current Password" id="current_password" ref="oldPasswordInput"
|
||||
:placeholder="'Please Enter Current Password'" v-model="form.old_password"
|
||||
:error="form.errors.old_password" type="password" class="block w-full" autocomplete="current-password">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.old_password">
|
||||
{{ form.errors.old_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
|
||||
<!-- <div class="col-span-6 sm:col-span-4"> -->
|
||||
<!-- <FormControl label="New Password" id="password" :placeholder="'Please Enter New Password'"
|
||||
ref="newPasswordInput" v-model="form.new_password" type="password" class="block w-full"
|
||||
autocomplete="new-password" :error="form.errors.new_password">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.new_password">
|
||||
{{ form.errors.new_password }}
|
||||
</div>
|
||||
</FormControl> -->
|
||||
<PasswordMeter ref="newPasswordInput" v-model="form.new_password" :errors="form.errors"
|
||||
@score="handleScore" />
|
||||
|
||||
<!-- </div> -->
|
||||
|
||||
<FormField label="Confirm password" help="Required. New password one more time"
|
||||
:class="{ 'text-red-400': form.errors.confirm_password }">
|
||||
<FormControl label="Confirm Password" :placeholder="'Please Enter Confirm Password'" id="confirm_password"
|
||||
v-model="form.confirm_password" type="password" class="block w-full" autocomplete="new-password"
|
||||
:error="form.errors.confirm_password">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.confirm_password">
|
||||
{{ form.errors.confirm_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<!-- <BaseDivider /> -->
|
||||
|
||||
<template #footer>
|
||||
|
||||
|
||||
|
||||
<div class="flex items-center justify-end gap-3 mt-5">
|
||||
<ActionMessage v-if="flash.message" :on="form.recentlySuccessful" color="success">
|
||||
{{ flash.message }}
|
||||
</ActionMessage>
|
||||
<ActionMessage v-if="flash.warning" :on="form.recentlySuccessful" color="warning">
|
||||
{{ flash.warning }}
|
||||
</ActionMessage>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Change password" @click.prevent="updatePassword()"
|
||||
:disabled="form.processing == true || enabled == false" />
|
||||
</BaseButtons>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
|
@ -0,0 +1,179 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, Ref, ref } from 'vue';
|
||||
import { useForm, usePage } from '@inertiajs/vue3';
|
||||
import ActionMessage from '@/Components/action-message.vue'
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import { mdiAccount } from '@mdi/js';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import AvatarInput from '@/Components/avatar-input.vue';
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
defaultUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const errors: Ref<any> = computed(() => {
|
||||
return usePage().props.errors || {}
|
||||
});
|
||||
const flash: Ref<any> = computed(() => {
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
const fullName = computed(() => `${props.user.first_name} ${props.user.last_name}`);
|
||||
const recentlyHasError = ref(false);
|
||||
|
||||
const form = useForm({
|
||||
first_name: props.user.first_name,
|
||||
last_name: props.user.last_name,
|
||||
login: props.user.login,
|
||||
email: props.user.email,
|
||||
// mobile: `${props.user.mobile}`,
|
||||
avatar: undefined as File | undefined,
|
||||
});
|
||||
|
||||
// const verificationLinkSent = ref(null);
|
||||
const avatarInput: Ref<HTMLInputElement | null> = ref(null);
|
||||
|
||||
const updateProfileInformation = () => {
|
||||
// if (avatarInput.value) {
|
||||
// form.avatar = avatarInput.value?.files ? avatarInput.value.files[0] : undefined;
|
||||
// }
|
||||
|
||||
form.put(stardust.route('settings.profile.update', [props.user.id]), {
|
||||
errorBag: 'updateProfileInformation',
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
// clearPhotoFileInput();
|
||||
},
|
||||
onError: () => {
|
||||
if (form.errors.avatar) {
|
||||
if (avatarInput.value) {
|
||||
avatarInput.value.value = '';
|
||||
}
|
||||
}
|
||||
recentlyHasError.value = true
|
||||
setTimeout(() => {
|
||||
recentlyHasError.value = false
|
||||
}, 5000)
|
||||
},
|
||||
});
|
||||
};
|
||||
// const sendEmailVerification = () => {
|
||||
// verificationLinkSent.value = true;
|
||||
// };
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox id="passwordForm" title="Basic Info" :icon="mdiAccount" form :show-header-icon="false">
|
||||
<!-- <FormValidationErrors v-bind:errors="errors" /> -->
|
||||
|
||||
<AvatarInput class="h-24 w-24 rounded-full" v-model="form.avatar" ref="avatarInput"
|
||||
:default-src="defaultUrl ? defaultUrl : '/api/avatar?name=' + fullName + '&size=50'">
|
||||
</AvatarInput>
|
||||
<div class="text-red-400 text-sm" v-if="errors.avatar && Array.isArray(errors.avatar)">
|
||||
{{ errors.avatar.join(', ') }}
|
||||
</div>
|
||||
|
||||
<FormField label="First Name" :class="{ 'text-red-400': form.errors.first_name }">
|
||||
<FormControl id="first_name" label="First Name" v-model="form.first_name" :error="form.errors.first_name"
|
||||
type="text" :placeholder="'First Name'" class="w-full" autocomplete="first_name">
|
||||
<div class="text-red-400 text-sm" v-if="errors.first_name && Array.isArray(errors.first_name)">
|
||||
{{ errors.first_name.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Last Name" :class="{ 'text-red-400': form.errors.last_name }">
|
||||
<FormControl id="last_name" label="Last Name" v-model="form.last_name" :error="form.errors.last_name"
|
||||
type="text" :placeholder="'Last Name'" class="w-full" autocomplete="last_name">
|
||||
<div class="text-red-400 text-sm" v-if="errors.last_name && Array.isArray(errors.last_name)">
|
||||
{{ errors.last_name.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Username" :class="{ 'text-red-400': form.errors.login }">
|
||||
<FormControl id="username" label="Username" v-model="form.login" class="w-full"
|
||||
:is-read-only="!user.is_admin">
|
||||
<div class="text-red-400 text-sm" v-if="errors.login && Array.isArray(errors.login)">
|
||||
{{ errors.login.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
</FormField>
|
||||
|
||||
<FormField label="Enter Email">
|
||||
<FormControl v-model="form.email" type="text" placeholder="Email" :errors="form.errors.email"
|
||||
:is-read-only="!user.is_admin">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email && Array.isArray(errors.email)">
|
||||
{{ errors.email.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<!-- Email -->
|
||||
<!-- <div>
|
||||
<FormControl label="Email" id="email" v-model="form.email" :readonly="!user.is_super_admin"
|
||||
:disabled="!user.is_super_admin" class="w-full" />
|
||||
|
||||
<div v-if="user.email_verified_at === null">
|
||||
<p class="text-sm mt-2">
|
||||
{{ 'Your email address is unverified.' }}
|
||||
|
||||
<Link :href="route('verification.send')" method="post" as="button"
|
||||
class="underline text-gray-600 hover:text-gray-900" @click.prevent="sendEmailVerification">
|
||||
{{ 'Click here to re-send the verification email.' }}
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div v-show="verificationLinkSent" class="mt-2 font-medium text-sm text-green-600">
|
||||
{{ 'A new verification link has been sent to your email address.' }}
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="relative">
|
||||
<FormControl label="Mobile" id="mobile" class="w-full" input-class="w-full pl-5" v-model="form.mobile"
|
||||
:readonly="!user.is_super_admin" :disabled="!user.is_super_admin" />
|
||||
<span class="absolute top-9 left-0 inline-flex items-center ml-2 font-bold text-secondary-light">+</span>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="col-span-2 flex justify-end items-center mt-5"> -->
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-end gap-3 ">
|
||||
<ActionMessage :on="recentlyHasError" color="warning">
|
||||
<ul class="list-disc list-inside space-y-2 text-sm">
|
||||
<li v-for="(messages, field) in errors" :key="field" class="flex flex-col">
|
||||
<span class="font-semibold capitalize text-gray-700 dark:text-gray-300">{{ field }}:</span>
|
||||
<span class="text-red-600 dark:text-red-400 ml-4">{{ messages.join(', ') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ActionMessage>
|
||||
<ActionMessage v-if="flash.message" :on="form.recentlySuccessful" color="success">
|
||||
{{ flash.message }}
|
||||
</ActionMessage>
|
||||
<ActionMessage v-if="flash.warning" :on="form.recentlySuccessful" color="warning">
|
||||
{{ flash.warning }}
|
||||
</ActionMessage>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Save Changes"
|
||||
@click.prevent="updateProfileInformation" :disabled="form.processing" />
|
||||
</BaseButtons>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
|
||||
<!-- </div>
|
||||
</div> -->
|
||||
</template>
|
139
resources/js/Pages/profile/show.vue
Normal file
139
resources/js/Pages/profile/show.vue
Normal file
|
@ -0,0 +1,139 @@
|
|||
<script lang="ts" setup>
|
||||
|
||||
/*=========================================================================================
|
||||
File Name: show profile
|
||||
|
||||
----------------------------------------------------------------------------------------
|
||||
Author: Arno Kaimbacher
|
||||
Author URL: https://jakint.at/
|
||||
==========================================================================================*/
|
||||
|
||||
|
||||
|
||||
// import { useUrlSearchParams } from '@vueuse/core';
|
||||
// import { usePage } from '@inertiajs/vue3';
|
||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue';
|
||||
import { ref, computed } from 'vue';
|
||||
// import IconRounded from '@/Components/IconRounded.vue';
|
||||
|
||||
|
||||
// import AdminLayout from '@/Layouts/AdminLayout.vue';
|
||||
// import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
|
||||
import UpdatePasswordForm from '@/Pages/profile/partials/update-password-form.vue';
|
||||
import UpdateProfileInformationForm from '@/Pages/profile/partials/update-profile-information-form.vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import { mdiAccount, mdiArrowLeftBoldOutline, mdiFormTextboxPassword } from '@mdi/js';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
||||
defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
defaultUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{ id: 1, title: 'My Profile', icon: mdiAccount },
|
||||
{ id: 2, title: 'Change Password', icon: mdiFormTextboxPassword },
|
||||
];
|
||||
|
||||
// const params = useUrlSearchParams('history');
|
||||
const selectedTab = ref(0);
|
||||
|
||||
function changeTab(index: number) {
|
||||
selectedTab.value = index;
|
||||
}
|
||||
|
||||
const user = computed(() => usePage().props.user);
|
||||
// const sessions = computed(() => usePage().props.sessions);
|
||||
// const Layout = computed(() => (user.value.is_super_admin ? AdminLayout : AppLayout));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Component :is="LayoutAuthenticated">
|
||||
|
||||
<Head title="Profile"></Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccount" title="Profile" main>
|
||||
<BaseButton :route-name="stardust.route('dashboard')" :icon="mdiArrowLeftBoldOutline" label="Back"
|
||||
color="white" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<!-- <NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
</NotificationBar> -->
|
||||
|
||||
<div class="">
|
||||
<TabGroup :selectedIndex="selectedTab" @change="changeTab">
|
||||
<TabList class="flex space-x-7 p-1">
|
||||
<Tab v-for="(tab, index) in tabs" as="template" :key="index" v-slot="{ selected }">
|
||||
<button :class="[
|
||||
'inline-flex items-center justify-center font-semibold focus:outline-none disabled:opacity-25 pb-2',
|
||||
selected
|
||||
? 'text-dark border-b-2 border-primary transition-all duration-500 inline-block'
|
||||
: 'text-light',
|
||||
]">
|
||||
<!-- <IconRounded :name="tab.icon" class="h-5 mr-3" /> -->
|
||||
<BaseIcon :path="tab.icon" class="flex-none" w="w-12" />
|
||||
{{ tab.title }}
|
||||
</button>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<transition v-show="selectedTab >= 0" enter-active-class="transition duration-100 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0" enter-to-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-in"
|
||||
leave-from-class="transform scale-100 opacity-100"
|
||||
leave-to-class="transform scale-95 opacity-0">
|
||||
<TabPanels class="mt-2">
|
||||
<TabPanel :key="Date.now().toString() + 1" :class="[]">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<UpdateProfileInformationForm class="p-5" :user="user" :default-url="defaultUrl" />
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel :key="Date.now().toString() + 3" :class="[]">
|
||||
<!-- <div class="grid grid-cols-1 md:grid-cols-2 gap-x-5 gap-y-7 items-start"> -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<UpdatePasswordForm class="p-5" />
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
</TabPanels>
|
||||
</transition>
|
||||
</TabGroup>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</Component>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.cool-link::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: #fda92d;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.cool-link:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- <script>
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
layout: false,
|
||||
});
|
||||
</script> -->
|
|
@ -2,7 +2,7 @@ import '../css/app.css';
|
|||
import { createApp, h } from 'vue';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
|
||||
import { createInertiaApp } from '@inertiajs/vue3';
|
||||
import { Head, Link, createInertiaApp } from '@inertiajs/vue3';
|
||||
// import DefaultLayout from '@/Layouts/Default.vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
|
@ -36,6 +36,7 @@ createInertiaApp({
|
|||
progress: {
|
||||
// color: '#4B5563',
|
||||
color: '#22C55E',
|
||||
showSpinner: true,
|
||||
},
|
||||
// Webpack
|
||||
// resolve: async (name: string) => {
|
||||
|
@ -59,6 +60,9 @@ createInertiaApp({
|
|||
.use(EmitterPlugin);
|
||||
// .component('inertia-link', Link)
|
||||
|
||||
app.component('Head', Head);
|
||||
app.component('Link', Link);
|
||||
|
||||
// Listen for navigation event to handle layout changes
|
||||
// window.addEventListener('inertia:navigate', () => {
|
||||
// layoutService.isAsideMobileExpanded = false;
|
||||
|
|
|
@ -27,6 +27,11 @@ export default [
|
|||
icon: mdiLock,
|
||||
label: 'Security',
|
||||
},
|
||||
{
|
||||
route: 'settings.profile.edit',
|
||||
icon: mdiLock,
|
||||
label: 'Profile',
|
||||
},
|
||||
// {
|
||||
// route: 'dataset.create',
|
||||
// icon: mdiPublish,
|
||||
|
@ -152,9 +157,9 @@ export default [
|
|||
// label: 'Create Dataset',
|
||||
// },
|
||||
{
|
||||
href: 'https://gitea.geologie.ac.at/geolba/tethys',
|
||||
href: 'https://gitea.geosphere.at/geolba/tethys.backend',
|
||||
icon: mdiGithub,
|
||||
label: 'Gitea',
|
||||
label: 'Forgejo',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
@vite(['resources/js/app.ts'])
|
||||
|
||||
@routes('test')
|
||||
@inertiaHead
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue