initial commit

This commit is contained in:
Inshal
2024-10-25 19:58:19 +05:00
commit 2046156f90
1558 changed files with 210706 additions and 0 deletions

View File

@@ -0,0 +1,319 @@
<script setup>
import avatar1 from '@images/avatars/avatar-1.png';
import {
emailValidator,
requiredEmail,
requiredName,
requiredPhone,
validUSAPhone
} from '@validators';
import { useStore } from 'vuex';
const store = useStore();
let imageBlob = null;
const profileData = ref([]);
const errors = ref({
name: undefined,
email: undefined,
phone_no:undefined,
})
const accountData = {
avatarImg: avatar1,
name: '',
last_name: '',
email: '',
phone_no: '',
}
const refVForm = ref()
const refInputEl = ref()
const isConfirmDialogOpen = ref(false)
const accountDataLocal = ref(structuredClone(accountData))
const isAccountDeactivated = ref(false)
const validateAccountDeactivation = [v => !!v || 'Please confirm account deactivation']
const getIsTonalSnackbarVisible = ref(false);
const ImageBase64 = ref();
const resetForm = () => {
accountDataLocal.value = structuredClone(accountData)
}
const changeAvatar = file => {
const fileReader = new FileReader()
const { files } = file.target
if (files && files.length) {
fileReader.readAsDataURL(files[0])
fileReader.onload = () => {
if (typeof fileReader.result === 'string') {
accountDataLocal.value.avatarImg = fileReader.result
}
ImageBase64.value = fileReader.result.split(',')[1];
}
}
}
onMounted(async () => {
await store.dispatch('adminDetial');
let list = await store.getters.getAdminDetail
accountDataLocal.value.email = list.email
accountDataLocal.value.name = list.name
accountDataLocal.value.last_name = list.last_name;
accountDataLocal.value.phone_no = list.phone_no
if(!list.image_path){
accountDataLocal.value.avatarImg = avatar1;
}else{
accountDataLocal.value.avatarImg = list.image_path
}
});
// reset avatar image
const resetAvatar = () => {
accountDataLocal.value.avatarImg = accountData.avatarImg
}
const timezones = [
'(GMT-11:00) International Date Line West',
'(GMT-11:00) Midway Island',
'(GMT-10:00) Hawaii',
'(GMT-09:00) Alaska',
'(GMT-08:00) Pacific Time (US & Canada)',
'(GMT-08:00) Tijuana',
'(GMT-07:00) Arizona',
'(GMT-07:00) Chihuahua',
'(GMT-07:00) La Paz',
'(GMT-07:00) Mazatlan',
'(GMT-07:00) Mountain Time (US & Canada)',
'(GMT-06:00) Central America',
'(GMT-06:00) Central Time (US & Canada)',
'(GMT-06:00) Guadalajara',
'(GMT-06:00) Mexico City',
'(GMT-06:00) Monterrey',
'(GMT-06:00) Saskatchewan',
'(GMT-05:00) Bogota',
'(GMT-05:00) Eastern Time (US & Canada)',
'(GMT-05:00) Indiana (East)',
'(GMT-05:00) Lima',
'(GMT-05:00) Quito',
'(GMT-04:00) Atlantic Time (Canada)',
'(GMT-04:00) Caracas',
'(GMT-04:00) La Paz',
'(GMT-04:00) Santiago',
'(GMT-03:30) Newfoundland',
'(GMT-03:00) Brasilia',
'(GMT-03:00) Buenos Aires',
'(GMT-03:00) Georgetown',
'(GMT-03:00) Greenland',
'(GMT-02:00) Mid-Atlantic',
'(GMT-01:00) Azores',
'(GMT-01:00) Cape Verde Is.',
'(GMT+00:00) Casablanca',
'(GMT+00:00) Dublin',
'(GMT+00:00) Edinburgh',
'(GMT+00:00) Lisbon',
'(GMT+00:00) London',
]
const currencies = [
'USD',
'EUR',
'GBP',
'AUD',
'BRL',
'CAD',
'CNY',
'CZK',
'DKK',
'HKD',
'HUF',
'INR',
]
const onSubmit = async () => {
const { valid } = await refVForm.value.validate()
console.log(valid)
if (valid) {
try {
console.log(ImageBase64.value);
await store.dispatch('profileUpdate',{
name: accountDataLocal.value.name,
last_name: accountDataLocal.value.last_name,
phone_no: accountDataLocal.value.phone_no,
image: ImageBase64.value, //ecelData,
})
} catch (error) {
console.error(error)
}
await store.dispatch('adminDetial');
let list = await store.getters.getAdminDetail
console.log('list',list)
accountDataLocal.value.avatarImg = list.image_path
accountDataLocal.value.name = list.name
accountDataLocal.value.last_name = list.last_name
accountDataLocal.value.phone_no = list.phone_no
}
}
const formatPhoneNumber = () => {
// Remove non-numeric characters from the input
const numericValue = accountDataLocal.value.phone_no.replace(/\D/g, '');
// Apply formatting logic
if (numericValue.length <= 10) {
accountDataLocal.value.phone_no = numericValue.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
} else {
// Limit the input to a maximum of 14 characters
const truncatedValue = numericValue.slice(0, 10);
accountDataLocal.value.phone_no = truncatedValue.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
};
</script>
<template>
<VRow>
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex mb-10">
<VSnackbar v-model="getIsTonalSnackbarVisible" :timeout="5000" location="top end" variant="flat"
color="success">
<VIcon
class="ri-success-line success-icon"
/> Profile Update Successfully
</VSnackbar>
<!-- 👉 Avatar -->
<VAvatar
rounded
size="100"
class="me-6"
:image="accountDataLocal.avatarImg"
/>
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-4">
<div class="d-flex flex-wrap gap-4">
<VBtn
color="primary"
@click="refInputEl?.click()"
>
<VIcon
icon="ri-upload-cloud-line"
class="d-sm-none"
/>
<span class="d-none d-sm-block">Upload Logo</span>
</VBtn>
<input
ref="refInputEl"
type="file"
name="file"
accept=".jpeg,.png,.jpg,GIF"
hidden
@input="changeAvatar"
>
</div>
<p class="text-body-1 mb-0">
Allowed JPG, GIF or PNG. Max size of 800K
</p>
</form>
</div>
<!-- 👉 Form -->
<VForm ref="refVForm">
<VRow>
<!-- 👉 First Name -->
<VCol
md="6"
cols="12"
>
<VTextField
v-model="accountDataLocal.name"
label="Name"
:rules="[requiredName]"
:error-messages="errors.name"
/>
</VCol>
<!-- 👉 Last Name -->
<VCol
md="6"
cols="12"
>
<VTextField
v-model="accountDataLocal.last_name"
placeholder="Doe"
label="Last Name"
/>
</VCol>
<!-- 👉 Email -->
<VCol
cols="12"
md="6"
>
<VTextField readonly
v-model="accountDataLocal.email"
label="E-mail"
placeholder="johndoe@gmail.com"
type="email"
:rules="[requiredEmail, emailValidator]"
:error-messages="errors.email"
/>
</VCol>
<!-- 👉 Organization -->
<!-- 👉 Phone -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="accountDataLocal.phone_no"
label="Phone Number"
placeholder="+1 (917) 543-9876"
:rules="[requiredPhone, validUSAPhone]"
:error-messages="errors.phone"
@input="formatPhoneNumber"
max="14"
pattern="^\(\d{3}\) \d{3}-\d{4}$"
/>
</VCol>
<!-- 👉 Form Actions -->
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<VBtn @click.prevent="onSubmit">Save changes</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Confirm Dialog -->
<ConfirmDialog
v-model:isDialogVisible="isConfirmDialogOpen"
confirmation-question="Are you sure you want to deactivate your account?"
confirm-title="Deactivated!"
confirm-msg="Your account has been deactivated successfully."
cancel-title="Cancelled"
cancel-msg="Account Deactivation Cancelled!"
/>
</template>

View File

@@ -0,0 +1,515 @@
<script setup>
import BillingHistoryTable from './BillingHistoryTable.vue'
import mastercard from '@images/icons/payments/mastercard.png'
import visa from '@images/icons/payments/visa.png'
const selectedPaymentMethod = ref('credit-debit-atm-card')
const isPricingPlanDialogVisible = ref(false)
const isConfirmDialogVisible = ref(false)
const isCardEditDialogVisible = ref(false)
const isCardDetailSaveBilling = ref(false)
const creditCards = [
{
name: 'Tom McBride',
number: '5531234567899856',
expiry: '12/23',
isPrimary: true,
type: 'visa',
cvv: '456',
image: mastercard,
},
{
name: 'Mildred Wagner',
number: '4851234567895896',
expiry: '10/27',
isPrimary: false,
type: 'mastercard',
cvv: '123',
image: visa,
},
]
const countryList = [
'United States',
'Canada',
'United Kingdom',
'Australia',
'New Zealand',
'India',
'Russia',
'China',
'Japan',
]
const currentCardDetails = ref()
const openEditCardDialog = cardDetails => {
currentCardDetails.value = cardDetails
isCardEditDialogVisible.value = true
}
const cardNumber = ref(135632156548789)
const cardName = ref('john Doe')
const cardExpiryDate = ref('05/24')
const cardCvv = ref(420)
const resetPaymentForm = () => {
cardNumber.value = 135632156548789
cardName.value = 'john Doe'
cardExpiryDate.value = '05/24'
cardCvv.value = 420
selectedPaymentMethod.value = 'credit-debit-atm-card'
}
</script>
<template>
<VRow>
<!-- 👉 Current Plan -->
<VCol cols="12">
<VCard title="Current Plan">
<VCardText>
<VRow>
<VCol
cols="12"
md="6"
>
<div class="d-flex flex-column gap-y-6">
<div class="d-flex flex-column gap-y-1">
<h6 class="text-h6">
Your Current Plan is Basic
</h6>
<div>
A simple start for everyone
</div>
</div>
<div class="d-flex flex-column gap-y-1">
<h6 class="text-h6">
Active until Dec 09, 2021
</h6>
<div>
We will send you a notification upon Subscription expiration
</div>
</div>
<div class="d-flex flex-column gap-y-1">
<div class="d-flex align-center gap-x-2">
<h6 class="text-h6">
$199 Per Month
</h6>
<VChip
color="primary"
size="small"
>
Popular
</VChip>
</div>
<p class="text-base mb-0">
Standard plan for small to medium businesses
</p>
</div>
</div>
</VCol>
<VCol
cols="12"
md="6"
>
<VAlert
type="warning"
variant="tonal"
title="We need your attention!"
text="Your plan requires update"
/>
<!-- progress -->
<h6 class="d-flex text-h6 justify-space-between mt-6 mb-1">
<div>Days</div>
<div>12 of 30 Days</div>
</h6>
<VProgressLinear
color="primary"
rounded
height="6"
model-value="75"
/>
<p class="text-base mt-1">
18 days remaining until your plan requires update
</p>
</VCol>
<VCol cols="12">
<div class="d-flex flex-wrap gap-4">
<VBtn @click="isPricingPlanDialogVisible = true">
upgrade plan
</VBtn>
<VBtn
color="error"
variant="outlined"
@click="isConfirmDialogVisible = true"
>
Cancel Subscription
</VBtn>
</div>
</VCol>
</VRow>
<!-- 👉 Confirm Dialog -->
<ConfirmDialog
v-model:isDialogVisible="isConfirmDialogVisible"
confirmation-question="Are you sure to cancel your subscription?"
cancel-msg="Unsubscription Cancelled!!"
cancel-title="Cancelled"
confirm-msg="Your subscription cancelled successfully."
confirm-title="Unsubscribed!"
/>
<!-- 👉 plan and pricing dialog -->
<PricingPlanDialog v-model:is-dialog-visible="isPricingPlanDialogVisible" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Payment Methods -->
<VCol cols="12">
<VCard title="Payment Methods">
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<VCol
cols="12"
md="6"
>
<VRow>
<!-- 👉 card type switch -->
<VCol cols="12">
<VRadioGroup
v-model="selectedPaymentMethod"
inline
>
<VRadio
value="credit-debit-atm-card"
label="Credit/Debit/ATM Card"
color="primary"
/>
<VRadio
value="cod-cheque"
label="COD/Cheque"
color="primary"
/>
</VRadioGroup>
</VCol>
<VCol cols="12">
<VRow v-show="selectedPaymentMethod === 'credit-debit-atm-card'">
<!-- 👉 Card Number -->
<VCol cols="12">
<VTextField
v-model="cardNumber"
label="Card Number"
placeholder="1234 1234 1234 1234"
type="number"
/>
</VCol>
<!-- 👉 Name -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="cardName"
label="Name"
placeholder="John Doe"
/>
</VCol>
<!-- 👉 Expiry date -->
<VCol
cols="6"
md="3"
>
<VTextField
v-model="cardExpiryDate"
label="Expiry Date"
placeholder="MM/YY"
/>
</VCol>
<!-- 👉 Cvv code -->
<VCol
cols="6"
md="3"
>
<VTextField
v-model="cardCvv"
type="number"
label="CVV Code"
placeholder="123"
/>
</VCol>
<!-- 👉 Future Billing switch -->
<VCol cols="12">
<VSwitch
v-model="isCardDetailSaveBilling"
density="compact"
label="Save card for future billing?"
/>
</VCol>
<!-- 👉 Payment method action button -->
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<VBtn type="submit">
Save changes
</VBtn>
<VBtn
color="secondary"
variant="outlined"
@click="resetPaymentForm"
>
Reset
</VBtn>
</VCol>
</VRow>
<p
v-show="selectedPaymentMethod === 'cod-cheque'"
class="text-base"
>
Cash on delivery is a mode of payment where you make the payment after the goods/services are received.
</p>
<p
v-show="selectedPaymentMethod === 'cod-cheque'"
class="text-base"
>
You can pay cash or make the payment via debit/credit card directly to the delivery person.
</p>
</VCol>
</VRow>
</VCol>
<!-- 👉 Saved Cards -->
<VCol
cols="12"
md="6"
>
<h6 class="text-h6 mb-6">
My Cards
</h6>
<div class="d-flex flex-column gap-y-4">
<VCard
v-for="card in creditCards"
:key="card.name"
color="rgba(var(--v-theme-on-surface), var(--v-hover-opacity))"
flat
>
<VCardText class="d-flex flex-sm-row flex-column">
<div class="text-no-wrap">
<img :src="card.image">
<div class="d-flex align-center gap-x-4">
<h6 class="text-h6 my-2">
{{ card.name }}
</h6>
<VChip
v-if="card.isPrimary"
color="primary"
size="small"
>
Primary
</VChip>
</div>
<div>**** **** **** {{ card.number.substring(card.number.length - 4) }}</div>
</div>
<VSpacer />
<div class="d-flex flex-column text-sm-end">
<div class="d-flex flex-wrap gap-4 order-sm-0 order-1">
<VBtn
variant="outlined"
@click="openEditCardDialog(card)"
>
Edit
</VBtn>
<VBtn
color="error"
variant="outlined"
>
Delete
</VBtn>
</div>
<div class="my-4 text-body-2 order-sm-1 order-0">
Card expires at {{ card.expiry }}
</div>
</div>
</VCardText>
</VCard>
</div>
<!-- 👉 Add Edit Card Dialog -->
<CardAddEditDialog
v-model:isDialogVisible="isCardEditDialogVisible"
:card-details="currentCardDetails"
/>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Billing Address -->
<VCol cols="12">
<VCard title="Billing Address">
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- 👉 Company name -->
<VCol
cols="12"
md="6"
>
<VTextField
label="Company Name"
placeholder="Themeselection"
/>
</VCol>
<!-- 👉 Billing Email -->
<VCol
cols="12"
md="6"
>
<VTextField
label="Billing Email"
placeholder="themeselection@email.com"
/>
</VCol>
<!-- 👉 Tax ID -->
<VCol
cols="12"
md="6"
>
<VTextField
label="Tax ID"
placeholder="123 123 1233"
/>
</VCol>
<!-- 👉 Vat Number -->
<VCol
cols="12"
md="6"
>
<VTextField
label="VAT Number"
placeholder="121212"
/>
</VCol>
<!-- 👉 Mobile -->
<VCol
cols="12"
md="6"
>
<VTextField
dirty
label="Phone Number"
type="number"
prefix="US (+1)"
placeholder="+1 123 456 7890"
/>
</VCol>
<!-- 👉 Country -->
<VCol
cols="12"
md="6"
>
<VSelect
label="Country"
:items="countryList"
placeholder="Select Country"
/>
</VCol>
<!-- 👉 Billing Address -->
<VCol cols="12">
<VTextField
label="Billing Address"
placeholder="1234 Main St"
/>
</VCol>
<!-- 👉 State -->
<VCol
cols="12"
md="6"
>
<VTextField
label="State"
placeholder="New York"
/>
</VCol>
<!-- 👉 Zip Code -->
<VCol
cols="12"
md="6"
>
<VTextField
label="Zip Code"
type="number"
placeholder="100006"
/>
</VCol>
<!-- 👉 Actions Button -->
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<VBtn type="submit">
Save changes
</VBtn>
<VBtn
type="reset"
color="secondary"
variant="outlined"
>
Reset
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Billing History -->
<VCol cols="12">
<BillingHistoryTable />
</VCol>
</VRow>
</template>
<style lang="scss">
.pricing-dialog {
.pricing-title {
font-size: 1.5rem !important;
}
.v-card {
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
box-shadow: none;
}
}
</style>

View File

@@ -0,0 +1,222 @@
<script setup>
import asana from '@images/icons/brands/asana.png'
import behance from '@images/icons/brands/behance.png'
import dribbble from '@images/icons/brands/dribbble.png'
import facebook from '@images/icons/brands/facebook.png'
import github from '@images/icons/brands/github.png'
import google from '@images/icons/brands/google.png'
import linkedin from '@images/icons/brands/linkedin.png'
import mailchimp from '@images/icons/brands/mailchimp.png'
import slack from '@images/icons/brands/slack.png'
import twitter from '@images/icons/brands/twitter.png'
const connectedAccounts = ref([
{
logo: google,
name: 'Google',
subtitle: 'Calendar and contacts',
connected: true,
},
{
logo: slack,
name: 'Slack',
subtitle: 'Communication',
connected: false,
},
{
logo: github,
name: 'GitHub',
subtitle: 'Manage your Git repositories',
connected: true,
},
{
logo: mailchimp,
name: 'MailChimp',
subtitle: 'Email marketing service',
connected: true,
},
{
logo: asana,
name: 'Asana',
subtitle: 'Task management',
connected: false,
},
])
const socialAccounts = ref([
{
logo: facebook,
name: 'Facebook',
connected: false,
},
{
logo: twitter,
name: 'Twitter',
links: {
username: '@ThemeSelection',
link: 'https://twitter.com/Theme_Selection',
},
connected: true,
},
{
logo: linkedin,
name: 'LinkedIn',
links: {
username: '@ThemeSelection',
link: 'https://in.linkedin.com/company/themeselection',
},
connected: true,
},
{
logo: dribbble,
name: 'Dribbble',
connected: false,
},
{
logo: behance,
name: 'Behance',
connected: false,
},
])
</script>
<template>
<!-- 👉 Connected Accounts -->
<VCard>
<VRow>
<VCol
cols="12"
md="6"
class="pe-md-0 pb-0 pb-md-3"
>
<VCard flat>
<VCardItem class="pb-6">
<VCardTitle class="mb-1">
Connected Accounts
</VCardTitle>
<VCardSubtitle>Display content from your connected accounts on your site</VCardSubtitle>
</VCardItem>
<VCardText>
<VList class="card-list">
<VListItem
v-for="item in connectedAccounts"
:key="item.logo"
>
<template #prepend>
<VAvatar
start
size="32"
rounded
>
<img
:src="item.logo"
height="32"
width="32"
>
</VAvatar>
</template>
<VListItemTitle>
<h6 class="text-h6">
{{ item.name }}
</h6>
</VListItemTitle>
<VListItemSubtitle>
{{ item.subtitle }}
</VListItemSubtitle>
<template #append>
<VListItemAction>
<VSwitch
v-model="item.connected"
density="compact"
class="me-1"
/>
</VListItemAction>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Social Accounts -->
<VCol
cols="12"
md="6"
class="ps-md-0 pt-0 pt-md-3"
>
<VCard flat>
<VCardItem class="pb-6">
<VCardTitle class="mb-1">
Social Accounts
</VCardTitle>
<VCardSubtitle>Display content from social accounts on your site</VCardSubtitle>
</VCardItem>
<VCardText>
<VList class="card-list">
<VListItem
v-for="item in socialAccounts"
:key="item.logo"
>
<template #prepend>
<VAvatar
start
rounded="0"
size="32"
>
<img
:src="item.logo"
height="32"
width="32"
>
</VAvatar>
</template>
<VListItemTitle>
<h6 class="text-h6">
{{ item.name }}
</h6>
</VListItemTitle>
<VListItemSubtitle
v-if="item.links?.link"
tag="a"
target="_blank"
rel="noopener noreferrer"
:href="item.links?.link"
style="opacity: 1;"
>
{{ item.links?.username }}
</VListItemSubtitle>
<VListItemSubtitle v-else>
Not Connected
</VListItemSubtitle>
<template #append>
<VListItemAction>
<VBtn
icon
variant="outlined"
:color="item.connected ? 'error' : 'secondary'"
rounded
>
<VIcon :icon="item.connected ? 'ri-delete-bin-line' : 'ri-link' " />
</VBtn>
</VListItemAction>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</VCol>
</VRow>
</VCard>
</template>
<style lang="scss">
.card-list{
--v-card-list-gap: 1rem;
}
</style>

View File

@@ -0,0 +1,116 @@
<script setup>
const recentDevices = ref([
{
type: 'New for you',
email: true,
browser: true,
app: true,
},
{
type: 'Account activity',
email: true,
browser: true,
app: true,
},
{
type: 'A new browser used to sign in',
email: true,
browser: true,
app: false,
},
{
type: 'A new device is linked',
email: true,
browser: false,
app: false,
},
])
const selectedNotification = ref('Only when I\'m online')
</script>
<template>
<VCard>
<VCardItem>
<VCardTitle>Recent Devices</VCardTitle>
<VCardSubtitle>
We need permission from your browser to show notifications.
<a href="javascript:void(0)">Request Permission</a>
</VCardSubtitle>
</VCardItem>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">
Type
</th>
<th scope="col">
EMAIL
</th>
<th scope="col">
BROWSER
</th>
<th scope="col">
App
</th>
</tr>
</thead>
<tbody>
<tr
v-for="device in recentDevices"
:key="device.type"
>
<td class="text-high-emphasis">
{{ device.type }}
</td>
<td>
<VCheckbox v-model="device.email" />
</td>
<td>
<VCheckbox v-model="device.browser" />
</td>
<td>
<VCheckbox v-model="device.app" />
</td>
</tr>
</tbody>
</VTable>
<VDivider />
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="text-base font-weight-medium mb-6">
When should we send you notifications?
</div>
<VRow>
<VCol
cols="12"
sm="6"
>
<VSelect
v-model="selectedNotification"
mandatory
:items="['Only when I\'m online', 'Anytime']"
class="mb-6"
/>
</VCol>
</VRow>
<div class="d-flex flex-wrap gap-4">
<VBtn type="submit">
Save Changes
</VBtn>
<VBtn
color="secondary"
variant="outlined"
type="reset"
>
Reset
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,429 @@
<script setup>
import sittingGirlWithLaptopDark from '@images/illustrations/sitting-girl-with-laptop-dark.png';
import sittingGirlWithLaptopLight from '@images/illustrations/sitting-girl-with-laptop-light.png';
import { useStore } from 'vuex';
const store = useStore();
const refVForm = ref(null)
const isCurrentPasswordVisible = ref(false)
const isNewPasswordVisible = ref(false)
const isConfirmPasswordVisible = ref(false)
const currentPassword = ref('')
const newPassword = ref('')
const confirmPassword = ref('')
const passwordConfirmationTarget = computed(() => newPassword.value);
const passwordRequirements = [
'Minimum 8 characters long - the more, the better',
'At least one lowercase character',
'At least one number, symbol, or whitespace character',
]
const serverKeys = [
{
name: 'Server Key 1',
key: '23eaf7f0-f4f7-495e-8b86-fad3261282ac',
createdOn: '28 Apr 2021, 18:20 GTM+4:10',
permission: 'Full Access',
},
{
name: 'Server Key 2',
key: 'bb98e571-a2e2-4de8-90a9-2e231b5e99',
createdOn: '12 Feb 2021, 10:30 GTM+2:30',
permission: 'Read Only',
},
{
name: 'Server Key 3',
key: '2e915e59-3105-47f2-8838-6e46bf83b711',
createdOn: '28 Dec 2020, 12:21 GTM+4:10',
permission: 'Full Access',
},
]
const recentDevicesHeaders = [
{
title: 'BROWSER',
key: 'browser',
},
{
title: 'DEVICE',
key: 'device',
},
{
title: 'LOCATION',
key: 'location',
},
{
title: 'RECENT ACTIVITY',
key: 'recentActivity',
},
]
const recentDevices = [
{
browser: 'Chrome on Windows',
device: 'HP Spectre 360',
location: 'New York, NY',
recentActivity: '28 Apr 2022, 18:20',
deviceIcon: {
icon: 'ri-macbook-line',
color: 'primary',
},
},
{
browser: 'Chrome on iPhone',
device: 'iPhone 12x',
location: 'Los Angeles, CA',
recentActivity: '20 Apr 2022, 10:20',
deviceIcon: {
icon: 'ri-android-line',
color: 'error',
},
},
{
browser: 'Chrome on Android',
device: 'Oneplus 9 Pro',
location: 'San Francisco, CA',
recentActivity: '16 Apr 2022, 04:20',
deviceIcon: {
icon: 'ri-smartphone-line',
color: 'success',
},
},
{
browser: 'Chrome on macOS',
device: 'Apple iMac',
location: 'New York, NY',
recentActivity: '28 Apr 2022, 18:20',
deviceIcon: {
icon: 'ri-mac-line',
color: 'secondary',
},
},
{
browser: 'Chrome on Windows',
device: 'HP Spectre 360',
location: 'Los Angeles, CA',
recentActivity: '20 Apr 2022, 10:20',
deviceIcon: {
icon: 'ri-macbook-line',
color: 'primary',
},
},
{
browser: 'Chrome on Android',
device: 'Oneplus 9 Pro',
location: 'San Francisco, CA',
recentActivity: '16 Apr 2022, 04:20',
deviceIcon: {
icon: 'ri-android-line',
color: 'success',
},
},
]
const save = async () => {
const { valid } = await refVForm.value.validate()
console.log(valid)
if (valid) {
try {
await store.dispatch('adminPasswordUpadate',{
password: currentPassword.value,
new_password : newPassword.value,
confirm_password : confirmPassword.value,
})
} catch (error) {
console.error(error)
}
currentPassword.value = null
newPassword.value = null
confirmPassword.value = null
}
}
const sittingGirlImg = useGenerateImageVariant(sittingGirlWithLaptopLight, sittingGirlWithLaptopDark)
const isOneTimePasswordDialogVisible = ref(false)
</script>
<template>
<VRow>
<!-- SECTION: Change Password -->
<VCol cols="12">
<VCard>
<VCardItem class="pb-6">
<VCardTitle>Change Password</VCardTitle>
</VCardItem>
<VForm ref="refVForm">
<VCardText class="pt-0">
<!-- 👉 Current Password -->
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 current password -->
<VTextField
v-model="currentPassword"
:type="isCurrentPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isCurrentPasswordVisible ? 'ri-eye-off-line' : 'ri-eye-line'"
autocomplete="on"
label="Current Password"
placeholder="············"
@click:append-inner="isCurrentPasswordVisible = !isCurrentPasswordVisible"
/>
</VCol>
</VRow>
<!-- 👉 New Password -->
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 new password -->
<VTextField
v-model="newPassword"
:type="isNewPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isNewPasswordVisible ? 'ri-eye-off-line' : 'ri-eye-line'"
label="New Password"
autocomplete="on"
placeholder="············"
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
:rules="[passwordValidator]"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 confirm password -->
<VTextField
v-model="confirmPassword"
:type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isConfirmPasswordVisible ? 'ri-eye-off-line' : 'ri-eye-line'"
autocomplete="on"
label="Confirm New Password"
placeholder="············"
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
:rules="[value => confirmedValidator(value, passwordConfirmationTarget)]"
/>
</VCol>
</VRow>
</VCardText>
<!-- 👉 Password Requirements -->
<VCardText>
<h6 class="text-h6 text-medium-emphasis mt-1">
Password Requirements:
</h6>
<VList>
<VListItem
v-for="(item, index) in passwordRequirements"
:key="index"
class="px-0"
>
<template #prepend>
<VIcon
size="8"
icon="ri-circle-fill"
color="rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))"
/>
</template>
<VListItemTitle class="text-medium-emphasis text-wrap">
{{ item }}
</VListItemTitle>
</VListItem>
</VList>
<!-- 👉 Action Buttons -->
<div class="d-flex flex-wrap gap-4">
<VBtn @click="save">Save changes</VBtn>
<!-- <VBtn
type="reset"
color="secondary"
variant="outlined"
>
Reset
</VBtn>-->
</div>
</VCardText>
</VForm>
</VCard>
</VCol>
<!-- !SECTION -->
<!-- SECTION Two-steps verification -->
<VCol cols="12" style="display: none;">
<VCard>
<VCardItem class="pb-6">
<VCardTitle>Two-steps verification</VCardTitle>
</VCardItem>
<VCardText>
<p>
Two factor authentication is not enabled yet.
</p>
<p class="mb-6">
Two-factor authentication adds an additional layer of security to your account by requiring more than just a password to log in.
<a
href="javascript:void(0)"
class="text-decoration-none"
>Learn more.</a>
</p>
<VBtn @click="isOneTimePasswordDialogVisible = true">
Enable 2FA
</VBtn>
</VCardText>
</VCard>
</VCol>
<!-- !SECTION -->
<VCol cols="12" style="display: none;">
<!-- SECTION: Create an API key -->
<VCard title="Create an API key">
<VRow>
<!-- 👉 Choose API Key -->
<VCol
cols="12"
md="5"
order-md="0"
order="1"
>
<VCardText class="pt-7">
<VForm @submit.prevent="() => {}">
<!-- 👉 Choose API Key -->
<VSelect
label="Choose the API key type you want to create"
placeholder="Select API key type"
:items="['Full Control', 'Modify', 'Read & Execute', 'List Folder Contents', 'Read Only', 'Read & Write']"
/>
<!-- 👉 Name the API Key -->
<VTextField
label="Name the API key"
placeholder="Name the API key"
class="my-5"
/>
<!-- 👉 Create Key Button -->
<VBtn
type="submit"
block
>
Create Key
</VBtn>
</VForm>
</VCardText>
</VCol>
<!-- 👉 Lady image -->
<VCol
cols="12"
md="7"
order="0"
order-md="1"
class="d-flex flex-column justify-center align-center"
>
<VImg
:src="sittingGirlImg"
:width="310"
:style="$vuetify.display.smAndDown ? '' : 'position: absolute; bottom: 0;'"
/>
</VCol>
</VRow>
</VCard>
<!-- !SECTION -->
</VCol>
<VCol cols="12" style="display: none;">
<!-- SECTION: API Keys List -->
<VCard>
<VCardItem class="pb-4">
<VCardTitle>API Key List &amp; Access</VCardTitle>
</VCardItem>
<VCardText>
<p class="mb-6">
An API key is a simple encrypted string that identifies an application without any principal. They are useful for accessing public data anonymously, and are used to associate API requests with your project for quota and billing.
</p>
<!-- 👉 Server Status -->
<div class="d-flex flex-column gap-y-6">
<div
v-for="serverKey in serverKeys"
:key="serverKey.key"
class="bg-var-theme-background pa-4"
>
<div class="d-flex align-center flex-wrap mb-2 gap-x-3">
<h6 class="text-h6">
{{ serverKey.name }}
</h6>
<VChip
color="primary"
size="small"
>
{{ serverKey.permission }}
</VChip>
</div>
<h6 class="text-h6 d-flex gap-x-3 text-medium-emphasis align-center mb-2">
{{ serverKey.key }}
<VIcon
:size="20"
icon="ri-file-copy-line"
class="cursor-pointer"
/>
</h6>
<div class="text-disabled">
Created on {{ serverKey.createdOn }}
</div>
</div>
</div>
</VCardText>
</VCard>
<!-- !SECTION -->
</VCol>
<!-- SECTION Recent Devices -->
<VCol cols="12" style="display: none;">
<!-- 👉 Table -->
<VCard
title="Recent Devices"
class="recentDeviceCard"
>
<VDataTable
:headers="recentDevicesHeaders"
:items="recentDevices"
hide-default-footer
class="text-no-wrap"
>
<template #item.browser="{ item }">
<div class="d-flex gap-x-3">
<VIcon
size="20"
:icon="item.deviceIcon.icon"
:color="item.deviceIcon.color"
/>
<h6 class="text-h6">
{{ item.browser }}
</h6>
</div>
</template>
<!-- TODO Refactor this after vuetify provides proper solution for removing default footer -->
<template #bottom />
</VDataTable>
</VCard>
</VCol>
<!-- !SECTION -->
</VRow>
<!-- SECTION Enable One time password -->
<TwoFactorAuthDialog v-model:isDialogVisible="isOneTimePasswordDialogVisible" style="display: none;"/>
<!-- !SECTION -->
</template>

View File

@@ -0,0 +1,407 @@
<script setup>
const searchQuery = ref('')
const selectedStatus = ref()
const selectedRows = ref([])
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const updateOptions = options => {
page.value = options.page
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
// 👉 headers
const headers = [
{
title: '#',
key: 'id',
},
{
title: 'Trending',
key: 'trending',
sortable: false,
},
{
title: 'Client',
key: 'client',
},
{
title: 'Total',
key: 'total',
},
{
title: 'Issued Date',
key: 'date',
},
{
title: 'Balance',
key: 'balance',
},
{
title: 'Action',
key: 'actions',
sortable: false,
},
]
const {
data: invoiceData,
execute: fetchInvoices,
} = await useApi(createUrl('/apps/invoice', {
query: {
q: searchQuery,
status: selectedStatus,
itemsPerPage,
page,
sortBy,
orderBy,
},
}))
const invoices = computed(() => invoiceData.value.invoices)
const totalInvoices = computed(() => invoiceData.value.totalInvoices)
// 👉 Invoice balance variant resolver
const resolveInvoiceBalanceVariant = (balance, total) => {
if (balance === total)
return {
status: 'Unpaid',
chip: { color: 'error' },
}
if (balance === 0)
return {
status: 'Paid',
chip: { color: 'success' },
}
return {
status: balance,
chip: { variant: 'text' },
}
}
const resolveInvoiceStatusVariantAndIcon = status => {
if (status === 'Partial Payment')
return {
variant: 'warning',
icon: 'ri-line-chart-line',
}
if (status === 'Paid')
return {
variant: 'success',
icon: 'ri-check-line',
}
if (status === 'Downloaded')
return {
variant: 'info',
icon: 'ri-arrow-down-line',
}
if (status === 'Draft')
return {
variant: 'secondary',
icon: 'ri-save-line',
}
if (status === 'Sent')
return {
variant: 'primary',
icon: 'ri-mail-line',
}
if (status === 'Past Due')
return {
variant: 'error',
icon: 'ri-error-warning-line',
}
return {
variant: 'secondary',
icon: 'ri-close-line',
}
}
const computedMoreList = computed(() => {
return paramId => [
{
title: 'Download',
value: 'download',
prependIcon: 'ri-download-line',
},
{
title: 'Edit',
value: 'edit',
prependIcon: 'ri-pencil-line',
to: {
name: 'apps-invoice-edit-id',
params: { id: paramId },
},
},
{
title: 'Duplicate',
value: 'duplicate',
prependIcon: 'ri-stack-line',
},
]
})
const deleteInvoice = async id => {
await $api(`/apps/invoice/${ id }`, { method: 'DELETE' })
fetchInvoices()
}
</script>
<template>
<section v-if="invoices">
<!-- 👉 Invoice Filters -->
<VCard id="invoice-list">
<VCardText class="d-flex align-center flex-wrap gap-4">
<!-- 👉 Actions -->
<div class="me-3">
<!-- 👉 Create invoice -->
<VBtn
prepend-icon="ri-add-line"
:to="{ name: 'apps-invoice-add' }"
>
Create invoice
</VBtn>
</div>
<VSpacer />
<div class="d-flex align-center flex-wrap gap-4">
<!-- 👉 Search -->
<div class="invoice-list-search">
<VTextField
v-model="searchQuery"
density="compact"
placeholder="Search Invoice"
/>
</div>
<!-- 👉 Filter Invoice -->
<VSelect
v-model="selectedStatus"
density="compact"
placeholder="Select Status"
clearable
clear-icon="ri-close-line"
:items="['Downloaded', 'Draft', 'Sent', 'Paid', 'Partial Payment', 'Past Due']"
style="inline-size: 12rem;"
/>
</div>
</VCardText>
<!-- SECTION Datatable -->
<VDataTableServer
v-model="selectedRows"
v-model:items-per-page="itemsPerPage"
v-model:page="page"
show-select
:items-length="totalInvoices"
:headers="headers"
:items="invoices"
item-value="id"
class="text-no-wrap billing-history-table"
@update:options="updateOptions"
>
<!-- id -->
<template #item.id="{ item }">
<RouterLink :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
#{{ item.id }}
</RouterLink>
</template>
<!-- trending -->
<template #item.trending="{ item }">
<VTooltip>
<template #activator="{ props }">
<VAvatar
:size="28"
v-bind="props"
:color="resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).variant"
variant="tonal"
>
<VIcon
:size="16"
:icon="resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).icon"
/>
</VAvatar>
</template>
<p class="mb-0">
{{ item.invoiceStatus }}
</p>
<p class="mb-0">
Balance: {{ item.balance }}
</p>
<p class="mb-0">
Due date: {{ item.dueDate }}
</p>
</VTooltip>
</template>
<!-- client -->
<template #item.client="{ item }">
<div class="d-flex align-center">
<VAvatar
size="34"
:color="!item.avatar.length ? resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).variant : undefined"
:variant="!item.avatar.length ? 'tonal' : undefined"
class="me-3"
>
<VImg
v-if="item.avatar.length"
:src="item.avatar"
/>
<span v-else>{{ avatarText(item.client.name) }}</span>
</VAvatar>
<div class="d-flex flex-column">
<RouterLink
:to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }"
class="client-title font-weight-medium mb-0"
>
{{ item.client.name }}
</RouterLink>
<div class="text-body-2">
{{ item.client.companyEmail }}
</div>
</div>
</div>
</template>
<!-- Total -->
<template #item.total="{ item }">
${{ item.total }}
</template>
<!-- Issued Date -->
<template #item.date="{ item }">
{{ item.issuedDate }}
</template>
<!-- Balance -->
<template #item.balance="{ item }">
<VChip
v-if="typeof ((resolveInvoiceBalanceVariant(item.balance, item.total)).status) === 'string'"
:color="resolveInvoiceBalanceVariant(item.balance, item.total).chip.color"
size="small"
>
{{ (resolveInvoiceBalanceVariant(item.balance, item.total)).status }}
</VChip>
<div
v-else
class="text-body-1 text-high-emphasis"
>
{{ Number((resolveInvoiceBalanceVariant(item.balance, item.total)).status) > 0 ? `$${(resolveInvoiceBalanceVariant(item.balance, item.total)).status}` : `-$${Math.abs(Number((resolveInvoiceBalanceVariant(item.balance, item.total)).status))}` }}
</div>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn
size="small"
icon="ri-delete-bin-7-line"
@click="deleteInvoice(item.id)"
/>
<IconBtn
:to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }"
size="small"
>
<VIcon icon="ri-eye-line" />
</IconBtn>
<MoreBtn
:menu-list="computedMoreList(item.id)"
size="small"
item-props
/>
</template>
<!-- Pagination -->
<template #bottom>
<VDivider />
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
Rows Per Page:
<VSelect
v-model="itemsPerPage"
class="per-page-select"
variant="plain"
:items="[10, 20, 25, 50, 100]"
/>
</div>
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
{{ paginationMeta({ page, itemsPerPage }, totalInvoices) }}
</p>
<div class="d-flex gap-x-2 align-center me-2">
<VBtn
class="flip-in-rtl"
icon="ri-arrow-left-s-line"
variant="text"
density="comfortable"
color="high-emphasis"
:disabled="page <= 1"
@click="page <= 1 ? page = 1 : page--"
/>
<VBtn
class="flip-in-rtl"
icon="ri-arrow-right-s-line"
density="comfortable"
variant="text"
color="high-emphasis"
:disabled="page >= Math.ceil(totalInvoices / itemsPerPage)"
@click="page >= Math.ceil(totalInvoices / itemsPerPage) ? page = Math.ceil(totalInvoices / itemsPerPage) : page++ "
/>
</div>
</div>
</template>
</VDataTableServer>
<!-- !SECTION -->
</VCard>
</section>
</template>
<style lang="scss">
#invoice-list {
.invoice-list-actions {
inline-size: 8rem;
}
.invoice-list-search {
inline-size: 12rem;
}
}
.billing-history-table{
&.v-table--density-default{
.v-table__wrapper{
table{
tbody{
tr{
td{
block-size: 60px;
}
}
}
}
}
}
}
</style>
<style lang="scss" scoped>
.client-title{
&:not(:hover){
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)) !important;
}
}
</style>

View File

@@ -0,0 +1,182 @@
<script setup>
import axios from 'axios';
import { ref } from 'vue';
import { useStore } from 'vuex';
import { ADMIN_AUTH_CALENDLY_API, ADMIN_AUTH_CALENDLY_EVENTS_API, ADMIN_AUTH_CALENDLY_RESET_EVENTS_API, ADMIN_AUTH_CALENDLY_SET_EVENTS_API } from '../../../constants.js';
const store = useStore();
const errors = ref({ name: undefined });
const accountData = { name: '' };
const refVForm = ref();
const isConfirmDialogOpen = ref(false);
const accountDataLocal = ref(structuredClone(accountData));
const getIsTonalSnackbarVisible = ref(false);
const events = ref([]);
const selectedEventIndex = ref(null);
onMounted(async () => {
await getEvents();
});
// Fetch events after form submission
const onSubmit = async () => {
const { valid } = await refVForm.value.validate();
console.log(valid);
if (valid) {
try {
await store.dispatch('updateIsLoading', true);
// First API call for authorization
const response = await axios.post(ADMIN_AUTH_CALENDLY_API);
const authUrl = response.data.url; // Assuming the URL is returned in the response as `url`
await store.dispatch('updateIsLoading', false);
store.dispatch('updateSuccessMsg', true);
store.dispatch('updateIsTonalSnackbarMsg', 'Connection Request Sent!');
// Open the auth URL in a new tab
if (authUrl) {
window.open(authUrl, '_blank'); // Open the URL in a new tab
}
await getEvents();
} catch (error) {
await store.dispatch('updateIsLoading', false);
store.dispatch('updateErrorMessage', error);
console.error("Error fetching events:", error);
}
}
};
const onReset = async () => {
const { valid } = await refVForm.value.validate();
console.log(valid);
if (valid) {
try {
await store.dispatch('updateIsLoading', true);
// First API call for authorization
const response = await axios.post(ADMIN_AUTH_CALENDLY_RESET_EVENTS_API);
const authUrl = response.data.url; // Assuming the URL is returned in the response as `url`
await store.dispatch('updateIsLoading', false);
store.dispatch('updateSuccessMsg', true);
store.dispatch('updateIsTonalSnackbarMsg', 'Event URI reset!');
} catch (error) {
await store.dispatch('updateIsLoading', false);
store.dispatch('updateErrorMessage', error);
console.error("Error fetching events:", error);
}
}
};
const getEvents = async() => {
events.value = []
// Fetch events after successful authorization
const respEv = await axios.post(ADMIN_AUTH_CALENDLY_EVENTS_API);
console.log('Events Response: ', respEv.data);
// Check if the API returned events and update the reactive events array
if (respEv.data.events && Array.isArray(respEv.data.events)) {
events.value.push(...respEv.data.events); // Assign events to the ref
} else {
console.warn("No events found in the response.");
}
console.log('events.value',events.value)
};
const eventClick = async (url,index) => {
console.log('url',url)
if(selectedEventIndex.value != index){
await store.dispatch('updateIsLoading', true);
try {
// Define your payload data
const payload = {
// Add any necessary data here
url: url,
// You can add more key-value pairs as needed
};
// Make the POST request with the URL and payload
const response = await axios.post(ADMIN_AUTH_CALENDLY_SET_EVENTS_API, payload);
// Log the response from the server
console.log('Response:', response.data);
await store.dispatch('updateIsLoading', false);
store.dispatch('updateSuccessMsg', true);
store.dispatch('updateIsTonalSnackbarMsg', response.data.message);
selectedEventIndex.value = index;
// Handle the response as needed (e.g., updating state, showing notifications, etc.)
} catch (error) {
await store.dispatch('updateIsLoading', false);
store.dispatch('updateErrorMessage', error);
console.error('Error making POST request:', error);
}
}
};
</script>
<template>
<VRow>
<VCol cols="12">
<VCard>
<VCardText>
<!-- 👉 Form -->
<VForm ref="refVForm">
<VRow>
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<p>To connect the calendly you have to click on "Connect" button then it will authorize to get the timeslots.</p>
</VCol>
<VCol
cols="2"
class="d-flex flex-wrap gap-4"
>
<VBtn @click.prevent="onSubmit" block>Connect</VBtn>
</VCol>
<VCol
cols="2"
class="d-flex flex-wrap gap-4"
>
<VBtn @click.prevent="onReset" block>Reset</VBtn>
</VCol>
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<v-list>
<v-list-item v-for="(event, index) in events"
:key="index"
:class="{'selected': selectedEventIndex === index}" style="border:1px solid silver;border-radius: 5px;cursor: pointer;">
<v-list-item-content @click="eventClick(event.uri,index)">
<v-list-item-title>{{ event.slug }}</v-list-item-title>
<v-list-item-subtitle>{{ event.type }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Confirm Dialog -->
<ConfirmDialog
v-model:isDialogVisible="isConfirmDialogOpen"
confirmation-question="Are you sure you want to deactivate your account?"
confirm-title="Deactivated!"
confirm-msg="Your account has been deactivated successfully."
cancel-title="Cancelled"
cancel-msg="Account Deactivation Cancelled!"
/>
</template>
<style>
.selected {
border: 2px solid green !important; /* Change border to green when selected */
}
</style>

View File

@@ -0,0 +1,504 @@
<script setup>
import { useStore } from 'vuex';
const store = useStore();
const accountData = {
favicon:'',
domain_name: '',
footer_text: '',
header_title: '',
logo: '',
plan_description: '',
plan_main_title:'',
id:'',
}
const refVForm = ref(null)
const refInputEl = ref()
const refInputElFavicon = ref()
const isConfirmDialogOpen = ref(false)
const accountDataLocal = ref(structuredClone(accountData))
const isAccountDeactivated = ref(false)
const validateAccountDeactivation = [v => !!v || 'Please confirm account deactivation']
const imageBase64 = ref(null)
const faviconBase64 = ref(null)
const resetForm = () => {
accountDataLocal.value = structuredClone(accountData)
}
const resetFormFavicon = () => {
accountDataLocal.value = structuredClone(accountData)
}
const changeAvatar = file => {
const fileReader = new FileReader()
const { files } = file.target
if (files && files.length) {
fileReader.readAsDataURL(files[0])
fileReader.onload = () => {
if (typeof fileReader.result === 'string') {
accountDataLocal.value.logo = fileReader.result
}
imageBase64.value = fileReader.result.split(',')[1]
}
}
}
const changeAvatarFavicon = file => {
const fileReader = new FileReader()
const { files } = file.target
if (files && files.length) {
fileReader.readAsDataURL(files[0])
fileReader.onload = () => {
if (typeof fileReader.result === 'string') {
accountDataLocal.value.favicon = fileReader.result
}
faviconBase64.value = fileReader.result.split(',')[1]
}
}
}
// reset avatar image
const resetAvatar = () => {
accountDataLocal.value.logo = accountData.logo
}
const resetAvatarFavicon = () => {
accountDataLocal.value.favicon = accountData.favicon
}
const timezones = [
'(GMT-11:00) International Date Line West',
'(GMT-11:00) Midway Island',
'(GMT-10:00) Hawaii',
'(GMT-09:00) Alaska',
'(GMT-08:00) Pacific Time (US & Canada)',
'(GMT-08:00) Tijuana',
'(GMT-07:00) Arizona',
'(GMT-07:00) Chihuahua',
'(GMT-07:00) La Paz',
'(GMT-07:00) Mazatlan',
'(GMT-07:00) Mountain Time (US & Canada)',
'(GMT-06:00) Central America',
'(GMT-06:00) Central Time (US & Canada)',
'(GMT-06:00) Guadalajara',
'(GMT-06:00) Mexico City',
'(GMT-06:00) Monterrey',
'(GMT-06:00) Saskatchewan',
'(GMT-05:00) Bogota',
'(GMT-05:00) Eastern Time (US & Canada)',
'(GMT-05:00) Indiana (East)',
'(GMT-05:00) Lima',
'(GMT-05:00) Quito',
'(GMT-04:00) Atlantic Time (Canada)',
'(GMT-04:00) Caracas',
'(GMT-04:00) La Paz',
'(GMT-04:00) Santiago',
'(GMT-03:30) Newfoundland',
'(GMT-03:00) Brasilia',
'(GMT-03:00) Buenos Aires',
'(GMT-03:00) Georgetown',
'(GMT-03:00) Greenland',
'(GMT-02:00) Mid-Atlantic',
'(GMT-01:00) Azores',
'(GMT-01:00) Cape Verde Is.',
'(GMT+00:00) Casablanca',
'(GMT+00:00) Dublin',
'(GMT+00:00) Edinburgh',
'(GMT+00:00) Lisbon',
'(GMT+00:00) London',
]
const currencies = [
'USD',
'EUR',
'GBP',
'AUD',
'BRL',
'CAD',
'CNY',
'CZK',
'DKK',
'HKD',
'HUF',
'INR',
]
onMounted(async () => {
await store.dispatch('siteSetting');
let list = await store.getters.getSiteSetting
console.log('list',list)
accountDataLocal.value.logo = list.logo
accountDataLocal.value.favicon = list.favicon
accountDataLocal.value.plan_main_title = list.plan_main_title
accountDataLocal.value.plan_description = list.plan_description
accountDataLocal.value.header_title = list.header_title
accountDataLocal.value.footer_text = list.footer_text
accountDataLocal.value.domain_name = list.domain_name
accountDataLocal.value.id = list.id
});
const convertImageToBase64 = (event) => {
const file = event.target.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
imageBase64.value = reader.result.split(',')[1]
}
}
const save = async () => {
const { valid } = await refVForm.value.validate()
console.log(valid)
if (valid) {
try {
console.log(imageBase64.value)
await store.dispatch('siteSettingUpdate',{
plan_main_title: accountDataLocal.value.plan_main_title,
plan_description: accountDataLocal.value.plan_description,
header_title: accountDataLocal.value.header_title,
footer_text: accountDataLocal.value.footer_text,
domain_name: accountDataLocal.value.domain_name,
id: accountDataLocal.value.id,
logo:imageBase64.value, //ecelData,
favicon:faviconBase64.value//imageBase64.value
})
} catch (error) {
console.error(error)
}
await store.dispatch('siteSetting');
let list = await store.getters.getSiteSetting
console.log('list',list)
accountDataLocal.value.logo = list.logo
accountDataLocal.value.favicon = list.favicon
accountDataLocal.value.plan_main_title = list.plan_main_title
accountDataLocal.value.plan_description = list.plan_description
accountDataLocal.value.header_title = list.header_title
accountDataLocal.value.footer_text = list.footer_text
accountDataLocal.value.domain_name = list.domain_name
}
}
</script>
<template>
<VRow>
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex mb-10">
<!-- 👉 Avatar -->
<VAvatar
rounded
size="70"
class="me-6"
:image="accountDataLocal.logo"
/>
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-4">
<div class="d-flex flex-wrap gap-4">
<VBtn
color="primary"
@click="refInputEl?.click()"
>
<VIcon
icon="ri-upload-cloud-line"
class="d-sm-none"
/>
<span class="d-none d-sm-block">Upload Logo</span>
</VBtn>
<input
ref="refInputEl"
type="file"
name="file"
accept=".jpeg,.png,.webp,.jpg,GIF"
hidden
@input="changeAvatar"
>
<VBtn
type="reset"
color="error"
variant="outlined"
@click="resetAvatar"
>
<span class="d-none d-sm-block">Reset</span>
<VIcon
icon="ri-refresh-line"
class="d-sm-none"
/>
</VBtn>
</div>
<p class="text-body-1 mb-0">
Allowed JPG, GIF ,webp or PNG. Max size of 800K
</p>
</form>
</div>
<div class="d-flex mb-10">
<!-- 👉 Avatar -->
<VAvatar
rounded
size="70"
class="me-6"
:image="accountDataLocal.favicon"
/>
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-4">
<div class="d-flex flex-wrap gap-4">
<VBtn
color="primary"
@click="refInputElFavicon?.click()"
>
<VIcon
icon="ri-upload-cloud-line"
class="d-sm-none"
/>
<span class="d-none d-sm-block">Upload Favicon</span>
</VBtn>
<input
ref="refInputElFavicon"
type="file"
name="file"
accept=".jpeg,.png,.webp,.jpg,GIF"
hidden
@input="changeAvatarFavicon"
>
<VBtn
type="reset"
color="error"
variant="outlined"
@click="resetAvatarFavicon"
>
<span class="d-none d-sm-block">Reset</span>
<VIcon
icon="ri-refresh-line"
class="d-sm-none"
/>
</VBtn>
</div>
<p class="text-body-1 mb-0">
Allowed JPG,webp, GIF or PNG. Max size of 800K
</p>
</form>
</div>
<!-- 👉 Form -->
<VForm ref="refVForm" >
<VRow>
<!-- 👉 First Name -->
<VCol
md="6"
cols="12"
>
<VTextField
v-model="accountDataLocal.plan_main_title"
placeholder="Plan Main Page Title"
label="Plan Page Main Title"
:rules="[requiredValidator]"
/>
</VCol>
<!-- 👉 Last Name -->
<VCol
md="6"
cols="12"
>
<VTextField
v-model="accountDataLocal.plan_description"
placeholder="Doe"
label="Plan Description"
:rules="[requiredValidator]"
/>
</VCol>
<!-- 👉 Header Title -->
<VCol
cols="12"
md="6"
>
<VTextField
v-model="accountDataLocal.header_title"
label="Header Title"
placeholder="Header Title"
:rules="[requiredValidator]"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="accountDataLocal.domain_name"
label="Domain Name"
placeholder="Domain Name"
:rules="[requiredValidator]"
/>
</VCol>
<!-- 👉 Email -->
<VCol
cols="12"
md="6"
>
<VTextarea
v-model="accountDataLocal.footer_text"
label="Footer Text"
placeholder="Footer Text"
:rules="[requiredValidator]"
/>
</VCol>
<!-- 👉 Phone -->
<!-- 👉 Address -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VTextField
v-model="accountDataLocal.address"
label="Address"
placeholder="123 Main St, New York, NY 10001"
/>
</VCol>
<!-- 👉 State -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VTextField
v-model="accountDataLocal.state"
label="State"
placeholder="New York"
/>
</VCol>
<!-- 👉 Zip Code -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VTextField
v-model="accountDataLocal.zip"
label="Zip Code"
placeholder="10001"
/>
</VCol>
<!-- 👉 Country -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VSelect
v-model="accountDataLocal.country"
multiple
chips
closable-chips
label="Country"
:items="['USA', 'Canada', 'UK', 'India', 'Australia']"
placeholder="Select Country"
/>
</VCol>
<!-- 👉 Language -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VSelect
v-model="accountDataLocal.language"
label="Language"
multiple
chips
closable-chips
placeholder="Select Language"
:items="['English', 'Spanish', 'Arabic', 'Hindi', 'Urdu']"
/>
</VCol>
<!-- 👉 Timezone -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VSelect
v-model="accountDataLocal.timezone"
label="Timezone"
placeholder="Select Timezone"
:items="timezones"
:menu-props="{ maxHeight: 200 }"
/>
</VCol>
<!-- 👉 Currency -->
<VCol
cols="12"
md="6"
style="display: none;"
>
<VSelect
v-model="accountDataLocal.currency"
label="Currency"
placeholder="Select Currency"
:items="currencies"
:menu-props="{ maxHeight: 200 }"
/>
</VCol>
<!-- 👉 Form Actions -->
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<VBtn @click="save">Save changes</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" style="display: none;">
<!-- 👉 Delete Account -->
<VCard title="Delete Account">
<VCardText>
<!-- 👉 Checkbox and Button -->
<div>
<VCheckbox
v-model="isAccountDeactivated"
:rules="validateAccountDeactivation"
label="I confirm my account deactivation"
/>
</div>
<VBtn
:disabled="!isAccountDeactivated"
color="error"
class="mt-3"
@click="isConfirmDialogOpen = true"
>
Deactivate Account
</VBtn>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Confirm Dialog -->
<ConfirmDialog
v-model:isDialogVisible="isConfirmDialogOpen"
confirmation-question="Are you sure you want to deactivate your account?"
confirm-title="Deactivated!"
confirm-msg="Your account has been deactivated successfully."
cancel-title="Cancelled"
cancel-msg="Account Deactivation Cancelled!"
/>
</template>

View File

@@ -0,0 +1,100 @@
<script setup>
import AccountSettingsAccount from '@/views/pages/account-settings/AccountSettingsAccount.vue'
import AccountSettingsBillingAndPlans from '@/views/pages/account-settings/AccountSettingsBillingAndPlans.vue'
import AccountSettingsConnections from '@/views/pages/account-settings/AccountSettingsConnections.vue'
import AccountSettingsNotification from '@/views/pages/account-settings/AccountSettingsNotification.vue'
import AccountSettingsSecurity from '@/views/pages/account-settings/AccountSettingsSecurity.vue'
import WebsiteSettings from '@/views/pages/account-settings/WebsiteSettings.vue'
const route = useRoute('admin-account-settings-tab')
const activeTab = computed({
get: () => route.params.tab,
set: () => route.params.tab,
})
// tabs
const tabs = [
{
title: 'Account',
icon: 'ri-group-line',
tab: 'account',
action: 'read',
subject: "Profile Update",
},
{
title: 'Security',
icon: 'ri-lock-line',
tab: 'security',
action: 'read',
subject: "Security",
},
{
title: 'Site Setting',
icon: 'ri-link',
tab: 'site-settings',
action: 'read',
subject: "Site Settings",
},
]
definePage({ meta: { navActiveLink: 'admin-account-settings-tab' } })
</script>
<template>
<div>
<VTabs
v-model="activeTab"
class="v-tabs-pill"
>
<VTab
v-for="item in tabs"
:key="item.icon"
:value="item.tab"
:to="{ name: 'admin-account-settings-tab', params: { tab: item.tab } }"
>
<VIcon
start
:icon="item.icon"
v-if="$can(item.action, item.subject)"
/>
<span v-if="$can(item.action, item.subject)">{{ item.title }}</span>
</VTab>
</VTabs>
<VWindow
v-model="activeTab"
class="mt-5 disable-tab-transition"
:touch="false"
>
<!-- Account -->
<VWindowItem value="account" v-if="$can('read', 'Profile Update')">
<AccountSettingsAccount />
</VWindowItem>
<!-- Security -->
<VWindowItem value="security" v-if="$can('read', 'Security')">
<AccountSettingsSecurity />
</VWindowItem>
<!-- site setting -->
<VWindowItem value="site-settings" v-if="$can('read', 'Site Settings')">
<WebsiteSettings />
</VWindowItem>
<!-- Billing -->
<VWindowItem value="billing-plans">
<AccountSettingsBillingAndPlans />
</VWindowItem>
<!-- Notification -->
<VWindowItem value="notification">
<AccountSettingsNotification />
</VWindowItem>
<!-- Connections -->
<VWindowItem value="connection">
<AccountSettingsConnections />
</VWindowItem>
</VWindow>
</div>
</template>