Initial commit

This commit is contained in:
2025-08-04 16:33:07 +03:30
commit f798e8e35c
9595 changed files with 1208683 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
<script setup>
import avatar1 from '@images/avatars/avatar-1.png'
const accountData = {
avatarImg: avatar1,
firstName: 'john',
lastName: 'Doe',
email: 'johnDoe@example.com',
org: 'Pixinvent',
phone: '+1 (917) 543-9876',
address: '123 Main St, New York, NY 10001',
state: 'New York',
zip: '10001',
country: 'USA',
language: 'English',
timezone: '(GMT-11:00) International Date Line West',
currency: 'USD',
}
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 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
}
}
}
// 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',
]
</script>
<template>
<VRow>
<VCol cols="12">
<VCard>
<VCardText class="d-flex">
<!-- 👉 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"
size="small"
@click="refInputEl?.click()"
>
<VIcon
icon="tabler-cloud-upload"
class="d-sm-none"
/>
<span class="d-none d-sm-block">Upload new photo</span>
</VBtn>
<input
ref="refInputEl"
type="file"
name="file"
accept=".jpeg,.png,.jpg,GIF"
hidden
@input="changeAvatar"
>
<VBtn
type="reset"
size="small"
color="secondary"
variant="tonal"
@click="resetAvatar"
>
<span class="d-none d-sm-block">Reset</span>
<VIcon
icon="tabler-refresh"
class="d-sm-none"
/>
</VBtn>
</div>
<p class="text-body-1 mb-0">
Allowed JPG, GIF or PNG. Max size of 800K
</p>
</form>
</VCardText>
<VCardText class="pt-2">
<!-- 👉 Form -->
<VForm class="mt-3">
<VRow>
<!-- 👉 First Name -->
<VCol
md="6"
cols="12"
>
<AppTextField
v-model="accountDataLocal.firstName"
placeholder="John"
label="First Name"
/>
</VCol>
<!-- 👉 Last Name -->
<VCol
md="6"
cols="12"
>
<AppTextField
v-model="accountDataLocal.lastName"
placeholder="Doe"
label="Last Name"
/>
</VCol>
<!-- 👉 Email -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="accountDataLocal.email"
label="E-mail"
placeholder="johndoe@gmail.com"
type="email"
/>
</VCol>
<!-- 👉 Organization -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="accountDataLocal.org"
label="Organization"
placeholder="Pixinvent"
/>
</VCol>
<!-- 👉 Phone -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="accountDataLocal.phone"
label="Phone Number"
placeholder="+1 (917) 543-9876"
/>
</VCol>
<!-- 👉 Address -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="accountDataLocal.address"
label="Address"
placeholder="123 Main St, New York, NY 10001"
/>
</VCol>
<!-- 👉 State -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="accountDataLocal.state"
label="State"
placeholder="New York"
/>
</VCol>
<!-- 👉 Zip Code -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="accountDataLocal.zip"
label="Zip Code"
placeholder="10001"
/>
</VCol>
<!-- 👉 Country -->
<VCol
cols="12"
md="6"
>
<AppSelect
v-model="accountDataLocal.country"
label="Country"
:items="['USA', 'Canada', 'UK', 'India', 'Australia']"
placeholder="Select Country"
/>
</VCol>
<!-- 👉 Language -->
<VCol
cols="12"
md="6"
>
<AppSelect
v-model="accountDataLocal.language"
label="Language"
placeholder="Select Language"
:items="['English', 'Spanish', 'Arabic', 'Hindi', 'Urdu']"
/>
</VCol>
<!-- 👉 Timezone -->
<VCol
cols="12"
md="6"
>
<AppSelect
v-model="accountDataLocal.timezone"
label="Timezone"
placeholder="Select Timezone"
:items="timezones"
:menu-props="{ maxHeight: 200 }"
/>
</VCol>
<!-- 👉 Currency -->
<VCol
cols="12"
md="6"
>
<AppSelect
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>Save changes</VBtn>
<VBtn
color="secondary"
variant="tonal"
type="reset"
@click.prevent="resetForm"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
<VCol cols="12">
<!-- 👉 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-6"
@click="isConfirmDialogOpen = true"
>
Deactivate Account
</VBtn>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Confirm Dialog -->
<ConfirmDialog
v-model:is-dialog-visible="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,513 @@
<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/24',
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>
<div class="mb-6">
<h3 class="text-body-1 text-high-emphasis font-weight-medium mb-1">
Your Current Plan is Basic
</h3>
<p class="text-body-1">
A simple start for everyone
</p>
</div>
<div class="mb-6">
<h3 class="text-body-1 text-high-emphasis font-weight-medium mb-1">
Active until Dec 09, 2021
</h3>
<p class="text-body-1">
We will send you a notification upon Subscription expiration
</p>
</div>
<div>
<h3 class="text-body-1 text-high-emphasis font-weight-medium mb-1">
<span class="me-2">$199 Per Month</span>
<VChip
color="primary"
size="small"
label
>
Popular
</VChip>
</h3>
<p class="text-base mb-0">
Standard plan for small to medium businesses
</p>
</div>
</div>
</VCol>
<VCol
cols="12"
md="6"
>
<VAlert
icon="tabler-alert-triangle"
type="warning"
variant="tonal"
>
<VAlertTitle class="mb-1">
We need your attention!
</VAlertTitle>
<span>Your plan requires update</span>
</VAlert>
<!-- progress -->
<h6 class="d-flex font-weight-medium text-body-1 text-high-emphasis mt-6 mb-1">
<span>Days</span>
<VSpacer />
<span>12 of 30 Days</span>
</h6>
<VProgressLinear
color="primary"
rounded
model-value="15"
/>
<p class="text-body-2 mt-1 mb-0">
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="tonal"
@click="isConfirmDialogVisible = true"
>
Cancel Subscription
</VBtn>
</div>
</VCol>
</VRow>
<!-- 👉 Confirm Dialog -->
<ConfirmDialog
v-model:is-dialog-visible="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"
class="me-6"
/>
<VRadio
value="paypal-account"
label="Paypal account"
color="primary"
/>
</VRadioGroup>
</VCol>
<VCol cols="12">
<VRow>
<!-- 👉 Card Number -->
<VCol cols="12">
<AppTextField
v-model="cardNumber"
label="Card Number"
placeholder="1234 1234 1234 1234"
type="number"
/>
</VCol>
<!-- 👉 Name -->
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="cardName"
label="Name"
placeholder="John Doe"
/>
</VCol>
<!-- 👉 Expiry date -->
<VCol
cols="6"
md="3"
>
<AppTextField
v-model="cardExpiryDate"
label="Expiry Date"
placeholder="MM/YY"
/>
</VCol>
<!-- 👉 Cvv code -->
<VCol
cols="6"
md="3"
>
<AppTextField
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>
</VRow>
</VCol>
<VCol
cols="12"
class="d-flex flex-wrap gap-4"
>
<VBtn type="submit">
Save changes
</VBtn>
<VBtn
color="secondary"
variant="tonal"
@click="resetPaymentForm"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VCol>
<!-- 👉 Saved Cards -->
<VCol
cols="12"
md="6"
>
<h6 class="text-body-1 text-high-emphasis font-weight-medium mb-6">
My Cards
</h6>
<div class="d-flex flex-column gap-y-6">
<VCard
v-for="card in creditCards"
:key="card.name"
flat
color="rgba(var(--v-theme-on-surface),var(--v-hover-opacity))"
>
<VCardText class="d-flex flex-sm-row flex-column">
<div class="text-no-wrap">
<img
:src="card.image"
height="25"
>
<h4 class="my-2 text-body-1 text-high-emphasis d-flex align-center">
<div class="me-4 font-weight-medium">
{{ card.name }}
</div>
<VChip
v-if="card.isPrimary"
label
color="primary"
size="small"
>
Primary
</VChip>
</h4>
<div class="text-body-1">
**** **** **** {{ 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="tonal"
size="small"
@click="openEditCardDialog(card)"
>
Edit
</VBtn>
<VBtn
color="error"
size="small"
variant="tonal"
>
Delete
</VBtn>
</div>
<span class="text-body-2 my-4 order-sm-1 order-0">Card expires at {{ card.expiry }}</span>
</div>
</VCardText>
</VCard>
</div>
<!-- 👉 Add Edit Card Dialog -->
<CardAddEditDialog
v-model:is-dialog-visible="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"
>
<AppTextField
label="Company Name"
placeholder="Pixinvent"
/>
</VCol>
<!-- 👉 Billing Email -->
<VCol
cols="12"
md="6"
>
<AppTextField
label="Billing Email"
placeholder="pixinvent@email.com"
/>
</VCol>
<!-- 👉 Tax ID -->
<VCol
cols="12"
md="6"
>
<AppTextField
label="Tax ID"
placeholder="123 123 1233"
/>
</VCol>
<!-- 👉 Vat Number -->
<VCol
cols="12"
md="6"
>
<AppTextField
label="VAT Number"
placeholder="121212"
/>
</VCol>
<!-- 👉 Mobile -->
<VCol
cols="12"
md="6"
>
<AppTextField
dirty
label="Phone Number"
type="number"
prefix="US (+1)"
placeholder="+1 123 456 7890"
/>
</VCol>
<!-- 👉 Country -->
<VCol
cols="12"
md="6"
>
<AppSelect
label="Country"
:items="countryList"
placeholder="Select Country"
/>
</VCol>
<!-- 👉 Billing Address -->
<VCol cols="12">
<AppTextField
label="Billing Address"
placeholder="1234 Main St"
/>
</VCol>
<!-- 👉 State -->
<VCol
cols="12"
md="6"
>
<AppTextField
label="State"
placeholder="New York"
/>
</VCol>
<!-- 👉 Zip Code -->
<VCol
cols="12"
md="6"
>
<AppTextField
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="tonal"
>
Discard
</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,204 @@
<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',
color: 'yellow',
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: '@Pixinvent',
link: 'https://twitter.com/Pixinvents',
},
connected: true,
},
{
logo: linkedin,
name: 'LinkedIn',
links: {
username: '@Pixinvent',
link: 'https://in.linkedin.com/in/pixinvent-creative-studio-561a4713b',
},
connected: true,
},
{
logo: dribbble,
name: 'Dribbble',
connected: false,
},
{
logo: behance,
name: 'Behance',
connected: false,
},
])
</script>
<template>
<VCard>
<VRow>
<VCol
cols="12"
md="6"
class="pe-md-0 pb-0 pb-md-3"
>
<!-- 👉 Connected Accounts -->
<VCard
title="Connected Accounts"
subtitle="Display content from your connected accounts on your site"
flat
>
<VCardText>
<VList class="card-list">
<VListItem
v-for="item in connectedAccounts"
:key="item.logo"
>
<template #prepend>
<VAvatar>
<img
:src="item.logo"
height="32"
>
</VAvatar>
</template>
<VListItemTitle>
<h6 class="text-h6">
{{ item.name }}
</h6>
</VListItemTitle>
<VListItemSubtitle class="text-xs">
{{ item.subtitle }}
</VListItemSubtitle>
<template #append>
<VListItemAction>
<VSwitch
v-model="item.connected"
density="compact"
class="me-1"
/>
</VListItemAction>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="6"
class="ps-md-0 pt-0 pt-md-3"
>
<!-- 👉 Social Accounts -->
<VCard
title="Social Accounts"
subtitle="Display content from social accounts on your site"
flat
>
<VCardText>
<VList class="card-list">
<VListItem
v-for="item in socialAccounts"
:key="item.logo"
>
<template #prepend>
<VAvatar rounded>
<img
:src="item.logo"
height="32"
>
</VAvatar>
</template>
<VListItemTitle>
<h6 class="text-h6">
{{ item.name }}
</h6>
</VListItemTitle>
<VListItemSubtitle v-if="item.links?.link">
<a
:href="item.links.link"
target="_blank"
rel="noopener noreferrer"
>{{ item.links?.username }}</a>
</VListItemSubtitle>
<VListItemSubtitle
v-else
class="text-xs"
>
Not Connected
</VListItemSubtitle>
<template #append>
<VListItemAction>
<IconBtn
variant="tonal"
:color="item.connected ? 'error' : 'secondary'"
class="rounded"
>
<VIcon
size="22"
:icon="item.connected ? 'tabler-trash' : 'tabler-link' "
/>
</IconBtn>
</VListItemAction>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</VCol>
</VRow>
</VCard>
</template>
<style lang="scss">
.card-list {
--v-card-list-gap: 16px;
}
</style>

View File

@@ -0,0 +1,118 @@
<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>
<p class="text-body-1 mb-0">
We need permission from your browser to show notifications. <span class="text-primary cursor-pointer">Request Permission</span>
</p>
</VCardItem>
<VCardText class="px-0">
<VDivider />
<VTable class="text-no-wrap rounded">
<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-body-1 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>
<VCardText>
<VForm @submit.prevent="() => {}">
<h6 class="text-body-1 font-weight-medium mb-6">
When should we send you notifications?
</h6>
<VRow>
<VCol
cols="12"
sm="6"
>
<AppSelect
v-model="selectedNotification"
mandatory
placeholder="Select an option"
:items="['Only when I\'m online', 'Anytime']"
/>
</VCol>
</VRow>
<div class="d-flex flex-wrap gap-4 mt-6">
<VBtn type="submit">
Save Changes
</VBtn>
<VBtn
color="secondary"
variant="tonal"
type="reset"
>
Discard
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,414 @@
<script setup>
import laptopGirl from '@images/illustrations/laptop-girl.png'
const isCurrentPasswordVisible = ref(false)
const isNewPasswordVisible = ref(false)
const isConfirmPasswordVisible = ref(false)
const currentPassword = ref('')
const newPassword = ref('')
const confirmPassword = ref('')
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: 'tabler-brand-windows',
color: 'primary',
},
},
{
browser: 'Chrome on iPhone',
device: 'iPhone 12x',
location: 'Los Angeles, CA',
recentActivity: '20 Apr 2022, 10:20',
deviceIcon: {
icon: 'tabler-device-mobile',
color: 'error',
},
},
{
browser: 'Chrome on Android',
device: 'Oneplus 9 Pro',
location: 'San Francisco, CA',
recentActivity: '16 Apr 2022, 04:20',
deviceIcon: {
icon: 'tabler-brand-android',
color: 'success',
},
},
{
browser: 'Chrome on macOS',
device: 'Apple iMac',
location: 'New York, NY',
recentActivity: '28 Apr 2022, 18:20',
deviceIcon: {
icon: 'tabler-brand-apple',
color: 'secondary',
},
},
{
browser: 'Chrome on Windows',
device: 'HP Spectre 360',
location: 'Los Angeles, CA',
recentActivity: '20 Apr 2022, 10:20',
deviceIcon: {
icon: 'tabler-brand-windows',
color: 'primary',
},
},
{
browser: 'Chrome on Android',
device: 'Oneplus 9 Pro',
location: 'San Francisco, CA',
recentActivity: '16 Apr 2022, 04:20',
deviceIcon: {
icon: 'tabler-brand-android',
color: 'success',
},
},
]
const isOneTimePasswordDialogVisible = ref(false)
</script>
<template>
<VRow>
<!-- SECTION: Change Password -->
<VCol cols="12">
<VCard title="Change Password">
<VForm>
<VCardText class="pt-0">
<!-- 👉 Current Password -->
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 current password -->
<AppTextField
v-model="currentPassword"
:type="isCurrentPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isCurrentPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
label="Current Password"
autocomplete="on"
placeholder="············"
@click:append-inner="isCurrentPasswordVisible = !isCurrentPasswordVisible"
/>
</VCol>
</VRow>
<!-- 👉 New Password -->
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 new password -->
<AppTextField
v-model="newPassword"
:type="isNewPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isNewPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
label="New Password"
autocomplete="on"
placeholder="············"
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 confirm password -->
<AppTextField
v-model="confirmPassword"
:type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isConfirmPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
label="Confirm New Password"
autocomplete="on"
placeholder="············"
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
/>
</VCol>
</VRow>
</VCardText>
<!-- 👉 Password Requirements -->
<VCardText>
<h6 class="text-h6 text-medium-emphasis mb-4">
Password Requirements:
</h6>
<VList class="card-list">
<VListItem
v-for="item in passwordRequirements"
:key="item"
:title="item"
class="text-medium-emphasis"
>
<template #prepend>
<VIcon
size="10"
icon="tabler-circle-filled"
/>
</template>
</VListItem>
</VList>
</VCardText>
<!-- 👉 Action Buttons -->
<VCardText class="d-flex flex-wrap gap-4">
<VBtn>Save changes</VBtn>
<VBtn
type="reset"
color="secondary"
variant="tonal"
>
Reset
</VBtn>
</VCardText>
</VForm>
</VCard>
</VCol>
<!-- !SECTION -->
<!-- SECTION Two-steps verification -->
<VCol cols="12">
<VCard title="Two-steps verification">
<VCardText>
<h5 class="text-h5 text-medium-emphasis mb-4">
Two factor authentication is not enabled yet.
</h5>
<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 two-factor authentication
</VBtn>
</VCardText>
</VCard>
</VCol>
<!-- !SECTION -->
<VCol cols="12">
<!-- SECTION: Create an API key -->
<VCard title="Create an API key">
<VRow no-gutters>
<!-- 👉 Choose API Key -->
<VCol
cols="12"
md="5"
order-md="0"
order="1"
>
<VCardText class="pt-1">
<VForm @submit.prevent="() => { }">
<VRow>
<!-- 👉 Choose API Key -->
<VCol cols="12">
<AppSelect
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']"
/>
</VCol>
<!-- 👉 Name the API Key -->
<VCol cols="12">
<AppTextField
label="Name the API key"
placeholder="Name the API key"
/>
</VCol>
<!-- 👉 Create Key Button -->
<VCol cols="12">
<VBtn
type="submit"
block
>
Create Key
</VBtn>
</VCol>
</VRow>
</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="laptopGirl"
:width="$vuetify.display.smAndDown ? '150' : '200'"
:style="$vuetify.display.smAndDown ? 'margin-block-end: 24px' : 'position: absolute; bottom: 0;'"
/>
</VCol>
</VRow>
</VCard>
<!-- !SECTION -->
</VCol>
<VCol cols="12">
<!-- SECTION: API Keys List -->
<VCard>
<VCardItem class="pb-4">
<VCardTitle>API Key List & Access</VCardTitle>
</VCardItem>
<VCardText>
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.
</VCardText>
<!-- 👉 Server Status -->
<VCardText class="d-flex flex-column gap-y-6">
<VCard
v-for="serverKey in serverKeys"
:key="serverKey.key"
flat
class="pa-4"
color="rgba(var(--v-theme-on-surface),var(--v-hover-opacity))"
>
<div class="d-flex flex-column gap-y-2">
<div class="d-flex align-center flex-wrap">
<h5 class="text-h5 me-3">
{{ serverKey.name }}
</h5>
<VChip
label
color="primary"
size="small"
>
{{ serverKey.permission }}
</VChip>
</div>
<div class="d-flex align-center text-base font-weight-medium">
<h6 class="text-h6 text-medium-emphasis me-3">
{{ serverKey.key }}
</h6>
<div class="cursor-pointer">
<VIcon
icon="tabler-copy"
size="20"
/>
</div>
</div>
<div class="text-disabled">
Created on {{ serverKey.createdOn }}
</div>
</div>
</VCard>
</VCardText>
</VCard>
<!-- !SECTION -->
</VCol>
<!-- SECTION Recent Devices -->
<VCol cols="12">
<!-- 👉 Table -->
<VCard title="Recent Devices">
<VDivider />
<VDataTable
:headers="recentDevicesHeaders"
:items="recentDevices"
hide-default-footer
class="text-no-wrap"
>
<template #item.browser="{ item }">
<div class="d-flex">
<VIcon
start
size="22"
:icon="item.deviceIcon.icon"
:color="item.deviceIcon.color"
/>
<div class="text-high-emphasis text-body-1 font-weight-medium">
{{ item.browser }}
</div>
</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:is-dialog-visible="isOneTimePasswordDialogVisible" />
<!-- !SECTION -->
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 16px;
}
.server-close-btn {
inset-inline-end: 0.5rem;
}
</style>

View File

@@ -0,0 +1,329 @@
<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 => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
// 👉 headers
const headers = [
{
title: '#',
key: 'id',
},
{
title: 'Status',
key: 'status',
sortable: false,
},
{
title: 'Client',
key: 'client',
},
{
title: 'Total',
key: 'total',
},
{
title: 'Issued Date',
key: 'date',
},
{
title: 'Balance',
key: 'balance',
},
{
title: 'Actions',
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: 'tabler-chart-pie',
}
if (status === 'Paid')
return {
variant: 'success',
icon: 'tabler-check',
}
if (status === 'Downloaded')
return {
variant: 'info',
icon: 'tabler-arrow-down',
}
if (status === 'Draft')
return {
variant: 'primary',
icon: 'tabler-folder',
}
if (status === 'Sent')
return {
variant: 'secondary',
icon: 'tabler-mail',
}
if (status === 'Past Due')
return {
variant: 'error',
icon: 'tabler-alert-circle',
}
return {
variant: 'secondary',
icon: 'tabler-x',
}
}
const deleteInvoice = async id => {
await $api(`/apps/invoice/${ id }`, { method: 'DELETE' })
fetchInvoices()
}
</script>
<template>
<VCard
v-if="invoices"
id="invoice-list"
title="Billing History"
>
<VCardText class="d-flex align-center flex-wrap gap-4">
<!-- 👉 Create invoice -->
<div class="d-flex gap-2">
<VLabel>Show</VLabel>
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 20, 25, 50]"
/>
</div>
<VBtn
prepend-icon="tabler-plus"
:to="{ name: 'apps-invoice-add' }"
>
Create invoice
</VBtn>
<VSpacer />
<div class="d-flex align-end flex-wrap gap-4">
<!-- 👉 Search -->
<div class="invoice-list-search">
<AppTextField
v-model="searchQuery"
placeholder="Search Invoice"
/>
</div>
<div class="invoice-list-status">
<AppSelect
v-model="selectedStatus"
placeholder="Invoice Status"
clearable
clear-icon="tabler-x"
:items="['Downloaded', 'Draft', 'Sent', 'Paid', 'Partial Payment', 'Past Due']"
style="inline-size: 12rem;"
/>
</div>
</div>
</VCardText>
<VDivider />
<!-- 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"
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- id -->
<template #item.id="{ item }">
<div class="text-body-1">
<RouterLink :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
#{{ item.id }}
</RouterLink>
</div>
</template>
<!-- status -->
<template #item.status="{ 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
class="font-weight-medium text-body-1 text-high-emphasis mb-0 text-link"
:to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }"
>
{{ item.client.name }}
</RouterLink>
<span class="text-body-2">{{ item.client.companyEmail }}</span>
</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"
label
>
{{ (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 @click="deleteInvoice(item.id)">
<VIcon
icon="tabler-trash"
size="20"
/>
</IconBtn>
<IconBtn :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
<VIcon
icon="tabler-eye"
size="20"
/>
</IconBtn>
<IconBtn>
<VIcon
icon="tabler-dots-vertical"
size="20"
/>
</IconBtn>
</template>
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalInvoices"
/>
</template>
</VDataTableServer>
<!-- !SECTION -->
</VCard>
</template>
<style lang="scss">
#invoice-list {
.invoice-list-actions {
inline-size: 8rem;
}
.invoice-list-search {
inline-size: 12rem;
}
}
</style>