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,54 @@
<script setup>
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import misc404 from '@images/pages/404.png'
import miscMaskDark from '@images/pages/misc-mask-dark.png'
import miscMaskLight from '@images/pages/misc-mask-light.png'
const authThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark)
definePage({
alias: '/pages/misc/not-found/:error(.*)',
meta: {
layout: 'blank',
public: true,
},
})
</script>
<template>
<div class="misc-wrapper">
<ErrorHeader
status-code="404"
title="Page Not Found ⚠️"
description="We couldn't find the page you are looking for."
/>
<VBtn
to="/"
class="mb-11"
>
Back to Home
</VBtn>
<!-- 👉 Image -->
<div class="misc-avatar w-100 text-center">
<VImg
:src="misc404"
alt="error 404"
:max-height="$vuetify.display.smAndDown ? 350 : 500"
class="mx-auto"
/>
</div>
<img
class="misc-footer-img d-none d-md-block"
:src="authThemeMask"
alt="misc-footer-img"
height="320"
>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/misc.scss";
</style>

View File

@@ -0,0 +1,36 @@
<script setup>
definePage({
meta: {
action: 'read',
subject: 'AclDemo',
},
})
</script>
<template>
<VRow>
<VCol
cols="12"
md="6"
>
<VCard title="Common">
<VCardText>No ability is required to view this card</VCardText>
<VCardText>
This card is visible to both 'user' and 'admin'
</VCardText>
</VCard>
</VCol>
<VCol
v-if="$can('read', 'all')"
cols="12"
md="6"
>
<VCard title="Analytics">
<VCardText>User with 'Analytics' subject's 'Read' ability can view this card</VCardText>
<VCardText class="text-danger">
This card is visible to 'admin' only
</VCardText>
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,315 @@
<script setup>
import { VideoPlayer } from '@videojs-player/vue'
import InstructorPoster from '@images/pages/instructor-poster.png'
import 'video.js/dist/video-js.css'
const courseDetails = ref()
const { data, error } = await useApi('/apps/academy/course-details')
if (error.value)
console.log(error.value)
else if (data.value)
courseDetails.value = data.value
const panelStatus = ref(0)
</script>
<template>
<VRow>
<VCol
cols="12"
md="8"
>
<VCard>
<VCardItem
title="UI/UX Basic Fundamentals"
class="pb-6"
>
<template #subtitle>
<div class="text-body-1">
Prof. <span class="text-h6 d-inline-block">{{ courseDetails?.instructor }}</span>
</div>
</template>
<template #append>
<div class="d-flex gap-4 align-center">
<VChip
variant="tonal"
color="error"
size="small"
>
UI/UX
</VChip>
<VIcon
size="24"
class="cursor-pointer"
icon="tabler-share"
/>
<VIcon
size="24"
class="cursor-pointer"
icon="tabler-bookmarks"
/>
</div>
</template>
</VCardItem>
<VCardText>
<VCard
flat
border
>
<div class="px-2 pt-2">
<VideoPlayer
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
:poster="InstructorPoster"
controls
plays-inline
:height="$vuetify.display.mdAndUp ? 440 : 250"
class="w-100 rounded"
/>
</div>
<VCardText>
<h5 class="text-h5 mb-4">
About this course
</h5>
<p class="text-body-1">
{{ courseDetails?.about }}
</p>
<VDivider class="my-6" />
<h5 class="text-h5 mb-4">
By the numbers
</h5>
<div class="d-flex gap-x-12 gap-y-5 flex-wrap">
<div>
<VList class="card-list text-medium-emphasis">
<VListItem>
<template #prepend>
<VIcon
icon="tabler-check"
size="20"
/>
</template>
<VListItemTitle>Skill Level: {{ courseDetails?.skillLevel }}</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon
icon="tabler-users"
size="20"
/>
</template>
<VListItemTitle>Students: {{ courseDetails?.totalStudents }}</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon
icon="tabler-world"
size="20"
/>
</template>
<VListItemTitle>Languages: {{ courseDetails?.language }}</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon
icon="tabler-file"
size="20"
/>
</template>
<VListItemTitle>Captions: {{ courseDetails?.isCaptions }}</VListItemTitle>
</VListItem>
</VList>
</div>
<div>
<VList class="card-list text-medium-emphasis">
<VListItem>
<template #prepend>
<VIcon
icon="tabler-video"
size="20"
/>
</template>
<VListItemTitle>Lectures: {{ courseDetails?.totalLectures }}</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon
icon="tabler-clock"
size="20"
/>
</template>
<VListItemTitle>Video: {{ courseDetails?.length }}</VListItemTitle>
</VListItem>
</VList>
</div>
</div>
<VDivider class="my-6" />
<h5 class="text-h5 mb-4">
Description
</h5>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="courseDetails?.description" />
<VDivider class="my-6" />
<h5 class="text-h5 mb-4">
Instructor
</h5>
<div class="d-flex align-center gap-x-4">
<VAvatar
:image="courseDetails?.instructorAvatar"
size="38"
/>
<div>
<h6 class="text-h6 mb-1">
{{ courseDetails?.instructor }}
</h6>
<div class="text-body-2">
{{ courseDetails?.instructorPosition }}
</div>
</div>
</div>
</VCardText>
</VCard>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<div class="course-content">
<VExpansionPanels
v-model="panelStatus"
variant="accordion"
class="expansion-panels-width-border"
>
<template
v-for="(section, index) in courseDetails?.content"
:key="index"
>
<VExpansionPanel
elevation="0"
:value="index"
expand-icon="tabler-chevron-right"
collapse-icon="tabler-chevron-down"
>
<template #title>
<div>
<h5 class="text-h5 mb-1">
{{ section.title }}
</h5>
<div class="text-medium-emphasis font-weight-normal">
{{ section.status }} | {{ section.time }}
</div>
</div>
</template>
<template #text>
<VList class="card-list">
<VListItem
v-for="(topic, id) in section.topics"
:key="id"
class="py-4"
>
<template #prepend>
<VCheckbox
:model-value="topic.isCompleted"
class="me-1"
/>
</template>
<VListItemTitle class="text-high-emphasis font-weight-medium">
{{ id + 1 }} . {{ topic.title }}
</VListItemTitle>
<VListItemSubtitle>
<div class="text-body-2">
{{ topic.time }}
</div>
</VListItemSubtitle>
</VListItem>
</VList>
</template>
</VExpansionPanel>
</template>
</VExpansionPanels>
</div>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.course-content {
position: sticky;
inset-block: 4rem 0;
}
.card-list {
--v-card-list-gap: 16px;
}
</style>
<style lang="scss">
@use "@layouts/styles/mixins" as layoutsMixins;
body .v-layout .v-application__wrap {
.course-content {
.v-expansion-panels {
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 6px;
.v-expansion-panel {
&--active {
.v-expansion-panel-title--active {
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
.v-expansion-panel-title__overlay {
opacity: var(--v-hover-opacity) !important;
}
}
}
.v-expansion-panel-title {
.v-expansion-panel-title__overlay {
background-color: rgba(var(--v-theme-on-surface));
opacity: var(--v-hover-opacity) !important;
}
&:hover {
.v-expansion-panel-title__overlay {
opacity: var(--v-hover-opacity) !important;
}
}
&__icon {
.v-icon {
block-size: 1.5rem !important;
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
font-size: 1.5rem !important;
inline-size: 1.5rem !important;
@include layoutsMixins.rtl {
transform: scaleX(-1);
}
}
}
}
.v-expansion-panel-text {
&__wrapper {
padding-block: 1rem;
padding-inline: 0.75rem;
}
}
}
}
}
.card-list {
.v-list-item__prepend {
.v-list-item__spacer {
inline-size: 8px !important;
}
}
}
}
</style>

View File

@@ -0,0 +1,253 @@
<script setup>
import AcademyAssignmentProgress from '@/views/apps/academy/AcademyAssignmentProgress.vue'
import AcademyCardPopularInstructors from '@/views/apps/academy/AcademyCardPopularInstructors.vue'
import AcademyCardTopCourses from '@/views/apps/academy/AcademyCardTopCourses.vue'
import AcademyCourseTable from '@/views/apps/academy/AcademyCourseTable.vue'
import AcademyTopicYouAreInterested from '@/views/apps/academy/AcademyTopicYouAreInterested.vue'
import AcademyUpcomingWebinar from '@/views/apps/academy/AcademyUpcomingWebinar.vue'
import customCheck from '@images/svg/Check.svg'
import customLaptop from '@images/svg/laptop.svg'
import customLightbulb from '@images/svg/lightbulb.svg'
const donutChartColors = {
donut: {
series1: '#22A95E',
series2: '#24B364',
series3: '#56CA00',
series4: '#53D28C',
series5: '#7EDDA9',
series6: '#A9E9C5',
},
}
const timeSpendingChartConfig = {
chart: {
height: 157,
width: 130,
parentHeightOffset: 0,
type: 'donut',
},
labels: [
'36h',
'56h',
'16h',
'32h',
'56h',
'16h',
],
colors: [
donutChartColors.donut.series1,
donutChartColors.donut.series2,
donutChartColors.donut.series3,
donutChartColors.donut.series4,
donutChartColors.donut.series5,
donutChartColors.donut.series6,
],
stroke: { width: 0 },
dataLabels: {
enabled: false,
formatter(val) {
return `${ Number.parseInt(val) }%`
},
},
legend: { show: false },
tooltip: { theme: false },
grid: { padding: { top: 0 } },
plotOptions: {
pie: {
donut: {
size: '75%',
labels: {
show: true,
value: {
fontSize: '1.125rem',
color: 'rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity))',
fontWeight: 500,
offsetY: -15,
formatter(val) {
return `${ Number.parseInt(val) }%`
},
},
name: { offsetY: 20 },
total: {
show: true,
fontSize: '15px',
label: 'Total',
color: 'rgba(var(--v-theme-on-background), var(--v-disabled-opacity))',
formatter() {
return '231h'
},
},
},
},
},
},
}
const timeSpendingChartSeries = [
23,
35,
10,
20,
35,
23,
]
</script>
<template>
<div>
<VRow class="py-6">
<!-- 👉 Welcome -->
<VCol
cols="12"
md="8"
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
>
<div class="pe-3">
<h5 class="text-h5 mb-2">
Welcome back,<span class="text-h4"> Felecia 👋🏻 </span>
</h5>
<div
class="text-wrap text-body-1"
style="max-inline-size: 360px;"
>
Your progress this week is Awesome. let's keep it up
and get a lot of points reward!
</div>
<div class="d-flex justify-space-between flex-wrap gap-4 flex-column flex-md-row mt-4">
<div
v-for="{ title, value, icon, color } in [
{ title: 'Hours Spent', value: '34h', icon: customLaptop, color: 'primary' },
{ title: 'Test Results', value: '82%', icon: customLightbulb, color: 'info' },
{ title: 'Course Completed', value: '14', icon: customCheck, color: 'warning' },
]"
:key="title"
>
<div class="d-flex align-center">
<VAvatar
variant="tonal"
:color="color"
rounded
size="54"
class="text-primary me-4"
>
<VIcon
:icon="icon"
size="38"
/>
</VAvatar>
<div>
<h6 class="text-h6 text-medium-emphasis">
{{ title }}
</h6>
<h4
class="text-h4"
:class="`text-${color}`"
>
{{ value }}
</h4>
</div>
</div>
</div>
</div>
</div>
</VCol>
<!-- 👉 Time Spending -->
<VCol
cols="12"
md="4"
>
<div class="d-flex justify-space-between align-center">
<div class="d-flex flex-column ps-3">
<h5 class="text-h5 mb-1 text-no-wrap">
Time Spending
</h5>
<div class="text-body-1 mb-7">
Weekly Report
</div>
<h4 class="text-h4 mb-2">
231<span class="text-medium-emphasis">h</span> 14<span class="text-medium-emphasis">m</span>
</h4>
<div>
<VChip
color="success"
label
size="small"
>
+18.4%
</VChip>
</div>
</div>
<div>
<VueApexCharts
type="donut"
height="150"
width="150"
:options="timeSpendingChartConfig"
:series="timeSpendingChartSeries"
/>
</div>
</div>
</VCol>
</VRow>
<VRow class="match-height">
<!-- 👉 Topics you are interested in -->
<VCol
cols="12"
md="8"
>
<!-- 👉 Topic You are Interested in -->
<AcademyTopicYouAreInterested />
</VCol>
<!-- 👉 Popular Instructors -->
<VCol
cols="12"
md="4"
sm="6"
>
<AcademyCardPopularInstructors />
</VCol>
<!-- 👉 Academy Top Courses -->
<VCol
cols="12"
md="4"
sm="6"
>
<AcademyCardTopCourses />
</VCol>
<!-- 👉 Academy Upcoming Webinar -->
<VCol
cols="12"
md="4"
sm="6"
>
<AcademyUpcomingWebinar />
</VCol>
<!-- 👉 Academy Assignment Progress -->
<VCol
cols="12"
md="4"
sm="6"
>
<AcademyAssignmentProgress />
</VCol>
<!-- 👉 Academy Course Table -->
<VCol>
<AcademyCourseTable />
</VCol>
</VRow>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
</style>

View File

@@ -0,0 +1,234 @@
<script setup>
import { VideoPlayer } from '@videojs-player/vue'
import AcademyMyCourses from '@/views/apps/academy/AcademyMyCourses.vue'
import boyAppAcademy from '@images/illustrations/boy-app-academy.png'
import girlAppAcademy from '@images/illustrations/girl-app-academy.png'
import academyCourseIllustration1 from '@images/pages/academy-course-illustration1.png'
import academyCourseIllustration2Dark from '@images/pages/academy-course-illustration2-dark.png'
import academyCourseIllustration2Light from '@images/pages/academy-course-illustration2-light.png'
import guitarCoursePoster from '@images/pages/guitar-course-poster.png'
import singingCoursePoster from '@images/pages/singing-course-poster.png'
const academyCourseIllustration2 = useGenerateImageVariant(academyCourseIllustration2Light, academyCourseIllustration2Dark)
const searchQuery = ref('')
</script>
<template>
<div>
<VCard class="mb-6">
<VCardText class="py-12 position-relative">
<div
class="d-flex flex-column gap-y-4 mx-auto"
:class="$vuetify.display.mdAndUp ? 'w-50' : $vuetify.display.xs ? 'w-100' : 'w-75'"
>
<h4
class="text-h4 text-center text-wrap mx-auto"
:class="$vuetify.display.mdAndUp ? 'w-75' : 'w-100'"
>
Education, talents, and career
opportunities. <span class="text-primary text-no-wrap"> All in one place.</span>
</h4>
<p class="text-center text-wrap text-body-1 mx-auto mb-0">
Grow your skill with the most reliable online courses and certifications in marketing, information technology, programming, and data science.
</p>
<div class="d-flex justify-center align-center gap-4 flex-wrap">
<div
class="flex-grow-1"
style="max-inline-size: 350px;"
>
<AppTextField
v-model="searchQuery"
placeholder="Find your course"
/>
</div>
<VBtn
color="primary"
density="comfortable"
icon="tabler-search"
class="rounded"
/>
</div>
</div>
<img
:src="academyCourseIllustration1"
class="illustration1 d-none d-md-block flip-in-rtl"
height="180"
>
<img
:src="academyCourseIllustration2"
class="illustration2 d-none d-md-block"
height="100"
>
</VCardText>
</VCard>
<AcademyMyCourses :search-query="searchQuery" />
<div class="mb-6">
<VRow>
<VCol
v-for="{ title, btnText, color, description, image } in [
{ title: 'Earn a Certificate', description: 'Get the right professional certificate program for you.', btnText: 'View Programs', color: 'primary', image: boyAppAcademy },
{ title: 'Best Rated Courses', description: 'Enroll now in the most popular and best rated courses.', btnText: 'View Courses', color: 'error', image: girlAppAcademy },
]"
:key="title"
cols="12"
md="6"
>
<VCard
flat
:color="`rgba(var(--v-theme-${color}), var(--v-selected-opacity))`"
>
<VCardText>
<div class="d-flex justify-space-between gap-4 flex-column-reverse flex-sm-row">
<div class="text-center text-sm-start">
<h5
class="text-h5 mb-1"
:class="`text-${color}`"
>
<div class="d-flex justify-space-between gap-4 flex-column-reverse flex-sm-row">
<div class="text-center text-sm-start">
<h5
class="text-h5 mb-1"
:class="`text-${color}`"
>
{{ title }}
</h5>
</div>
</div>
</h5>
<p
class="text-body-1 mx-auto"
style="max-inline-size: 300px;"
>
{{ description }}
</p>
<VBtn :color="color">
{{ btnText }}
</VBtn>
</div>
<div class="align-self-center">
<div class="align-self-center">
<img
:src="image"
height="127"
class="flip-in-rtl"
>
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
<VCard>
<VCardText>
<VRow>
<VCol
cols="12"
md="4"
>
<div class="d-flex flex-column align-center gap-y-4 h-100 justify-center">
<VAvatar
variant="tonal"
size="52"
rounded
color="primary"
>
<VIcon
icon="tabler-gift"
size="36"
/>
</VAvatar>
<h4 class="text-h4 font-weight-medium">
Today's Free Courses
</h4>
<p class="text-body-1 text-center mb-0">
We offers 284 Free Online courses from top tutors and companies to help you start or advance your career skills. Learn online for free and fast today!
</p>
<VBtn>Get Premium Courses</VBtn>
</div>
</VCol>
<VCol
cols="12"
md="4"
sm="6"
>
<VCard
flat
border
>
<div class="px-2 pt-2">
<VideoPlayer
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
:poster="guitarCoursePoster"
controls
plays-inline
:height="$vuetify.display.mdAndUp ? 200 : 150"
class="w-100 rounded"
/>
</div>
<VCardText>
<h5 class="text-h5 mb-2">
Your First Singing Lesson
</h5>
<p class="text-body-1 mb-0">
In the same way as any other artistic domain, singing lends itself perfectly to self-teaching.
</p>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
sm="6"
>
<VCard
flat
border
>
<div class="px-2 pt-2">
<VideoPlayer
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
:poster="singingCoursePoster"
controls
plays-inline
:height="$vuetify.display.mdAndUp ? 200 : 150"
class="w-100 rounded"
/>
</div>
<VCardText>
<h5 class="text-h5 mb-2">
Guitar for Beginners
</h5>
<p class="text-body-1 mb-0">
The Fender Acoustic Guitar is best choice for beginners and professionals.
</p>
</VCardText>
</VCard>
</VCol>
</VRow>
</VCardText>
</VCard>
</div>
</template>
<style lang="scss">
@import "video.js/dist/video-js.css";
.illustration1 {
position: absolute;
inset-block-end: 0;
inset-inline-end: 0;
}
.illustration2 {
position: absolute;
inset-block-start: 2rem;
inset-inline-start: 2.5rem;
}
</style>

View File

@@ -0,0 +1,190 @@
<script setup>
import FullCalendar from '@fullcalendar/vue3'
import {
blankEvent,
useCalendar,
} from '@/views/apps/calendar/useCalendar'
import { useCalendarStore } from '@/views/apps/calendar/useCalendarStore'
// Components
import CalendarEventHandler from '@/views/apps/calendar/CalendarEventHandler.vue'
// 👉 Store
const store = useCalendarStore()
// 👉 Event
const event = ref(structuredClone(blankEvent))
const isEventHandlerSidebarActive = ref(false)
watch(isEventHandlerSidebarActive, val => {
if (!val)
event.value = structuredClone(blankEvent)
})
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
// 👉 useCalendar
const { refCalendar, calendarOptions, addEvent, updateEvent, removeEvent, jumpToDate } = useCalendar(event, isEventHandlerSidebarActive, isLeftSidebarOpen)
// SECTION Sidebar
// 👉 Check all
const checkAll = computed({
/*GET: Return boolean `true` => if length of options matches length of selected filters => Length matches when all events are selected
SET: If value is `true` => then add all available options in selected filters => Select All
Else if => all filters are selected (by checking length of both array) => Empty Selected array => Deselect All
*/
get: () => store.selectedCalendars.length === store.availableCalendars.length,
set: val => {
if (val)
store.selectedCalendars = store.availableCalendars.map(i => i.label)
else if (store.selectedCalendars.length === store.availableCalendars.length)
store.selectedCalendars = []
},
})
const jumpToDateFn = date => {
jumpToDate(date)
}
</script>
<template>
<div>
<VCard>
<!-- `z-index: 0` Allows overlapping vertical nav on calendar -->
<VLayout style="z-index: 0;">
<!-- 👉 Navigation drawer -->
<VNavigationDrawer
v-model="isLeftSidebarOpen"
data-allow-mismatch
width="292"
absolute
touchless
location="start"
class="calendar-add-event-drawer"
:temporary="$vuetify.display.mdAndDown"
>
<div style="margin: 1.5rem;">
<VBtn
block
prepend-icon="tabler-plus"
@click="isEventHandlerSidebarActive = true"
>
Add event
</VBtn>
</div>
<VDivider />
<div class="d-flex align-center justify-center pa-2">
<AppDateTimePicker
id="calendar-date-picker"
:model-value="new Date().toJSON().slice(0, 10)"
:config="{ inline: true }"
class="calendar-date-picker"
@update:model-value="jumpToDateFn"
/>
</div>
<VDivider />
<div class="pa-6">
<h6 class="text-lg font-weight-medium mb-4">
Event Filters
</h6>
<div class="d-flex flex-column calendars-checkbox">
<VCheckbox
id="check-all-events"
v-model="checkAll"
label="View all"
/>
<VCheckbox
v-for="(calendar, index) in store.availableCalendars"
:id="`${index}`"
:key="calendar.label"
v-model="store.selectedCalendars"
:value="calendar.label"
:color="calendar.color"
:label="calendar.label"
/>
</div>
</div>
</VNavigationDrawer>
<VMain>
<VCard flat>
<FullCalendar
ref="refCalendar"
:options="calendarOptions"
/>
</VCard>
</VMain>
</VLayout>
</VCard>
<CalendarEventHandler
v-model:is-drawer-open="isEventHandlerSidebarActive"
:event="event"
@add-event="addEvent"
@update-event="updateEvent"
@remove-event="removeEvent"
/>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/libs/full-calendar";
.calendars-checkbox {
.v-label {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
opacity: var(--v-high-emphasis-opacity);
}
}
.calendar-add-event-drawer {
&.v-navigation-drawer:not(.v-navigation-drawer--temporary) {
border-end-start-radius: 0.375rem;
border-start-start-radius: 0.375rem;
}
&.v-navigation-drawer--temporary:not(.v-navigation-drawer--active) {
transform: translateX(-110%) !important;
}
}
.calendar-date-picker {
display: none;
+.flatpickr-input {
+.flatpickr-calendar.inline {
border: none;
box-shadow: none;
.flatpickr-months {
border-block-end: none;
}
}
}
& ~ .flatpickr-calendar .flatpickr-weekdays {
margin-block: 0 4px;
}
}
@media screen and (max-width: 1279px) {
.calendar-add-event-drawer {
border-width: 0;
}
}
</style>
<style lang="scss" scoped>
.v-layout {
overflow: visible !important;
.v-card {
overflow: visible;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
<script setup>
import { ref, nextTick } from 'vue';
const props = defineProps({
msg: String,
isInputDisabled: Boolean,
activeModelColor: String,
selectedAttachments: Array,
showSuggestions: Boolean,
suggestions: Array,
selectedSuggestionIndex: Number,
});
const emit = defineEmits([
'update:msg',
'send',
'compositionstart',
'compositionend',
'keydown',
'remove-attachment',
'select-suggestion',
]);
const inputRef = ref();
</script>
<template>
<div class="input-wrapper">
<div
class="chat-input-container rounded-3xl pa-3 mb-2"
:class="{ 'pt-0': selectedAttachments.length === 0 }"
>
<div
v-if="selectedAttachments.length > 0"
class="d-flex flex-wrap gap-2 mt-3 mb-2 attachment-chip-container"
>
<VChip
v-for="(file, index) in selectedAttachments"
:key="index"
closable
@click:close="emit('remove-attachment', index)"
color="grey-lighten-3"
class="align-center attachment-chip"
>
<VIcon start :icon="'tabler-file'" size="small"></VIcon>
<span class="attachment-name">{{ file.name }}</span>
<span class="attachment-size">({{ (file.size / (1024 * 1024)).toFixed(1) }} MB)</span>
</VChip>
</div>
<div class="position-relative">
<VTextarea
ref="inputRef"
:model-value="msg"
auto-grow
rows="1"
row-height="20"
:disabled="isInputDisabled"
hide-details
variant="plain"
persistent-placeholder
density="comfortable"
class="chat-textarea"
placeholder="Type your message..."
no-resize
@update:model-value="emit('update:msg', $event)"
@keydown="emit('keydown', $event)"
@compositionstart="emit('compositionstart')"
@compositionend="emit('compositionend')"
></VTextarea>
<div v-if="showSuggestions" class="suggestions-dropdown rounded pa-2">
<div class="suggestions-header text-caption text-grey-darken-1 mb-1 px-2">Suggestions</div>
<div
v-for="(suggestion, index) in suggestions"
:key="index"
class="suggestion-item pa-2 rounded d-flex align-center"
:class="{ 'suggestion-selected': selectedSuggestionIndex === index }"
@click="emit('select-suggestion', suggestion)"
@mouseenter="emit('update:selectedSuggestionIndex', index)"
>
<VIcon icon="tabler-search" size="16" class="me-2 text-grey-darken-1" />
<span class="suggestion-text">{{ suggestion }}</span>
</div>
</div>
</div>
<div class="d-flex align-center justify-end mt-1">
<VBtn
icon
size="large"
:color="activeModelColor"
@click="emit('send')"
:disabled="isInputDisabled || !msg.trim()"
>
<VIcon icon="tabler-send" />
</VBtn>
</div>
</div>
</div>
</template>
<style scoped>
.attachment-chip-container {
overflow-x: auto;
padding-bottom: 4px;
margin-right: -8px;
margin-left: -8px;
padding-left: 8px;
padding-right: 8px;
-ms-overflow-style: none;
scrollbar-width: none;
}
.attachment-chip-container::-webkit-scrollbar {
display: none;
}
.attachment-chip {
white-space: nowrap;
max-width: 100%;
overflow: hidden;
}
.attachment-name {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
}
.attachment-size {
margin-left: 4px;
}
.suggestions-dropdown {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
background: rgb(var(--v-theme-surface));
box-shadow: 0 4px 16px rgba(var(--v-theme-on-surface), 0.15);
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
z-index: 15;
margin-bottom: 8px;
max-height: 240px;
overflow-y: auto;
animation: suggestions-fade-in 0.2s ease-out;
}
.suggestions-header {
font-weight: 500;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.suggestion-item {
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 2px;
border: 1px solid transparent;
}
.suggestion-item:hover,
.suggestion-item.suggestion-selected {
background: rgba(var(--v-theme-primary), 0.08);
border-color: rgba(var(--v-theme-primary), 0.2);
transform: translateX(2px);
}
.suggestion-text {
font-size: 0.875rem;
color: rgb(var(--v-theme-on-surface));
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@keyframes suggestions-fade-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -0,0 +1,54 @@
<script setup>
const props = defineProps({
form: Object,
activeModelColor: String,
});
const emit = defineEmits(["select"]);
</script>
<template>
<div class="form-container">
<div class="form-title font-weight-bold mb-2">
{{ form.title }}
</div>
<div class="form-question mb-3">
{{ form.question }}
</div>
<div class="form-options d-flex gap-2">
<VBtn
v-for="(option, optIndex) in form.options"
:key="optIndex"
:color="activeModelColor"
variant="outlined"
size="small"
class="form-option-btn"
@click="emit('select', option)"
>
{{ option }}
</VBtn>
</div>
</div>
</template>
<style scoped>
.form-title {
font-size: 1.1rem;
color: rgb(var(--v-theme-on-surface));
}
.form-question {
font-size: 0.9rem;
line-height: 1.4;
}
.form-options .form-option-btn {
margin-bottom: 8px;
border-radius: 8px;
text-transform: none;
}
.form-options .form-option-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>

View File

@@ -0,0 +1,107 @@
<script setup>
import { computed } from 'vue';
import { useTheme } from 'vuetify';
import { themes } from '@/plugins/vuetify/theme';
const props = defineProps({
message: Object,
activeModelColor: String,
activeModelBgColor: String,
models: Array,
selectedModelIdentifier: String,
});
const getFileIcon = (fileType) => {
if (fileType.startsWith('image/')) return 'tabler-photo';
if (fileType.includes('pdf')) return 'tabler-file-type-pdf';
if (fileType.includes('word')) return 'tabler-file-type-doc';
if (fileType.includes('text')) return 'tabler-file-text';
return 'tabler-file';
};
const getFileSize = (size) => {
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
};
</script>
<template>
<li
class="my-3 d-flex"
:class="[
message.sender === 'user' ? 'justify-end' : 'justify-start',
message.isAnimating ? 'is-animating' : '',
message.sender === 'user' ? 'user-message' : 'bot-message',
message.isSystemMessage ? 'system-message' : '',
]"
>
<template v-if="message.sender !== 'user'">
<VAvatar
size="32"
:color="message.isSystemMessage ? 'grey' : activeModelColor"
variant="tonal"
class="me-2 message-avatar"
>
<VIcon
:icon="message.isSystemMessage ? 'tabler-info-circle' : 'tabler-robot'"
/>
</VAvatar>
</template>
<div
class="pa-3 rounded-lg message-bubble"
:class="[
message.sender === 'user'
? 'bg-primary text-white'
: message.isSystemMessage
? 'bg-grey-lighten-3 text-grey-darken-3'
: `bg-${activeModelBgColor} text-white`,
message.isAnimating ? 'animate-message' : '',
]"
>
<div v-if="!message.form && !message.multiForm && !message.type">
{{ message.text }}
</div>
<div v-if="message.isAttachment && message.files" class="attachment-preview mt-2">
<div
v-for="(file, fileIndex) in message.files"
:key="fileIndex"
class="d-flex align-center mb-1"
>
<VIcon :icon="getFileIcon(file.type)" size="small" class="me-1"></VIcon>
<span class="text-caption">{{ file.name }} ({{ getFileSize(file.size) }})</span>
</div>
</div>
</div>
<template v-if="message.sender === 'user'">
<VAvatar size="32" color="grey" variant="tonal" class="ms-2 message-avatar">
<VIcon icon="tabler-user" />
</VAvatar>
</template>
</li>
</template>
<style scoped>
.message-avatar {
opacity: 0;
animation: fade-in 0.3s ease forwards;
animation-delay: 0.15s;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.attachment-preview {
font-size: 0.85rem;
opacity: 0.9;
}
</style>

View File

@@ -0,0 +1,63 @@
<script setup>
const props = defineProps({
models: Array,
selectedModelIdentifier: String,
activeModelColor: String,
showModelMenu: Boolean,
});
const emit = defineEmits(['update:model', 'update:menu']);
const selectedModelName = computed(() => {
const model = props.models.find((m) => m.identifier === props.selectedModelIdentifier);
return model ? model.name : 'Select Model';
});
</script>
<template>
<VMenu :model-value="showModelMenu" location="top" @update:model-value="emit('update:menu', $event)">
<template v-slot:activator="{ props: menuProps }">
<VBtn
v-bind="menuProps"
:color="activeModelColor"
variant="text"
class="text-none model-select-btn"
density="comfortable"
>
<span class="d-none d-sm-block">{{ selectedModelName }}</span>
<span class="d-block d-sm-none">Model</span>
<VIcon icon="tabler-chevron-down" class="ms-1" />
</VBtn>
</template>
<VList density="compact" max-height="300">
<VListItem
v-for="model in models"
:key="model.identifier"
:value="model.identifier"
@click="emit('update:model', model.identifier); emit('update:menu', false)"
>
<template v-slot:prepend>
<div
class="model-color-indicator"
:style="`background-color: var(--v-theme-${model.color})`"
></div>
</template>
<VListItemTitle>{{ model.name }}</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</template>
<style scoped>
.model-select-btn {
min-width: auto;
padding: 0 8px;
}
.model-color-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
</style>

View File

@@ -0,0 +1,99 @@
<script setup>
const props = defineProps({
step: Object,
stepNumber: Number,
totalSteps: Number,
activeModelColor: String,
activeModelBgColor: String,
});
const emit = defineEmits(["cancel", "select"]);
</script>
<template>
<div class="multi-form-container">
<div class="multi-form-header mb-3">
<div class="d-flex justify-space-between align-center mb-2">
<div class="form-title font-weight-bold">
{{ step.formTitle }}
</div>
<div class="d-flex align-center">
<VChip :color="activeModelColor" size="small" variant="tonal">
{{ stepNumber }}/{{ totalSteps }}
</VChip>
<VBtn icon size="x-small" @click="emit('cancel')" class="ms-2">
<VIcon icon="tabler-x" />
</VBtn>
</div>
</div>
<VProgressLinear
:model-value="(stepNumber / totalSteps) * 100"
:color="activeModelColor"
height="4"
rounded
class="mb-3"
></VProgressLinear>
</div>
<div class="step-title font-weight-medium mb-2">
{{ step.title }}
</div>
<div class="form-question mb-3">
{{ step.question }}
</div>
<div v-if="step.type === 'options'" class="form-options">
<div class="d-flex flex-wrap gap-2">
<VBtn
v-for="(option, optIndex) in step.options"
:key="optIndex"
:color="activeModelColor"
variant="outlined"
size="small"
class="form-option-btn"
@click="emit('select', { id: step.id, value: option })"
>
{{ option }}
</VBtn>
</div>
</div>
</div>
</template>
<style scoped>
.multi-form-container {
max-width: 100%;
}
.multi-form-header {
border-bottom: 1px solid rgba(var(--v-theme-on-surface), 0.1);
padding-bottom: 12px;
}
.form-title {
font-size: 1.1rem;
color: rgb(var(--v-theme-on-surface));
}
.step-title {
font-size: 0.95rem;
color: rgba(var(--v-theme-on-surface), 0.8);
}
.form-question {
font-size: 0.9rem;
line-height: 1.4;
}
.form-options .form-option-btn {
margin-bottom: 8px;
border-radius: 8px;
text-transform: none;
}
.form-options .form-option-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>

View File

@@ -0,0 +1,48 @@
<script setup>
const props = defineProps({
replies: Array,
activeModelColor: String,
isInputCentered: Boolean,
});
const emit = defineEmits(['select']);
</script>
<template>
<div class="quick-replies-container">
<div
v-for="(reply, index) in replies"
:key="index"
class="quick-reply-item"
>
<VBtn
:variant="isInputCentered ? 'tonal' : 'outlined'"
:color="activeModelColor"
size="small"
@click="emit('select', reply)"
class="w-100"
>
{{ reply }}
</VBtn>
</div>
</div>
</template>
<style scoped>
.quick-replies-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-gap: 8px;
margin-bottom: 8px;
width: 100%;
}
@media (max-width: 600px) {
.quick-replies-container {
grid-template-columns: repeat(2, 1fr);
}
}
.quick-reply-item {
width: 100%;
}
</style>

View File

@@ -0,0 +1,113 @@
<script setup>
const props = defineProps({
suggestions: Array,
selectedIndex: Number,
activeModelColor: String,
});
const emit = defineEmits(["select", "hover"]);
</script>
<template>
<div class="suggestions-dropdown rounded pa-2">
<div class="suggestions-header text-caption text-grey-darken-1 mb-1 px-2">
Suggestions
</div>
<div
v-for="(suggestion, index) in suggestions"
:key="index"
class="suggestion-item pa-2 rounded d-flex align-center"
:class="{ 'suggestion-selected': selectedIndex === index }"
@click="emit('select', suggestion)"
@mouseenter="emit('hover', index)"
>
<VIcon icon="tabler-search" size="16" class="me-2 text-grey-darken-1" />
<span class="suggestion-text">{{ suggestion }}</span>
</div>
</div>
</template>
<style scoped>
.suggestions-dropdown {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
background: rgb(var(--v-theme-surface));
box-shadow: 0 4px 16px rgba(var(--v-theme-on-surface), 0.15);
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
z-index: 15;
margin-bottom: 8px;
max-height: 240px;
overflow-y: auto;
animation: suggestions-fade-in 0.2s ease-out;
}
.suggestions-header {
font-weight: 500;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.suggestion-item {
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 2px;
border: 1px solid transparent;
}
.suggestion-item:hover,
.suggestion-item.suggestion-selected {
background: rgba(var(--v-theme-primary), 0.08);
border-color: rgba(var(--v-theme-primary), 0.2);
transform: translateX(2px);
}
.suggestion-text {
font-size: 0.875rem;
color: rgb(var(--v-theme-on-surface));
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@keyframes suggestions-fade-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 600px) {
.suggestions-dropdown {
max-height: 200px;
}
.suggestion-text {
font-size: 0.8rem;
}
}
.suggestions-dropdown::-webkit-scrollbar {
width: 4px;
}
.suggestions-dropdown::-webkit-scrollbar-track {
background: rgba(var(--v-theme-on-surface), 0.05);
border-radius: 2px;
}
.suggestions-dropdown::-webkit-scrollbar-thumb {
background: rgba(var(--v-theme-on-surface), 0.2);
border-radius: 2px;
}
.suggestions-dropdown::-webkit-scrollbar-thumb:hover {
background: rgba(var(--v-theme-on-surface), 0.3);
}
</style>

View File

@@ -0,0 +1,72 @@
<script setup>
const props = defineProps({
activeModelColor: String,
activeModelBgColor: String,
});
</script>
<template>
<li class="my-3 d-flex justify-start typing-indicator-container">
<VAvatar
size="32"
:color="activeModelColor"
variant="tonal"
class="me-2 message-avatar"
>
<VIcon icon="tabler-robot" />
</VAvatar>
<div
class="pa-3 rounded-lg message-bubble"
:class="`bg-${activeModelBgColor} text-${activeModelColor}`"
>
<div class="bot-typing-indicator">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
</li>
</template>
<style scoped>
.typing-indicator-container {
animation: fade-in 0.3s ease forwards;
}
.bot-typing-indicator {
display: flex;
align-items: center;
justify-content: center;
height: 20px;
}
.bot-typing-indicator .dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(var(--v-theme-on-surface), 0.6);
margin: 0 2px;
animation: pulse 1.5s infinite ease-in-out;
}
.bot-typing-indicator .dot:nth-child(2) {
animation-delay: 0.2s;
}
.bot-typing-indicator .dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes pulse {
0%, 100% {
transform: scale(0.7);
opacity: 0.5;
}
50% {
transform: scale(1.2);
opacity: 1;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<script setup>
const props = defineProps({
modelValue: Boolean,
files: Array,
fileError: String,
activeModelColor: String,
});
const emit = defineEmits(["update:modelValue", "triggerInput", "remove", "done"]);
</script>
<template>
<VDialog :model-value="modelValue" max-width="500" @update:modelValue="emit('update:modelValue', $event)">
<VCard>
<VCardTitle class="pb-2 pt-4">
<span class="text-h5">Upload Files</span>
</VCardTitle>
<VCardText>
<div class="text-subtitle-2 mb-4">
Select files to upload (Max 5 files, 5MB each)
</div>
<div
class="upload-area pa-6 border-dashed rounded d-flex flex-column align-center justify-center"
@click="emit('triggerInput')"
@dragover.prevent
@drop.prevent="$event => emit('triggerInput', $event)"
>
<VIcon :icon="'tabler-upload'" :size="36" :color="activeModelColor" class="mb-3" />
<div class="text-body-1 mb-1">Drag files here or click to upload</div>
<div class="text-caption text-grey">Supported formats: Images, PDF, DOC, DOCX, TXT</div>
</div>
<div v-if="fileError" class="text-error mt-2">{{ fileError }}</div>
<div v-if="files.length > 0">
<div class="text-subtitle-2 mt-4 mb-2">Selected files:</div>
<VList density="compact" class="bg-grey-lighten-5 rounded">
<VListItem v-for="(file, index) in files" :key="index">
<template v-slot:prepend>
<VIcon :icon="file.type.startsWith('image/') ? 'tabler-photo' : file.type.includes('pdf') ? 'tabler-file-type-pdf' : file.type.includes('word') ? 'tabler-file-type-doc' : file.type.includes('text') ? 'tabler-file-text' : 'tabler-file'" />
</template>
<VListItemTitle>{{ file.name }}</VListItemTitle>
<VListItemSubtitle>{{ (file.size / (1024 * 1024)).toFixed(1) }} MB</VListItemSubtitle>
<template v-slot:append>
<VBtn icon size="small" variant="text" @click="emit('remove', index)">
<VIcon icon="tabler-x" />
</VBtn>
</template>
</VListItem>
</VList>
</div>
</VCardText>
<VCardActions class="pb-4 px-4">
<VSpacer></VSpacer>
<VBtn color="grey-darken-1" variant="text" @click="emit('update:modelValue', false)">Cancel</VBtn>
<VBtn :color="activeModelColor" variant="tonal" @click="emit('done')" :disabled="files.length === 0">Done</VBtn>
</VCardActions>
</VCard>
</VDialog>
</template>
<style scoped>
.border-dashed {
border: 2px dashed rgba(var(--v-theme-on-surface), 0.2);
}
.upload-area {
cursor: pointer;
transition: all 0.3s ease;
}
.upload-area:hover {
background-color: rgba(var(--v-theme-on-surface), 0.05);
border-color: rgba(var(--v-theme-on-surface), 0.4);
}
</style>

View File

@@ -0,0 +1,120 @@
<script setup>
import ECommerceAddCustomerDrawer from '@/views/apps/ecommerce/ECommerceAddCustomerDrawer.vue'
import CustomerBioPanel from '@/views/apps/ecommerce/customer/view/CustomerBioPanel.vue'
import CustomerTabAddressAndBilling from '@/views/apps/ecommerce/customer/view/CustomerTabAddressAndBilling.vue'
import CustomerTabNotification from '@/views/apps/ecommerce/customer/view/CustomerTabNotification.vue'
import CustomerTabOverview from '@/views/apps/ecommerce/customer/view/CustomerTabOverview.vue'
import CustomerTabSecurity from '@/views/apps/ecommerce/customer/view/CustomerTabSecurity.vue'
const route = useRoute('apps-ecommerce-customer-details-id')
const customerData = ref()
const userTab = ref(null)
const tabs = [
{
title: 'Overview',
icon: 'tabler-user',
},
{
title: 'Security',
icon: 'tabler-lock',
},
{
title: 'Address & Billing',
icon: 'tabler-map-pin',
},
{
title: 'Notifications',
icon: 'tabler-bell',
},
]
const { data } = await useApi(`/apps/ecommerce/customers/${ route.params.id }`)
if (data.value)
customerData.value = data.value
const isAddCustomerDrawerOpen = ref(false)
</script>
<template>
<div>
<!-- 👉 Header -->
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<h4 class="text-h4 mb-1">
Customer ID #{{ route.params.id }}
</h4>
<div class="text-body-1">
Aug 17, 2020, 5:48 (ET)
</div>
</div>
<div class="d-flex gap-4">
<VBtn
variant="tonal"
color="error"
>
Delete Customer
</VBtn>
</div>
</div>
<!-- 👉 Customer Profile -->
<VRow v-if="customerData">
<VCol
v-if="customerData"
cols="12"
md="5"
lg="4"
>
<CustomerBioPanel :customer-data="customerData" />
</VCol>
<VCol
cols="12"
md="7"
lg="8"
>
<VTabs
v-model="userTab"
class="v-tabs-pill mb-3 disable-tab-transition"
>
<VTab
v-for="tab in tabs"
:key="tab.title"
>
<VIcon
size="20"
start
:icon="tab.icon"
/>
{{ tab.title }}
</VTab>
</VTabs>
<VWindow
v-model="userTab"
class="disable-tab-transition"
:touch="false"
>
<VWindowItem>
<CustomerTabOverview />
</VWindowItem>
<VWindowItem>
<CustomerTabSecurity />
</VWindowItem>
<VWindowItem>
<CustomerTabAddressAndBilling />
</VWindowItem>
<VWindowItem>
<CustomerTabNotification />
</VWindowItem>
</VWindow>
</VCol>
</VRow>
<div v-else>
<VAlert
type="error"
variant="tonal"
>
Invoice with ID {{ route.params.id }} not found!
</VAlert>
</div>
<ECommerceAddCustomerDrawer v-model:is-drawer-open="isAddCustomerDrawerOpen" />
</div>
</template>

View File

@@ -0,0 +1,173 @@
<script setup>
import ECommerceAddCustomerDrawer from '@/views/apps/ecommerce/ECommerceAddCustomerDrawer.vue'
const searchQuery = ref('')
const isAddCustomerDrawerOpen = ref(false)
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
// Data table Headers
const headers = [
{
title: 'Customer',
key: 'customer',
},
{
title: 'Customer Id',
key: 'customerId',
},
{
title: 'Country',
key: 'country',
},
{
title: 'Orders',
key: 'orders',
},
{
title: 'Total Spent',
key: 'totalSpent',
},
]
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const { data: customerData } = await useApi(createUrl('/apps/ecommerce/customers', {
query: {
q: searchQuery,
itemsPerPage,
page,
sortBy,
orderBy,
},
}))
const customers = computed(() => customerData.value.customers)
const totalCustomers = computed(() => customerData.value.total)
</script>
<template>
<div>
<VCard>
<VCardText>
<div class="d-flex justify-space-between flex-wrap gap-y-4">
<AppTextField
v-model="searchQuery"
style="max-inline-size: 280px; min-inline-size: 280px;"
placeholder="Search Name"
/>
<div class="d-flex flex-row gap-4 align-center flex-wrap">
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 20, 50, 100]"
/>
<VBtn
prepend-icon="tabler-upload"
variant="tonal"
color="secondary"
>
Export
</VBtn>
<VBtn
prepend-icon="tabler-plus"
@click="isAddCustomerDrawerOpen = !isAddCustomerDrawerOpen"
>
Add Customer
</VBtn>
</div>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:items="customers"
item-value="customer"
:headers="headers"
:items-length="totalCustomers"
show-select
class="text-no-wrap"
@update:options="updateOptions"
>
<template #item.customer="{ item }">
<div class="d-flex align-center gap-x-3">
<VAvatar
size="34"
:variant="!item.avatar ? 'tonal' : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span v-else>{{ avatarText(item.customer) }}</span>
</VAvatar>
<div class="d-flex flex-column">
<RouterLink
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: item.customerId } }"
class="text-link font-weight-medium d-inline-block"
style="line-height: 1.375rem;"
>
{{ item.customer }}
</RouterLink>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<template #item.customerId="{ item }">
<div class="text-body-1 text-high-emphasis">
#{{ item.customerId }}
</div>
</template>
<template #item.orders="{ item }">
{{ item.order }}
</template>
<template #item.country="{ item }">
<div class="d-flex gap-x-2">
<img
:src="item.countryFlag"
height="22"
width="22"
>
<span class="text-body-1">{{ item.country }}</span>
</div>
</template>
<template #item.totalSpent="{ item }">
<h6 class="text-h6">
${{ item.totalSpent }}
</h6>
</template>
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalCustomers"
/>
</template>
</VDataTableServer>
</VCard>
<ECommerceAddCustomerDrawer v-model:is-drawer-open="isAddCustomerDrawerOpen" />
</div>
</template>
<style lang="scss" scoped>
.customer-title:hover {
color: rgba(var(--v-theme-primary)) !important;
}
</style>

View File

@@ -0,0 +1,573 @@
<script setup>
const selectedStatus = ref('All')
const searchQuery = ref('')
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const selectedRows = ref([])
const {
data: ReviewData,
execute: fetchReviews,
} = await useApi(createUrl('/apps/ecommerce/reviews', {
query: {
q: searchQuery,
status: selectedStatus,
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const reviews = computed(() => ReviewData.value.reviews)
const totalReviews = computed(() => ReviewData.value.total)
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const deleteReview = async id => {
await $api(`/apps/ecommerce/reviews/${ id }`, { method: 'DELETE' })
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
fetchReviews()
}
const reviewCardData = [
{
rating: 5,
value: 124,
},
{
rating: 4,
value: 40,
},
{
rating: 3,
value: 12,
},
{
rating: 2,
value: 7,
},
{
rating: 1,
value: 2,
},
]
const headers = [
{
title: 'Product',
key: 'product',
},
{
title: 'Reviewer',
key: 'reviewer',
},
{
title: 'Review',
key: 'review',
sortable: false,
},
{
title: 'Date',
key: 'date',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const labelColor = 'rgba(var(--v-theme-on-surface), var(--v-disabled-opacity))'
const config = {
colorsLabel: { success: '#28c76f29' },
colors: { success: '#28c76f' },
}
const reviewStatChartSeries = [{
data: [
20,
40,
60,
80,
100,
80,
60,
],
}]
const reviewStatChartConfig = {
chart: {
height: 160,
width: 190,
type: 'bar',
toolbar: { show: false },
},
legend: { show: false },
grid: {
show: false,
padding: {
top: -25,
bottom: -12,
},
},
colors: [
config.colorsLabel.success,
config.colorsLabel.success,
config.colorsLabel.success,
config.colorsLabel.success,
config.colors.success,
config.colorsLabel.success,
config.colorsLabel.success,
],
plotOptions: {
bar: {
barHeight: '75%',
columnWidth: '25%',
borderRadius: 4,
distributed: true,
},
},
dataLabels: { enabled: false },
xaxis: {
categories: [
'M',
'T',
'W',
'T',
'F',
'S',
'S',
],
axisBorder: { show: false },
axisTicks: { show: false },
labels: {
style: {
colors: labelColor,
fontSize: '13px',
},
},
},
yaxis: { labels: { show: false } },
responsive: [
{
breakpoint: 0,
options: {
chart: { width: '100%' },
plotOptions: { bar: { columnWidth: '40%' } },
},
},
{
breakpoint: 1440,
options: {
chart: {
height: 150,
width: 190,
toolbar: { show: !1 },
},
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 1400,
options: {
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 1200,
options: {
chart: {
height: 130,
width: 190,
toolbar: { show: !1 },
},
plotOptions: {
bar: {
borderRadius: 6,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 992,
chart: {
height: 150,
width: 190,
toolbar: { show: !1 },
},
options: {
plotOptions: {
bar: {
borderRadius: 5,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 883,
options: {
plotOptions: {
bar: {
borderRadius: 5,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 768,
options: {
chart: {
height: 150,
width: 190,
toolbar: { show: !1 },
},
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 600,
options: {
chart: {
width: '100%',
height: '200',
type: 'bar',
},
plotOptions: {
bar: {
borderRadius: 6,
columnWidth: '30% ',
},
},
},
},
{
breakpoint: 420,
options: {
plotOptions: {
chart: {
width: '100%',
height: '200',
type: 'bar',
},
bar: {
borderRadius: 4,
columnWidth: '30%',
},
},
},
},
],
}
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Total Review Card -->
<VCard>
<VCardText>
<div class="d-flex gap-6 flex-column flex-sm-row">
<div>
<div class="d-flex align-center gap-x-2">
<h3 class="text-h3 text-primary">
4.89
</h3>
<VIcon
icon="tabler-star-filled"
color="primary"
size="32"
/>
</div>
<h6 class="my-2 text-h6">
Total 187 reviews
</h6>
<div class="mb-2 text-wrap">
All reviews are from genuine customers
</div>
<VChip
color="primary"
label
size="small"
>
+5 This week
</VChip>
</div>
<VDivider :vertical="$vuetify.display.smAndUp" />
<div class="flex-grow-1">
<div
v-for="(review, index) in reviewCardData"
:key="index"
class="d-flex align-center gap-x-4"
:class="index !== reviewCardData.length - 1 ? 'mb-3' : ''"
>
<div class="text-no-wrap text-sm">
{{ review.rating }} Star
</div>
<div
class="flex-grow-1"
style="min-inline-size: 150px;"
>
<VProgressLinear
color="primary"
height="8"
:model-value="(review.value / 185) * 100"
rounded
/>
</div>
<div class="text-sm">
{{ review.value }}
</div>
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="6"
>
<VCard>
<VCardText>
<div class="d-flex justify-space-between flex-sm-row flex-column">
<div>
<h5 class="text-h5 mb-2">
Reviews statistics
</h5>
<div class="mb-8 mb-sm-12">
<div class="d-inline-block me-2">
12 New Reviews
</div>
<VChip
color="success"
size="small"
label
>
+8.4%
</VChip>
</div>
<div>
<div class="text-high-emphasis text-body-1 mb-2">
<span class="text-success">87%</span> Positive Reviews
</div>
<div class="text-body-2">
Weekly Report
</div>
</div>
</div>
<div>
<VueApexCharts
id="shipment-statistics"
type="bar"
height="152"
:options="reviewStatChartConfig"
:series="reviewStatChartSeries"
/>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex justify-space-between flex-wrap gap-6 ">
<div>
<AppTextField
v-model="searchQuery"
style="max-inline-size: 200px; min-inline-size: 200px;"
placeholder="Search Review"
/>
</div>
<div class="d-flex flex-row gap-4 align-center flex-wrap">
<AppSelect
v-model="itemsPerPage"
:items="[10, 25, 50, 100]"
style="inline-size: 6.25rem;"
/>
<AppSelect
v-model="selectedStatus"
style="max-inline-size: 7.5rem;min-inline-size: 7.5rem;"
:items="[
{ title: 'All', value: 'All' },
{ title: 'Published', value: 'Published' },
{ title: 'Pending', value: 'Pending' },
]"
/>
<VBtn
prepend-icon="tabler-upload"
variant="tonal"
color="default"
>
Export
</VBtn>
</div>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:headers="headers"
:items="reviews"
show-select
:items-length="totalReviews"
class="text-no-wrap"
@update:options="updateOptions"
>
<template #item.product="{ item }">
<div class="d-flex gap-x-4 align-center">
<VAvatar
:image="item.productImage"
:size="38"
variant="tonal"
rounded
/>
<div class="d-flex flex-column">
<h6 class="text-h6">
{{ item.product }}
</h6>
<div class="text-body-2 text-wrap clamp-text">
{{ item.companyName }}
</div>
</div>
</div>
</template>
<template #item.reviewer="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
:image="item.avatar"
size="34"
/>
<div class="d-flex flex-column">
<RouterLink
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: 478426 } }"
class="font-weight-medium"
style="line-height: 1.375rem;"
>
{{ item.reviewer }}
</RouterLink>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<template #item.review="{ item }">
<div class="my-4">
<VRating
:id="item.id"
:name="`${item.id}`"
readonly
:model-value="item.review"
size="24"
class="mb-1"
/>
<h6 class="text-h6 mb-1">
{{ item.head }}
</h6>
<p class="text-sm text-wrap mb-0">
{{ item.para }}
</p>
</div>
</template>
<template #item.date="{ item }">
{{ new Date(item.date).toDateString() }}
</template>
<template #item.status="{ item }">
<VChip
:color="item.status === 'Published' ? 'success' : 'warning'"
label
size="small"
>
{{ item.status }}
</VChip>
</template>
<template #item.actions="{ item }">
<IconBtn>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem
value="view"
:to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.id } }"
>
View
</VListItem>
<VListItem
value="delete"
@click="deleteReview(item.id)"
>
Delete
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalReviews"
/>
</template>
</VDataTableServer>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart";
</style>

View File

@@ -0,0 +1,535 @@
<script setup>
import product21 from '@images/ecommerce-images/product-21.png'
import product22 from '@images/ecommerce-images/product-22.png'
import product23 from '@images/ecommerce-images/product-23.png'
import product24 from '@images/ecommerce-images/product-24.png'
const orderData = ref()
const route = useRoute('apps-ecommerce-order-details-id')
const { data } = await useApi(`/apps/ecommerce/orders/${ route.params.id }`)
if (data.value)
orderData.value = data.value
const isConfirmDialogVisible = ref(false)
const isUserInfoEditDialogVisible = ref(false)
const isEditAddressDialogVisible = ref(false)
const headers = [
{
title: 'Product',
key: 'productName',
},
{
title: 'Price',
key: 'price',
},
{
title: 'Quantity',
key: 'quantity',
},
{
title: 'Total',
key: 'total',
},
]
const resolvePaymentStatus = payment => {
if (payment === 1)
return {
text: 'Paid',
color: 'success',
}
if (payment === 2)
return {
text: 'Pending',
color: 'warning',
}
if (payment === 3)
return {
text: 'Cancelled',
color: 'secondary',
}
if (payment === 4)
return {
text: 'Failed',
color: 'error',
}
}
const resolveStatus = status => {
if (status === 'Delivered')
return {
text: 'Delivered',
color: 'success',
}
if (status === 'Out for Delivery')
return {
text: 'Out for Delivery',
color: 'primary',
}
if (status === 'Ready to Pickup')
return {
text: 'Ready to Pickup',
color: 'info',
}
if (status === 'Dispatched')
return {
text: 'Dispatched',
color: 'warning',
}
}
const userData = {
id: null,
fullName: orderData.value?.customer,
company: 'Pixinvent',
role: 'Web developer',
username: 'T1940',
country: 'United States',
contact: '+1 (609) 972-22-22',
email: orderData.value?.email,
status: 'Active',
taxId: 'Tax-8894',
language: 'English',
currentPlan: '',
avatar: '',
taskDone: null,
projectDone: null,
}
const currentBillingAddress = {
fullName: orderData.value?.customer,
firstName: orderData.value?.customer.split(' ')[0],
lastName: orderData.value?.customer.split(' ')[1],
selectedCountry: 'USA',
addressLine1: '45 Rocker Terrace',
addressLine2: 'Latheronwheel',
landmark: 'KW5 8NW, London',
contact: '+1 (609) 972-22-22',
country: 'USA',
city: 'London',
state: 'London',
zipCode: 110001,
}
const orderDetail = [
{
productName: 'OnePlus 7 Pro',
productImage: product21,
subtitle: 'Storage: 128gb',
price: 799,
quantity: 1,
total: 799,
},
{
productName: 'Face Cream',
productImage: product22,
subtitle: 'Gender: Women',
price: 89,
quantity: 1,
total: 89,
},
{
productName: 'Wooden Chair',
productImage: product23,
subtitle: 'Material: Woodem',
price: 289,
quantity: 2,
total: 578,
},
{
productName: 'Nike Jorden',
productImage: product24,
subtitle: 'Size: 8UK',
price: 299,
quantity: 2,
total: 598,
},
]
</script>
<template>
<div>
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<div class="d-flex gap-2 align-center mb-2 flex-wrap">
<h5 class="text-h5">
Order #{{ route.params.id }}
</h5>
<div class="d-flex gap-x-2">
<VChip
v-if="orderData?.payment"
variant="tonal"
:color="resolvePaymentStatus(orderData.payment)?.color"
label
size="small"
>
{{ resolvePaymentStatus(orderData.payment)?.text }}
</VChip>
<VChip
v-if="orderData?.status"
v-bind="resolveStatus(orderData?.status)"
label
size="small"
/>
</div>
</div>
<div class="text-body-1">
Aug 17, 2020, 5:48 (ET)
</div>
</div>
<VBtn
variant="tonal"
color="error"
@click="isConfirmDialogVisible = !isConfirmDialogVisible"
>
Delete Order
</VBtn>
</div>
<VRow>
<VCol
cols="12"
md="8"
>
<!-- 👉 Order Details -->
<VCard class="mb-6">
<VCardItem>
<template #title>
<h5 class="text-h5">
Order Details
</h5>
</template>
<template #append>
<div class="text-base font-weight-medium text-primary cursor-pointer">
Edit
</div>
</template>
</VCardItem>
<VDivider />
<VDataTable
:headers="headers"
:items="orderDetail"
item-value="productName"
show-select
class="text-no-wrap"
>
<template #item.productName="{ item }">
<div class="d-flex gap-x-3 align-center">
<VAvatar
size="34"
:image="item.productImage"
:rounded="0"
/>
<div class="d-flex flex-column align-start">
<h6 class="text-h6">
{{ item.productName }}
</h6>
<span class="text-body-2">
{{ item.subtitle }}
</span>
</div>
</div>
</template>
<template #item.price="{ item }">
<div class="text-body-1">
${{ item.price }}
</div>
</template>
<template #item.total="{ item }">
<div class="text-body-1">
${{ item.total }}
</div>
</template>
<template #item.quantity="{ item }">
<div class="text-body-1">
{{ item.quantity }}
</div>
</template>
<template #bottom />
</VDataTable>
<VDivider />
<VCardText>
<div class="d-flex align-end flex-column">
<table class="text-high-emphasis">
<tbody>
<tr>
<td width="200px">
Subtotal:
</td>
<td class="font-weight-medium">
$2,093
</td>
</tr>
<tr>
<td>Shipping Total: </td>
<td class="font-weight-medium">
$2
</td>
</tr>
<tr>
<td>Tax: </td>
<td class="font-weight-medium">
$28
</td>
</tr>
<tr>
<td class="text-high-emphasis font-weight-medium">
Total:
</td>
<td class="font-weight-medium">
$2,113
</td>
</tr>
</tbody>
</table>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Activity -->
<VCard title="Shipping Activity">
<VCardText>
<VTimeline
truncate-line="both"
line-inset="9"
align="start"
side="end"
line-color="primary"
density="compact"
>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<div class="app-timeline-title">
Order was placed (Order ID: #32543)
</div>
<div class="app-timeline-meta">
Tuesday 10:20 AM
</div>
</div>
<p class="app-timeline-text mb-0 mt-3">
Your order has been placed successfully
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Pick-up</span>
<span class="app-timeline-meta">Wednesday 11:29 AM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Pick-up scheduled with courier
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Dispatched</span>
<span class="app-timeline-meta">Thursday 8:15 AM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Item has been picked up by courier.
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Package arrived</span>
<span class="app-timeline-meta">Saturday 15:20 AM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Package arrived at an Amazon facility, NY
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Dispatched for delivery</span>
<span class="app-timeline-meta">Today 14:12 PM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Package has left an Amazon facility , NY
</p>
</VTimelineItem>
<VTimelineItem
dot-color="secondary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Delivery</span>
</div>
<p class="app-timeline-text mb-4 mt-3">
Package will be delivered by tomorrow
</p>
</VTimelineItem>
</VTimeline>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<!-- 👉 Customer Details -->
<VCard class="mb-6">
<VCardText class="d-flex flex-column gap-y-6">
<h5 class="text-h5">
Customer details
</h5>
<div class="d-flex align-center">
<VAvatar
v-if="orderData"
:variant="!orderData?.avatar.length ? 'tonal' : undefined"
:rounded="1"
class="me-3"
>
<VImg
v-if="orderData?.avatar"
:src="orderData?.avatar"
/>
<span
v-else
class="font-weight-medium"
>{{ avatarText(orderData?.customer) }}</span>
</VAvatar>
<div>
<h6 class="text-h6">
{{ orderData?.customer }}
</h6>
<div class="text-body-1">
Customer ID: #{{ orderData?.order }}
</div>
</div>
</div>
<div class="d-flex gap-x-3 align-center">
<VAvatar
variant="tonal"
color="success"
>
<VIcon icon="tabler-shopping-cart" />
</VAvatar>
<h6 class="text-h6">
12 Orders
</h6>
</div>
<div class="d-flex flex-column gap-y-1">
<div class="d-flex justify-space-between align-center">
<h6 class="text-h6">
Contact Info
</h6>
<div
class="text-base text-primary cursor-pointer font-weight-medium"
@click="isUserInfoEditDialogVisible = !isUserInfoEditDialogVisible"
>
Edit
</div>
</div>
<span>Email: {{ orderData?.email }}</span>
<span>Mobile: +1 (609) 972-22-22</span>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Address -->
<VCard class="mb-6">
<VCardItem>
<VCardTitle>Shipping Address</VCardTitle>
<template #append>
<div class="d-flex align-center justify-space-between">
<div
class="text-base font-weight-medium text-primary cursor-pointer"
@click="isEditAddressDialogVisible = !isEditAddressDialogVisible"
>
Edit
</div>
</div>
</template>
</VCardItem>
<VCardText>
<div class="text-body-1">
45 Rocker Terrace <br> Latheronwheel <br> KW5 8NW, London <br> UK
</div>
</VCardText>
</VCard>
<!-- 👉 Billing Address -->
<VCard>
<VCardText>
<div class="d-flex align-center justify-space-between mb-2">
<h5 class="text-h5">
Billing Address
</h5>
<div
class="text-base font-weight-medium text-primary cursor-pointer"
@click="isEditAddressDialogVisible = !isEditAddressDialogVisible"
>
Edit
</div>
</div>
<div>
45 Rocker Terrace <br> Latheronwheel <br> KW5 8NW, London <br> UK
</div>
<div class="mt-6">
<h5 class="text-h5 mb-1">
Mastercard
</h5>
<div class="text-body-1">
Card Number: ******{{ orderData?.methodNumber }}
</div>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
<ConfirmDialog
v-model:is-dialog-visible="isConfirmDialogVisible"
confirmation-question="Are you sure to cancel your Order?"
cancel-msg="Order cancelled!!"
cancel-title="Cancelled"
confirm-msg="Your order cancelled successfully."
confirm-title="Cancelled!"
/>
<UserInfoEditDialog
v-model:is-dialog-visible="isUserInfoEditDialogVisible"
:user-data="userData"
/>
<AddEditAddressDialog
v-model:is-dialog-visible="isEditAddressDialogVisible"
:billing-address="currentBillingAddress"
/>
</div>
</template>

View File

@@ -0,0 +1,386 @@
<script setup>
import masterCardDark from '@images/icons/payments/img/master-dark.png'
import masterCardLight from '@images/icons/payments/img/mastercard.png'
import paypalDark from '@images/icons/payments/img/paypal-dark.png'
import paypalLight from '@images/icons/payments/img/paypal-light.png'
const widgetData = ref([
{
title: 'Pending Payment',
value: 56,
icon: 'tabler-calendar-stats',
},
{
title: 'Completed',
value: 12689,
icon: 'tabler-checks',
},
{
title: 'Refunded',
value: 124,
icon: 'tabler-wallet',
},
{
title: 'Failed',
value: 32,
icon: 'tabler-alert-octagon',
},
])
const mastercard = useGenerateImageVariant(masterCardLight, masterCardDark)
const paypal = useGenerateImageVariant(paypalLight, paypalDark)
const searchQuery = ref('')
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const selectedRows = ref([])
// Data table Headers
const headers = [
{
title: 'Order',
key: 'order',
},
{
title: 'Date',
key: 'date',
},
{
title: 'Customers',
key: 'customers',
},
{
title: 'Payment',
key: 'payment',
sortable: false,
},
{
title: 'Status',
key: 'status',
},
{
title: 'Method',
key: 'method',
sortable: false,
},
{
title: 'Action',
key: 'actions',
sortable: false,
},
]
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const resolvePaymentStatus = status => {
if (status === 1)
return {
text: 'Paid',
color: 'success',
}
if (status === 2)
return {
text: 'Pending',
color: 'warning',
}
if (status === 3)
return {
text: 'Cancelled',
color: 'secondary',
}
if (status === 4)
return {
text: 'Failed',
color: 'error',
}
}
const resolveStatus = status => {
if (status === 'Delivered')
return {
text: 'Delivered',
color: 'success',
}
if (status === 'Out for Delivery')
return {
text: 'Out for Delivery',
color: 'primary',
}
if (status === 'Ready to Pickup')
return {
text: 'Ready to Pickup',
color: 'info',
}
if (status === 'Dispatched')
return {
text: 'Dispatched',
color: 'warning',
}
}
const {
data: ordersData,
execute: fetchOrders,
} = await useApi(createUrl('/apps/ecommerce/orders', {
query: {
q: searchQuery,
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const orders = computed(() => ordersData.value.orders)
const totalOrder = computed(() => ordersData.value.total)
const deleteOrder = async id => {
await $api(`/apps/ecommerce/orders/${ id }`, { method: 'DELETE' })
// Delete from selectedRows
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
// Refetch Orders
fetchOrders()
}
</script>
<template>
<div>
<VCard class="mb-6">
<!-- 👉 Widgets -->
<VCardText>
<VRow>
<template
v-for="(data, id) in widgetData"
:key="id"
>
<VCol
cols="12"
sm="6"
md="3"
class="px-6"
>
<div
class="d-flex justify-space-between"
:class="$vuetify.display.xs
? id !== widgetData.length - 1 ? 'border-b pb-4' : ''
: $vuetify.display.sm
? id < (widgetData.length / 2) ? 'border-b pb-4' : ''
: ''"
>
<div class="d-flex flex-column">
<h4 class="text-h4">
{{ data.value }}
</h4>
<div class="text-body-1">
{{ data.title }}
</div>
</div>
<VAvatar
variant="tonal"
rounded
size="42"
>
<VIcon
:icon="data.icon"
size="26"
class="text-high-emphasis"
/>
</VAvatar>
</div>
</VCol>
<VDivider
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
: $vuetify.display.smAndUp ? id % 2 === 0
: false"
vertical
inset
length="60"
/>
</template>
</VRow>
</VCardText>
</VCard>
<VCard>
<!-- 👉 Filters -->
<VCardText>
<div class="d-flex justify-sm-space-between justify-start flex-wrap gap-4">
<AppTextField
v-model="searchQuery"
placeholder="Search Order"
style=" max-inline-size: 200px; min-inline-size: 200px;"
/>
<div class="d-flex gap-x-4 align-center">
<AppSelect
v-model="itemsPerPage"
style="min-inline-size: 6.25rem;"
:items="[5, 10, 20, 50, 100]"
/>
<VBtn
variant="tonal"
color="secondary"
prepend-icon="tabler-upload"
text="Export"
/>
</div>
</div>
</VCardText>
<VDivider />
<!-- 👉 Order Table -->
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:headers="headers"
:items="orders"
:items-length="totalOrder"
show-select
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- Order ID -->
<template #item.order="{ item }">
<RouterLink :to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.order } }">
#{{ item.order }}
</RouterLink>
</template>
<!-- Date -->
<template #item.date="{ item }">
{{ new Date(item.date).toDateString() }}
</template>
<!-- Customers -->
<template #item.customers="{ item }">
<div class="d-flex align-center gap-x-3">
<VAvatar
size="34"
:color="!item.avatar.length ? 'primary' : ''"
:variant="!item.avatar.length ? 'tonal' : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span
v-else
class="font-weight-medium"
>{{ avatarText(item.customer) }}</span>
</VAvatar>
<div class="d-flex flex-column">
<div class="text-body-1 font-weight-medium">
<RouterLink
:to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }"
class="text-link"
>
{{ item.customer }}
</RouterLink>
</div>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<!-- Payments -->
<template #item.payment="{ item }">
<div
:class="`text-${resolvePaymentStatus(item.payment)?.color}`"
class="font-weight-medium d-flex align-center gap-x-2"
>
<VIcon
icon="tabler-circle-filled"
size="10"
/>
<div style="line-height: 22px;">
{{ resolvePaymentStatus(item.payment)?.text }}
</div>
</div>
</template>
<!-- Status -->
<template #item.status="{ item }">
<VChip
v-bind="resolveStatus(item.status)"
label
size="small"
/>
</template>
<!-- Method -->
<template #item.method="{ item }">
<div class="d-flex align-center">
<img
:src="item.method === 'mastercard' ? mastercard : paypal"
height="18"
>
<div class="text-body-1">
...{{ item.method === 'mastercard' ? item.methodNumber : '@gmail.com' }}
</div>
</div>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem
value="view"
:to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.order } }"
>
View
</VListItem>
<VListItem
value="delete"
@click="deleteOrder(item.id)"
>
Delete
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalOrder"
/>
</template>
</VDataTableServer>
</VCard>
</div>
</template>
<style lang="scss" scoped>
.customer-title:hover {
color: rgba(var(--v-theme-primary)) !important;
}
.product-widget {
border-block-end: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
padding-block-end: 1rem;
}
</style>

View File

@@ -0,0 +1,570 @@
<script setup>
import { ref } from 'vue'
const optionCounter = ref(1)
const activeTab = ref('Restock')
const isTaxChargeToProduct = ref(true)
const shippingList = [
{
desc: 'You\'ll be responsible for product delivery.Any damage or delay during shipping may cost you a Damage fee',
title: 'Fulfilled by Seller',
value: 'Fulfilled by Seller',
},
{
desc: 'Your product, Our responsibility.For a measly fee, we will handle the delivery process for you.',
title: 'Fulfilled by Company name',
value: 'Fulfilled by Company name',
},
]
const shippingType = ref('Fulfilled by Company name')
const deliveryType = ref('Worldwide delivery')
const selectedAttrs = ref([
'Biodegradable',
'Expiry Date',
])
const inventoryTabsData = [
{
icon: 'tabler-cube',
title: 'Restock',
value: 'Restock',
},
{
icon: 'tabler-car',
title: 'Shipping',
value: 'Shipping',
},
{
icon: 'tabler-map-pin',
title: 'Global Delivery',
value: 'Global Delivery',
},
{
icon: 'tabler-world',
title: 'Attributes',
value: 'Attributes',
},
{
icon: 'tabler-lock',
title: 'Advanced',
value: 'Advanced',
},
]
const content = ref(`<p>
Keep your account secure with authentication step.
</p>`)
</script>
<template>
<div>
<div class="d-flex flex-wrap justify-start justify-sm-space-between gap-y-4 gap-x-6 mb-6">
<div class="d-flex flex-column justify-center">
<h4 class="text-h4 font-weight-medium">
Add a new product
</h4>
<div class="text-body-1">
Orders placed across your store
</div>
</div>
<div class="d-flex gap-4 align-center flex-wrap">
<VBtn
variant="tonal"
color="secondary"
>
Discard
</VBtn>
<VBtn
variant="tonal"
color="primary"
>
Save Draft
</VBtn>
<VBtn>Publish Product</VBtn>
</div>
</div>
<VRow>
<VCol md="8">
<!-- 👉 Product Information -->
<VCard
class="mb-6"
title="Product Information"
>
<VCardText>
<VRow>
<VCol cols="12">
<AppTextField
label="Name"
placeholder="iPhone 14"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="SKU"
placeholder="FXSK123U"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Barcode"
placeholder="0123-4567"
/>
</VCol>
<VCol>
<span class="mb-1">Description (optional)</span>
<ProductDescriptionEditor
v-model="content"
placeholder="Product Description"
class="border rounded"
/>
</VCol>
</VRow>
</VCardText>
</VCard>
<!-- 👉 Media -->
<VCard class="mb-6">
<VCardItem>
<template #title>
Product Image
</template>
<template #append>
<span class="text-primary font-weight-medium text-sm cursor-pointer">Add Media from URL</span>
</template>
</VCardItem>
<VCardText>
<DropZone />
</VCardText>
</VCard>
<!-- 👉 Variants -->
<VCard
title="Variants"
class="mb-6"
>
<VCardText>
<template
v-for="i in optionCounter"
:key="i"
>
<VRow>
<VCol
cols="12"
md="4"
>
<AppSelect
:items="['Size', 'Color', 'Weight']"
placeholder="Select Variant"
label="Options"
/>
</VCol>
<VCol
cols="12"
md="8"
class="d-flex align-self-end"
>
<AppTextField
placeholder="38"
type="number"
/>
</VCol>
</VRow>
</template>
<VBtn
class="mt-6"
prepend-icon="tabler-plus"
@click="optionCounter++"
>
Add another option
</VBtn>
</VCardText>
</VCard>
<!-- 👉 Inventory -->
<VCard
title="Inventory"
class="inventory-card"
>
<VCardText>
<VRow>
<VCol
cols="12"
md="4"
>
<div class="pe-3">
<VTabs
v-model="activeTab"
direction="vertical"
color="primary"
class="v-tabs-pill"
>
<VTab
v-for="(tab, index) in inventoryTabsData"
:key="index"
>
<VIcon
:icon="tab.icon"
class="me-2"
/>
<div class="text-truncate font-weight-medium text-start">
{{ tab.title }}
</div>
</VTab>
</VTabs>
</div>
</VCol>
<VDivider :vertical="!$vuetify.display.smAndDown" />
<VCol
cols="12"
md="8"
>
<VWindow
v-model="activeTab"
class="w-100"
:touch="false"
>
<VWindowItem value="Restock">
<div class="d-flex flex-column gap-y-4 ps-3">
<p class="mb-0">
Options
</p>
<div class="d-flex gap-x-4 align-center">
<AppTextField
label="Add to Stock"
placeholder="Quantity"
/>
<VBtn class="align-self-end">
Confirm
</VBtn>
</div>
<div>
<div class="text-base text-high-emphasis pb-2">
Product in stock now: 54
</div>
<div class="text-base text-high-emphasis pb-2">
Product in transit: 390
</div>
<div class="text-base text-high-emphasis pb-2">
Last time restocked: 24th June, 2022
</div>
<div class="text-base text-high-emphasis pb-2">
Total stock over lifetime: 2,430
</div>
</div>
</div>
</VWindowItem>
<VWindowItem value="Shipping">
<VRadioGroup
v-model="shippingType"
label="Shipping Type"
class="ms-3"
>
<VRadio
v-for="item in shippingList"
:key="item.value"
:value="item.value"
class="mb-4"
>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
{{ item.title }}
</div>
<div class="text-sm">
{{ item.desc }}
</div>
</div>
</template>
</VRadio>
</VRadioGroup>
</VWindowItem>
<VWindowItem value="Global Delivery">
<div class="ps-3">
<h5 class="text-h5 mb-6">
Global Delivery
</h5>
<VRadioGroup
v-model="deliveryType"
label="Global Delivery"
>
<VRadio
value="Worldwide delivery"
class="mb-4"
>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
Worldwide delivery
</div>
<div class="text-sm">
Only available with Shipping method:
<span class="text-primary">
Fulfilled by Company name
</span>
</div>
</div>
</template>
</VRadio>
<VRadio
value="Selected Countries"
class="mb-4"
>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
Selected Countries
</div>
<VTextField
placeholder="USA"
style="min-inline-size: 200px;"
/>
</div>
</template>
</VRadio>
<VRadio>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
Local delivery
</div>
<div class="text-sm">
Deliver to your country of residence
<span class="text-primary">
Change profile address
</span>
</div>
</div>
</template>
</VRadio>
</VRadioGroup>
</div>
</VWindowItem>
<VWindowItem value="Attributes">
<div class="ps-3">
<div class="mb-6 text-h6">
Attributes
</div>
<div class="d-flex flex-column gap-y-1">
<VCheckbox
v-model="selectedAttrs"
label="Fragile Product"
value="Fragile Product"
/>
<VCheckbox
v-model="selectedAttrs"
value="Biodegradable"
label="Biodegradable"
/>
<VCheckbox
v-model="selectedAttrs"
value="Frozen Product"
>
<template #label>
<div class="d-flex flex-column mb-1">
<div>Frozen Product</div>
<VTextField
placeholder="40 C"
type="number"
/>
</div>
</template>
</VCheckbox>
<VCheckbox
v-model="selectedAttrs"
value="Expiry Date"
>
<template #label>
<div class="d-flex flex-column mb-1">
<div>Expiry Date of Product</div>
<AppDateTimePicker
model-value="2025-06-14"
placeholder="Select a Date"
/>
</div>
</template>
</VCheckbox>
</div>
</div>
</VWindowItem>
<VWindowItem value="Advanced">
<div class="ps-3">
<h5 class="text-h5 mb-6">
Advanced
</h5>
<div class="d-flex flex-sm-row flex-column flex-wrap justify-space-between gap-x-6 gap-y-4">
<AppSelect
label="Product ID Type"
placeholder="Select Product Type"
:items="['ISBN', 'UPC', 'EAN', 'JAN']"
/>
<AppTextField
label="Product Id"
placeholder="100023"
/>
</div>
</div>
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<VCol
md="4"
cols="12"
>
<!-- 👉 Pricing -->
<VCard
title="Pricing"
class="mb-6"
>
<VCardText>
<AppTextField
label="Best Price"
placeholder="Price"
class="mb-6"
/>
<AppTextField
label="Discounted Price"
placeholder="$499"
class="mb-6"
/>
<VCheckbox
v-model="isTaxChargeToProduct"
label="Charge Tax on this product"
/>
<VDivider class="my-2" />
<div class="d-flex flex-raw align-center justify-space-between ">
<span>In stock</span>
<VSwitch density="compact" />
</div>
</VCardText>
</VCard>
<!-- 👉 Organize -->
<VCard title="Organize">
<VCardText>
<div class="d-flex flex-column gap-y-4">
<AppSelect
placeholder="Select Vendor"
label="Vendor"
:items="['Men\'s Clothing', 'Women\'s Clothing', 'Kid\'s Clothing']"
/>
<div>
<VLabel class="d-flex">
<div class="d-flex text-sm justify-space-between w-100">
<div class="text-high-emphasis">
Category
</div>
</div>
</VLabel>
<div class="d-flex gap-x-4">
<AppSelect
placeholder="Select Category"
:items="['Household', 'Office', 'Electronics', 'Management', 'Automotive']"
/>
<VBtn
rounded
icon="tabler-plus"
variant="tonal"
/>
</div>
</div>
<AppSelect
placeholder="Select Collection"
label="Collection"
:items="['Men\'s Clothing', 'Women\'s Clothing', 'Kid\'s Clothing']"
/>
<AppSelect
placeholder="Select Status"
label="Status"
:items="['Published', 'Inactive', 'Scheduled']"
/>
<AppTextField
label="Tags"
placeholder="Fashion, Trending, Summer"
/>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style lang="scss" scoped>
.drop-zone {
border: 2px dashed rgba(var(--v-theme-on-surface), 0.12);
border-radius: 6px;
}
</style>
<style lang="scss">
.inventory-card {
.v-tabs.v-tabs-pill {
.v-slide-group-item--active.v-tab--selected.text-primary {
h6 {
color: #fff !important;
}
}
}
.v-radio-group,
.v-checkbox {
.v-selection-control {
align-items: start !important;
}
.v-label.custom-input {
border: none !important;
}
}
}
.ProseMirror {
p {
margin-block-end: 0;
}
padding: 0.5rem;
outline: none;
p.is-editor-empty:first-child::before {
block-size: 0;
color: #adb5bd;
content: attr(data-placeholder);
float: inline-start;
pointer-events: none;
}
}
</style>

View File

@@ -0,0 +1,255 @@
<script setup>
import ECommerceAddCategoryDrawer from '@/views/apps/ecommerce/ECommerceAddCategoryDrawer.vue'
import product1 from '@images/ecommerce-images/product-1.png'
import product10 from '@images/ecommerce-images/product-10.png'
import product11 from '@images/ecommerce-images/product-11.png'
import product12 from '@images/ecommerce-images/product-12.png'
import product14 from '@images/ecommerce-images/product-14.png'
import product17 from '@images/ecommerce-images/product-17.png'
import product19 from '@images/ecommerce-images/product-19.png'
import product2 from '@images/ecommerce-images/product-2.png'
import product25 from '@images/ecommerce-images/product-25.png'
import product28 from '@images/ecommerce-images/product-28.png'
import product9 from '@images/ecommerce-images/product-9.png'
const categoryData = ref([
{
id: 1,
categoryTitle: 'Smart Phone',
description: 'Choose from wide range of smartphones online at best prices.',
totalProduct: 12548,
totalEarning: 98784,
image: product1,
},
{
id: 2,
categoryTitle: 'Clothing, Shoes, and jewellery',
description: 'Fashion for a wide selection of clothing, shoes, jewellery and watches.',
totalProduct: 4689,
totalEarning: 45627,
image: product9,
},
{
id: 3,
categoryTitle: 'Home and Kitchen',
description: 'Browse through the wide range of Home and kitchen products.',
totalProduct: 12548,
totalEarning: 98784,
image: product10,
},
{
id: 4,
categoryTitle: 'Beauty and Personal Care',
description: 'Explore beauty and personal care products, shop makeup and etc.',
totalProduct: 12548,
totalEarning: 98784,
image: product19,
},
{
id: 5,
categoryTitle: 'Books',
description: 'Over 25 million titles across categories such as business and etc.',
totalProduct: 12548,
totalEarning: 98784,
image: product25,
},
{
id: 6,
categoryTitle: 'Games',
description: 'Every month, get exclusive in-game loot, free games, a free subscription.',
totalProduct: 12548,
totalEarning: 98784,
image: product12,
},
{
id: 7,
categoryTitle: 'Baby Products',
description: 'Buy baby products across different categories from top brands.',
totalProduct: 12548,
totalEarning: 98784,
image: product14,
},
{
id: 8,
categoryTitle: 'Grocery',
description: 'Shop grocery Items through at best prices in India.',
totalProduct: 12548,
totalEarning: 98784,
image: product28,
},
{
id: 9,
categoryTitle: 'Computer Accessories',
description: 'Enhance your computing experience with our range of computer accessories.',
totalProduct: 9876,
totalEarning: 65421,
image: product17,
},
{
id: 10,
categoryTitle: 'Fitness Tracker',
description: 'Monitor your health and fitness goals with our range of advanced fitness trackers.',
totalProduct: 1987,
totalEarning: 32067,
image: product10,
},
{
id: 11,
categoryTitle: 'Smart Home Devices',
description: 'Transform your home into a smart home with our innovative smart home devices.',
totalProduct: 2345,
totalEarning: 87654,
image: product11,
},
{
id: 12,
categoryTitle: 'Audio Speakers',
description: 'Immerse yourself in rich audio quality with our wide range of speakers.',
totalProduct: 5678,
totalEarning: 32145,
image: product2,
},
])
const headers = [
{
title: 'Categories',
key: 'categoryTitle',
},
{
title: 'Total Products',
key: 'totalProduct',
},
{
title: 'Total Earning',
key: 'totalEarning',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const itemsPerPage = ref(10)
const page = ref(1)
const searchQuery = ref('')
const isAddProductDrawerOpen = ref(false)
</script>
<template>
<div>
<VCard>
<VCardText>
<div class="d-flex justify-sm-space-between flex-wrap gap-y-4 gap-x-6 justify-start">
<AppTextField
v-model="searchQuery"
placeholder="Search Category"
style="max-inline-size: 280px; min-inline-size: 280px;"
/>
<div class="d-flex align-center flex-wrap gap-4">
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 15]"
style="max-inline-size: 100px; min-inline-size: 100px;"
/>
<VBtn
prepend-icon="tabler-plus"
@click="isAddProductDrawerOpen = !isAddProductDrawerOpen"
>
Add Category
</VBtn>
</div>
</div>
</VCardText>
<VDivider />
<div class="category-table">
<VDataTable
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:headers="headers"
:items="categoryData"
item-value="categoryTitle"
:search="searchQuery"
show-select
class="text-no-wrap"
>
<template #item.actions>
<IconBtn>
<VIcon
icon="tabler-edit"
size="22"
/>
</IconBtn>
<IconBtn>
<VIcon
icon="tabler-dots-vertical"
size="22"
/>
</IconBtn>
</template>
<template #item.categoryTitle="{ item }">
<div class="d-flex gap-x-3 align-center">
<VAvatar
variant="tonal"
rounded
size="38"
>
<img
:src="item.image"
:alt="item.categoryTitle"
width="38"
height="38"
>
</VAvatar>
<div>
<h6 class="text-h6">
{{ item.categoryTitle }}
</h6>
<div class="text-body-2">
{{ item.description }}
</div>
</div>
</div>
</template>
<template #item.totalEarning="{ item }">
<div class="text-body-1 text-end pe-4">
{{ (item.totalEarning).toLocaleString("en-IN", { style: "currency", currency: 'USD' }) }}
</div>
</template>
<template #item.totalProduct="{ item }">
<div class="text-end pe-4">
{{ (item.totalProduct).toLocaleString() }}
</div>
</template>
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="categoryData.length"
/>
</template>
</VDataTable>
</div>
</VCard>
<ECommerceAddCategoryDrawer v-model:is-drawer-open="isAddProductDrawerOpen" />
</div>
</template>
<style lang="scss">
.category-table {
.v-table {
th:nth-child(3),
th:nth-child(4) {
.v-data-table-header__content {
justify-content: end;
}
}
}
}
</style>

View File

@@ -0,0 +1,495 @@
<script setup>
const widgetData = ref([
{
title: 'In-Store Sales',
value: '$5,345',
icon: 'tabler-smart-home',
desc: '5k orders',
change: 5.7,
},
{
title: 'Website Sales',
value: '$674,347',
icon: 'tabler-device-laptop',
desc: '21k orders',
change: 12.4,
},
{
title: 'Discount',
value: '$14,235',
icon: 'tabler-gift',
desc: '6k orders',
},
{
title: 'Affiliate',
value: '$8,345',
icon: 'tabler-wallet',
desc: '150 orders',
change: -3.5,
},
])
const headers = [
{
title: 'Product',
key: 'product',
},
{
title: 'Category',
key: 'category',
},
{
title: 'Stock',
key: 'stock',
sortable: false,
},
{
title: 'SKU',
key: 'sku',
},
{
title: 'Price',
key: 'price',
},
{
title: 'QTY',
key: 'qty',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const selectedStatus = ref()
const selectedCategory = ref()
const selectedStock = ref()
const searchQuery = ref('')
const selectedRows = ref([])
const status = ref([
{
title: 'Scheduled',
value: 'Scheduled',
},
{
title: 'Publish',
value: 'Published',
},
{
title: 'Inactive',
value: 'Inactive',
},
])
const categories = ref([
{
title: 'Accessories',
value: 'Accessories',
},
{
title: 'Home Decor',
value: 'Home Decor',
},
{
title: 'Electronics',
value: 'Electronics',
},
{
title: 'Shoes',
value: 'Shoes',
},
{
title: 'Office',
value: 'Office',
},
{
title: 'Games',
value: 'Games',
},
])
const stockStatus = ref([
{
title: 'In Stock',
value: true,
},
{
title: 'Out of Stock',
value: false,
},
])
// 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
}
const resolveCategory = category => {
if (category === 'Accessories')
return {
color: 'error',
icon: 'tabler-device-watch',
}
if (category === 'Home Decor')
return {
color: 'info',
icon: 'tabler-home',
}
if (category === 'Electronics')
return {
color: 'primary',
icon: 'tabler-device-imac',
}
if (category === 'Shoes')
return {
color: 'success',
icon: 'tabler-shoe',
}
if (category === 'Office')
return {
color: 'warning',
icon: 'tabler-briefcase',
}
if (category === 'Games')
return {
color: 'primary',
icon: 'tabler-device-gamepad-2',
}
}
const resolveStatus = statusMsg => {
if (statusMsg === 'Scheduled')
return {
text: 'Scheduled',
color: 'warning',
}
if (statusMsg === 'Published')
return {
text: 'Publish',
color: 'success',
}
if (statusMsg === 'Inactive')
return {
text: 'Inactive',
color: 'error',
}
}
const {
data: productsData,
execute: fetchProducts,
} = await useApi(createUrl('/apps/ecommerce/products', {
query: {
q: searchQuery,
stock: selectedStock,
category: selectedCategory,
status: selectedStatus,
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const products = computed(() => productsData.value.products)
const totalProduct = computed(() => productsData.value.total)
const deleteProduct = async id => {
await $api(`apps/ecommerce/products/${ id }`, { method: 'DELETE' })
// Delete from selectedRows
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
// Refetch products
fetchProducts()
}
</script>
<template>
<div>
<!-- 👉 widgets -->
<VCard class="mb-6">
<VCardText class="px-3">
<VRow>
<template
v-for="(data, id) in widgetData"
:key="id"
>
<VCol
cols="12"
sm="6"
md="3"
class="px-6"
>
<div
class="d-flex justify-space-between"
:class="$vuetify.display.xs
? id !== widgetData.length - 1 ? 'border-b pb-4' : ''
: $vuetify.display.sm
? id < (widgetData.length / 2) ? 'border-b pb-4' : ''
: ''"
>
<div class="d-flex flex-column gap-y-1">
<div class="text-body-1 text-capitalize">
{{ data.title }}
</div>
<h4 class="text-h4">
{{ data.value }}
</h4>
<div class="d-flex align-center gap-x-2">
<div class="text-no-wrap">
{{ data.desc }}
</div>
<VChip
v-if="data.change"
label
:color="data.change > 0 ? 'success' : 'error'"
size="small"
>
{{ prefixWithPlus(data.change) }}%
</VChip>
</div>
</div>
<VAvatar
variant="tonal"
rounded
size="44"
>
<VIcon
:icon="data.icon"
size="28"
class="text-high-emphasis"
/>
</VAvatar>
</div>
</VCol>
<VDivider
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
: $vuetify.display.smAndUp ? id % 2 === 0
: false"
vertical
inset
length="92"
/>
</template>
</VRow>
</VCardText>
</VCard>
<!-- 👉 products -->
<VCard
title="Filters"
class="mb-6"
>
<VCardText>
<VRow>
<!-- 👉 Select Status -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedStatus"
placeholder="Status"
:items="status"
clearable
clear-icon="tabler-x"
/>
</VCol>
<!-- 👉 Select Category -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedCategory"
placeholder="Category"
:items="categories"
clearable
clear-icon="tabler-x"
/>
</VCol>
<!-- 👉 Select Stock Status -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedStock"
placeholder="Stock"
:items="stockStatus"
clearable
clear-icon="tabler-x"
/>
</VCol>
</VRow>
</VCardText>
<VDivider />
<div class="d-flex flex-wrap gap-4 ma-6">
<div class="d-flex align-center">
<!-- 👉 Search -->
<AppTextField
v-model="searchQuery"
placeholder="Search Product"
style="inline-size: 200px;"
class="me-3"
/>
</div>
<VSpacer />
<div class="d-flex gap-4 flex-wrap align-center">
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 20, 25, 50]"
/>
<!-- 👉 Export button -->
<VBtn
variant="tonal"
color="secondary"
prepend-icon="tabler-upload"
>
Export
</VBtn>
<VBtn
color="primary"
prepend-icon="tabler-plus"
@click="$router.push('/apps/ecommerce/product/add')"
>
Add Product
</VBtn>
</div>
</div>
<VDivider class="mt-4" />
<!-- 👉 Datatable -->
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:headers="headers"
show-select
:items="products"
:items-length="totalProduct"
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- product -->
<template #item.product="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
v-if="item.image"
size="38"
variant="tonal"
rounded
:image="item.image"
/>
<div class="d-flex flex-column">
<span class="text-body-1 font-weight-medium text-high-emphasis">{{ item.productName }}</span>
<span class="text-body-2">{{ item.productBrand }}</span>
</div>
</div>
</template>
<!-- category -->
<template #item.category="{ item }">
<VAvatar
size="30"
variant="tonal"
:color="resolveCategory(item.category)?.color"
class="me-4"
>
<VIcon
:icon="resolveCategory(item.category)?.icon"
size="18"
/>
</VAvatar>
<span class="text-body-1 text-high-emphasis">{{ item.category }}</span>
</template>
<!-- stock -->
<template #item.stock="{ item }">
<VSwitch :model-value="item.stock" />
</template>
<!-- status -->
<template #item.status="{ item }">
<VChip
v-bind="resolveStatus(item.status)"
density="default"
label
size="small"
/>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn>
<VIcon icon="tabler-edit" />
</IconBtn>
<IconBtn>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem
value="download"
prepend-icon="tabler-download"
>
Download
</VListItem>
<VListItem
value="delete"
prepend-icon="tabler-trash"
@click="deleteProduct(item.id)"
>
Delete
</VListItem>
<VListItem
value="duplicate"
prepend-icon="tabler-copy"
>
Duplicate
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalProduct"
/>
</template>
</VDataTableServer>
</VCard>
</div>
</template>

View File

@@ -0,0 +1,393 @@
<script setup>
import paperImg from '@images/svg/paper-send.svg?raw'
import rocketImg from '@images/svg/rocket.svg?raw'
import userInfoImg from '@images/svg/user-info.svg?raw'
const rocketIcon = h('div', {
innerHTML: rocketImg,
style: 'font-size: 2.625rem;color: rgb(var(--v-theme-primary))',
})
const userInfoIcon = h('div', {
innerHTML: paperImg,
style: 'font-size: 2.625rem;color: rgb(var(--v-theme-primary))',
})
const paperIcon = h('div', {
innerHTML: userInfoImg,
style: 'font-size: 2.625rem;color: rgb(var(--v-theme-primary))',
})
const widgetData = [
{
title: 'Total Earning',
value: '$24,983',
icon: 'tabler-currency-dollar',
color: 'primary',
},
{
title: 'Unpaid Earning',
value: '$8,647',
icon: 'tabler-gift',
color: 'success',
},
{
title: 'Signup',
value: '2,367',
icon: 'tabler-users',
color: 'error',
},
{
title: 'Conversion Rate',
value: '4.5%',
icon: 'tabler-infinity',
color: 'info',
},
]
const stepsData = [
{
icon: rocketIcon,
desc: 'Create & validate your referral link and get',
value: '$50',
},
{
icon: paperIcon,
desc: 'For every new signup you get',
value: '10%',
},
{
icon: userInfoIcon,
desc: 'Get other friends to generate link and get',
value: '$100',
},
]
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
// Data Table Headers
const headers = [
{
title: 'Users',
key: 'users',
},
{
title: 'Referred ID',
key: 'referred-id',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Value',
key: 'value',
},
{
title: 'Earnings',
key: 'earning',
},
]
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const { data: referralData } = await useApi(createUrl('/apps/ecommerce/referrals', {
query: {
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const resolveAvatarbg = status => {
if (status === 'Rejected')
return { color: 'error' }
if (status === 'Unpaid')
return { color: 'warning' }
if (status === 'Paid')
return { color: 'success' }
}
const referrals = computed(() => referralData.value.referrals)
const totalReferrals = computed(() => referralData.value.total)
const resolveStatus = status => {
if (status === 'Rejected')
return {
text: 'Rejected',
color: 'error',
}
if (status === 'Unpaid')
return {
text: 'Unpaid',
color: 'warning',
}
if (status === 'Paid')
return {
text: 'Paid',
color: 'success',
}
}
</script>
<template>
<div>
<!-- 👉 Header -->
<VRow class="match-height">
<!-- 👉 Widgets -->
<VCol
v-for="(data, index) in widgetData"
:key="index"
cols="12"
md="3"
sm="6"
>
<VCard>
<VCardText>
<div class="d-flex justify-space-between align-center">
<div class="d-flex flex-column">
<h5 class="text-h5 mb-1">
{{ data.value }}
</h5>
<div class="text-body-2">
{{ data.title }}
</div>
</div>
<VAvatar
size="40"
variant="tonal"
:color="data.color"
>
<VIcon :icon="data.icon" />
</VAvatar>
</div>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Icon Steps -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardItem>
<VCardTitle class="mb-1">
How to use
</VCardTitle>
<VCardSubtitle>
Integrate your referral code in 3 easy steps.
</VCardSubtitle>
</VCardItem>
<VCardText>
<div class="d-flex flex-column flex-sm-row gap-6 justify-sm-space-between align-center">
<div
v-for="(step, index) in stepsData"
:key="index"
class="d-flex flex-column align-center gap-y-2"
style="max-inline-size: 185px;"
>
<div class="icon-container">
<VIcon
:icon="step.icon"
color="primary"
size="36"
/>
</div>
<div class="text-body-1 text-wrap text-center">
{{ step.desc }}
</div>
<h6 class="text-primary text-h6">
{{ step.value }}
</h6>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Invite -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardText>
<div class="mb-5">
<h5 class="text-h5 mb-5">
Invite your friends
</h5>
<div class="d-flex align-center flex-wrap gap-4 flex-wrap">
<AppTextField
label="Enter friends email address and invite them"
placeholder="Email Address"
/>
<VBtn class="align-self-end">
Submit
</VBtn>
</div>
</div>
<div>
<h5 class="text-h5 mb-5">
Share the referral link
</h5>
<div class="d-flex gap-4 align-center flex-wrap">
<AppTextField
label="Share referral link in social media"
placeholder="pixinvent.com/?ref=6478"
/>
<div class="d-flex align-self-end gap-x-2">
<VBtn
icon
class="rounded"
color="#3B5998"
>
<VIcon
color="white"
icon="tabler-brand-facebook"
/>
</VBtn>
<VBtn
icon
class="rounded"
color="#55ACEE"
>
<VIcon
color="white"
icon="tabler-brand-twitter"
/>
</VBtn>
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Referral Table -->
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex justify-space-between align-center flex-wrap gap-4">
<h5 class="text-h5">
Referred Users
</h5>
<div class="d-flex flex-wrap gap-4">
<div class="d-flex gap-4 align-center flex-wrap">
<AppSelect
v-model="itemsPerPage"
:items="[10, 25, 50, 100]"
style="inline-size: 100px;"
/>
<VBtn
prepend-icon="tabler-upload"
color="default"
variant="tonal"
>
Export
</VBtn>
</div>
</div>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:items="referrals"
:headers="headers"
:items-length="totalReferrals"
show-select
@update:options="updateOptions"
>
<template #item.users="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
size="34"
:variant="!item.avatar ? 'tonal' : undefined"
:color="!item.avatar ? resolveAvatarbg(item.status)?.color : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span v-else>{{ avatarText(item.user) }}</span>
</VAvatar>
<div>
<div class="font-weight-medium text-high-emphasis">
<RouterLink
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: 478426 } }"
class="text-link"
>
{{ item.user }}
</RouterLink>
</div>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<template #item.referred-id="{ item }">
{{ item.referredId }}
</template>
<template #item.status="{ item }">
<VChip
v-bind="resolveStatus(item.status)"
label
size="small"
/>
</template>
<template #item.earning="{ item }">
<div class="text-body-1 text-high-emphasis">
{{ item.earning }}
</div>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalReferrals"
/>
</template>
</VDataTableServer>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style lang="scss" scoped>
.icon-container {
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed rgb(var(--v-theme-primary));
border-radius: 50%;
block-size: 70px;
inline-size: 70px;
}
.icon {
color: #000;
font-size: 42px;
}
</style>

View File

@@ -0,0 +1,107 @@
<script setup>
import SettingsCheckout from '@/views/apps/ecommerce/settings/SettingsCheckout.vue'
import SettingsLocations from '@/views/apps/ecommerce/settings/SettingsLocations.vue'
import SettingsNotifications from '@/views/apps/ecommerce/settings/SettingsNotifications.vue'
import SettingsPayment from '@/views/apps/ecommerce/settings/SettingsPayment.vue'
import SettingsShippingAndDelivery from '@/views/apps/ecommerce/settings/SettingsShippingAndDelivery.vue'
import SettingsStoreDetails from '@/views/apps/ecommerce/settings/SettingsStoreDetails.vue'
const tabsData = [
{
icon: 'tabler-building-store',
title: 'Store Details',
},
{
icon: 'tabler-credit-card',
title: 'Payments',
},
{
icon: 'tabler-shopping-cart',
title: 'Checkout',
},
{
icon: 'tabler-discount',
title: 'Shipping & Delivery',
},
{
icon: 'tabler-map-pin',
title: 'Location',
},
{
icon: 'tabler-bell-ringing',
title: 'Notifications',
},
]
const activeTab = ref(null)
</script>
<template>
<VRow>
<VCol
cols="12"
md="4"
>
<h5 class="text-h5 mb-4">
Getting Started
</h5>
<VTabs
v-model="activeTab"
direction="vertical"
class="v-tabs-pill disable-tab-transition"
>
<VTab
v-for="(tabItem, index) in tabsData"
:key="index"
:prepend-icon="tabItem.icon"
>
{{ tabItem.title }}
</VTab>
</VTabs>
</VCol>
<VCol
cols="12"
md="8"
>
<VWindow
v-model="activeTab"
class="disable-tab-transition"
:touch="false"
>
<VWindowItem>
<SettingsStoreDetails />
</VWindowItem>
<VWindowItem>
<SettingsPayment />
</VWindowItem>
<VWindowItem>
<SettingsCheckout />
</VWindowItem>
<VWindowItem>
<SettingsShippingAndDelivery />
</VWindowItem>
<VWindowItem>
<SettingsLocations />
</VWindowItem>
<VWindowItem>
<SettingsNotifications />
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</template>
<style lang="scss">
.my-class {
padding: 1.25rem;
border-radius: 0.375rem;
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
}
</style>

View File

@@ -0,0 +1,557 @@
<script setup>
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import ComposeDialog from '@/views/apps/email/ComposeDialog.vue'
import EmailLeftSidebarContent from '@/views/apps/email/EmailLeftSidebarContent.vue'
import EmailView from '@/views/apps/email/EmailView.vue'
import { useEmail } from '@/views/apps/email/useEmail'
definePage({ meta: { layoutWrapperClasses: 'layout-content-height-fixed' } })
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
// Composables
const route = useRoute()
const { labels, resolveLabelColor, emailMoveToFolderActions, shallShowMoveToActionFor, moveSelectedEmailTo, updateEmails, updateEmailLabels } = useEmail()
// Compose dialog
const isComposeDialogVisible = ref(false)
// Ref
const q = ref('')
// Email Selection
// ------------------------------------------------
const selectedEmails = ref([])
const {
data: emailData,
execute: fetchEmails,
} = await useApi(createUrl('/apps/email', {
query: {
q,
filter: () => 'filter' in route.params ? route.params.filter : undefined,
label: () => 'label' in route.params ? route.params.label : undefined,
},
}))
const emails = computed(() => emailData.value.emails)
const emailsMeta = computed(() => emailData.value.emailsMeta)
const toggleSelectedEmail = emailId => {
const emailIndex = selectedEmails.value.indexOf(emailId)
if (emailIndex === -1)
selectedEmails.value.push(emailId)
else
selectedEmails.value.splice(emailIndex, 1)
}
const selectAllEmailCheckbox = computed(() => emails.value.length && emails.value.length === selectedEmails.value.length)
const isSelectAllEmailCheckboxIndeterminate = computed(() => Boolean(selectedEmails.value.length) && emails.value.length !== selectedEmails.value.length)
const isAllMarkRead = computed(() => {
return selectedEmails.value.every(emailId => emails.value.find(email => email.id === emailId)?.isRead)
})
const selectAllCheckboxUpdate = () => {
selectedEmails.value = !selectAllEmailCheckbox.value ? emails.value.map(email => email.id) : []
}
// Email View
const openedEmail = ref(null)
const emailViewMeta = computed(() => {
const returnValue = {
hasNextEmail: false,
hasPreviousEmail: false,
}
if (openedEmail.value) {
const openedEmailIndex = emails.value.findIndex(e => e.id === openedEmail.value?.id)
returnValue.hasNextEmail = !!emails.value[openedEmailIndex + 1]
returnValue.hasPreviousEmail = !!emails.value[openedEmailIndex - 1]
}
return returnValue
})
const refreshOpenedEmail = async () => {
await fetchEmails()
if (openedEmail.value)
openedEmail.value = emails.value.find(e => e.id === openedEmail.value?.id)
}
/* You can optimize it so it doesn't fetch emails on each action.
Currently, if you just star the email, two API calls will get fired.
1. star the email
2. Fetch all latest emails
You can limit this to single API call by:
- making API to star the email
- modify the state (set that email's isStarred property to true/false) in the store instead of making API for fetching emails
😊 For simplicity of the code and possible of modification, we kept it simple.
*/
const handleActionClick = async (action, emailIds = selectedEmails.value) => {
selectedEmails.value = []
selectedEmails.value = []
if (!emailIds.length)
return
if (action === 'trash')
await updateEmails(emailIds, { isDeleted: true })
else if (action === 'spam')
await updateEmails(emailIds, { folder: 'spam' })
else if (action === 'unread')
await updateEmails(emailIds, { isRead: false })
else if (action === 'read')
await updateEmails(emailIds, { isRead: true })
else if (action === 'star')
await updateEmails(emailIds, { isStarred: true })
else if (action === 'unstar')
await updateEmails(emailIds, { isStarred: false })
if (openedEmail.value)
refreshOpenedEmail()
else
await fetchEmails()
}
const handleMoveMailsTo = async action => {
await moveSelectedEmailTo(action, selectedEmails.value)
await fetchEmails()
}
const handleEmailLabels = async labelTitle => {
await updateEmailLabels(selectedEmails.value, labelTitle)
await fetchEmails()
}
const changeOpenedEmail = dir => {
if (!openedEmail.value)
return
const openedEmailIndex = emails.value.findIndex(e => e.id === openedEmail.value?.id)
const newEmailIndex = dir === 'previous' ? openedEmailIndex - 1 : openedEmailIndex + 1
openedEmail.value = emails.value[newEmailIndex]
}
const openEmail = async email => {
openedEmail.value = email
await handleActionClick('read', [email.id])
}
watch(() => route.params, () => {
selectedEmails.value = []
}, { deep: true })
</script>
<template>
<VLayout
style=" z-index: 0;min-block-size: 100%;"
class="email-app-layout"
>
<VNavigationDrawer
v-model="isLeftSidebarOpen"
data-allow-mismatch
absolute
touchless
location="start"
:temporary="$vuetify.display.mdAndDown"
>
<EmailLeftSidebarContent
:emails-meta="emailsMeta"
@toggle-compose-dialog-visibility="isComposeDialogVisible = !isComposeDialogVisible"
/>
</VNavigationDrawer>
<EmailView
:email="openedEmail"
:email-meta="emailViewMeta"
@refresh="refreshOpenedEmail"
@navigated="changeOpenedEmail"
@close="openedEmail = null"
@trash="handleActionClick('trash', openedEmail ? [openedEmail.id] : [])"
@unread="handleActionClick('unread', openedEmail ? [openedEmail.id] : [])"
@star="handleActionClick('star', openedEmail ? [openedEmail.id] : [])"
@unstar="handleActionClick('unstar', openedEmail ? [openedEmail.id] : [])"
/>
<VMain>
<VCard
flat
class="email-content-list h-100 d-flex flex-column"
>
<div class="d-flex align-center">
<IconBtn
class="d-lg-none ms-3"
@click="isLeftSidebarOpen = true"
>
<VIcon icon="tabler-menu-2" />
</IconBtn>
<!-- 👉 Search -->
<VTextField
v-model="q"
density="default"
class="email-search px-sm-2 flex-grow-1 py-1"
placeholder="Search mail"
>
<template #prepend-inner>
<VIcon
icon="tabler-search"
size="24"
class="me-1 text-medium-emphasis"
/>
</template>
</VTextField>
</div>
<VDivider />
<!-- 👉 Action bar -->
<div class="py-2 px-4 d-flex align-center d-flex gap-x-1">
<!-- TODO: Make checkbox primary on indeterminate state -->
<VCheckbox
:model-value="selectAllEmailCheckbox"
:indeterminate="isSelectAllEmailCheckboxIndeterminate"
class="d-flex"
@update:model-value="selectAllCheckboxUpdate"
/>
<div
class="w-100 d-flex align-center action-bar-actions gap-x-1"
:style="{
visibility:
isSelectAllEmailCheckboxIndeterminate || selectAllEmailCheckbox
? undefined
: 'hidden',
}"
>
<!-- Trash -->
<IconBtn
v-show="('filter' in route.params ? route.params.filter !== 'trashed' : true)"
@click="handleActionClick('trash')"
>
<VIcon
icon="tabler-trash"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
Delete Mail
</VTooltip>
</IconBtn>
<!-- Mark unread/read -->
<IconBtn @click="isAllMarkRead ? handleActionClick('unread') : handleActionClick('read') ">
<VIcon
:icon="isAllMarkRead ? 'tabler-mail' : 'tabler-mail-opened'"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
{{ isAllMarkRead ? 'Mark as Unread' : 'Mark as Read' }}
</VTooltip>
</IconBtn>
<!-- Move to folder -->
<IconBtn>
<VIcon
icon="tabler-folder"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
Folder
</VTooltip>
<VMenu activator="parent">
<VList density="compact">
<template
v-for="moveTo in emailMoveToFolderActions"
:key="moveTo.title"
>
<VListItem
:class="shallShowMoveToActionFor(moveTo.action) ? 'd-flex' : 'd-none'"
href="#"
class="items-center"
@click="handleMoveMailsTo(moveTo.action)"
>
<template #prepend>
<VIcon
:icon="moveTo.icon"
class="me-2"
size="20"
/>
</template>
<VListItemTitle class="text-capitalize">
{{ moveTo.action }}
</VListItemTitle>
</VListItem>
</template>
</VList>
</VMenu>
</IconBtn>
<!-- Update labels -->
<IconBtn>
<VIcon
icon="tabler-tag"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
Label
</VTooltip>
<VMenu activator="parent">
<VList density="compact">
<VListItem
v-for="label in labels"
:key="label.title"
href="#"
@click="handleEmailLabels(label.title)"
>
<template #prepend>
<VBadge
inline
:color="resolveLabelColor(label.title)"
dot
/>
</template>
<VListItemTitle class="ms-2 text-capitalize">
{{ label.title }}
</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</div>
<VSpacer />
<IconBtn @click="fetchEmails">
<VIcon
icon="tabler-refresh"
size="22"
/>
</IconBtn>
<IconBtn>
<VIcon
icon="tabler-dots-vertical"
size="22"
/>
</IconBtn>
</div>
<VDivider />
<!-- 👉 Emails list -->
<PerfectScrollbar
tag="ul"
:options="{ wheelPropagation: false }"
class="email-list"
>
<li
v-for="email in emails"
v-show="emails?.length"
:key="email.id"
class="email-item d-flex align-center pa-4 gap-2 cursor-pointer"
:class="[{ 'email-read': email.isRead }]"
@click="openEmail(email)"
>
<VCheckbox
:model-value="selectedEmails.includes(email.id)"
class="flex-shrink-0"
@update:model-value="toggleSelectedEmail(email.id)"
@click.stop
/>
<IconBtn
:color="email.isStarred ? 'warning' : 'default'"
@click.stop=" handleActionClick(email.isStarred ? 'unstar' : 'star', [email.id])"
>
<VIcon
icon="tabler-star"
size="22"
/>
</IconBtn>
<VAvatar size="32">
<VImg
:src="email.from.avatar"
:alt="email.from.name"
/>
</VAvatar>
<h6 class="text-h6">
{{ email.from.name }}
</h6>
<span class="text-body-2 truncate">{{ email.subject }}</span>
<VSpacer />
<!-- 👉 Email meta -->
<div
class="email-meta align-center gap-2"
:class="$vuetify.display.xs ? 'd-none' : ''"
>
<VIcon
v-for="label in email.labels"
:key="label"
icon="tabler-circle-filled"
size="10"
:color="resolveLabelColor(label)"
/>
<span class="text-sm text-disabled">
{{ formatDateToMonthShort(email.time) }}
</span>
</div>
<!-- 👉 Email actions -->
<div class="email-actions d-none">
<IconBtn @click.stop="handleActionClick('trash', [email.id])">
<VIcon
icon="tabler-trash"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
Delete Mail
</VTooltip>
</IconBtn>
<IconBtn
class="mx-2"
@click.stop=" handleActionClick(email.isRead ? 'unread' : 'read', [email.id])"
>
<VIcon
:icon="email.isRead ? 'tabler-mail' : 'tabler-mail-opened'"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
{{ email.isRead ? 'Mark as Unread' : 'Mark as Read' }}
</VTooltip>
</IconBtn>
<IconBtn @click.stop="handleActionClick('spam', [email.id])">
<VIcon
icon="tabler-info-circle"
size="22"
/>
<VTooltip
activator="parent"
location="top"
>
Move to Spam
</VTooltip>
</IconBtn>
</div>
</li>
<li
v-show="!emails.length"
class="py-4 px-5 text-center"
>
<span class="text-high-emphasis">No items found.</span>
</li>
</PerfectScrollbar>
</VCard>
<ComposeDialog
v-show="isComposeDialogVisible"
@close="isComposeDialogVisible = false"
/>
</VMain>
</VLayout>
</template>
<style lang="scss">
@use "@styles/variables/vuetify";
@use "@core-scss/base/mixins";
// Remove border. Using variant plain cause UI issue, caret isn't align in center
.email-search {
.v-field__outline {
display: none;
}
.v-field__field {
.v-field__input {
font-size: 0.9375rem !important;
line-height: 1.375rem !important;
}
}
}
.email-app-layout {
border-radius: vuetify.$card-border-radius;
@include mixins.elevation(vuetify.$card-elevation);
$sel-email-app-layout: &;
@at-root {
.skin--bordered {
@include mixins.bordered-skin($sel-email-app-layout);
}
}
}
.email-content-list {
border-end-start-radius: 0;
border-start-start-radius: 0;
}
.email-list {
white-space: nowrap;
.email-item {
block-size: 4.375rem;
transition: all 0.2s ease-in-out;
will-change: transform, box-shadow;
&.email-read {
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
}
& + .email-item {
border-block-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
}
.email-item .email-meta {
display: flex;
}
.email-item:hover {
transform: translateY(-2px);
@include mixins.elevation(4);
// Don't show actions on hover on mobile & tablet devices
@media screen and (min-width: 1280px) {
.email-actions {
display: block !important;
}
.email-meta {
display: none;
}
}
+ .email-item {
border-color: transparent;
}
@media screen and (max-width: 600px) {
.email-actions {
display: none !important;
}
}
}
}
.email-compose-dialog {
position: absolute;
inset-block-end: 0;
inset-inline-end: 0;
min-inline-size: 100%;
@media only screen and (min-width: 800px) {
min-inline-size: 712px;
}
}
</style>

View File

@@ -0,0 +1,171 @@
<script setup>
import InvoiceEditable from '@/views/apps/invoice/InvoiceEditable.vue'
import InvoiceSendInvoiceDrawer from '@/views/apps/invoice/InvoiceSendInvoiceDrawer.vue'
// 👉 Default Blank Data
const invoiceData = ref({
invoice: {
id: 5037,
issuedDate: '',
service: '',
total: 0,
avatar: '',
invoiceStatus: '',
dueDate: '',
balance: 0,
client: {
address: '112, Lorem Ipsum, Florida',
company: 'Greeva Inc',
companyEmail: 'johndoe@greeva.com',
contact: '+1 123 3452 12',
country: 'USA',
name: 'John Doe',
},
},
paymentDetails: {
totalDue: '$12,110.55',
bankName: 'American Bank',
country: 'United States',
iban: 'ETD95476213',
swiftCode: 'BR91905',
},
purchasedProducts: [{
title: '',
cost: 0,
hours: 0,
description: '',
}],
note: '',
paymentMethod: '',
salesperson: '',
thanksNote: '',
})
const paymentTerms = ref(true)
const clientNotes = ref(false)
const paymentStub = ref(false)
const selectedPaymentMethod = ref('Bank Account')
const paymentMethods = [
'Bank Account',
'PayPal',
'UPI Transfer',
]
const isSendPaymentSidebarVisible = ref(false)
const addProduct = value => {
invoiceData.value?.purchasedProducts.push(value)
}
const removeProduct = id => {
invoiceData.value?.purchasedProducts.splice(id, 1)
}
</script>
<template>
<VRow>
<!-- 👉 InvoiceEditable -->
<VCol
cols="12"
md="9"
>
<InvoiceEditable
:data="invoiceData"
@push="addProduct"
@remove="removeProduct"
/>
</VCol>
<!-- 👉 Right Column: Invoice Action -->
<VCol
cols="12"
md="3"
>
<VCard class="mb-8">
<VCardText>
<!-- 👉 Send Invoice -->
<VBtn
block
prepend-icon="tabler-send"
class="mb-4"
@click="isSendPaymentSidebarVisible = true"
>
Send Invoice
</VBtn>
<!-- 👉 Preview -->
<VBtn
block
color="secondary"
variant="tonal"
class="mb-4"
:to="{ name: 'apps-invoice-preview-id', params: { id: '5036' } }"
>
Preview
</VBtn>
<!-- 👉 Save -->
<VBtn
block
color="secondary"
variant="tonal"
>
Save
</VBtn>
</VCardText>
</VCard>
<!-- 👉 Select payment method -->
<AppSelect
id="payment-method"
v-model="selectedPaymentMethod"
:items="paymentMethods"
label="Accept Payment Via"
class="mb-6"
/>
<!-- 👉 Payment Terms -->
<div class="d-flex align-center justify-space-between">
<VLabel for="payment-terms">
Payment Terms
</VLabel>
<div>
<VSwitch
id="payment-terms"
v-model="paymentTerms"
/>
</div>
</div>
<!-- 👉 Client Notes -->
<div class="d-flex align-center justify-space-between">
<VLabel for="client-notes">
Client Notes
</VLabel>
<div>
<VSwitch
id="client-notes"
v-model="clientNotes"
/>
</div>
</div>
<!-- 👉 Payment Stub -->
<div class="d-flex align-center justify-space-between">
<VLabel for="payment-stub">
Payment Stub
</VLabel>
<div>
<VSwitch
id="payment-stub"
v-model="paymentStub"
/>
</div>
</div>
</VCol>
</VRow>
<!-- 👉 Send Invoice Sidebar -->
<InvoiceSendInvoiceDrawer v-model:is-drawer-open="isSendPaymentSidebarVisible" />
</template>

View File

@@ -0,0 +1,177 @@
<script setup>
import InvoiceAddPaymentDrawer from '@/views/apps/invoice/InvoiceAddPaymentDrawer.vue'
import InvoiceEditable from '@/views/apps/invoice/InvoiceEditable.vue'
import InvoiceSendInvoiceDrawer from '@/views/apps/invoice/InvoiceSendInvoiceDrawer.vue'
const invoiceData = ref()
const route = useRoute('apps-invoice-edit-id')
const { data: invoiceDetails } = await useApi(`/apps/invoice/${ route.params.id }`)
if (invoiceDetails.value) {
invoiceData.value = {
invoice: invoiceDetails.value.invoice,
paymentDetails: invoiceDetails.value.paymentDetails,
purchasedProducts: [{
title: 'App Design',
cost: 24,
hours: 2,
description: 'Designed UI kit & app pages.',
}],
note: 'It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance projects. Thank You!',
paymentMethod: 'Bank Account',
salesperson: 'Tom Cook',
thanksNote: 'Thanks for your business',
}
}
const addProduct = value => {
invoiceData.value?.purchasedProducts.push(value)
}
const removeProduct = id => {
invoiceData.value?.purchasedProducts.splice(id, 1)
}
const isSendSidebarActive = ref(false)
const isAddPaymentSidebarActive = ref(false)
const paymentTerms = ref(true)
const clientNotes = ref(false)
const paymentStub = ref(false)
const selectedPaymentMethod = ref('Bank Account')
const paymentMethods = [
'Bank Account',
'PayPal',
'UPI Transfer',
]
</script>
<template>
<VRow v-if="invoiceData && invoiceData?.invoice">
<!-- 👉 InvoiceEditable -->
<VCol
cols="12"
md="9"
>
<InvoiceEditable
v-if="invoiceData?.invoice"
:data="invoiceData"
@push="addProduct"
@remove="removeProduct"
/>
</VCol>
<!-- 👉 Right Column: Invoice Action -->
<VCol
cols="12"
md="3"
>
<VCard class="mb-8">
<VCardText>
<!-- 👉 Send Invoice Trigger button -->
<VBtn
block
prepend-icon="tabler-send"
class="mb-4"
@click="isSendSidebarActive = true"
>
Send Invoice
</VBtn>
<div class="d-flex flex-wrap gap-4">
<!-- 👉 Preview button -->
<VBtn
color="secondary"
variant="tonal"
class="flex-grow-1"
:to="{ name: 'apps-invoice-preview-id', params: { id: route.params.id } }"
>
Preview
</VBtn>
<!-- 👉 Save button -->
<VBtn
color="secondary"
variant="tonal"
class="mb-4 flex-grow-1"
>
Save
</VBtn>
</div>
<!-- 👉 Add Payment trigger button -->
<VBtn
block
color="success"
prepend-icon="tabler-currency-dollar"
@click="isAddPaymentSidebarActive = true"
>
Add Payment
</VBtn>
</VCardText>
</VCard>
<!-- 👉 Accept payment via -->
<AppSelect
id="payment-method"
v-model="selectedPaymentMethod"
:items="paymentMethods"
label="Accept Payment Via"
class="mb-4"
/>
<!-- 👉 Payment Terms -->
<div class="d-flex align-center justify-space-between">
<VLabel for="payment-terms">
Payment Terms
</VLabel>
<div>
<VSwitch
id="payment-terms"
v-model="paymentTerms"
/>
</div>
</div>
<!-- 👉 Client Notes -->
<div class="d-flex align-center justify-space-between">
<VLabel for="client-notes">
Client Notes
</VLabel>
<div>
<VSwitch
id="client-notes"
v-model="clientNotes"
/>
</div>
</div>
<!-- 👉 Payment Stub -->
<div class="d-flex align-center justify-space-between">
<VLabel for="payment-stub">
Payment Stub
</VLabel>
<div>
<VSwitch
id="payment-stub"
v-model="paymentStub"
/>
</div>
</div>
</VCol>
<!-- 👉 Invoice send drawer -->
<InvoiceSendInvoiceDrawer v-model:is-drawer-open="isSendSidebarActive" />
<!-- 👉 Invoice add payment drawer -->
<InvoiceAddPaymentDrawer v-model:is-drawer-open="isAddPaymentSidebarActive" />
</VRow>
<section v-else>
<VAlert
type="error"
variant="tonal"
>
Invoice with ID {{ route.params.id }} not found!
</VAlert>
</section>
</template>

View File

@@ -0,0 +1,442 @@
<script setup>
const searchQuery = ref('')
const selectedStatus = ref(null)
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
}
const widgetData = ref([
{
title: 'Clients',
value: 24,
icon: 'tabler-user',
},
{
title: 'Invoices',
value: 165,
icon: 'tabler-file-invoice',
},
{
title: 'Paid',
value: '$2.46k',
icon: 'tabler-checks',
},
{
title: 'Unpaid',
value: '$876',
icon: 'tabler-circle-off',
},
])
// 👉 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-2',
}
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-help',
}
return {
variant: 'secondary',
icon: 'tabler-x',
}
}
const computedMoreList = computed(() => {
return paramId => [
{
title: 'Download',
value: 'download',
prependIcon: 'tabler-download',
},
{
title: 'Edit',
value: 'edit',
prependIcon: 'tabler-pencil',
to: {
name: 'apps-invoice-edit-id',
params: { id: paramId },
},
},
{
title: 'Duplicate',
value: 'duplicate',
prependIcon: 'tabler-layers-intersect',
},
]
})
const deleteInvoice = async id => {
await $api(`/apps/invoice/${ id }`, { method: 'DELETE' })
// Delete from selectedRows
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
// Refetch Invoices
fetchInvoices()
}
</script>
<template>
<section v-if="invoices">
<!-- 👉 Invoice Widgets -->
<VCard class="mb-6">
<VCardText class="px-3">
<VRow>
<template
v-for="(data, id) in widgetData"
:key="id"
>
<VCol
cols="12"
sm="6"
md="3"
class="px-6"
>
<div
class="d-flex justify-space-between align-center"
:class="$vuetify.display.xs
? id !== widgetData.length - 1 ? 'border-b pb-4' : ''
: $vuetify.display.sm
? id < (widgetData.length / 2) ? 'border-b pb-4' : ''
: ''"
>
<div class="d-flex flex-column">
<h4 class="text-h4">
{{ data.value }}
</h4>
<span class="text-body-1 text-capitalize">{{ data.title }}</span>
</div>
<VAvatar
variant="tonal"
rounded
size="42"
>
<VIcon
:icon="data.icon"
size="26"
color="high-emphasis"
/>
</VAvatar>
</div>
</VCol>
<VDivider
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
: $vuetify.display.smAndUp ? id % 2 === 0
: false"
vertical
inset
length="60"
/>
</template>
</VRow>
</VCardText>
</VCard>
<VCard id="invoice-list">
<VCardText class="d-flex justify-space-between align-center flex-wrap gap-4">
<div class="d-flex gap-4 align-center flex-wrap">
<div class="d-flex align-center gap-2">
<span>Show</span>
<AppSelect
:model-value="itemsPerPage"
:items="[
{ value: 10, title: '10' },
{ value: 25, title: '25' },
{ value: 50, title: '50' },
{ value: 100, title: '100' },
{ value: -1, title: 'All' },
]"
style="inline-size: 5.5rem;"
@update:model-value="itemsPerPage = parseInt($event, 10)"
/>
</div>
<!-- 👉 Create invoice -->
<VBtn
prepend-icon="tabler-plus"
:to="{ name: 'apps-invoice-add' }"
>
Create invoice
</VBtn>
</div>
<div class="d-flex align-center flex-wrap gap-4">
<!-- 👉 Search -->
<div class="invoice-list-filter">
<AppTextField
v-model="searchQuery"
placeholder="Search Invoice"
/>
</div>
<!-- 👉 Select status -->
<div class="invoice-list-filter">
<AppSelect
v-model="selectedStatus"
placeholder="Invoice Status"
clearable
clear-icon="tabler-x"
single-line
:items="['Downloaded', 'Draft', 'Sent', 'Paid', 'Partial Payment', 'Past Due']"
/>
</div>
</div>
</VCardText>
<VDivider />
<!-- SECTION Datatable -->
<VDataTableServer
v-model:model-value="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"
@update:options="updateOptions"
>
<!-- id -->
<template #item.id="{ item }">
<RouterLink :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
#{{ item.id }}
</RouterLink>
</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
:to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }"
class="text-link font-weight-medium"
>
{{ item.client.name }}
</RouterLink>
<span class="text-sm text-medium-emphasis">{{ item.client.companyEmail }}</span>
</div>
</div>
</template>
<!-- Total -->
<template #item.total="{ item }">
${{ item.total }}
</template>
<!-- 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"
label
size="x-small"
>
{{ (resolveInvoiceBalanceVariant(item.balance, item.total)).status }}
</VChip>
<template v-else>
<span class="text-base 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))}` }}
</span>
</template>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn @click="deleteInvoice(item.id)">
<VIcon icon="tabler-trash" />
</IconBtn>
<IconBtn :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
<VIcon icon="tabler-eye" />
</IconBtn>
<MoreBtn
:menu-list="computedMoreList(item.id)"
item-props
color="undefined"
/>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalInvoices"
/>
</template>
</VDataTableServer>
<!-- !SECTION -->
</VCard>
</section>
<section v-else>
<VCard>
<VCardTitle>No Invoice Found</VCardTitle>
</VCard>
</section>
</template>
<style lang="scss">
#invoice-list {
.invoice-list-actions {
inline-size: 8rem;
}
.invoice-list-filter {
inline-size: 12rem;
}
}
</style>

View File

@@ -0,0 +1,452 @@
<script setup>
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
import InvoiceAddPaymentDrawer from '@/views/apps/invoice/InvoiceAddPaymentDrawer.vue'
import InvoiceSendInvoiceDrawer from '@/views/apps/invoice/InvoiceSendInvoiceDrawer.vue'
const route = useRoute('apps-invoice-preview-id')
const isAddPaymentSidebarVisible = ref(false)
const isSendPaymentSidebarVisible = ref(false)
const { data: invoiceData } = await useApi(`/apps/invoice/${ Number(route.params.id) }`)
const invoice = ref()
const paymentDetails = ref()
if (invoiceData.value) {
invoice.value = invoiceData.value.invoice
paymentDetails.value = invoiceData.value.paymentDetails
}
const purchasedProducts = [
{
name: 'Premium Branding Package',
description: 'Branding & Promotion',
qty: 1,
hours: 15,
price: 32,
},
{
name: 'SMM',
description: 'Social media templates',
qty: 1,
hours: 14,
price: 28,
},
{
name: 'Web Design',
description: 'Web designing package',
qty: 1,
hours: 12,
price: 24,
},
{
name: 'SEO',
description: 'Search engine optimization',
qty: 1,
hours: 5,
price: 22,
},
]
const printInvoice = () => {
window.print()
}
</script>
<template>
<section v-if="invoice && paymentDetails">
<VRow>
<VCol
cols="12"
md="9"
>
<VCard class="invoice-preview-wrapper pa-6 pa-sm-12">
<!-- SECTION Header -->
<div class="invoice-header-preview d-flex flex-wrap justify-space-between flex-column flex-sm-row print-row bg-var-theme-background gap-6 rounded pa-6 mb-6">
<!-- 👉 Left Content -->
<div>
<div class="d-flex align-center app-logo mb-6">
<!-- 👉 Logo -->
<VNodeRenderer :nodes="themeConfig.app.logo" />
<!-- 👉 Title -->
<h6 class="app-logo-title">
{{ themeConfig.app.title }}
</h6>
</div>
<!-- 👉 Address -->
<h6 class="text-h6 font-weight-regular">
Office 149, 450 South Brand Brooklyn
</h6>
<h6 class="text-h6 font-weight-regular">
San Diego County, CA 91905, USA
</h6>
<h6 class="text-h6 font-weight-regular">
+1 (123) 456 7891, +44 (876) 543 2198
</h6>
</div>
<!-- 👉 Right Content -->
<div>
<!-- 👉 Invoice ID -->
<h6 class="font-weight-medium text-lg mb-6">
Invoice #{{ invoice.id }}
</h6>
<!-- 👉 Issue Date -->
<h6 class="text-h6 font-weight-regular">
<span>Date Issued: </span>
<span>{{ new Date(invoice.issuedDate).toLocaleDateString('en-GB') }}</span>
</h6>
<!-- 👉 Due Date -->
<h6 class="text-h6 font-weight-regular">
<span>Due Date: </span>
<span>{{ new Date(invoice.dueDate).toLocaleDateString('en-GB') }}</span>
</h6>
</div>
</div>
<!-- !SECTION -->
<!-- 👉 Payment Details -->
<VRow class="print-row mb-6">
<VCol class="text-no-wrap">
<h6 class="text-h6 mb-4">
Invoice To:
</h6>
<p class="mb-0">
{{ invoice.client.name }}
</p>
<p class="mb-0">
{{ invoice.client.company }}
</p>
<p class="mb-0">
{{ invoice.client.address }}, {{ invoice.client.country }}
</p>
<p class="mb-0">
{{ invoice.client.contact }}
</p>
<p class="mb-0">
{{ invoice.client.companyEmail }}
</p>
</VCol>
<VCol class="text-no-wrap">
<h6 class="text-h6 mb-4">
Bill To:
</h6>
<table>
<tbody>
<tr>
<td class="pe-4">
Total Due:
</td>
<td>
{{ paymentDetails.totalDue }}
</td>
</tr>
<tr>
<td class="pe-4">
Bank Name:
</td>
<td>
{{ paymentDetails.bankName }}
</td>
</tr>
<tr>
<td class="pe-4">
Country:
</td>
<td>
{{ paymentDetails.country }}
</td>
</tr>
<tr>
<td class="pe-4">
IBAN:
</td>
<td>
{{ paymentDetails.iban }}
</td>
</tr>
<tr>
<td class="pe-4">
SWIFT Code:
</td>
<td>
{{ paymentDetails.swiftCode }}
</td>
</tr>
</tbody>
</table>
</VCol>
</VRow>
<!-- 👉 invoice Table -->
<VTable class="invoice-preview-table border text-high-emphasis overflow-hidden mb-6">
<thead>
<tr>
<th scope="col">
ITEM
</th>
<th scope="col">
DESCRIPTION
</th>
<th
scope="col"
class="text-center"
>
HOURS
</th>
<th
scope="col"
class="text-center"
>
QTY
</th>
<th
scope="col"
class="text-center"
>
TOTAL
</th>
</tr>
</thead>
<tbody class="text-base">
<tr
v-for="item in purchasedProducts"
:key="item.name"
>
<td class="text-no-wrap">
{{ item.name }}
</td>
<td class="text-no-wrap">
{{ item.description }}
</td>
<td class="text-center">
{{ item.hours }}
</td>
<td class="text-center">
{{ item.qty }}
</td>
<td class="text-center">
${{ item.price }}
</td>
</tr>
</tbody>
</VTable>
<!-- 👉 Total -->
<div class="d-flex justify-space-between flex-column flex-sm-row print-row">
<div class="mb-2">
<div class="d-flex align-center mb-1">
<h6 class="text-h6 me-2">
Salesperson:
</h6>
<span>Jenny Parker</span>
</div>
<p>Thanks for your business</p>
</div>
<div>
<table class="w-100">
<tbody>
<tr>
<td class="pe-16">
Subtotal:
</td>
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
<h6 class="text-base font-weight-medium">
$1800
</h6>
</td>
</tr>
<tr>
<td class="pe-16">
Discount:
</td>
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
<h6 class="text-base font-weight-medium">
$28
</h6>
</td>
</tr>
<tr>
<td class="pe-16">
Tax:
</td>
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
<h6 class="text-base font-weight-medium">
21%
</h6>
</td>
</tr>
</tbody>
</table>
<VDivider class="my-2" />
<table class="w-100">
<tbody>
<tr>
<td class="pe-16">
Total:
</td>
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
<h6 class="text-base font-weight-medium">
$1690
</h6>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<VDivider class="my-6 border-dashed" />
<p class="mb-0">
<span class="text-high-emphasis font-weight-medium me-1">
Note:
</span>
<span>It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance projects. Thank You!</span>
</p>
</VCard>
</VCol>
<VCol
cols="12"
md="3"
class="d-print-none"
>
<VCard>
<VCardText>
<!-- 👉 Send Invoice Trigger button -->
<VBtn
block
prepend-icon="tabler-send"
class="mb-4"
@click="isSendPaymentSidebarVisible = true"
>
Send Invoice
</VBtn>
<VBtn
block
color="secondary"
variant="tonal"
class="mb-4"
>
Download
</VBtn>
<div class="d-flex flex-wrap gap-4">
<VBtn
variant="tonal"
color="secondary"
class="flex-grow-1"
@click="printInvoice"
>
Print
</VBtn>
<VBtn
color="secondary"
variant="tonal"
class="mb-4 flex-grow-1"
:to="{ name: 'apps-invoice-edit-id', params: { id: route.params.id } }"
>
Edit
</VBtn>
</div>
<!-- 👉 Add Payment trigger button -->
<VBtn
block
prepend-icon="tabler-currency-dollar"
color="success"
@click="isAddPaymentSidebarVisible = true"
>
Add Payment
</VBtn>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- 👉 Add Payment Sidebar -->
<InvoiceAddPaymentDrawer v-model:is-drawer-open="isAddPaymentSidebarVisible" />
<!-- 👉 Send Invoice Sidebar -->
<InvoiceSendInvoiceDrawer v-model:is-drawer-open="isSendPaymentSidebarVisible" />
</section>
<section v-else>
<VAlert
type="error"
variant="tonal"
>
Invoice with ID {{ route.params.id }} not found!
</VAlert>
</section>
</template>
<style lang="scss">
.invoice-preview-table {
--v-table-header-color: var(--v-theme-surface);
&.v-table .v-table__wrapper table thead tr th {
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)) !important;
}
}
@media print {
.v-theme--dark {
--v-theme-surface: 255, 255, 255;
--v-theme-on-surface: 47, 43, 61;
--v-theme-on-background: 47, 43, 61;
}
body {
background: none !important;
}
.invoice-header-preview,
.invoice-preview-wrapper {
padding: 0 !important;
}
.product-buy-now {
display: none;
}
.v-navigation-drawer,
.layout-vertical-nav,
.app-customizer-toggler,
.layout-footer,
.layout-navbar,
.layout-navbar-and-nav-container {
display: none;
}
.v-card {
box-shadow: none !important;
.print-row {
flex-direction: row !important;
}
}
.layout-content-wrapper {
padding-inline-start: 0 !important;
}
.v-table__wrapper {
overflow: hidden !important;
}
.vue-devtools__anchor {
display: none;
}
}
</style>

View File

@@ -0,0 +1,85 @@
<script setup>
import KanbanBoardComp from '@/views/apps/kanban/KanbanBoard.vue'
// 👉 initial kanban data fetch
const {
data: kanban,
execute: refetchKanban,
} = await useApi('/apps/kanban')
const addNewBoard = async newBoardName => {
await $api('/apps/kanban/board/add', {
method: 'POST',
body: { title: newBoardName },
})
refetchKanban()
}
const deleteBoard = async boardId => {
await $api(`/apps/kanban/board/${ boardId }`, { method: 'DELETE' })
refetchKanban()
}
const renameTheBoard = async kanbanBoard => {
await $api('/apps/kanban/board/rename', {
method: 'PUT',
body: kanbanBoard,
})
refetchKanban()
}
const addNewItem = async newItem => {
await $api('/apps/kanban/item/add', {
method: 'POST',
body: newItem,
})
refetchKanban()
}
const editItemFn = async editItem => {
await $api('/apps/kanban/item/update', {
method: 'PUT',
body: editItem,
})
refetchKanban()
}
const deleteItemFn = async deleteItem => {
if (deleteItem.item && deleteItem.item.id) {
await $api(`/apps/kanban/item/${ deleteItem.item.id }`, {
method: 'DELETE',
body: deleteItem,
})
refetchKanban()
}
}
const updateItemState = async kanbanState => {
await $api('/apps/kanban/item/state-update', {
method: 'PUT',
body: kanbanState,
})
}
const updateBoardState = async kanbanBoardIds => {
await $api('/apps/kanban/board/state-update', {
method: 'PUT',
body: kanbanBoardIds,
})
}
</script>
<template>
<KanbanBoardComp
v-if="kanban"
:kanban-data="kanban"
@add-new-board="addNewBoard"
@delete-board="deleteBoard"
@rename-board="renameTheBoard"
@add-new-item="addNewItem"
@edit-item="editItemFn"
@delete-item="deleteItemFn"
@update-items-state="updateItemState"
@update-board-state="updateBoardState"
/>
</template>

View File

@@ -0,0 +1,55 @@
<script setup>
import LogisticsCardStatistics from '@/views/apps/logistics/LogisticsCardStatistics.vue'
import LogisticsDeliveryExpectations from '@/views/apps/logistics/LogisticsDeliveryExpectations.vue'
import LogisticsDeliveryPerformance from '@/views/apps/logistics/LogisticsDeliveryPerformance.vue'
import LogisticsOrderByCountries from '@/views/apps/logistics/LogisticsOrderByCountries.vue'
import LogisticsOverviewTable from '@/views/apps/logistics/LogisticsOverviewTable.vue'
import LogisticsShipmentStatistics from '@/views/apps/logistics/LogisticsShipmentStatistics.vue'
import LogisticsVehicleOverview from '@/views/apps/logistics/LogisticsVehicleOverview.vue'
</script>
<template>
<VRow class="match-height">
<VCol cols="12">
<LogisticsCardStatistics />
</VCol>
<VCol
cols="12"
md="6"
>
<LogisticsVehicleOverview />
</VCol>
<VCol
cols="12"
md="6"
>
<LogisticsShipmentStatistics />
</VCol>
<VCol
cols="12"
md="4"
>
<LogisticsDeliveryPerformance />
</VCol>
<VCol
cols="12"
md="4"
>
<LogisticsDeliveryExpectations />
</VCol>
<VCol
cols="12"
md="4"
>
<LogisticsOrderByCountries />
</VCol>
<VCol cols="12">
<LogisticsOverviewTable />
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,402 @@
// ❗ WARNING please use your access token from mapbox.com
<script setup>
import mapboxgl from 'mapbox-gl'
import {
onMounted,
ref,
} from 'vue'
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import { useDisplay } from 'vuetify'
import fleetImg from '@images/misc/fleet-car.png'
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
const accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN
const map = ref()
const vuetifyDisplay = useDisplay()
definePage({ meta: { layoutWrapperClasses: 'layout-content-height-fixed' } })
const carImgs = ref([
fleetImg,
fleetImg,
fleetImg,
fleetImg,
])
const refCars = ref([])
const showPanel = ref([
true,
false,
false,
false,
])
const geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
-73.999024,
40.75249842,
],
},
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
-74.03,
40.75699842,
],
},
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
-73.967524,
40.7599842,
],
},
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
-74.0325,
40.742992,
],
},
},
],
}
const activeIndex = ref(0)
onMounted(async () => {
await new Promise(resolve => setTimeout(resolve, 100))
mapboxgl.accessToken = accessToken
map.value = new mapboxgl.Map({
container: 'mapContainer',
style: 'mapbox://styles/mapbox/light-v9',
center: [
-73.999024,
40.75249842,
],
zoom: 12.25,
})
for (let index = 0; index < geojson.features.length; index++)
new mapboxgl.Marker({ element: refCars.value[index] }).setLngLat(geojson.features[index].geometry.coordinates).addTo(map.value)
refCars.value[activeIndex.value].classList.add('marker-focus')
})
const vehicleTrackingData = [
{
name: 'VOL-342808',
location: 'Chelsea, NY, USA',
progress: 88,
driverName: 'Veronica Herman',
},
{
name: 'VOL-954784',
location: 'Lincoln Harbor, NY, USA',
progress: 100,
driverName: 'Myrtle Ullrich',
},
{
name: 'VOL-342808',
location: 'Midtown East, NY, USA',
progress: 60,
driverName: 'Barry Schowalter',
},
{
name: 'VOL-343908',
location: 'Hoboken, NY, USA',
progress: 28,
driverName: 'Helen Jacobs',
},
]
const flyToLocation = (geolocation, index) => {
activeIndex.value = index
showPanel.value.fill(false)
showPanel.value[index] = !showPanel.value[index]
if (vuetifyDisplay.mdAndDown.value)
isLeftSidebarOpen.value = false
map.value.flyTo({
center: geolocation,
zoom: 16,
})
}
watch(activeIndex, () => {
refCars.value.forEach((car, index) => {
if (index === activeIndex.value)
car.classList.add('marker-focus')
else
car.classList.remove('marker-focus')
})
})
</script>
<template>
<VLayout class="fleet-app-layout">
<VNavigationDrawer
v-model="isLeftSidebarOpen"
data-allow-mismatch
width="360"
absolute
touchless
location="start"
border="none"
>
<VCard
class="h-100 fleet-navigation-drawer"
flat
>
<VCardItem>
<VCardTitle>
Fleet
</VCardTitle>
<template #append>
<IconBtn
class="d-lg-none navigation-close-btn"
@click="isLeftSidebarOpen = !isLeftSidebarOpen"
>
<VIcon icon="tabler-x" />
</IconBtn>
</template>
</VCardItem>
<!-- 👉 Perfect Scrollbar -->
<PerfectScrollbar
:options="{ wheelPropagation: false, suppressScrollX: true }"
style="block-size: calc(100% - 60px);"
>
<VCardText class="pt-0">
<div
v-for="(vehicle, index) in vehicleTrackingData"
:key="index"
class="mb-6"
>
<div
class="d-flex align-center justify-space-between cursor-pointer"
@click="flyToLocation(geojson.features[index].geometry.coordinates, index)"
>
<div class="d-flex gap-x-4 align-center">
<VAvatar
icon="tabler-car"
variant="tonal"
color="secondary"
/>
<div>
<div class="text-body-1 text-high-emphasis">
{{ vehicle.name }}
</div>
<div class="text-body-1">
{{ vehicle.location }}
</div>
</div>
</div>
<IconBtn size="small">
<VIcon
:icon="showPanel[index] ? 'tabler-chevron-down' : $vuetify.locale.isRtl ? 'tabler-chevron-left' : 'tabler-chevron-right'"
class="text-high-emphasis"
/>
</IconBtn>
</div>
<VExpandTransition mode="out-in">
<div v-show="showPanel[index]">
<div class="py-8">
<div class="d-flex justify-space-between mb-1">
<span class="text-body-1 text-high-emphasis">Delivery Process</span>
<span class="text-body-1">{{ vehicle.progress }}%</span>
</div>
<VProgressLinear
:model-value="vehicle.progress"
color="primary"
rounded
height="6"
/>
</div>
<div>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
line-inset="6"
class="ps-2 v-timeline--variant-outlined fleet-timeline"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgb(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="ps-1">
<div class="text-caption text-success">
TRACKING NUMBER CREATED
</div>
<div class="app-timeline-title">
{{ vehicle.driverName }}
</div>
<div class="text-body-2">
Sep 01, 7:53 AM
</div>
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgb(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-caption text-uppercase text-success">
OUT FOR DELIVERY
</div>
<div class="app-timeline-title">
Veronica Herman
</div>
<div class="text-body-2">
Sep 03, 8:02 AM
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgb(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-caption text-uppercase text-success">
ARRIVED
</div>
<div class="app-timeline-title">
Veronica Herman
</div>
<div class="text-body-2">
Sep 04, 8:18 AM
</div>
</VTimelineItem>
</VTimeline>
</div>
</div>
</VExpandTransition>
</div>
</VCardText>
</PerfectScrollbar>
</VCard>
</VNavigationDrawer>
<VMain>
<div class="h-100">
<IconBtn
class="d-lg-none navigation-toggle-btn rounded-sm"
variant="elevated"
@click="isLeftSidebarOpen = true"
>
<VIcon icon="tabler-menu-2" />
</IconBtn>
<!-- 👉 Fleet map -->
<div
id="mapContainer"
class="basemap"
/>
<img
v-for="(car, index) in carImgs"
:key="index"
ref="refCars"
:src="car"
alt="car Img marker"
height="42"
width="20"
>
</div>
</VMain>
</VLayout>
</template>
<style lang="scss">
@use "@styles/variables/vuetify";
@use "@core-scss/base/mixins";
@import "mapbox-gl/dist/mapbox-gl.css";
.fleet-app-layout {
border-radius: vuetify.$card-border-radius;
@include mixins.elevation(vuetify.$card-elevation);
$sel-fleet-app-layout: &;
@at-root {
.skin--bordered {
@include mixins.bordered-skin($sel-fleet-app-layout);
}
}
}
.navigation-toggle-btn {
position: absolute;
z-index: 1;
inset-block-start: 1rem;
inset-inline-start: 1rem;
}
.navigation-close-btn {
position: absolute;
z-index: 1;
inset-block-start: 1rem;
inset-inline-end: 1rem;
}
.basemap {
block-size: 100%;
inline-size: 100%;
}
.marker-focus {
filter: drop-shadow(0 0 7px rgb(var(--v-theme-primary)));
}
.mapboxgl-ctrl-bottom-left,
.mapboxgl-ctrl-bottom-right {
display: none;
}
.fleet-navigation-drawer {
.v-timeline .v-timeline-divider__dot .v-timeline-divider__inner-dot {
box-shadow: none;
}
}
.fleet-timeline {
&.v-timeline .v-timeline-item:not(:last-child) {
.v-timeline-item__body {
margin-block-end: 0.25rem;
}
}
}
/* stylelint-disable-next-line selector-id-pattern */
#mapContainer {
block-size: 100vh !important;
}
</style>

View File

@@ -0,0 +1,201 @@
<script setup>
const headers = [
{
title: 'Name',
key: 'name',
},
{
title: 'Assigned To',
key: 'assignedTo',
sortable: false,
},
{
title: 'Created Date',
key: 'createdDate',
sortable: false,
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const search = 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
}
const isPermissionDialogVisible = ref(false)
const isAddPermissionDialogVisible = ref(false)
const permissionName = ref('')
const colors = {
'support': {
color: 'info',
text: 'Support',
},
'users': {
color: 'success',
text: 'Users',
},
'manager': {
color: 'warning',
text: 'Manager',
},
'administrator': {
color: 'primary',
text: 'Administrator',
},
'restricted-user': {
color: 'error',
text: 'Restricted User',
},
}
const { data: permissionsData } = await useApi(createUrl('/apps/permissions', {
query: {
q: search,
itemsPerPage,
page,
sortBy,
orderBy,
},
}))
const permissions = computed(() => permissionsData.value.permissions)
const totalPermissions = computed(() => permissionsData.value.totalPermissions)
const editPermission = name => {
isPermissionDialogVisible.value = true
permissionName.value = name
}
</script>
<template>
<VRow>
<VCol cols="12">
<VCard>
<VCardText class="d-flex align-center justify-space-between flex-wrap gap-4">
<div class="d-flex gap-2 align-center">
<p class="text-body-1 mb-0">
Show
</p>
<AppSelect
:model-value="itemsPerPage"
:items="[
{ value: 5, title: '5' },
{ value: 25, title: '25' },
{ value: 50, title: '50' },
{ value: 100, title: '100' },
{ value: -1, title: 'All' },
]"
style="inline-size: 5.5rem;"
@update:model-value="itemsPerPage = parseInt($event, 10)"
/>
</div>
<div class="d-flex align-center gap-4 flex-wrap">
<AppTextField
v-model="search"
placeholder="Search Permission"
style="inline-size: 15.625rem;"
/>
<VBtn
density="default"
prepend-icon="tabler-plus"
@click="isAddPermissionDialogVisible = true"
>
Add Permission
</VBtn>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:items-length="totalPermissions"
:items-per-page-options="[
{ value: 5, title: '5' },
{ value: 10, title: '10' },
{ value: -1, title: '$vuetify.dataFooter.itemsPerPageAll' },
]"
:headers="headers"
:items="permissions"
item-value="name"
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- Name -->
<template #item.name="{ item }">
<div class="text-high-emphasis text-body-1">
{{ item.name }}
</div>
</template>
<!-- Assigned To -->
<template #item.assignedTo="{ item }">
<div class="d-flex gap-4">
<VChip
v-for="text in item.assignedTo"
:key="text"
label
size="small"
:color="colors[text].color"
class="font-weight-medium"
>
{{ colors[text].text }}
</VChip>
</div>
</template>
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalPermissions"
/>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<VBtn
icon
size="small"
color="medium-emphasis"
variant="text"
@click="editPermission(item.name)"
>
<VIcon
size="22"
icon="tabler-edit"
/>
</VBtn>
<IconBtn>
<VIcon
icon="tabler-dots-vertical"
size="22"
/>
</IconBtn>
</template>
</VDataTableServer>
</VCard>
<AddEditPermissionDialog
v-model:is-dialog-visible="isPermissionDialogVisible"
v-model:permission-name="permissionName"
/>
<AddEditPermissionDialog v-model:is-dialog-visible="isAddPermissionDialogVisible" />
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,36 @@
<script setup>
import RoleCards from '@/views/apps/roles/RoleCards.vue'
import UserList from '@/views/apps/roles/UserList.vue'
</script>
<template>
<VRow>
<VCol cols="12">
<h4 class="text-h4 mb-1">
Roles List
</h4>
<p class="text-body-1 mb-0">
A role provided access to predefined menus and features so that depending on assigned role an administrator can have access to what he need
</p>
</VCol>
<!-- 👉 Roles Cards -->
<VCol cols="12">
<RoleCards />
</VCol>
<VCol cols="12">
<h4 class="text-h4 mb-1 mt-6">
Total users with their roles
</h4>
<p class="text-body-1 mb-0">
Find all of your companys administrator accounts and their associate roles.
</p>
</VCol>
<VCol cols="12">
<!-- 👉 User List -->
<UserList />
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,525 @@
<script setup>
import AddNewUserDrawer from '@/views/apps/user/list/AddNewUserDrawer.vue'
// 👉 Store
const searchQuery = ref('')
const selectedRole = ref()
const selectedPlan = ref()
const selectedStatus = ref()
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const selectedRows = ref([])
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
// Headers
const headers = [
{
title: 'User',
key: 'user',
},
{
title: 'Role',
key: 'role',
},
{
title: 'Plan',
key: 'plan',
},
{
title: 'Billing',
key: 'billing',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const {
data: usersData,
execute: fetchUsers,
} = await useApi(createUrl('/apps/users', {
query: {
q: searchQuery,
status: selectedStatus,
plan: selectedPlan,
role: selectedRole,
itemsPerPage,
page,
sortBy,
orderBy,
},
}))
const users = computed(() => usersData.value.users)
const totalUsers = computed(() => usersData.value.totalUsers)
// 👉 search filters
const roles = [
{
title: 'Admin',
value: 'admin',
},
{
title: 'Author',
value: 'author',
},
{
title: 'Editor',
value: 'editor',
},
{
title: 'Maintainer',
value: 'maintainer',
},
{
title: 'Subscriber',
value: 'subscriber',
},
]
const plans = [
{
title: 'Basic',
value: 'basic',
},
{
title: 'Company',
value: 'company',
},
{
title: 'Enterprise',
value: 'enterprise',
},
{
title: 'Team',
value: 'team',
},
]
const status = [
{
title: 'Pending',
value: 'pending',
},
{
title: 'Active',
value: 'active',
},
{
title: 'Inactive',
value: 'inactive',
},
]
const resolveUserRoleVariant = role => {
const roleLowerCase = role.toLowerCase()
if (roleLowerCase === 'subscriber')
return {
color: 'success',
icon: 'tabler-user',
}
if (roleLowerCase === 'author')
return {
color: 'error',
icon: 'tabler-device-desktop',
}
if (roleLowerCase === 'maintainer')
return {
color: 'info',
icon: 'tabler-chart-pie',
}
if (roleLowerCase === 'editor')
return {
color: 'warning',
icon: 'tabler-edit',
}
if (roleLowerCase === 'admin')
return {
color: 'primary',
icon: 'tabler-crown',
}
return {
color: 'primary',
icon: 'tabler-user',
}
}
const resolveUserStatusVariant = stat => {
const statLowerCase = stat.toLowerCase()
if (statLowerCase === 'pending')
return 'warning'
if (statLowerCase === 'active')
return 'success'
if (statLowerCase === 'inactive')
return 'secondary'
return 'primary'
}
const isAddNewUserDrawerVisible = ref(false)
const addNewUser = async userData => {
await $api('/apps/users', {
method: 'POST',
body: userData,
})
// Refetch User
fetchUsers()
}
const deleteUser = async id => {
await $api(`/apps/users/${ id }`, { method: 'DELETE' })
// Delete from selectedRows
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
// Refetch User
fetchUsers()
}
const widgetData = ref([
{
title: 'Session',
value: '21,459',
change: 29,
desc: 'Total Users',
icon: 'tabler-users',
iconColor: 'primary',
},
{
title: 'Paid Users',
value: '4,567',
change: 18,
desc: 'Last Week Analytics',
icon: 'tabler-user-plus',
iconColor: 'error',
},
{
title: 'Active Users',
value: '19,860',
change: -14,
desc: 'Last Week Analytics',
icon: 'tabler-user-check',
iconColor: 'success',
},
{
title: 'Pending Users',
value: '237',
change: 42,
desc: 'Last Week Analytics',
icon: 'tabler-user-search',
iconColor: 'warning',
},
])
</script>
<template>
<section>
<!-- 👉 Widgets -->
<div class="d-flex mb-6">
<VRow>
<template
v-for="(data, id) in widgetData"
:key="id"
>
<VCol
cols="12"
md="3"
sm="6"
>
<VCard>
<VCardText>
<div class="d-flex justify-space-between">
<div class="d-flex flex-column gap-y-1">
<div class="text-body-1 text-high-emphasis">
{{ data.title }}
</div>
<div class="d-flex gap-x-2 align-center">
<h4 class="text-h4">
{{ data.value }}
</h4>
<div
class="text-base"
:class="data.change > 0 ? 'text-success' : 'text-error'"
>
({{ prefixWithPlus(data.change) }}%)
</div>
</div>
<div class="text-sm">
{{ data.desc }}
</div>
</div>
<VAvatar
:color="data.iconColor"
variant="tonal"
rounded
size="42"
>
<VIcon
:icon="data.icon"
size="26"
/>
</VAvatar>
</div>
</VCardText>
</VCard>
</VCol>
</template>
</VRow>
</div>
<VCard class="mb-6">
<VCardItem class="pb-4">
<VCardTitle>Filters</VCardTitle>
</VCardItem>
<VCardText>
<VRow>
<!-- 👉 Select Role -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedRole"
placeholder="Select Role"
:items="roles"
clearable
clear-icon="tabler-x"
/>
</VCol>
<!-- 👉 Select Plan -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedPlan"
placeholder="Select Plan"
:items="plans"
clearable
clear-icon="tabler-x"
/>
</VCol>
<!-- 👉 Select Status -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedStatus"
placeholder="Select Status"
:items="status"
clearable
clear-icon="tabler-x"
/>
</VCol>
</VRow>
</VCardText>
<VDivider />
<VCardText class="d-flex flex-wrap gap-4">
<div class="me-3 d-flex gap-3">
<AppSelect
:model-value="itemsPerPage"
:items="[
{ value: 10, title: '10' },
{ value: 25, title: '25' },
{ value: 50, title: '50' },
{ value: 100, title: '100' },
{ value: -1, title: 'All' },
]"
style="inline-size: 6.25rem;"
@update:model-value="itemsPerPage = parseInt($event, 10)"
/>
</div>
<VSpacer />
<div class="app-user-search-filter d-flex align-center flex-wrap gap-4">
<!-- 👉 Search -->
<div style="inline-size: 15.625rem;">
<AppTextField
v-model="searchQuery"
placeholder="Search User"
/>
</div>
<!-- 👉 Export button -->
<VBtn
variant="tonal"
color="secondary"
prepend-icon="tabler-upload"
>
Export
</VBtn>
<!-- 👉 Add user button -->
<VBtn
prepend-icon="tabler-plus"
@click="isAddNewUserDrawerVisible = true"
>
Add New User
</VBtn>
</div>
</VCardText>
<VDivider />
<!-- SECTION datatable -->
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:items="users"
item-value="id"
:items-length="totalUsers"
:headers="headers"
class="text-no-wrap"
show-select
@update:options="updateOptions"
>
<!-- User -->
<template #item.user="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
size="34"
:variant="!item.avatar ? 'tonal' : undefined"
:color="!item.avatar ? resolveUserRoleVariant(item.role).color : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span v-else>{{ avatarText(item.fullName) }}</span>
</VAvatar>
<div class="d-flex flex-column">
<h6 class="text-base">
<RouterLink
:to="{ name: 'apps-user-view-id', params: { id: item.id } }"
class="font-weight-medium text-link"
>
{{ item.fullName }}
</RouterLink>
</h6>
<div class="text-sm">
{{ item.email }}
</div>
</div>
</div>
</template>
<!-- 👉 Role -->
<template #item.role="{ item }">
<div class="d-flex align-center gap-x-2">
<VIcon
:size="22"
:icon="resolveUserRoleVariant(item.role).icon"
:color="resolveUserRoleVariant(item.role).color"
/>
<div class="text-capitalize text-high-emphasis text-body-1">
{{ item.role }}
</div>
</div>
</template>
<!-- Plan -->
<template #item.plan="{ item }">
<div class="text-body-1 text-high-emphasis text-capitalize">
{{ item.currentPlan }}
</div>
</template>
<!-- Status -->
<template #item.status="{ item }">
<VChip
:color="resolveUserStatusVariant(item.status)"
size="small"
label
class="text-capitalize"
>
{{ item.status }}
</VChip>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn @click="deleteUser(item.id)">
<VIcon icon="tabler-trash" />
</IconBtn>
<IconBtn>
<VIcon icon="tabler-eye" />
</IconBtn>
<VBtn
icon
variant="text"
color="medium-emphasis"
>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem :to="{ name: 'apps-user-view-id', params: { id: item.id } }">
<template #prepend>
<VIcon icon="tabler-eye" />
</template>
<VListItemTitle>View</VListItemTitle>
</VListItem>
<VListItem link>
<template #prepend>
<VIcon icon="tabler-pencil" />
</template>
<VListItemTitle>Edit</VListItemTitle>
</VListItem>
<VListItem @click="deleteUser(item.id)">
<template #prepend>
<VIcon icon="tabler-trash" />
</template>
<VListItemTitle>Delete</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</VBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalUsers"
/>
</template>
</VDataTableServer>
<!-- SECTION -->
</VCard>
<!-- 👉 Add New User -->
<AddNewUserDrawer
v-model:is-drawer-open="isAddNewUserDrawerVisible"
@user-data="addNewUser"
/>
</section>
</template>

View File

@@ -0,0 +1,105 @@
<script setup>
import UserBioPanel from '@/views/apps/user/view/UserBioPanel.vue'
import UserTabAccount from '@/views/apps/user/view/UserTabAccount.vue'
import UserTabBillingsPlans from '@/views/apps/user/view/UserTabBillingsPlans.vue'
import UserTabConnections from '@/views/apps/user/view/UserTabConnections.vue'
import UserTabNotifications from '@/views/apps/user/view/UserTabNotifications.vue'
import UserTabSecurity from '@/views/apps/user/view/UserTabSecurity.vue'
const route = useRoute('apps-user-view-id')
const userTab = ref(null)
const tabs = [
{
icon: 'tabler-users',
title: 'Account',
},
{
icon: 'tabler-lock',
title: 'Security',
},
{
icon: 'tabler-bookmark',
title: 'Billing & Plan',
},
{
icon: 'tabler-bell',
title: 'Notifications',
},
{
icon: 'tabler-link',
title: 'Connections',
},
]
const { data: userData } = await useApi(`/apps/users/${ route.params.id }`)
</script>
<template>
<VRow v-if="userData">
<VCol
cols="12"
md="5"
lg="4"
>
<UserBioPanel :user-data="userData" />
</VCol>
<VCol
cols="12"
md="7"
lg="8"
>
<VTabs
v-model="userTab"
class="v-tabs-pill"
>
<VTab
v-for="tab in tabs"
:key="tab.icon"
>
<VIcon
:size="18"
:icon="tab.icon"
class="me-1"
/>
<span>{{ tab.title }}</span>
</VTab>
</VTabs>
<VWindow
v-model="userTab"
class="mt-6 disable-tab-transition"
:touch="false"
>
<VWindowItem>
<UserTabAccount />
</VWindowItem>
<VWindowItem>
<UserTabSecurity />
</VWindowItem>
<VWindowItem>
<UserTabBillingsPlans />
</VWindowItem>
<VWindowItem>
<UserTabNotifications />
</VWindowItem>
<VWindowItem>
<UserTabConnections />
</VWindowItem>
</VWindow>
</VCol>
</VRow>
<div v-else>
<VAlert
type="error"
variant="tonal"
>
Invoice with ID {{ route.params.id }} not found!
</VAlert>
</div>
</template>

View File

@@ -0,0 +1,245 @@
<script setup>
import ApexChartAreaChart from '@/views/charts/apex-chart/ApexChartAreaChart.vue'
import ApexChartBalance from '@/views/charts/apex-chart/ApexChartBalance.vue'
import ApexChartDailySalesStates from '@/views/charts/apex-chart/ApexChartDailySalesStates.vue'
import ApexChartDataScience from '@/views/charts/apex-chart/ApexChartDataScience.vue'
import ApexChartExpenseRatio from '@/views/charts/apex-chart/ApexChartExpenseRatio.vue'
import ApexChartHorizontalBar from '@/views/charts/apex-chart/ApexChartHorizontalBar.vue'
import ApexChartMobileComparison from '@/views/charts/apex-chart/ApexChartMobileComparison.vue'
import ApexChartNewTechnologiesData from '@/views/charts/apex-chart/ApexChartNewTechnologiesData.vue'
import ApexChartStatistics from '@/views/charts/apex-chart/ApexChartStatistics.vue'
import ApexChartStocksPrices from '@/views/charts/apex-chart/ApexChartStocksPrices.vue'
</script>
<template>
<VRow id="apex-chart-wrapper">
<!-- 👉 Area chart -->
<VCol cols="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Area Chart</VCardTitle>
<VCardSubtitle>Commercial networks</VCardSubtitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ApexChartAreaChart />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Data Science -->
<VCol cols="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Data Science</VCardTitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ApexChartDataScience />
</VCardText>
</VCard>
</VCol>
<!-- 👉 New Technologies Data -->
<VCol cols="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>New Technologies Data</VCardTitle>
<template #append>
<VBtnToggle
density="compact"
color="primary"
variant="outlined"
divided
>
<VBtn>Daily</VBtn>
<VBtn>Monthly</VBtn>
<VBtn>Yearly</VBtn>
</VBtnToggle>
</template>
</VCardItem>
<VCardText>
<ApexChartNewTechnologiesData />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Balance Line Chart -->
<VCol cols="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Balance</VCardTitle>
<VCardSubtitle>Commercial networks &amp; enterprises</VCardSubtitle>
<template #append>
<div class="d-flex align-center">
<h6 class="text-h6 me-3">
$221,267
</h6>
<VChip
label
color="success"
>
<VIcon
start
icon="tabler-arrow-up"
size="15"
/>
<span>22%</span>
</VChip>
</div>
</template>
</VCardItem>
<VCardText>
<ApexChartBalance />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Balance Horizontal Bar -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Balance</VCardTitle>
<VCardSubtitle>$74,382.72</VCardSubtitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ApexChartHorizontalBar />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Stocks Prices -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Stocks Prices</VCardTitle>
<VCardSubtitle>$50,863.98</VCardSubtitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ApexChartStocksPrices />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Daily Sales States -->
<VCol
cols="12"
md="6"
>
<VCard title="Daily Sales States">
<VCardText>
<ApexChartDailySalesStates />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Statistics -->
<VCol
cols="12"
md="6"
>
<VCard title="Statistics">
<VCardText>
<ApexChartStatistics />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Mobile Comparison -->
<VCol
cols="12"
md="6"
>
<VCard title="Mobile Comparison">
<VCardText>
<ApexChartMobileComparison />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Expense Ratio Chart -->
<VCol
cols="12"
md="6"
>
<VCard
title="Expense Ratio"
subtitle="Spending on various categories"
>
<VCardText>
<ApexChartExpenseRatio />
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
.date-picker-wrapper {
inline-size: 10.5rem;
}
#apex-chart-wrapper {
.v-card-item__append {
padding-inline-start: 0;
}
}
</style>

View File

@@ -0,0 +1,202 @@
<script setup>
import ChartJsBarChart from '@/views/charts/chartjs/ChartJsBarChart.vue'
import ChartJsBubbleChart from '@/views/charts/chartjs/ChartJsBubbleChart.vue'
import ChartJsHorizontalBarChart from '@/views/charts/chartjs/ChartJsHorizontalBarChart.vue'
import ChartJsLineAreaChart from '@/views/charts/chartjs/ChartJsLineAreaChart.vue'
import ChartJsLineChart from '@/views/charts/chartjs/ChartJsLineChart.vue'
import ChartJsPolarAreaChart from '@/views/charts/chartjs/ChartJsPolarAreaChart.vue'
import ChartJsRadarChart from '@/views/charts/chartjs/ChartJsRadarChart.vue'
import ChartJsScatterChart from '@/views/charts/chartjs/ChartJsScatterChart.vue'
const chartJsCustomColors = {
white: '#fff',
yellow: '#ffe802',
primary: '#836af9',
areaChartBlue: '#2c9aff',
barChartYellow: '#ffcf5c',
polarChartGrey: '#4f5d70',
polarChartInfo: '#299aff',
lineChartYellow: '#d4e157',
polarChartGreen: '#28dac6',
lineChartPrimary: '#9e69fd',
lineChartWarning: '#ff9800',
horizontalBarInfo: '#26c6da',
polarChartWarning: '#ff8131',
scatterChartGreen: '#28c76f',
warningShade: '#ffbd1f',
areaChartBlueLight: '#84d0ff',
areaChartGreyLight: '#edf1f4',
scatterChartWarning: '#ff9f43',
}
</script>
<template>
<VRow id="chartjs-wrapper">
<!-- 👉 Statistics Line Chart -->
<VCol cols="12">
<VCard
title="Statistics"
subtitle="Commercial networks and enterprises"
>
<VCardText>
<ChartJsLineChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Radar Chart -->
<VCol
cols="12"
md="6"
>
<VCard title="Radar Chart">
<VCardText>
<ChartJsRadarChart />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Average Skills Polar Area Chart -->
<VCol
cols="12"
md="6"
>
<VCard title="Average Skills">
<VCardText>
<ChartJsPolarAreaChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Bubble Chart -->
<VCol cols="12">
<VCard title="Bubble Chart">
<template #append>
<span class="text-subtitle-2">$ 100,000</span>
</template>
<VCardText>
<ChartJsBubbleChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 New Product Data Scatter Chart -->
<VCol cols="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>New Product Data</VCardTitle>
<template #append>
<VBtnToggle
color="primary"
variant="outlined"
density="compact"
>
<VBtn>Daily</VBtn>
<VBtn>Monthly</VBtn>
<VBtn>Yearly</VBtn>
</VBtnToggle>
</template>
</VCardItem>
<VCardText>
<ChartJsScatterChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Data Science Area Line Chart -->
<VCol cols="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>
Data Science
</VCardTitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ChartJsLineAreaChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Latest Statistics -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Latest Statistics</VCardTitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ChartJsBarChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
<!-- 👉 Balance Horizontal Bar Chart -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Balance</VCardTitle>
<VCardSubtitle>$74,123</VCardSubtitle>
<template #append>
<div class="date-picker-wrapper">
<AppDateTimePicker
model-value="2022-06-09"
prepend-inner-icon="tabler-calendar"
placeholder="Select Date"
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
/>
</div>
</template>
</VCardItem>
<VCardText>
<ChartJsHorizontalBarChart :colors="chartJsCustomColors" />
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
.date-picker-wrapper {
inline-size: 10.5rem;
}
#chartjs-wrapper {
.v-card-item__append {
padding-inline-start: 0;
}
}
</style>

View File

@@ -0,0 +1,161 @@
<script setup>
import * as demoCode from '@/views/demos/components/alert/demoCodeAlert'
</script>
<template>
<VRow>
<VCol cols="12">
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<DemoAlertBasic />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>The <code>color</code> prop is used to change the background color of the alert.</p>
<DemoAlertColors />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>The <code>icon</code> prop allows you to add an icon to the beginning of the alert component. If a <code>type</code> is provided, this will override the default type icon. Additionally, setting the <code>icon</code> prop to false will remove the icon altogether.</p>
<DemoAlertIcons />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Border -->
<AppCardCode
title="Border"
:code="demoCode.border"
>
<p>The <code>border</code> prop adds a simple border to one of the 4 sides of the alert. This can be combined with props like <code>color</code>, <code>type</code> and <code>icon</code> to provide unique accents to the alert.</p>
<DemoAlertBorder />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Colored Border -->
<AppCardCode
title="Colored Border"
:code="demoCode.coloredBorder"
>
<p>The <code>colored-border</code> prop removes the alert background in order to accent the <code>border</code> prop. If a type is set, it will use the type's default color. If no <code>color</code> or <code>type</code> is set, the color will default to the inverted color of the applied theme (black for light and white/gray for dark).</p>
<DemoAlertColoredBorder />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>The <code>density</code> prop decreases the height of the alert based upon 1 of 3 levels of density. <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
<DemoAlertDensity />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Type -->
<AppCardCode
title="Type"
:code="demoCode.type"
>
<p>The <code>type</code> prop provides 4 default v-alert styles: <code>success</code>, <code>info</code>, <code>warning</code>, and <code>error</code>. Each of these styles provide a default icon and color.</p>
<DemoAlertType />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Closable -->
<AppCardCode
title="Closable"
:code="demoCode.closable"
>
<p>The <code>closable</code> prop adds a close button to the end of the alert component. Clicking this button will set its value to false and effectively hide the alert.</p>
<DemoAlertClosable />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 V-model support -->
<AppCardCode
title="v-model support"
:code="demoCode.vModelSupport"
>
<p>Clicking this button will set its value to <code>false</code> and effectively hide the alert. You can restore the alert by binding <code>v-model</code> and setting it to true.</p>
<DemoAlertVModelSupport />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Outlined -->
<AppCardCode
title="Outlined"
:code="demoCode.outlined"
>
<p>The <code>variant="outlined"</code> prop inverts the style of an alert, inheriting the currently applied <code>color</code>, applying it to the text and border, and making its background transparent.</p>
<DemoAlertOutlined />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Tonal -->
<AppCardCode
title="Tonal"
:code="demoCode.tonal"
>
<p>The <code>variant</code> prop provides an easy way to change the overall style of your alerts. The <code>variant="tonal"</code> prop is a simple alert variant that applies a reduced opacity background of the provided color.</p>
<DemoAlertTonal />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Elevation -->
<AppCardCode
title="Elevation"
:code="demoCode.elevation"
>
<p>Use <code>elevation</code> prop to set a box-shadow to alert.</p>
<DemoAlertElevation />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Prominent -->
<AppCardCode
title="Prominent"
:code="demoCode.prominent"
>
<p>The <code>prominent</code> prop provides a more pronounced alert by increasing the size of the icon.</p>
<DemoAlertProminent />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,112 @@
<script setup>
import * as demoCode from '@/views/demos/components/avatar/demoCodeAvatar'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>Use <code>color</code> prop to change the background color of avatar.</p>
<DemoAvatarColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Initials -->
<AppCardCode
title="Tonal"
:code="demoCode.tonal"
>
<p>Use <code>variant="tonal"</code> to create light background avatars.</p>
<DemoAvatarTonal />
</AppCardCode>
</VCol>
<VCol
md="6"
cols="12"
>
<!-- 👉 Sizes -->
<AppCardCode
title="Sizes"
:code="demoCode.sizes"
>
<p>The <code>size</code> prop allows you to change the height and width of the avatar.</p>
<DemoAvatarSizes />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>You can use <code>icon</code> prop of <code>v-avatar</code> component for rendering icons.</p>
<DemoAvatarIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Rounded -->
<AppCardCode
title="Rounded"
:code="demoCode.rounded"
>
<p>The <code>rounded</code> prop can be used to change the border radius of <code>v-avatar</code>.</p>
<DemoAvatarRounded />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Images -->
<AppCardCode
title="Images"
:code="demoCode.images"
>
<p>You can use <code>image</code> prop of <code>v-avatar</code> component for rendering image.</p>
<DemoAvatarImages />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Group -->
<AppCardCode
title="Group"
:code="demoCode.group"
>
<p>Use <code>v-avatar-group</code> class as a wrapper of avatars.</p>
<DemoAvatarGroup />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,157 @@
<script setup>
import * as demoCode from '@/views/demos/components/badge/demoCodeBadge'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Style -->
<AppCardCode
title="Style"
:code="demoCode.style"
>
<p>You can use various props like <code>bordered</code>, <code>dot</code>, <code>inline</code>, <code>rounded</code> etc. to style the badge.</p>
<DemoBadgeStyle />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Color -->
<AppCardCode
title="Color"
:code="demoCode.color"
>
<p>Use <code>color</code> prop to create various background badges.</p>
<DemoBadgeColor />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Position -->
<AppCardCode
title="Position"
:code="demoCode.position"
>
<p>You can use <code>location</code> prop to change the position of the badge. Possible values are <code>top-end</code>, <code>bottom-end</code>, <code>bottom-start</code>, <code>top-start</code>.</p>
<DemoBadgePosition />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icon -->
<AppCardCode
title="Icon"
:code="demoCode.icon"
>
<p>You can use <code>icon</code> prop or use <code>slot</code> to render the icon</p>
<DemoBadgeIcon />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Avatar Status -->
<AppCardCode
title="Avatar Status"
:code="demoCode.avatarStatus"
>
<p>You can use badge with avatar as status.</p>
<DemoBadgeAvatarStatus />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Dynamic notifications -->
<AppCardCode
title="Dynamic notifications"
:code="demoCode.dynamicNotifications"
>
<p>You can incorporate badges with dynamic content to make things such as a notification system.</p>
<DemoBadgeDynamicNotifications />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Show on hover -->
<AppCardCode
title="Show on hover"
:code="demoCode.showOnHover"
>
<p>You can do many things with visibility control, for example, show badge on hover.</p>
<DemoBadgeShowOnHover />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Tabs -->
<AppCardCode
title="Tabs"
:code="demoCode.tabs"
>
<p>Badges help convey information to the user in a variety of ways.</p>
<DemoBadgeTabs />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Maximum value content -->
<AppCardCode
title="Maximum Value"
:code="demoCode.maximumValue"
>
<p>Use <code>max</code> prop to cap the value of the badge content</p>
<DemoBadgeMaximumValue />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Tonal Badge -->
<AppCardCode
title="Tonal"
:code="demoCode.tonal"
>
<p>Use class <code>v-badge--tonal</code> for using tonal variant badge.</p>
<DemoBadgeTonal />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,189 @@
<script setup>
import * as demoCode from '@/views/demos/components/button/demoCodeButton'
</script>
<template>
<VRow>
<VCol cols="12">
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>The <code>color</code> prop is used to change the background color of the alert.</p>
<DemoButtonColors />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Outlined -->
<AppCardCode
title="Outlined"
:code="demoCode.outlined"
>
<p>The <code>outlined</code> variant option is used to create outlined buttons.</p>
<DemoButtonOutlined />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Flat -->
<AppCardCode
title="Flat"
:code="demoCode.flat"
>
<p>The <code>flat</code> buttons still maintain their background color, but have no box shadow.</p>
<DemoButtonFlat />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Rounded -->
<AppCardCode
title="Rounded"
:code="demoCode.rounded"
>
<p>Use the <code>rounded</code> prop to control the border radius of buttons.</p>
<DemoButtonRounded />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Text -->
<AppCardCode
title="Text"
:code="demoCode.text"
>
<p>Use <code>text</code> variant option to create text button. Text buttons have no box shadow and no background.</p>
<DemoButtonText />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Plain -->
<AppCardCode
title="Plain"
:code="demoCode.plain"
>
<p>Use <code>plain</code> variant option to a create a plain button. Plain buttons have a lower baseline opacity that reacts to hover and focus.</p>
<DemoButtonPlain />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Tonal -->
<AppCardCode
title="Tonal"
:code="demoCode.tonal"
>
<p>Use <code>tonal</code> variant option to a create a light background button.</p>
<DemoButtonTonal />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Icon -->
<AppCardCode
title="Icon"
:code="demoCode.icon"
>
<p>Icons can be used inside of buttons to add emphasis to the action.</p>
<DemoButtonIcon />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Icon Only -->
<AppCardCode
title="Icon Only"
:code="demoCode.iconOnly"
>
<p>Use <code>VIcon</code> component inside button to create buttons that looks like rest of the theme.</p>
<DemoButtonIconOnly />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Sizing -->
<AppCardCode
title="Sizing"
:code="demoCode.sizing"
>
<p>Buttons can be given different sizing options to fit a multitude of scenarios.</p>
<DemoButtonSizing />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Block -->
<AppCardCode
title="Block"
:code="demoCode.block"
>
<p>The <code>block</code> prop allows buttons to extend the full available width.</p>
<DemoButtonBlock />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Loaders -->
<AppCardCode
title="Loaders"
:code="demoCode.loaders"
>
<p>Using the <code>loading</code> prop, you can notify a user that there is processing taking place. The default behavior is to use a <code>v-progress-circular</code> component but this can be customized.</p>
<DemoButtonLoaders />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Router -->
<AppCardCode
title="Router"
:code="demoCode.router"
>
<p>Use <code>to</code> prop to create button with router support.</p>
<VAlert
color="warning"
variant="tonal"
class="mb-4"
>
Note: On click of the link button, You will get redirected to another page.
</VAlert>
<DemoButtonRouter />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Link -->
<AppCardCode
title="Link"
:code="demoCode.link"
>
<p>Designates that the component is a link. This is automatic when using the <code>href</code> or <code>to</code> prop.</p>
<VAlert
color="warning"
variant="tonal"
class="mb-4"
>
Note: On click of the link button, You will get redirected to another page.
</VAlert>
<DemoButtonLink />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Button Group -->
<AppCardCode
title="Group"
:code="demoCode.group"
>
<p>
Wrap buttons with the <code>v-btn-toggle</code> component to create a group button. You can add a visual divider between buttons with the <code>divided</code> prop.
</p>
<DemoButtonGroup />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,157 @@
<script setup>
import * as demoCode from '@/views/demos/components/chip/demoCodeChip'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Color -->
<AppCardCode
title="Color"
:code="demoCode.color"
>
<p>Use <code>color</code> prop to change the background color of chips.</p>
<DemoChipColor />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Elevated -->
<AppCardCode
title="Elevated"
:code="demoCode.elevated"
>
<p>Use <code>elevated</code> variant option to create filled chips.</p>
<DemoChipElevated />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Outlined -->
<AppCardCode
title="Outlined"
:code="demoCode.outlined"
>
<p>Use <code>outlined</code> variant option to create outline border chips.</p>
<DemoChipOutlined />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Label -->
<AppCardCode
title="Rounded"
:code="demoCode.rounded"
>
<p>To use the rounded chip, set <code>label</code> props value to <strong>false</strong>.</p>
<DemoChipRounded />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Closable -->
<AppCardCode
title="Closable"
:code="demoCode.closable"
>
<p>Closable chips can be controlled with a <code>v-model</code>.</p>
<DemoChipClosable />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Chip With Icon -->
<AppCardCode
title="With Icon"
:code="demoCode.withIcon"
>
<p>Chips can use text or any icon available in the Material Icons font library.</p>
<DemoChipWithIcon />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Avatar -->
<AppCardCode
title="With Avatar"
:code="demoCode.withAvatar"
>
<p>Use <code>pill</code> prop to remove the <code>v-avatar</code> padding.</p>
<DemoChipWithAvatar />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Sizes -->
<AppCardCode
title="Sizes"
:code="demoCode.sizes"
>
<p>The <code>v-chip</code> component can have various sizes from <code>x-small</code> to <code>x-large</code>.</p>
<DemoChipSizes />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 In Selects -->
<AppCardCode
title="In Selects"
:code="demoCode.inSelects"
>
<p>Selects can use <code>chips</code> to display the selected data. Try adding your own tags below.</p>
<DemoChipInSelects />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Expandable -->
<AppCardCode
title="Expandable"
:code="demoCode.expandable"
>
<p>Chips can be combined with <code>v-menu</code> to enable a specific set of actions for a chip.</p>
<DemoChipExpandable />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,127 @@
<script setup>
import * as demoCode from '@/views/demos/components/dialog/demoCodeDialog'
</script>
<template>
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>A dialog contains two slots, one for its activator and one for its content (default). Good for Privacy Policies.</p>
<DemoDialogBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Persistent -->
<AppCardCode
title="Persistent"
:code="demoCode.persistent"
>
<p>Use <code>persistent</code> prop to create persistent dialog.</p>
<DemoDialogPersistent />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Scrollable -->
<AppCardCode
title="Scrollable"
:code="demoCode.scrollable"
>
<p>Use <code>scrollable</code> prop to create scrollable dialog.</p>
<DemoDialogScrollable />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Loader -->
<AppCardCode
title="Loader"
:code="demoCode.loader"
>
<p>The <code>v-dialog</code> component makes it easy to create a customized loading experience for your application.</p>
<DemoDialogLoader />
</AppCardCode>
</VCol>
<VCol
col="12"
md="6"
>
<!-- 👉 Nesting -->
<AppCardCode
title="Nesting"
:code="demoCode.nesting"
>
<p>Dialogs can be nested: you can open one dialog from another.</p>
<DemoDialogNesting />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Overflowed -->
<AppCardCode
title="Overflowed"
:code="demoCode.overflowed"
>
<p>Modals that do not fit within the available window space will scroll the container.</p>
<DemoDialogOverflowed />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Fullscreen -->
<AppCardCode
title="Fullscreen"
:code="demoCode.fullscreen"
>
<p>Due to limited space, full-screen dialogs may be more appropriate for mobile devices than dialogs used on devices with larger screens.</p>
<DemoDialogFullscreen />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Form -->
<AppCardCode
title="Form"
:code="demoCode.form"
>
<p>Just a simple example of a form in a dialog.</p>
<DemoDialogForm />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,121 @@
<script setup>
import * as demoCode from '@/views/demos/components/expansion-panel/demoCodeExpansionPanel'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
variant="outlined"
:code="demoCode.basic"
>
<p>Expansion panels in their simplest form display a list of expandable items. However, with the <code>multiple</code> prop, the expansion-panel can remain open until explicitly closed.</p>
<DemoExpansionPanelBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Accordion -->
<AppCardCode
title="Accordion"
variant="outlined"
:code="demoCode.accordion"
>
<p>Use <code>accordion</code> variant option to create <strong>Accordion</strong> Panels. Accordion expansion-panel hasn't got margins around active panel.</p>
<DemoExpansionPanelAccordion />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Inset -->
<AppCardCode
title="Inset"
variant="outlined"
:code="demoCode.inset"
>
<p>Use <code>inset</code> variant option to create Inset Panels. The Inset expansion-panel becomes smaller when activated.</p>
<DemoExpansionPanelInset />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Popout -->
<AppCardCode
title="Popout"
variant="outlined"
:code="demoCode.popout"
>
<p>
Use <code>popout</code> variant option to create expansion-panel with popout design. With it, expansion-panel is enlarged when activated.
</p>
<DemoExpansionPanelPopout />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Custom Icon -->
<AppCardCode
title="Custom Icon"
variant="outlined"
:code="demoCode.customIcon"
>
<p>Expand action icon can be customized with <code>expand-icon</code> prop or the <code>actions</code> slot. Also, use the <code>.no-icon-rotate</code> class in conjunction with the <code>VExpansionPanels</code> component to disable icon rotation.</p>
<DemoExpansionPanelCustomIcon />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Model -->
<AppCardCode
title="Model"
variant="outlined"
:code="demoCode.model"
>
<p>Expansion panels can be controlled externally by modifying the <code>v-model</code>. If <code>multiple</code> prop is used then it is an array containing the indices of the open items.</p>
<DemoExpansionPanelModel />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 With Border -->
<AppCardCode
title="With Border"
variant="outlined"
:code="demoCode.withBorder"
>
<p>Please use the <code>.expansion-panels-width-border</code> class in conjunction with the <code>VExpansionPanels</code> component to create panels with borders.</p>
<DemoExpansionPanelWithBorder />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,205 @@
<script setup>
import * as demoCode from '@/views/demos/components/list/demoCodeList'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
no-padding
:code="demoCode.basic"
>
<VCardText><code>v-list</code> component can contain an avatar, content, actions and much more.</VCardText>
<VCardText>
<DemoListBasic />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Rounded -->
<AppCardCode
title="Rounded"
no-padding
:code="demoCode.rounded"
>
<VCardText>You can make <code>v-list-item</code> rounded using <code>rounded</code> prop.</VCardText>
<VCardText>
<DemoListRounded />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
no-padding
>
<VCardText>Use <code>density</code> prop to adjusts the spacing within the component. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</VCardText>
<VCardText>
<DemoListDensity />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Nav -->
<AppCardCode
title="Nav"
no-padding
:code="demoCode.nav"
>
<VCardText>Lists can receive an alternative <code>nav</code> styling that reduces the width <code>v-list-item</code> takes up as well as adding a border radius.</VCardText>
<VCardText>
<DemoListNav />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Action and item group -->
<AppCardCode
title="Action and item group"
no-padding
:code="demoCode.actionAndItemGroup"
>
<VCardText>A <code>three-line</code> list with actions. Utilizing <code>v-list-group</code>, easily connect actions to your tiles.</VCardText>
<VCardText>
<DemoListActionAndItemGroup />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Sub Group -->
<AppCardCode
title="Sub Group"
no-padding
:code="demoCode.subGroup"
>
<VCardText>
Using the <code>v-list-group</code> component you can create up to 2 levels in depth using the sub-group prop.
</VCardText>
<VCardText>
<DemoListSubGroup />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Two lines and Subheader -->
<AppCardCode
title="Two lines and subheader"
no-padding
:code="demoCode.twoLinesAndSubheader"
>
<VCardText>Lists can contain subheaders, dividers, and can contain 1 or more lines. The subtitle will overflow with ellipsis if it extends past one line.</VCardText>
<VCardText>
<DemoListTwoLinesAndSubheader />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Three Line -->
<AppCardCode
title="Three Line"
no-padding
:code="demoCode.threeLine"
>
<VCardText>For three line lists, the subtitle will clamp vertically at 2 lines and then ellipsis. This feature uses line-clamp and is not supported in all browsers.</VCardText>
<VCardText>
<DemoListThreeLine />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Users List -->
<AppCardCode
title="User List"
no-padding
:code="demoCode.userList"
>
<VCardText>
<DemoListUserList />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Progress List -->
<AppCardCode
title="Progress List"
no-padding
:code="demoCode.progressList"
>
<VCardText>
<DemoListProgressList />
</VCardText>
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Shaped -->
<AppCardCode
title="Shaped"
no-padding
:code="demoCode.shaped"
>
<VCardText>
Shaped lists have rounded borders on one side of the <code>v-list-item</code>.
</VCardText>
<VCardText>
<DemoListShaped />
</VCardText>
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,99 @@
<script setup>
import * as demoCode from '@/views/demos/components/menu/demoCodeMenu'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>
Remember to put the element that activates the menu in the activator slot.
</p>
<DemoMenuBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Custom transitions -->
<AppCardCode
title="Custom transitions"
:code="demoCode.customTransitions"
>
<p>Vuetify comes with 3 standard transitions, <code>scale</code>, <code>slide-x</code> and <code>slide-y</code>. Use <code>transition</code> prop to add transition to a menu.</p>
<DemoMenuCustomTransitions />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Location -->
<AppCardCode
title="Location"
:code="demoCode.location"
>
<p>Menu can be offset relative to the activator by using the <code>location</code> prop.</p>
<DemoMenuLocation />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Open on hover -->
<AppCardCode
title="Open on hover"
:code="demoCode.openOnHover"
>
<p>Menus can be accessed using hover instead of clicking with the <code>open-on-hover</code> prop.</p>
<DemoMenuOpenOnHover />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Popover -->
<AppCardCode
title="Popover"
:code="demoCode.popover"
>
<p>A menu can be configured to be static when opened, allowing it to function as a popover. This can be useful when there are multiple interactive items within the menu contents.</p>
<DemoMenuPopover />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Activator and tooltip -->
<AppCardCode
title="Activator and tooltip"
:code="demoCode.activatorAndTooltip"
>
<p>With the new <code>v-slot</code> syntax, nested activators such as those seen with a <code>v-menu</code> and <code>v-tooltip</code> attached to the same activator button, need a particular setup in order to function correctly</p>
<DemoMenuActivatorAndTooltip />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,157 @@
<script setup>
import * as demoCode from '@/views/demos/components/pagination/demoCodePagination'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-pagination</code> component is used to separate long sets of data.</p>
<DemoPaginationBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 outline basic variant -->
<AppCardCode
title="Outline"
:code="demoCode.outline"
>
<p>The <code>variant='outline'</code> prop is used to give outline to pagination item.</p>
<DemoPaginationOutline />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Circle -->
<AppCardCode
title="Circle"
:code="demoCode.circle"
>
<p>The <code>rounded</code> prop allows you to render pagination buttons with alternative styles.</p>
<DemoPaginationCircle />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 outline circle variant -->
<AppCardCode
title="Outline Circle"
:code="demoCode.outlineCircle"
>
<p>The <code>variant='outline'</code> and <code>rounded</code> prop is used to give rounded outline to pagination item.</p>
<DemoPaginationOutlineCircle />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Disabled -->
<AppCardCode
title="Disabled"
:code="demoCode.disabled"
>
<p>Pagination items can be manually deactivated using the <code>disabled</code> prop.</p>
<DemoPaginationDisabled />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>Previous and next page icons can be customized with the <code>prev-icon</code> and <code>next-icon</code> props.</p>
<DemoPaginationIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Length -->
<AppCardCode
title="Length"
:code="demoCode.length"
>
<p>Using the <code>length</code> prop you can set the length of <code>v-pagination</code>, if the number of page buttons exceeds the parent container, it will truncate the list.</p>
<DemoPaginationLength />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Total visible -->
<AppCardCode
title="Total visible"
:code="demoCode.totalVisible"
>
<p>You can also manually set the maximum number of visible page buttons with the <code>total-visible</code> prop.</p>
<DemoPaginationTotalVisible />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Color -->
<AppCardCode
title="Color"
:code="demoCode.color"
>
<p>Use <code>active-color</code> prop for create different color pagination.</p>
<DemoPaginationColor />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Size -->
<AppCardCode
title="Size"
:code="demoCode.size"
>
<p>Use <code>size</code> prop to sets the height and width of the component. Default unit is px. Can also use the following predefined sizes: <strong>x-small</strong>, <strong>small</strong>, <strong>default</strong>, <strong>large</strong>, and <strong>x-large</strong>.</p>
<DemoPaginationSize />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,67 @@
<script setup>
import * as demoCode from '@/views/demos/components/progress-circular/demoCodeProgressCircular'
</script>
<template>
<VRow class="match-height">
<!-- 👉 Progress circular color -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="color"
:code="demoCode.color"
>
<p>Alternate colors can be applied to <code>v-progress-circular</code> using the <code>color</code> prop.</p>
<DemoProgressCircularColor />
</AppCardCode>
</VCol>
<!-- 👉 Progress circular Indeterminate -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Indeterminate"
:code="demoCode.indeterminate"
>
<p>Using the <code>indeterminate</code> prop, a <code>v-progress-circular</code> continues to animate indefinitely.</p>
<DemoProgressCircularIndeterminate />
</AppCardCode>
</VCol>
<!-- 👉 Progress circular Rotate -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Rotate"
:code="demoCode.rotate"
>
<p>The <code>rotate</code> prop gives you the ability to customize the <code>v-progress-circular</code>'s origin.</p>
<DemoProgressCircularRotate />
</AppCardCode>
</VCol>
<!-- 👉 Progress circular Size and Width -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Size"
:code="demoCode.size"
>
<p>The <code>size</code> and <code>width</code> props allow you to easily alter the size and width of the <code>v-progress-circular</code> component.</p>
<DemoProgressCircularSize />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,113 @@
<script setup>
import * as demoCode from '@/views/demos/components/progress-linear/demoCodeProgressLinear'
</script>
<template>
<VRow class="match-height">
<!-- 👉 Progress linear color -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Color"
:code="demoCode.color"
>
<p>Use the props <code>color</code> and <code>background-color</code> to set colors.</p>
<DemoProgressLinearColor />
</AppCardCode>
</VCol>
<!-- 👉 Progress linear Buffering -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Buffering"
:code="demoCode.buffering"
>
<p>The primary value is controlled by <code>v-model</code>, whereas the buffer is controlled by the <code>buffer-value</code> prop.</p>
<DemoProgressLinearBuffering />
</AppCardCode>
</VCol>
<!-- 👉 Progress linear indeterminate -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Indeterminate"
:code="demoCode.indeterminate"
>
<p>for continuously animating progress bar,use prop <code>indeterminate</code>. This indicates continuous process. </p>
<DemoProgressLinearIndeterminate />
</AppCardCode>
</VCol>
<!-- 👉 Progress linear Reversed -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Reversed"
:code="demoCode.reversed"
>
<p>Use prop <code>reverse</code> to animate continuously in reverse direction. The component also has RTL support.</p>
<DemoProgressLinearReversed />
</AppCardCode>
</VCol>
<!-- 👉 Progress linear Rounded -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Rounded"
:code="demoCode.rounded"
>
<p>
The <code> rounded </code>prop is used to apply a border radius to the v-progress-linear component.
By default we have set <code>rounded</code> prop true. You can disable it by using <code>:rounded='false'</code>.
</p>
<DemoProgressLinearRounded />
</AppCardCode>
</VCol>
<!-- 👉 Progress linear Slot -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Slots"
:code="demoCode.slots"
>
<p>The v-progress-linear component will be responsive to user input when using <code>v-model</code>. You can use the default slot or bind a local model to display inside of the progress.</p>
<DemoProgressLinearSlots />
</AppCardCode>
</VCol>
<!-- 👉 Progress Linear Striped -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Striped"
:code="demoCode.striped"
>
<p> The <code>striped</code> prop is used to apply striped background.</p>
<DemoProgressLinearStriped />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,126 @@
<script setup>
import * as demoCode from '@/views/demos/components/snackbar/demoCodeSnackbar'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-snackbar</code> component is used to display a quick message to a user. Snackbars support positioning, removal delay, and callbacks.</p>
<DemoSnackbarBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 With Action -->
<AppCardCode
title="With Action"
:code="demoCode.withAction"
>
<p>Use <code>actions</code> slot to add action button. A <code>v-snackbar</code> in its simplest form displays a temporary and closable notification to the user.</p>
<DemoSnackbarWithAction />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Multi line -->
<AppCardCode
title="Multi Line"
:code="demoCode.multiLine"
>
<p>The <code>multi-line</code> property extends the height of the <code>v-snackbar</code> to give you a little more room for content.</p>
<DemoSnackbarMultiLine />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Timeout -->
<AppCardCode
title="Timeout"
:code="demoCode.timeout"
>
<p>The <code>timeout</code> property lets you customize the delay before the <code>v-snackbar</code> is hidden.</p>
<DemoSnackbarTimeout />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical -->
<AppCardCode
title="Vertical"
:code="demoCode.vertical"
>
<p>The <code>vertical</code> property allows you to stack the content of your <code>v-snackbar</code>.</p>
<DemoSnackbarVertical />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Position -->
<AppCardCode
title="Position"
:code="demoCode.position"
>
<p>Use <code>location</code> prop to change the position of snackbar.</p>
<DemoSnackbarPosition />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Variants -->
<AppCardCode
title="Variants"
:code="demoCode.variants"
>
<p>Apply different styles to the snackbar using props such as <code>shaped</code>, <code>rounded</code>, <code>color</code>, <code>text</code>, <code>outlined</code>, <code>tile</code> and more.</p>
<DemoSnackbarVariants />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Transition -->
<AppCardCode
title="Transition"
:code="demoCode.transition"
>
<p>Use transition prop to sets the component transition.</p>
<DemoSnackbarTransition />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,197 @@
<script setup>
import * as demoCode from '@/views/demos/components/tabs/demoCodeTabs'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
variant="outlined"
:code="demoCode.basic"
>
<p>The <code>v-tabs</code> component is used for hiding content behind a selectable item.</p>
<DemoTabsBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Stacked -->
<AppCardCode
title="Stacked"
variant="outlined"
:code="demoCode.stacked"
>
<p>Using <code>stacked</code> prop you can have buttons that use both icons and text.</p>
<DemoTabsStacked />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical -->
<AppCardCode
title="Vertical"
variant="outlined"
:code="demoCode.vertical"
>
<p>The <code>vertical</code> prop allows for <code>v-tab</code> components to stack vertically.</p>
<DemoTabsVertical />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Alignment -->
<AppCardCode
title="Alignment"
variant="outlined"
:code="demoCode.alignment"
>
<p>Use <code>align-tabs</code> prop to change the tabs alignment.</p>
<DemoTabsAlignment />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Pagination -->
<AppCardCode
title="Pagination"
variant="outlined"
:code="demoCode.pagination"
>
<p>If the tab items overflow their container, pagination controls will appear on desktop.</p>
<DemoTabsPagination />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Custom Icons -->
<AppCardCode
title="Custom Icons"
variant="outlined"
:code="demoCode.customIcons"
>
<p><code>prev-icon</code> and <code>next-icon</code> props can be used for applying custom pagination icons.</p>
<DemoTabsCustomIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Fixed tabs -->
<AppCardCode
title="Fixed"
variant="outlined"
:code="demoCode.fixed"
>
<p>The <code>fixed-tabs</code> prop forces <code>v-tab</code> to take up all available space up to the maximum width (300px).</p>
<DemoTabsFixed />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Grow -->
<AppCardCode
title="Grow"
variant="outlined"
:code="demoCode.grow"
>
<p>The <code>grow</code> prop will make the tab items take up all available space with no limit.</p>
<DemoTabsGrow />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Programmatic Navigation -->
<AppCardCode
title="Programmatic Navigation"
variant="outlined"
:code="demoCode.programmaticNavigation"
>
<DemoTabsProgrammaticNavigation />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Dynamic Tabs -->
<AppCardCode
title="Dynamic"
variant="outlined"
:code="demoCode.dynamic"
>
<p>Tabs can be dynamically added and removed. This allows you to update to any number and the <code>v-tabs</code> component will react.</p>
<DemoTabsDynamic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic Pill -->
<AppCardCode
title="Basic Pill"
variant="outlined"
:code="demoCode.basicPill"
>
<p>Use our custom class <code>.v-tabs-pill</code> along with <code>v-tabs</code> component to style pill tabs.</p>
<DemoTabsBasicPill />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical Pill -->
<AppCardCode
title="Vertical Pill"
variant="outlined"
:code="demoCode.verticalPill"
>
<p>Use our custom class .v-tabs-pill along with v-tabs component to style pill tabs.</p>
<DemoTabsVerticalPill />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,21 @@
<template>
<VRow>
<VCol
cols="12"
md="6"
>
<TimelineBasic />
</VCol>
<VCol
cols="12"
md="6"
>
<TimelineOutlined />
</VCol>
<VCol cols="12">
<TimelineWithIcons />
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,77 @@
<script setup>
import * as demoCode from '@/views/demos/components/tooltip/demoCodeTooltip'
</script>
<template>
<VRow>
<VCol cols="12">
<!-- 👉 Location -->
<AppCardCode
title="Location"
:code="demoCode.location"
>
<p>Use the <code>location</code> prop to specify on which side of the element the tooltip should show</p>
<DemoTooltipLocation />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Events -->
<AppCardCode
title="Events"
:code="demoCode.events"
>
<DemoTooltipEvents />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Delay On Hover -->
<AppCardCode
title="Delay On Hover"
:code="demoCode.delayOnHover"
>
<p>Delay (in ms) after which tooltip opens (when <code>open-on-hover</code> prop is set to true)</p>
<DemoTooltipDelayOnHover />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 V-model Support -->
<AppCardCode
title="V-Model Support"
:code="demoCode.vModelSupport"
>
<p>Tooltip visibility can be programmatically changed using <code>v-model</code>.</p>
<DemoTooltipVModelSupport />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Transition -->
<AppCardCode
title="Transition"
:code="demoCode.transition"
>
<p>Use <code>transition</code> prop to sets the component transition.</p>
<DemoTooltipTransition />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Tooltip on Various Elements -->
<AppCardCode
title="Tooltip on Various Elements"
:code="demoCode.tooltipOnVariousElements"
>
<p>Tooltips can wrap any element.</p>
<DemoTooltipTooltipOnVariousElements />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,106 @@
<script setup>
import AnalyticsAverageDailySales from '@/views/dashboards/analytics/AnalyticsAverageDailySales.vue'
import AnalyticsEarningReportsWeeklyOverview from '@/views/dashboards/analytics/AnalyticsEarningReportsWeeklyOverview.vue'
import AnalyticsMonthlyCampaignState from '@/views/dashboards/analytics/AnalyticsMonthlyCampaignState.vue'
import AnalyticsProjectTable from '@/views/dashboards/analytics/AnalyticsProjectTable.vue'
import AnalyticsSalesByCountries from '@/views/dashboards/analytics/AnalyticsSalesByCountries.vue'
import AnalyticsSalesOverview from '@/views/dashboards/analytics/AnalyticsSalesOverview.vue'
import AnalyticsSourceVisits from '@/views/dashboards/analytics/AnalyticsSourceVisits.vue'
import AnalyticsSupportTracker from '@/views/dashboards/analytics/AnalyticsSupportTracker.vue'
import AnalyticsTotalEarning from '@/views/dashboards/analytics/AnalyticsTotalEarning.vue'
import AnalyticsWebsiteAnalytics from '@/views/dashboards/analytics/AnalyticsWebsiteAnalytics.vue'
</script>
<template>
<VRow class="match-height">
<!-- 👉 Website analytics -->
<VCol
cols="12"
md="6"
>
<AnalyticsWebsiteAnalytics />
</VCol>
<!-- 👉 Average Daily Sales -->
<VCol
cols="12"
md="3"
sm="6"
>
<AnalyticsAverageDailySales />
</VCol>
<!-- 👉 Sales Overview -->
<VCol
cols="12"
md="3"
sm="6"
>
<AnalyticsSalesOverview />
</VCol>
<!-- 👉 Earning Reports Weekly Overview -->
<VCol
cols="12"
md="6"
>
<AnalyticsEarningReportsWeeklyOverview />
</VCol>
<!-- 👉 Support Tracker -->
<VCol
cols="12"
md="6"
>
<AnalyticsSupportTracker />
</VCol>
<!-- 👉 Sales by Countries -->
<VCol
cols="12"
sm="6"
lg="4"
>
<AnalyticsSalesByCountries />
</VCol>
<!-- 👉 Total Earning -->
<VCol
cols="12"
sm="6"
lg="4"
>
<AnalyticsTotalEarning />
</VCol>
<!-- 👉 Monthly Campaign State -->
<VCol
cols="12"
sm="6"
lg="4"
>
<AnalyticsMonthlyCampaignState />
</VCol>
<!-- 👉 Source Visits -->
<VCol
cols="12"
sm="6"
lg="4"
>
<AnalyticsSourceVisits />
</VCol>
<!-- 👉 Project Table -->
<VCol
cols="12"
lg="8"
>
<AnalyticsProjectTable />
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
</style>

View File

@@ -0,0 +1,958 @@
<script setup>
import { ref, computed, nextTick, onMounted, watch, onUnmounted } from "vue";
import CrmActiveProject from "@/views/dashboards/crm/CrmActiveProject.vue";
import CrmActivityTimeline from "@/views/dashboards/crm/CrmActivityTimeline.vue";
import CrmAnalyticsSales from "@/views/dashboards/crm/CrmAnalyticsSales.vue";
import CrmEarningReportsYearlyOverview from "@/views/dashboards/crm/CrmEarningReportsYearlyOverview.vue";
import CrmProjectStatus from "@/views/dashboards/crm/CrmProjectStatus.vue";
import CrmRecentTransactions from "@/views/dashboards/crm/CrmRecentTransactions.vue";
import CrmSalesByCountries from "@/views/dashboards/crm/CrmSalesByCountries.vue";
import ProjectActivityBarChart from "@/components/ProjectActivityBarChart.vue";
import AnalysisCard from "@/components/AnalysisCard.vue";
import CostOverview from "@/components/CostOverview.vue";
import GeneratedLeadsCard from "@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue";
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
const isEditMode = ref(false);
const handleEditModeChange = (event) => {
if (event.detail) {
isEditMode.value = event.detail.isActive;
}
};
const handleDropFromSidebar = async (e) => {
console.log('Drop event triggered', { isEditMode: isEditMode.value });
if (!isEditMode.value) return;
e.preventDefault();
e.stopPropagation();
const widgetId = e.dataTransfer.getData("text/plain");
const widgetData = e.dataTransfer.getData("application/json");
console.log('Widget ID from dataTransfer:', widgetId);
console.log('Widget Data:', widgetData);
if (widgetId && !cardOrder.value.includes(widgetId)) {
console.log('Adding widget to dashboard:', widgetId);
if (widgetData && !cardComponents[widgetId]) {
const widget = JSON.parse(widgetData);
cardComponents[widgetId] = {
component: GeneratedLeadsCard,
props: widget.props || {}
};
if (!cardSizes.value[widgetId]) {
cardSizes.value[widgetId] = { cols: 6, height: 33.33 };
}
}
cardOrder.value.push(widgetId);
await nextTick();
await calculateRowHeights();
const event = new CustomEvent('widget-added-to-dashboard', {
detail: { widgetId }
});
window.dispatchEvent(event);
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
detail: { widgets: cardOrder.value }
}));
}
}
const handleDragOverFromSidebar = (e) => {
if (!isEditMode.value) return;
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
const container = e.currentTarget;
container.classList.add('drag-over');
};
const handleDragLeaveFromSidebar = (e) => {
if (!isEditMode.value) return;
const container = e.currentTarget;
container.classList.remove('drag-over');
};
const cardOrder = ref([
"leads1",
"leads2",
"leads3",
"project-activity",
"analysis1",
"analysis2",
"cost-overview",
"earning-reports",
"analytics-sales",
"sales-countries",
"project-status",
"active-project",
"recent-transactions",
"activity-timeline",
]);
const defaultWidgetIds = [
"leads1",
"leads2",
"leads3",
"project-activity",
"analysis1",
"analysis2",
"cost-overview",
"earning-reports",
"analytics-sales",
"sales-countries",
"project-status",
"active-project",
"recent-transactions",
"activity-timeline"
];
const cardSizes = ref({
leads1: { cols: 4, height: 33.33 },
leads2: { cols: 4, height: 33.33 },
leads3: { cols: 4, height: 33.33 },
"project-activity": { cols: 8, height: 33.33 },
analysis1: { cols: 4, height: 33.33 },
analysis2: { cols: 4, height: 33.33 },
"cost-overview": { cols: 8, height: 33.33 },
"earning-reports": { cols: 8, height: 33.33 },
"analytics-sales": { cols: 4, height: 33.33 },
"sales-countries": { cols: 4, height: 33.33 },
"project-status": { cols: 4, height: 33.33 },
"active-project": { cols: 4, height: 33.33 },
"recent-transactions": { cols: 6, height: 33.33 },
"activity-timeline": { cols: 6, height: 33.33 },
"ecommerce-congratulations": { cols: 6, height: 33.33 },
"ecommerce-earning-reports": { cols: 8, height: 33.33 },
"ecommerce-expenses": { cols: 4, height: 33.33 },
"ecommerce-generated-leads": { cols: 4, height: 33.33 },
"ecommerce-invoice-table": { cols: 12, height: 33.33 },
"ecommerce-order": { cols: 6, height: 33.33 },
"ecommerce-popular-products": { cols: 6, height: 33.33 },
"ecommerce-revenue-report": { cols: 8, height: 33.33 },
"ecommerce-statistics": { cols: 12, height: 33.33 },
"ecommerce-total-profit": { cols: 6, height: 33.33 },
"ecommerce-transactions": { cols: 8, height: 33.33 },
});
const cardComponents = {
leads1: { component: GeneratedLeadsCard, props: { progress: 33 } },
leads2: {
component: GeneratedLeadsCard,
props: { donutColors: ["primary"], progress: 71 },
},
leads3: {
component: GeneratedLeadsCard,
props: { donutColors: ["warning"], progress: 56 },
},
"project-activity": { component: ProjectActivityBarChart, props: {} },
analysis1: {
component: AnalysisCard,
props: { chartName: "Active Projects Progress" },
},
analysis2: { component: AnalysisCard, props: { chartName: "Cost overview" } },
"cost-overview": { component: CostOverview, props: {} },
"earning-reports": { component: CrmEarningReportsYearlyOverview, props: {} },
"analytics-sales": { component: CrmAnalyticsSales, props: {} },
"sales-countries": { component: CrmSalesByCountries, props: {} },
"project-status": { component: CrmProjectStatus, props: {} },
"active-project": { component: CrmActiveProject, props: {} },
"recent-transactions": { component: CrmRecentTransactions, props: {} },
"activity-timeline": { component: CrmActivityTimeline, props: {} },
"ecommerce-congratulations": { component: EcommerceCongratulationsJohn, props: {} },
"ecommerce-earning-reports": { component: EcommerceEarningReports, props: {} },
"ecommerce-expenses": { component: EcommerceExpensesRadialBarCharts, props: {} },
"ecommerce-invoice-table": { component: EcommerceInvoiceTable, props: {} },
"ecommerce-order": { component: EcommerceOrder, props: {} },
"ecommerce-popular-products": { component: EcommercePopularProducts, props: {} },
"ecommerce-revenue-report": { component: EcommerceRevenueReport, props: {} },
"ecommerce-statistics": { component: EcommerceStatistics, props: {} },
"ecommerce-total-profit": { component: EcommerceTotalProfitLineCharts, props: {} },
"ecommerce-transactions": { component: EcommerceTransactions, props: {} },
};
window.addEventListener('remove-widget-from-dashboard', async (event) => {
const widgetId = event.detail?.widgetId
if (!widgetId) return
const idx = cardOrder.value.indexOf(widgetId)
if (idx > -1) {
cardOrder.value.splice(idx, 1)
await nextTick()
await calculateRowHeights()
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
detail: { widgets: cardOrder.value }
}))
}
})
const handleDeleteCard = async (cardId) => {
const cardIndex = cardOrder.value.indexOf(cardId)
if (cardIndex > -1) {
cardOrder.value.splice(cardIndex, 1)
await nextTick()
await calculateRowHeights()
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
detail: { widgets: cardOrder.value }
}))
}
}
const handleAddWidgetToDashboard = async (event) => {
if (event.detail && event.detail.widgetId) {
const widgetId = event.detail.widgetId;
const widget = event.detail.widget;
if (!cardComponents[widgetId] && widget.component) {
cardComponents[widgetId] = {
component: widget.component,
props: widget.props || {}
};
if (!cardSizes.value[widgetId]) {
cardSizes.value[widgetId] = widget.size || { cols: 6, height: 33.33 };
}
}
if (!cardOrder.value.includes(widgetId)) {
cardOrder.value.unshift(widgetId);
await nextTick();
await calculateRowHeights();
}
}
};
const restoreAllCards = async () => {
cardOrder.value = [
"leads1",
"leads2",
"leads3",
"project-activity",
"analysis1",
"analysis2",
"cost-overview",
"earning-reports",
"analytics-sales",
"sales-countries",
"project-status",
"active-project",
"recent-transactions",
"activity-timeline",
];
cardOrder.value = defaultWidgetIds.slice();
await nextTick();
await calculateRowHeights();
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
detail: { widgets: cardOrder.value }
}));
};
const cardRows = computed(() => {
const rows = [];
let currentRow = [];
let currentRowCols = 0;
cardOrder.value.forEach((cardId) => {
const cardCols = cardSizes.value[cardId].cols;
if (currentRowCols + cardCols > 12) {
if (currentRow.length > 0) {
rows.push([...currentRow]);
}
currentRow = [cardId];
currentRowCols = cardCols;
} else {
currentRow.push(cardId);
currentRowCols += cardCols;
}
});
if (currentRow.length > 0) {
rows.push(currentRow);
}
return rows;
});
const rowHeights = ref({});
const calculateRowHeights = async () => {
await nextTick();
const newRowHeights = {};
cardRows.value.forEach((row, rowIndex) => {
let maxHeight = 0;
row.forEach((cardId) => {
const cardElement = document.querySelector(
`[data-card-id="${cardId}"] .card-content`
);
if (cardElement) {
const height = cardElement.scrollHeight;
maxHeight = Math.max(maxHeight, height);
}
});
if (maxHeight > 0) {
newRowHeights[rowIndex] = `${maxHeight}px`;
}
});
rowHeights.value = newRowHeights;
};
const getCardHeight = (cardId) => {
if (cardSizes.value[cardId].height !== "auto") {
return cardSizes.value[cardId].height;
}
return "auto";
};
let draggedElement = null;
let draggedIndex = -1;
const handleDragStart = (e, cardId) => {
if (!isEditMode.value) {
e.preventDefault();
return false;
}
draggedElement = e.target.closest(".draggable-card");
draggedIndex = cardOrder.value.indexOf(cardId);
draggedElement.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/html", cardId);
};
const handleDragEnd = async () => {
if (draggedElement) {
draggedElement.classList.remove("dragging");
draggedElement = null;
draggedIndex = -1;
await calculateRowHeights();
}
};
const handleDragOver = (e) => {
if (!isEditMode.value) return;
e.preventDefault();
e.dataTransfer.dropEffect = "move";
};
const handleDrop = async (e, targetCardId) => {
if (!isEditMode.value) return;
e.preventDefault();
if (draggedIndex === -1) return;
const targetIndex = cardOrder.value.indexOf(targetCardId);
if (draggedIndex !== targetIndex) {
const draggedCard = cardOrder.value.splice(draggedIndex, 1)[0];
cardOrder.value.splice(targetIndex, 0, draggedCard);
await nextTick();
await calculateRowHeights();
}
};
const isResizing = ref(false);
const resizingCard = ref(null);
const resizeDirection = ref(null);
const startX = ref(0);
const startCols = ref(0);
const cleanupResize = () => {
if (isResizing.value) {
isResizing.value = false;
resizingCard.value = null;
resizeDirection.value = null;
document.removeEventListener("mousemove", handleResize);
document.removeEventListener("mouseup", handleResizeEnd);
document.body.style.cursor = "";
document.body.style.userSelect = "";
}
};
const handleResizeStart = (e, cardId, direction) => {
if (!isEditMode.value) return;
e.preventDefault();
e.stopPropagation();
if (direction !== "horizontal") return;
cleanupResize();
isResizing.value = true;
resizingCard.value = cardId;
resizeDirection.value = direction;
startX.value = e.clientX;
startCols.value = cardSizes.value[cardId].cols;
document.addEventListener("mousemove", handleResize);
document.addEventListener("mouseup", handleResizeEnd);
document.body.style.cursor = "ew-resize";
document.body.style.userSelect = "none";
};
const handleResize = async (e) => {
if (!isResizing.value || !resizingCard.value || resizeDirection.value !== "horizontal") return;
const deltaX = e.clientX - startX.value;
const containerWidth = document.querySelector(".v-row")?.offsetWidth || 1200;
const colWidth = containerWidth / 12;
const deltaColumns = Math.round(deltaX / colWidth);
const newCols = Math.max(4, Math.min(12, startCols.value + deltaColumns));
if (cardSizes.value[resizingCard.value].cols !== newCols) {
cardSizes.value[resizingCard.value].cols = newCols;
await nextTick();
await calculateRowHeights();
}
};
const handleResizeEnd = async () => {
if (!isResizing.value) return;
isResizing.value = false;
resizingCard.value = null;
resizeDirection.value = null;
startX.value = 0;
startCols.value = 0;
document.removeEventListener("mousemove", handleResize);
document.removeEventListener("mouseup", handleResizeEnd);
document.body.style.cursor = "";
document.body.style.userSelect = "";
await calculateRowHeights();
};
watch(
[cardOrder, cardSizes],
async () => {
await nextTick();
await calculateRowHeights();
},
{ deep: true }
);
onMounted(async () => {
window.addEventListener("crm-edit-mode-changed", handleEditModeChange);
window.addEventListener("add-widget-to-dashboard", handleAddWidgetToDashboard);
window.addEventListener('get-dashboard-widgets', () => {
const event = new CustomEvent('dashboard-widgets-state', {
detail: { widgets: cardOrder.value }
});
window.dispatchEvent(event);
});
await nextTick();
setTimeout(calculateRowHeights, 100);
});
onUnmounted(() => {
window.removeEventListener("crm-edit-mode-changed", handleEditModeChange);
window.removeEventListener("add-widget-to-dashboard", handleAddWidgetToDashboard);
cleanupResize();
});
</script>
<template>
<VRow class="match-height" :class="{ 'edit-mode-active': isEditMode, 'drag-over': false }"
@dragover="handleDragOverFromSidebar" @dragleave="handleDragLeaveFromSidebar" @drop="handleDropFromSidebar">
<VCol v-if="isEditMode" cols="12" class="pb-0">
<div class="edit-mode-banner">
<div class="banner-background">
<div class="banner-glow"></div>
<div class="banner-particles"></div>
</div>
<div class="banner-content">
<div class="banner-header">
<div class="banner-icon">
<VIcon icon="tabler-edit" size="24" class="edit-icon" />
<div class="icon-pulse"></div>
</div>
<div class="banner-text">
<h3 class="banner-title"> Edit Mode Enabled</h3>
<p class="banner-subtitle">
Customize your dashboard with drag & drop, resize, and delete
features
</p>
</div>
</div>
<div class="banner-actions">
<VBtn variant="elevated" size="default" color="white" @click="restoreAllCards" class="restore-btn-enhanced">
<VIcon icon="tabler-reload" size="18" class="me-2" />
Restore All Cards
</VBtn>
</div>
</div>
<div class="banner-decorations">
<div class="decoration decoration-1"></div>
<div class="decoration decoration-2"></div>
<div class="decoration decoration-3"></div>
</div>
</div>
</VCol>
<template v-for="cardId in cardOrder" :key="cardId">
<VCol :cols="12" :md="cardSizes[cardId].cols" class="draggable-card" :class="{
resizing: isResizing && resizingCard === cardId,
'edit-mode': isEditMode,
'view-mode': !isEditMode
}" :draggable="isEditMode" :data-card-id="cardId" @dragstart="handleDragStart($event, cardId)"
@dragend="handleDragEnd" @dragover.prevent.stop="handleDragOver($event)"
@drop.prevent.stop="handleDrop($event, cardId)">
<div class="card-wrapper" :style="{
height: getCardHeight(cardId),
}">
<div v-if="isEditMode" class="drag-handle" :title="'Move ' + cardId">
<v-icon size="18">mdi-drag-horizontal-variant</v-icon>
</div>
<VBtn v-if="isEditMode" variant="text" size="small" color="error" class="delete-btn"
:title="`Delete ${cardId}`" @click.stop="handleDeleteCard(cardId)">
<VIcon icon="tabler-trash-x" size="16" class="me-1" />
Delete
</VBtn>
<div v-if="isEditMode" class="resize-handle resize-horizontal" :title="'Resize ' + cardId"
@mousedown="handleResizeStart($event, cardId, 'horizontal')">
<v-icon size="16">mdi-resize</v-icon>
</div>
<component :is="cardComponents[cardId].component" v-bind="cardComponents[cardId].props"
class="card-content" />
</div>
</VCol>
</template>
</VRow>
</template>
<style scoped>
.draggable-card {
position: relative;
transition: all 0.3s ease;
}
.draggable-card.edit-mode {
cursor: move;
}
.draggable-card.edit-mode:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.draggable-card.edit-mode .card-wrapper {
border: 2px dashed rgba(var(--v-theme-primary), 0.3);
background: rgba(var(--v-theme-primary), 0.02);
}
.draggable-card.edit-mode:hover .card-wrapper {
border-color: rgba(var(--v-theme-primary), 0.6);
background: rgba(var(--v-theme-primary), 0.05);
}
.draggable-card.view-mode {
cursor: default;
}
.draggable-card.view-mode .card-wrapper {
border: 1px solid rgba(0, 0, 0, 0.1);
}
.dragging {
opacity: 0.5;
transform: rotate(5deg) scale(1.05);
z-index: 1000;
}
.resizing {
transition: none !important;
}
.card-wrapper {
position: relative;
height: 100%;
overflow: hidden;
border-radius: 8px;
transition: all 0.2s ease;
}
.card-content {
height: 100%;
overflow: auto;
}
.edit-mode-banner {
position: relative;
margin: 0 0 2rem 0;
border-radius: 20px;
overflow: hidden;
background: linear-gradient(135deg,
rgba(var(--v-theme-primary), 0.9) 0%,
rgba(var(--v-theme-secondary), 0.8) 50%,
rgba(var(--v-theme-accent), 0.9) 100%);
box-shadow: 0 8px 32px rgba(var(--v-theme-primary), 0.3),
0 2px 8px rgba(0, 0, 0, 0.1);
animation: bannerSlideIn 0.6s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.banner-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.banner-glow {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle,
rgba(255, 255, 255, 0.1) 0%,
transparent 70%);
animation: bannerGlow 4s ease-in-out infinite;
}
.banner-particles {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: radial-gradient(2px 2px at 20% 30%,
rgba(255, 255, 255, 0.3),
transparent),
radial-gradient(1px 1px at 60% 70%, rgba(255, 255, 255, 0.2), transparent),
radial-gradient(1px 1px at 90% 40%, rgba(255, 255, 255, 0.3), transparent),
radial-gradient(2px 2px at 40% 80%, rgba(255, 255, 255, 0.2), transparent);
background-size: 200px 200px, 150px 150px, 100px 100px, 250px 250px;
animation: particlesFloat 15s linear infinite;
}
.banner-content {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 2rem;
gap: 1.5rem;
}
.banner-header {
display: flex;
align-items: center;
gap: 1rem;
flex: 1;
}
.banner-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.edit-icon {
color: white;
z-index: 2;
}
.icon-pulse {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 12px;
background: rgba(255, 255, 255, 0.3);
animation: iconPulse 2s ease-in-out infinite;
}
.banner-text {
flex: 1;
}
.banner-title {
font-size: 1.4rem;
font-weight: 700;
color: white;
margin: 0 0 0.25rem 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
letter-spacing: -0.5px;
}
.banner-subtitle {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.85);
margin: 0;
line-height: 1.4;
font-weight: 400;
}
.banner-actions {
display: flex;
align-items: center;
}
.restore-btn-enhanced {
background: rgba(255, 255, 255, 0.95) !important;
color: rgba(var(--v-theme-primary), 0.9) !important;
border-radius: 12px !important;
padding: 12px 20px !important;
font-weight: 600 !important;
text-transform: none !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05) !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
backdrop-filter: blur(10px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
height: auto !important;
min-height: 44px !important;
}
.restore-btn-enhanced:hover {
background: white !important;
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 4px 8px rgba(0, 0, 0, 0.1) !important;
}
.restore-btn-enhanced:active {
transform: translateY(-1px) scale(1.01);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05) !important;
}
.restore-btn-enhanced .v-icon {
transition: transform 0.3s ease;
}
.restore-btn-enhanced:hover .v-icon {
transform: rotate(180deg);
}
.banner-decorations {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 1;
}
.decoration {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
.decoration-1 {
width: 80px;
height: 80px;
top: -40px;
right: 10%;
animation: decorationFloat1 8s ease-in-out infinite;
}
.decoration-2 {
width: 60px;
height: 60px;
bottom: -30px;
left: 15%;
animation: decorationFloat2 6s ease-in-out infinite reverse;
}
.decoration-3 {
width: 40px;
height: 40px;
top: 50%;
right: 5%;
animation: decorationFloat3 10s ease-in-out infinite;
}
.drag-handle {
position: absolute;
top: 8px;
right: 8px;
z-index: 20;
cursor: grab;
padding: 6px;
border-radius: 50%;
background: rgba(var(--v-theme-primary), 0.9);
border: 1px solid rgba(0, 0, 0, 0.1);
opacity: 0;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
color: white;
}
.drag-handle:hover {
background: rgba(var(--v-theme-primary), 1);
transform: scale(1.1);
}
.drag-handle:active {
cursor: grabbing;
transform: scale(0.95);
}
.delete-btn {
position: absolute;
top: 8px;
left: 8px;
z-index: 5;
opacity: 1 !important;
background: rgba(255, 82, 82, 0.9) !important;
color: white !important;
border-radius: 6px !important;
padding: 4px 8px !important;
font-size: 12px !important;
min-width: auto !important;
height: 28px !important;
box-shadow: 0 2px 6px rgba(255, 82, 82, 0.3);
transition: all 0.2s ease;
backdrop-filter: blur(10px);
}
.delete-btn:hover {
background: rgba(220, 38, 38, 1) !important;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
}
.delete-btn:active {
transform: scale(0.95);
}
.resize-handle {
position: absolute;
z-index: 15;
background: rgba(var(--v-theme-secondary), 0.9);
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
opacity: 0;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.resize-horizontal {
right: -6px;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 50px;
cursor: ew-resize;
}
.resize-handle:hover {
background: rgba(var(--v-theme-secondary), 1);
transform: scale(1.1);
}
.resize-horizontal:hover {
transform: translateY(-50%) scale(1.1);
}
.edit-mode:hover .drag-handle,
.edit-mode:hover .resize-handle {
opacity: 1;
}
.draggable-card.drag-over .card-wrapper {
border: 2px dashed rgba(var(--v-theme-success), 0.5) !important;
background: rgba(var(--v-theme-success), 0.05) !important;
}
.edit-mode-active {
padding: 1rem;
background: rgba(var(--v-theme-surface), 0.5);
border-radius: 12px;
border: 1px dashed rgba(var(--v-theme-primary), 0.2);
}
@media (max-width: 768px) {
.banner-content {
flex-direction: column;
text-align: center;
gap: 1rem;
padding: 1.25rem 1rem;
}
.banner-header {
flex-direction: column;
text-align: center;
gap: 0.75rem;
}
.banner-title {
font-size: 1.2rem;
}
.banner-subtitle {
font-size: 0.85rem;
}
.restore-btn-enhanced {
padding: 10px 16px !important;
font-size: 0.9rem !important;
}
.edit-mode .drag-handle,
.edit-mode .resize-handle {
opacity: 0.7;
}
.delete-btn {
font-size: 11px !important;
padding: 3px 6px !important;
height: 24px !important;
}
.resize-handle {
background: rgba(var(--v-theme-secondary), 0.8);
}
}
.resizing * {
user-select: none !important;
pointer-events: none !important;
}
</style>

View File

@@ -0,0 +1,97 @@
<script setup>
import { onMounted, nextTick } from 'vue'
import { GridStack } from 'gridstack'
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
import EcommerceGeneratedLeads from '@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue'
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
onMounted(async () => {
const grid = GridStack.init({
column: 12,
cellHeight: 50,
float: true,
draggable: { handle: '.grid-stack-item-content' },
resizable: true
})
await nextTick()
})
</script>
<template>
<div class="grid-stack">
<div class="grid-stack-item" gs-w="8" gs-h="4" gs-max-h="4" gs-x="0" gs-y="0">
<div class="grid-stack-item-content">
<EcommerceCongratulationsJohn />
</div>
</div>
<div class="grid-stack-item" gs-w="4" gs-h="4" gs-max-h="4" gs-x="8" gs-y="0">
<div class="grid-stack-item-content">
<EcommerceStatistics />
</div>
</div>
<div class="grid-stack-item" gs-w="7" gs-h="3" gs-max-h="3" gs-x="0" gs-y="4">
<div class="grid-stack-item-content">
<EcommerceTotalProfitLineCharts />
</div>
</div>
<div class="grid-stack-item" gs-w="5" gs-h="3" gs-max-h="3" gs-x="7" gs-y="4">
<div class="grid-stack-item-content">
<EcommerceExpensesRadialBarCharts />
</div>
</div>
<div class="grid-stack-item" gs-w="3" gs-h="5" gs-max-h="5" gs-x="0" gs-y="7">
<div class="grid-stack-item-content">
<EcommerceGeneratedLeads />
</div>
</div>
<div class="grid-stack-item" gs-w="6" gs-h="5" gs-max-h="5" gs-x="3" gs-y="7">
<div class="grid-stack-item-content">
<EcommerceRevenueReport />
</div>
</div>
<div class="grid-stack-item" gs-w="3" gs-h="3" gs-max-h="3" gs-x="9" gs-y="7">
<div class="grid-stack-item-content">
<EcommerceOrder />
</div>
</div>
<div class="grid-stack-item" gs-w="4" gs-h="10" gs-max-h="10" gs-x="0" gs-y="12">
<div class="grid-stack-item-content">
<EcommerceEarningReports />
</div>
</div>
<div class="grid-stack-item" gs-w="4" gs-h="10" gs-max-h="10" gs-x="4" gs-y="12">
<div class="grid-stack-item-content">
<EcommercePopularProducts />
</div>
</div>
<div class="grid-stack-item" gs-w="4" gs-h="4" gs-max-h="4" gs-x="8" gs-y="12">
<div class="grid-stack-item-content">
<EcommerceTransactions />
</div>
</div>
<div class="grid-stack-item" gs-w="12" gs-h="6" gs-max-h="6" gs-x="0" gs-y="16">
<div class="grid-stack-item-content">
<EcommerceInvoiceTable />
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,129 @@
<script setup>
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
import EcommerceGeneratedLeads from '@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue'
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
</script>
<template>
<VRow class="match-height">
<!-- 👉 Congratulation John -->
<VCol
cols="12"
md="5"
lg="4"
>
<EcommerceCongratulationsJohn />
</VCol>
<!-- 👉 Ecommerce Transition -->
<VCol
cols="12"
md="7"
lg="8"
>
<EcommerceStatistics class="h-100" />
</VCol>
<VCol
cols="12"
lg="4"
>
<VRow>
<!-- 👉 Total Profit Line -->
<VCol
cols="12"
lg="6"
md="3"
sm="6"
>
<EcommerceTotalProfitLineCharts />
</VCol>
<!-- 👉 Expenses Radial Bar Charts -->
<VCol
cols="12"
lg="6"
md="3"
sm="6"
>
<EcommerceExpensesRadialBarCharts />
</VCol>
<!-- 👉 Generated Leads -->
<VCol
cols="12"
md="6"
lg="12"
>
<EcommerceGeneratedLeads />
</VCol>
</VRow>
</VCol>
<!-- 👉 Revenue Report -->
<VCol
cols="12"
lg="8"
>
<EcommerceRevenueReport />
</VCol>
<!-- 👉 Earning Reports -->
<VCol
cols="12"
sm="6"
lg="4"
>
<EcommerceEarningReports />
</VCol>
<!-- 👉 Popular Products -->
<VCol
cols="12"
sm="6"
lg="4"
>
<EcommercePopularProducts />
</VCol>
<!-- 👉 Order -->
<VCol
cols="12"
sm="6"
lg="4"
>
<EcommerceOrder />
</VCol>
<!-- 👉 Transaction -->
<VCol
cols="12"
sm="6"
lg="4"
>
<EcommerceTransactions />
</VCol>
<!-- 👉 Invoice Table -->
<VCol
cols="12"
lg="8"
>
<EcommerceInvoiceTable />
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
</style>
<!-- کد اول -->

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
<script setup>
import * as demoCode from '@/views/demos/components/swiper/demoCodeSwiper'
</script>
<template>
<VRow>
<VCol>
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<DemoSwiperBasic />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Navigation"
:code="demoCode.navigation"
>
<DemoSwiperNavigation />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Pagination"
:code="demoCode.pagination"
>
<DemoSwiperPagination />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Progress"
:code="demoCode.progress"
>
<DemoSwiperProgress />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Multiple Slides Per View"
:code="demoCode.multipleSlidesPerView"
>
<DemoSwiperMultipleSlidesPerView />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Grid"
:code="demoCode.grid"
>
<DemoSwiperGrid />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
variant="text"
title="Centered Slides Option 1"
:code="demoCode.centeredSlidesOption1"
>
<DemoSwiperCenteredSlidesOption1 />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Centered Slides Option 2"
:code="demoCode.centeredSlidesOption2"
>
<DemoSwiperCenteredSlidesOption2 />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Fade"
:code="demoCode.fade"
>
<DemoSwiperFade />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Cube Effect"
:code="demoCode.cubeEffect"
>
<DemoSwiperCubeEffect />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Coverflow Effect"
:code="demoCode.coverflowEffect"
>
<DemoSwiperCoverflowEffect />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Autoplay"
:code="demoCode.autoplay"
>
<DemoSwiperAutoplay />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Gallery"
:code="demoCode.gallery"
>
<DemoSwiperGallery />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Lazy Loading"
:code="demoCode.lazyLoading"
>
<DemoSwiperLazyLoading />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Responsive Breakpoints"
:code="demoCode.responsiveBreakpoints"
>
<DemoSwiperResponsiveBreakpoints />
</AppCardCode>
</VCol>
<VCol>
<AppCardCode
title="Virtual Slides"
:code="demoCode.virtualSlides"
>
<DemoSwiperVirtualSlides />
</AppCardCode>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/swiper.scss"
</style>

View File

@@ -0,0 +1,134 @@
<script setup>
import { useShepherd } from 'vue-shepherd'
const route = useRoute()
// 👉 Hotkey
// eslint-disable-next-line camelcase
const { ctrl_k, meta_k } = useMagicKeys()
// 👉 Tour initialization
let tour = null
// 👉 watch command palette and route change
/* eslint-disable camelcase */
watch([
ctrl_k,
meta_k,
() => route.path,
], () => {
if (tour.isActive())
tour.cancel()
})
/* eslint-enable */
onMounted(() => {
const navbar = document.querySelector('.layout-navbar')
tour = useShepherd({
useModalOverlay: true,
stepsContainer: document.querySelector('.layout-wrapper'),
modelContainer: document.querySelector('.layout-wrapper'),
defaultStepOptions: {
cancelIcon: { enabled: true },
modalOverlayOpeningPadding: 2,
modalOverlayOpeningRadius: 5,
},
})
// 👉 Tour steps
tour.addSteps([
{
id: 'welcome',
title: 'Welcome',
arrow: true,
attachTo: {
element: navbar,
on: 'bottom',
},
text: 'Welcome to our tour page, Guide users to the key features of the product.',
buttons: [
{
action: tour.cancel,
classes: 'backBtnClass',
text: 'Back',
},
{
action: tour.next,
text: 'Next',
classes: 'nextBtnClass',
},
],
},
{
id: 'notification',
title: 'Notifications',
arrow: true,
attachTo: {
element: document.querySelector('#notification-btn'),
on: 'bottom',
},
text: 'Manage your notifications and stay up-to-date with latest updates.',
buttons: [
{
label: 'Back',
text: 'Back',
action: tour.back,
classes: 'backBtnClass',
},
{
label: 'Next',
text: 'Next',
action: tour.next,
classes: 'nextBtnClass',
},
],
},
{
id: 'footer',
title: 'Footer',
arrow: true,
attachTo: {
element: document.querySelector('.layout-footer'),
on: 'bottom',
},
text: 'Footer section of the page.',
buttons: [
{
label: 'Back',
text: 'Back',
action: tour.back,
classes: 'backBtnClass',
},
{
label: 'Finish',
text: 'Finish',
action: tour.complete,
classes: 'nextBtnClass',
},
],
},
])
})
</script>
<template>
<div>
<VCard title="Tour">
<VCardText>
<VBtn
variant="elevated"
@click="() => { tour && tour.start() }"
>
Start Tour
</VBtn>
</VCardText>
</VCard>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/libs/shepherd.scss";
</style>

View File

@@ -0,0 +1,129 @@
<script setup>
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
import authV2ForgotPasswordIllustrationDark from '@images/pages/auth-v2-forgot-password-illustration-dark.png'
import authV2ForgotPasswordIllustrationLight from '@images/pages/auth-v2-forgot-password-illustration-light.png'
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
import authV2MaskLight from '@images/pages/misc-mask-light.png'
const email = ref('')
const authThemeImg = useGenerateImageVariant(authV2ForgotPasswordIllustrationLight, authV2ForgotPasswordIllustrationDark)
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
definePage({
meta: {
layout: 'blank',
unauthenticatedOnly: true,
},
})
</script>
<template>
<RouterLink to="/">
<div class="auth-logo d-flex align-center gap-x-3">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="auth-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
<VRow
class="auth-wrapper bg-surface"
no-gutters
>
<VCol
md="8"
class="d-none d-md-flex"
>
<div class="position-relative bg-background w-100 me-0">
<div
class="d-flex align-center justify-center w-100 h-100"
style="padding-inline: 150px;"
>
<VImg
max-width="468"
:src="authThemeImg"
class="auth-illustration mt-16 mb-2"
/>
</div>
<img
class="auth-footer-mask"
:src="authThemeMask"
alt="auth-footer-mask"
height="280"
width="100"
>
</div>
</VCol>
<VCol
cols="12"
md="4"
class="d-flex align-center justify-center"
>
<VCard
flat
:max-width="500"
class="mt-12 mt-sm-0 pa-4"
>
<VCardText>
<h4 class="text-h4 mb-1">
Forgot Password? 🔒
</h4>
<p class="mb-0">
Enter your email and we'll send you instructions to reset your password
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="email"
autofocus
label="Email"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- Reset link -->
<VCol cols="12">
<VBtn
block
type="submit"
>
Send Reset Link
</VBtn>
</VCol>
<!-- back to login -->
<VCol cols="12">
<RouterLink
class="d-flex align-center justify-center"
:to="{ name: 'login' }"
>
<VIcon
icon="tabler-chevron-left"
size="20"
class="me-1 flip-in-rtl"
/>
<span>Back to login</span>
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth.scss";
</style>

View File

@@ -0,0 +1,176 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/autocomplete/demoCodeAutocomplete'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>
The <code> v-autocomplete </code> component offers simple and flexible type-ahead functionality. This is useful when searching large sets of data or even dynamically fetching information from an API.
</p>
<DemoAutocompleteBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>
You can use <code> density </code> prop to adjusts vertical spacing within the component. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.
</p>
<DemoAutocompleteDensity />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="12"
>
<!-- 👉 Variant -->
<AppCardCode
title="Variant"
:code="demoCode.variant"
>
<p>Use <code>Solo</code>, <code>Outlined</code>, <code>Underlined</code>, <code>Filled</code> and <code>Plain</code> options of <code>variant</code> prop to change the look of Autocomplete. </p>
<DemoAutocompleteVariant />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Multiple Select -->
<AppCardCode
title="Multiple"
:code="demoCode.multiple"
>
<p>Use <code>multiple</code> prop to select multiple. Accepts array for value</p>
<DemoAutocompleteMultiple />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Clearable input -->
<AppCardCode
title="Clearable"
:code="demoCode.clearable"
>
<p>Use <code>clearable</code> prop to add input clear functionality.</p>
<DemoAutocompleteClearable />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Chips -->
<AppCardCode
title="Chips"
:code="demoCode.chips"
>
<p>Use <code> chips </code> prop to use chips in select.</p>
<DemoAutocompleteChips />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Custom Filter -->
<AppCardCode
title="Custom-Filter"
:code="demoCode.customFilter"
>
<p>The <code> custom-filter </code> prop can be used to filter each individual item with custom logic.In example we will filter state based on their name and abbreviations </p>
<DemoAutocompleteCustomFilter />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 With slots -->
<AppCardCode
title="Slots"
:code="demoCode.slots"
>
<p>With the power of slots, you can customize the visual output of the select. In this example we add a profile picture for both the chips and list items using their props. </p>
<DemoAutocompleteSlots />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Asynchronous Items -->
<AppCardCode
title="Async items"
:code="demoCode.asyncItems"
>
<p>Sometimes you need to load data externally based upon a search query. </p>
<DemoAutocompleteAsyncItems />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 State Selector -->
<AppCardCode
title="State Selector"
:code="demoCode.stateSelector"
>
<p>Using a combination of v-autocomplete slots and transitions, you can create a stylish toggle able autocomplete field such as below state selector.</p>
<DemoAutocompleteStateSelector />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Validation -->
<AppCardCode
title="validation"
:code="demoCode.validation"
>
<p>Use <code>rules</code> prop to validate autocomplete. Accepts a mixed array of types function, boolean and string. Functions pass an input value as an argument and must return either true / false or a string containing an error message.</p>
<DemoAutocompleteValidation />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,142 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/checkbox/demoCodeCheckbox'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p><code>v-checkbox</code> in its simplest form provides a toggle between 2 values.</p>
<DemoCheckboxBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>Use <code>density</code> prop to reduces the input height. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
<DemoCheckboxDensity />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>Checkboxes can be colored by using any of the builtin colors and contextual names using the <code>color</code> prop.</p>
<DemoCheckboxColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Model as array -->
<AppCardCode
title="Model as array"
:code="demoCode.modelAsArray"
>
<p>Multiple <code>v-checkbox</code>'s can share the same <code>v-model</code> by using an array.</p>
<DemoCheckboxModelAsArray />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icon -->
<AppCardCode
title="Icon"
:code="demoCode.icon"
>
<p>Use <code>false-icon</code> and <code>true-icon</code> prop to change the icon on the checkbox.</p>
<DemoCheckboxIcon />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Checkbox Value -->
<AppCardCode
title="Checkbox Value"
:code="demoCode.checkboxValue"
>
<p>Use <code>false-value</code> and <code>true-value</code> prop to sets value for truthy and falsy state</p>
<DemoCheckboxCheckboxValue />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 States -->
<AppCardCode
title="States"
:code="demoCode.states"
>
<p><code>v-checkbox</code> can have different states such as <code>default</code>, <code>disabled</code>, and <code>indeterminate</code>.</p>
<DemoCheckboxStates />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Label Slot -->
<AppCardCode
title="Label Slot"
:code="demoCode.labelSlot"
>
<p>Checkbox labels can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
<DemoCheckboxLabelSlot />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Inline text-field -->
<AppCardCode
title="Inline text-field"
:code="demoCode.inlineTextField"
>
<p>You can place <code>v-checkbox</code> in line with other components such as <code>v-text-field</code>.</p>
<DemoCheckboxInlineTextField />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,100 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/combobox/demoCodeCombobox'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>With Combobox, you can allow a user to create new values that may not be present in a provided items list.</p>
<DemoComboboxBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>
You can use <code>Density</code> prop to reduce combobox height and lower max height of list items. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.
</p>
<DemoComboboxDensity />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Variant -->
<AppCardCode
title="Variant"
:code="demoCode.variant"
>
<p>Use <code>solo</code>, <code>outlined</code>, <code>underlined</code>, <code>filled</code> and <code>plain</code> options of <code>variant</code> prop to change the look of combobox. </p>
<DemoComboboxVariant />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Multiple -->
<AppCardCode
title="Multiple"
:code="demoCode.multiple"
>
<p>Previously known as tags - user is allowed to enter more than 1 value</p>
<DemoComboboxMultiple />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 No data with chips -->
<AppCardCode
title="No data with chips"
:code="demoCode.noDataWithChips"
>
<p>Previously known as tags - user is allowed to enter more than 1 value</p>
<DemoComboboxNoDataWithChips />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Clearable -->
<AppCardCode
title="Clearable"
:code="demoCode.clearable"
>
<p>Use <code>clearable</code> prop to clear combobox.</p>
<DemoComboboxClearable />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,85 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/custom-input/demoCodeCustomInput'
</script>
<template>
<VRow>
<!-- 👉 Custom Radios -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Custom Radios"
:code="demoCode.customRadios"
>
<DemoCustomInputCustomRadios />
</AppCardCode>
</VCol>
<!-- 👉 Custom Checkboxes -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Custom Checkboxes"
:code="demoCode.customCheckboxes"
>
<DemoCustomInputCustomCheckboxes />
</AppCardCode>
</VCol>
<!-- 👉 Custom Radios With Icon -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Custom Radios With Icon"
:code="demoCode.customRadiosWithIcon"
>
<DemoCustomInputCustomRadiosWithIcon />
</AppCardCode>
</VCol>
<!-- 👉 Custom Checkboxes with icon -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Custom Checkboxes With Icon"
:code="demoCode.customCheckboxesWithIcon"
>
<DemoCustomInputCustomCheckboxesWithIcon />
</AppCardCode>
</VCol>
<!-- 👉 Custom Radios with image -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Custom Radios With Image"
:code="demoCode.customRadiosWithImage"
>
<DemoCustomInputCustomRadiosWithImage />
</AppCardCode>
</VCol>
<!-- 👉 Custom Checkboxes with Image -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Custom Checkboxes With Image"
:code="demoCode.customCheckboxesWithImage"
>
<DemoCustomInputCustomCheckboxesWithImage />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,111 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/date-time-picker/demoCodeDateTimePicker'
</script>
<template>
<VRow>
<!-- 👉 Basic -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<DemoDateTimePickerBasic />
</AppCardCode>
</VCol>
<!-- 👉 Time Picker -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Time Picker"
:code="demoCode.timePicker"
>
<DemoDateTimePickerTimePicker />
</AppCardCode>
</VCol>
<!-- 👉 Date & TIme -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Date and Time"
:code="demoCode.dateAndTime"
>
<DemoDateTimePickerDateAndTime />
</AppCardCode>
</VCol>
<!-- 👉 Multiple Dates -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Multiple Dates"
:code="demoCode.multipleDates"
>
<DemoDateTimePickerMultipleDates />
</AppCardCode>
</VCol>
<!-- 👉 Range -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Range"
:code="demoCode.range"
>
<DemoDateTimePickerRange />
</AppCardCode>
</VCol>
<!-- 👉 Human Friendly -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Human Friendly"
:code="demoCode.humanFriendly"
>
<DemoDateTimePickerHumanFriendly />
</AppCardCode>
</VCol>
<!-- 👉 Disabled Range -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Disabled Range"
:code="demoCode.disabledRange"
>
<DemoDateTimePickerDisabledRange />
</AppCardCode>
</VCol>
<!-- 👉 Inline -->
<VCol
cols="12"
md="6"
>
<AppCardCode
title="Inline"
:code="demoCode.inline"
>
<DemoDateTimePickerInline />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,26 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/editor/demoCodeEditor'
</script>
<template>
<VRow>
<!-- 👉 Basic Editor -->
<VCol cols="12">
<AppCardCode
title="Basic Editor"
:code="demoCode.basicEditor"
>
<DemoEditorBasicEditor />
</AppCardCode>
</VCol>
<VCol cols="12">
<AppCardCode
title="Custom Editor"
:code="demoCode.customEditor"
>
<DemoEditorCustomEditor />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,188 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/file-input/demoCodeFileInput'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-file-input</code> component is used to selecting files.</p>
<DemoFileInputBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>You can reduces the file input height with <code>density</code> prop. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
<DemoFileInputDensity />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Variant -->
<AppCardCode
title="Variant"
:code="demoCode.variant"
>
<p>use <code>solo</code>, <code>filled</code>, <code>outlined</code>, <code>plain</code> and <code>underlined</code> option of <code>variant</code> prop to change the look of file input.</p>
<DemoFileInputVariant />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Accept -->
<AppCardCode
title="Accept"
:code="demoCode.accept"
>
<p><code>v-file-input</code> component can accept only specific media formats/file types if you want.</p>
<DemoFileInputAccept />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Chips -->
<AppCardCode
title="Chips"
:code="demoCode.chips"
>
<p>Use <code>chip</code> prop to display the selected file as a chip.</p>
<DemoFileInputChips />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Counter -->
<AppCardCode
title="Counter"
:code="demoCode.counter"
>
<p>When using the <code>show-size</code> property along with <code>counter</code>, the total number of files and size will be displayed under the input.</p>
<DemoFileInputCounter />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Multiple -->
<AppCardCode
title="Multiple"
:code="demoCode.multiple"
>
<p>
The <code>v-file-input</code> can contain multiple files at the same time when using the <code>multiple</code> prop.
</p>
<DemoFileInputMultiple />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Prepend icon -->
<AppCardCode
title="Prepend icon"
:code="demoCode.prependIcon"
>
<p>
The <code>v-file-input</code> has a default <code>prepend-icon</code> that can be set on the component or adjusted globally.
</p>
<DemoFileInputPrependIcon />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Show size -->
<AppCardCode
title="Show size"
:code="demoCode.showSize"
>
<p>The displayed size of the selected file(s) can be configured with the <code>show-size</code> property.</p>
<DemoFileInputShowSize />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Validation -->
<AppCardCode
title="Validation"
:code="demoCode.validation"
>
<p>You can use the <code>rules</code> prop to create your own custom validation parameters.</p>
<DemoFileInputValidation />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Selection slot -->
<AppCardCode
title="Selection slot"
:code="demoCode.selectionSlot"
>
<p>Using the <code>selection</code> slot, you can customize the appearance of your input selections.</p>
<DemoFileInputSelectionSlot />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Loading -->
<AppCardCode
title="Loading"
:code="demoCode.loading"
>
<p>Use <code>loading</code> prop to displays linear progress bar.</p>
<DemoFileInputLoading />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,129 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-layout/demoCodeFormLayout'
</script>
<template>
<div>
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 Horizontal Form -->
<AppCardCode
title="Horizontal Form"
:code="demoCode.horizontalForm"
>
<DemoFormLayoutHorizontalForm />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Horizontal Form with Icons -->
<AppCardCode
title="Horizontal Form with Icons"
:code="demoCode.horizontalFormWithIcons"
>
<DemoFormLayoutHorizontalFormWithIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical Form -->
<AppCardCode
title="Vertical Form"
:code="demoCode.verticalForm"
>
<DemoFormLayoutVerticalForm />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical Form with Icons -->
<AppCardCode
title="Vertical Form with Icons"
:code="demoCode.verticalFormWithIcons"
>
<DemoFormLayoutVerticalFormWithIcons />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Multiple Column -->
<AppCardCode
title="Multiple Column"
:code="demoCode.multipleColumn"
>
<DemoFormLayoutMultipleColumn />
</AppCardCode>
</VCol>
</VRow>
<VRow class="match-height my-3">
<VCol
cols="12"
md="6"
>
<!-- 👉 Form Hint -->
<AppCardCode
title="Form Hint"
:code="demoCode.formHint"
>
<DemoFormLayoutFormHint />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Form Validation -->
<AppCardCode
title="Form Validation"
:code="demoCode.formValidation"
>
<DemoFormLayoutFormValidation />
</AppCardCode>
</VCol>
</VRow>
<VRow>
<VCol cols="12">
<!-- 👉 Form with Tabs -->
<AppCardCode
title="Form with Tabs"
no-padding
:code="demoCode.formWithTabs"
>
<DemoFormLayoutFormWithTabs />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Collapsible Section -->
<h6 class="text-h6 mb-6">
Collapsible Section
</h6>
<DemoFormLayoutCollapsible />
</VCol>
<VCol cols="12">
<!-- 👉 Collapsible Section -->
<h6 class="text-h6 mb-6">
Sticky Section
</h6>
<DemoFormLayoutSticky />
</VCol>
</VRow>
</div>
</template>

View File

@@ -0,0 +1,39 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-validation/demoCodeFormValidation'
</script>
<template>
<VRow>
<VCol cols="12">
<!-- 👉 Simple Form Validation -->
<AppCardCode
title="Simple Form Validation"
:code="demoCode.simpleFormValidation"
>
<p>Use <code>Rules</code> prop to validate the input.</p>
<DemoFormValidationSimpleFormValidation />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Validating Multiple Rules -->
<AppCardCode
title="Validating Multiple Rules"
:code="demoCode.validatingMultipleRules"
>
<DemoFormValidationValidatingMultipleRules />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Validation Types -->
<AppCardCode
title="Validation Types"
:code="demoCode.validationTypes"
>
<DemoFormValidationValidationTypes />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,73 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-wizard/form-wizard-icons/demoCodeFormWizardIcons'
import DemoFormWizardIconsBasic from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsBasic.vue'
import DemoFormWizardIconsModernBasic from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsModernBasic.vue'
import DemoFormWizardIconsValidation from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsValidation.vue'
import DemoFormWizardIconsVertical from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsVertical.vue'
</script>
<template>
<VRow>
<!-- 👉 Basic -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Basic"
:code="demoCode.basic"
>
<DemoFormWizardIconsBasic />
</AppCardCode>
</VCol>
<!-- 👉 Validation -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Validation"
:code="demoCode.validation"
>
<DemoFormWizardIconsValidation />
</AppCardCode>
</VCol>
<!-- 👉 Vertical -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Vertical"
:code="demoCode.vertical"
>
<DemoFormWizardIconsVertical />
</AppCardCode>
</VCol>
</VRow>
<VDivider class="my-10 mx-n6" />
<h3 class="text-h3 my-4">
Modern
</h3>
<VRow>
<!-- 👉 Modern Basic -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Modern basic"
:code="demoCode.modernBasic"
>
<DemoFormWizardIconsModernBasic />
</AppCardCode>
</VCol>
<!-- 👉 Modern Vertical -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Modern Vertical"
:code="demoCode.modernVertical"
>
<DemoFormWizardIconsModernVertical />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,74 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-wizard/form-wizard-numbered/demoCodeFormWizardNumbered'
import DemoFormWizardNumberedModernBasic from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberdModernBasic.vue'
import DemoFormWizardNumberedBasic from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedBasic.vue'
import DemoFormWizardNumberedModernVertical from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedModernVertical.vue'
import DemoFormWizardNumberedValidation from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedValidation.vue'
import DemoFormWizardNumberedVertical from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedVertical.vue'
</script>
<template>
<VRow>
<!-- 👉 Basic -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Basic"
:code="demoCode.basic"
>
<DemoFormWizardNumberedBasic />
</AppCardCode>
</VCol>
<!-- 👉 Validation -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Validation"
:code="demoCode.validation"
>
<DemoFormWizardNumberedValidation />
</AppCardCode>
</VCol>
<!-- 👉 Vertical -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Vertical"
:code="demoCode.vertical"
>
<DemoFormWizardNumberedVertical />
</AppCardCode>
</VCol>
</VRow>
<VDivider class="my-10 mx-n6" />
<h3 class="text-h3 my-4">
Modern
</h3>
<VRow>
<!-- 👉 Modern Vertical -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Vertical"
:code="demoCode.vertical"
>
<DemoFormWizardNumberedModernVertical />
</AppCardCode>
</VCol>
<!-- 👉Modern Basic -->
<VCol cols="12">
<AppCardCode
variant="outlined"
title="Basic"
:code="demoCode.basic"
>
<DemoFormWizardNumberedModernBasic />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,112 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/radio/demoCodeRadio'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-radio</code> component is a simple radio button.</p>
<DemoRadioBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>Radios can be colored by using any of the built-in colors and contextual names using the <code>color</code> prop.</p>
<DemoRadioColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Inline -->
<AppCardCode
title="Inline"
:code="demoCode.inline"
>
<p>Use <code>inline</code> prop to displays radio buttons in row.</p>
<DemoRadioInline />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>Use <code>density</code> prop to adjusts the spacing within the component. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
<DemoRadioDensity />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Label Slot -->
<AppCardCode
title="Label Slot"
:code="demoCode.labelSlot"
>
<p>Radio Group labels can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
<DemoRadioLabelSlot />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icon -->
<AppCardCode
title="Icon"
:code="demoCode.icon"
>
<p>Use <code>false-icon</code> and <code>true-icon</code> prop to set icon on inactive and active state.</p>
<DemoRadioIcon />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Validation -->
<AppCardCode
title="Validation"
:code="demoCode.validation"
>
<p>Use <code>rules</code> prop to validate a radio. Accepts a mixed array of types <code>function</code>, <code>boolean</code> and <code>string</code>. Functions pass an input value as an argument and must return either <code>true</code> / <code>false</code> or a string containing an error message.</p>
<DemoRadioValidation />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,99 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/range-slider/demoCodeRangeSlider'
</script>
<template>
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-slider</code> component is a better visualization of the number input.</p>
<DemoRangeSliderBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Disabled -->
<AppCardCode
title="Disabled"
:code="demoCode.disabled"
>
<p>You cannot interact with <code>disabled</code> sliders.</p>
<DemoRangeSliderDisabled />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Color -->
<AppCardCode
title="Color"
:code="demoCode.color"
>
<p>Use <code>color</code> prop to the sets the slider color. <code>track-color</code> prop to sets the color of slider's unfilled track.</p>
<DemoRangeSliderColor />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Step -->
<AppCardCode
title="Step"
:code="demoCode.step"
>
<p><code>v-range-slider</code> can have steps other than 1. This can be helpful for some applications where you need to adjust values with more or less accuracy.</p>
<DemoRangeSliderStep />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Thumb label -->
<AppCardCode
title="Thumb label"
:code="demoCode.thumbLabel"
>
<p>
Using the <code>tick-labels</code> prop along with the <code>thumb-label</code> slot, you can create a very customized solution.
</p>
<DemoRangeSliderThumbLabel />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical -->
<AppCardCode
title="Vertical"
:code="demoCode.vertical"
>
<p>You can use the <code>vertical</code> prop to switch sliders to a vertical orientation. If you need to change the height of the slider, use css.</p>
<DemoRangeSliderVertical />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,157 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/rating/demoCodeRating'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-rating</code> component provides a simple interface for gathering user feedback.</p>
<DemoRatingBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>Control the space occupied by <code>v-rating</code> items using the <code>density</code> prop.</p>
<DemoRatingDensity />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>The <code>v-rating</code> component can be colored as you want, you can set both selected and not selected colors.</p>
<DemoRatingColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Size -->
<AppCardCode
title="Size"
:code="demoCode.size"
>
<p>Utilize the same sizing classes available in <code>v-icon</code> or provide your own with the <code>size</code> prop.</p>
<DemoRatingSize />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Length -->
<AppCardCode
title="Length"
:code="demoCode.length"
>
<p>Change the number of items by modifying the the <code>length</code> prop.</p>
<DemoRatingLength />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Clearable -->
<AppCardCode
title="Clearable"
:code="demoCode.clearable"
>
<p>Use <code>clearable</code> prop to allows for the component to be cleared. Triggers when the icon containing the current value is clicked.</p>
<DemoRatingClearable />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Readonly -->
<AppCardCode
title="Readonly"
:code="demoCode.readonly"
>
<p>For ratings that are not meant to be changed you can use <code>readonly</code> prop.</p>
<DemoRatingReadonly />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Hover -->
<AppCardCode
title="Hover"
:code="demoCode.hover"
>
<p>Provides visual feedback when hovering over icons</p>
<DemoRatingHover />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Incremented -->
<AppCardCode
title="Incremented"
:code="demoCode.incremented"
>
<p>The <code>half-increments</code> prop increases the granularity of the ratings, allow for .5 values as well.</p>
<DemoRatingIncremented />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Item slot -->
<AppCardCode
title="Item slot"
:code="demoCode.itemSlot"
>
<p>Slots enable advanced customization possibilities and provide you with more freedom in how you display the rating.</p>
<DemoRatingItemSlot />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,139 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/select/demoCodeSelect'
</script>
<template>
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>Select fields components are used for collecting user provided information from a list of options.</p>
<DemoSelectBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>You can use <code>density</code> prop to reduce the field height and lower max height of list items.</p>
<DemoSelectDensity />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Variant -->
<AppCardCode
title="Variant"
:code="demoCode.variant"
>
<p>
Use <code>filled</code>, <code>outlined</code>, <code>solo</code>, <code>underlined</code> and <code>plain</code> options of <code>variant</code> prop to change appearance of select.
</p>
<DemoSelectVariant />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Custom text and value -->
<AppCardCode
title="Custom text and value"
:code="demoCode.customTextAndValue"
>
<p>You can specify the specific properties within your items array that correspond to the title and value fields. In this example we also use the return-object prop which will return the entire object of the selected item on selection.</p>
<DemoSelectCustomTextAndValue />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>Use a custom <code>prepend</code> or <code>appended</code> icon.</p>
<DemoSelectIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Chips -->
<AppCardCode
title="Chips"
:code="demoCode.chips"
>
<p>Use <code>chips</code> prop to make selected option as chip.</p>
<DemoSelectChips />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Menu Props -->
<AppCardCode
title="Menu Props"
:code="demoCode.menuProps"
>
<p>Custom props can be passed directly to <code>v-menu</code> using <code>menuProps</code> prop.</p>
<DemoSelectMenuProps />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Multiple -->
<AppCardCode
title="Multiple"
:code="demoCode.multiple"
>
<p>Use <code>multiple</code> prop to select multiple option.</p>
<DemoSelectMultiple />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Selection slot -->
<AppCardCode
title="Selection slot"
:code="demoCode.selectionSlot"
>
<p>The <code>selection</code> slot can be used to customize the way selected values are shown in the input.</p>
<DemoSelectSelectionSlot />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,204 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/slider/demoCodeSlider'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>The <code>v-slider</code> component is a better visualization of the number input.</p>
<DemoSliderBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Disabled and Readonly -->
<AppCardCode
title="Disabled and Readonly"
:code="demoCode.disabledAndReadonly"
>
<p>You cannot interact with <code>disabled</code> and <code>readonly</code> sliders.</p>
<DemoSliderDisabledAndReadonly />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>You can set the colors of the slider using the props <code>color</code>, <code>track-color</code> and <code>thumb-color</code>.</p>
<DemoSliderColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>You can add icons to the slider with the <code>append-icon</code> and <code>prepend-icon</code> props.</p>
<DemoSliderIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Step -->
<AppCardCode
title="Step"
:code="demoCode.step"
>
<p>Using the <code>step</code> prop you can control the precision of the slider, and how much it should move each step.</p>
<DemoSliderStep />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Validation -->
<AppCardCode
title="Validation"
:code="demoCode.validation"
>
<p>Vuetify includes simple validation through the <code>rules</code> prop.</p>
<DemoSliderValidation />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Min and Max -->
<AppCardCode
title="Min and Max"
:code="demoCode.minAndMax"
>
<p>You can set <code>min</code> and <code>max</code> values of sliders.</p>
<DemoSliderMinAndMax />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Size -->
<AppCardCode
title="Size"
:code="demoCode.size"
>
<p>Use <code>thumb-size</code>, <code>tick-size</code>, and <code>track-size</code> prop to increase and decrease the size of thumb, tick and track. </p>
<DemoSliderSize />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Thumb -->
<AppCardCode
title="Thumb"
:code="demoCode.thumb"
>
<p>You can display a thumb label while sliding or always with the <code>thumb-label</code> prop.</p>
<DemoSliderThumb />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Ticks -->
<AppCardCode
title="Ticks"
:code="demoCode.ticks"
>
<p>Tick marks represent predetermined values to which the user can move the slider.</p>
<DemoSliderTicks />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical -->
<AppCardCode
title="Vertical"
:code="demoCode.vertical"
>
<p>
You can use the <code>vertical</code> prop to switch sliders to a vertical orientation.
</p>
<DemoSliderVertical />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Append text field -->
<AppCardCode
title="Append text field"
:code="demoCode.appendTextField"
>
<p>Sliders can be combined with other components in its <code>append</code> slot, such as <code>v-text-field</code>, to add additional functionality to the component.</p>
<DemoSliderAppendTextField />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Append and prepend -->
<AppCardCode
title="Append and prepend"
:code="demoCode.appendAndPrepend"
>
<p>Use slots such as <code>append</code> and <code>prepend</code> to easily customize the <code>v-slider</code> to fit any situation.</p>
<DemoSliderAppendAndPrepend />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,114 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/switch/demoCodeSwitch'
</script>
<template>
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>A <code>v-switch</code> in its simplest form provides a toggle between 2 values.</p>
<DemoSwitchBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Inset -->
<AppCardCode
title="Inset"
:code="demoCode.inset"
>
<p>To change the default <code>inset</code> switch, simply modify the inset prop to a <code>false</code> value.</p>
<DemoSwitchInset />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Colors -->
<AppCardCode
title="Colors"
:code="demoCode.colors"
>
<p>Switches can be colored by using any of the builtin colors and contextual names using the <code>color</code> prop.</p>
<DemoSwitchColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Model as array -->
<AppCardCode
title="Model as array"
:code="demoCode.modelAsArray"
>
<p>Multiple <code>v-switch</code>'s can share the same <code>v-model</code> by using an array.</p>
<DemoSwitchModelAsArray />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Label slot -->
<AppCardCode
title="Label slot"
:code="demoCode.labelSlot"
>
<p>Switch labels can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
<DemoSwitchLabelSlot />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 True and False Value -->
<AppCardCode
title="True and False Value"
:code="demoCode.trueAndFalseValue"
>
<p>
Use <code>false-value</code> and <code>true-value</code> prop to sets value for truthy and falsy state
</p>
<DemoSwitchTrueAndFalseValue />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 States -->
<AppCardCode
title="States"
:code="demoCode.states"
>
<p><code>v-switch</code> can have different states such as <code>default</code>, <code>disabled</code>, and <code>loading</code>.</p>
<DemoSwitchStates />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,175 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/textarea/demoCodeTextarea'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>
v-textarea in its simplest form is a multi-line text-field, useful for larger amounts of text.
</p>
<DemoTextareaBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Auto Grow -->
<AppCardCode
title="Auto Grow"
:code="demoCode.autoGrow"
>
<p>When using the <code>auto-grow</code> prop, textarea's will automatically increase in size when the contained text exceeds its size.</p>
<DemoTextareaAutoGrow />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Variant -->
<AppCardCode
title="Variant"
:code="demoCode.variant"
>
<p>Use <code>filled</code>, <code>plain</code>, <code>outlined</code>, <code>solo</code> and <code>underlined</code> option of <code>variant</code> prop to change the look of file input.</p>
<DemoTextareaVariant />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 States -->
<AppCardCode
title="States"
:code="demoCode.states"
>
<p>Use <code>disabled</code> and <code>readonly</code> prop to change the state of textarea.</p>
<DemoTextareaStates />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Browser autocomplete -->
<AppCardCode
title="Browser autocomplete"
:code="demoCode.browserAutocomplete"
>
<p>
The <code>autocomplete</code> prop gives you the option to enable the browser to predict user input.
</p>
<DemoTextareaBrowserAutocomplete />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Clearable -->
<AppCardCode
title="Clearable"
:code="demoCode.clearable"
>
<p>You can clear the text from a <code>v-textarea</code> by using the <code>clearable</code> prop, and customize the icon used with the <code>clearable-icon</code> prop.</p>
<DemoTextareaClearable />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Counter -->
<AppCardCode
title="Counter"
:code="demoCode.counter"
>
<p>
The <code>counter</code> prop informs the user of a character limit for the <code>v-textarea</code>.
</p>
<DemoTextareaCounter />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>The <code>append-icon</code>, <code>prepend-icon</code>, <code>append-inner-icon</code> and <code>prepend-inner-icon</code> props help add context to v-textarea.</p>
<DemoTextareaIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Rows -->
<AppCardCode
title="Rows"
:code="demoCode.rows"
>
<p>The <code>rows</code> prop allows you to define how many rows the textarea has, when combined with the <code>row-height</code> prop you can further customize your rows by defining their height.</p>
<DemoTextareaRows />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 No resize -->
<AppCardCode
title="No resize"
:code="demoCode.noResize"
>
<p><code>v-textarea</code>'s have the option to remain the same size regardless of their content's size, using the <code>no-resize</code> prop.</p>
<DemoTextareaNoResize />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Validation -->
<AppCardCode
title="Validation"
:code="demoCode.validation"
>
<p>Use <code>rules</code> prop to validate the textarea.</p>
<DemoTextareaValidation />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,229 @@
<script setup>
import * as demoCode from '@/views/demos/forms/form-elements/textfield/demoCodeTextfield'
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Basic -->
<AppCardCode
title="Basic"
:code="demoCode.basic"
>
<p>Text fields components are used for collecting user provided information.</p>
<DemoTextfieldBasic />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Density -->
<AppCardCode
title="Density"
:code="demoCode.density"
>
<p>The <code>density</code> prop decreases the height of the text field based upon levels of density; <code>default</code>,<code>comfortable</code>, and <code>compact</code>.</p>
<DemoTextfieldDensity />
</AppCardCode>
</VCol>
<VCol cols="12">
<!-- 👉 Variant -->
<AppCardCode
title="Variant"
:code="demoCode.variant"
>
<p>Use <code>solo</code>, <code>filled</code>, <code>outlined</code>, <code>plain</code> and <code>underlined</code> option of <code>variant</code> prop to change the look of the textfield. </p>
<DemoTextfieldVariant />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 State -->
<AppCardCode
title="State"
:code="demoCode.state"
>
<p>Text fields can be disabled or readonly.</p>
<DemoTextfieldState />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Counter -->
<AppCardCode
title="Counter"
:code="demoCode.counter"
>
<p>Use a <code>counter</code> prop to inform a user of the character limit.</p>
<DemoTextfieldCounter />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Clearable -->
<AppCardCode
title="Clearable"
:code="demoCode.clearable"
>
<p>When clearable, you can customize the clear icon with clear-icon.</p>
<DemoTextfieldClearable />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Custom Colors -->
<AppCardCode
title="Custom Colors"
:code="demoCode.customColors"
>
<p>Use <code>color</code> prop to change the input border color.</p>
<DemoTextfieldCustomColors />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icons -->
<AppCardCode
title="Icons"
:code="demoCode.icons"
>
<p>You can add icons to the text field with <code>prepend-icon</code>, <code>append-icon</code> and <code>append-inner-icon</code> and <code>prepend-inner-icon</code> props.</p>
<DemoTextfieldIcons />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Prefixes and suffixes -->
<AppCardCode
title="Prefixes and suffixes"
:code="demoCode.prefixesAndSuffixes"
>
<p>The <code>prefix</code> and <code>suffix</code> properties allows you to prepend and append inline non-modifiable text next to the text field.</p>
<DemoTextfieldPrefixesAndSuffixes />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Single line -->
<AppCardCode
title="Single line"
:code="demoCode.singleLine"
>
<p><code>single-line</code> text fields do not float their label on focus or with data.</p>
<DemoTextfieldSingleLine />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Validation -->
<AppCardCode
title="Validation"
:code="demoCode.validation"
>
<p>Vuetify includes simple validation through the <code>rules</code> prop.</p>
<DemoTextfieldValidation />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icon events -->
<AppCardCode
title="Icon events"
:code="demoCode.iconEvents"
>
<p><code>click:prepend</code>, <code>click:append</code>, <code>click:append-inner</code>, and <code>click:clear</code> will be emitted when you click on the respective icon</p>
<DemoTextfieldIconEvents />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Label Slot -->
<AppCardCode
title="Label Slot"
:code="demoCode.labelSlot"
>
<p>Text field label can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
<DemoTextfieldLabelSlot />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Password input -->
<AppCardCode
title="Password input"
:code="demoCode.passwordInput"
>
<p>Using the HTML input <code>type</code> password can be used with an appended icon and callback to control the visibility.</p>
<DemoTextfieldPasswordInput />
</AppCardCode>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Icon slots -->
<AppCardCode
title="Icon slots"
:code="demoCode.iconSlots"
>
<p>Instead of using <code>prepend</code>/<code>append</code>/<code>append-inner</code> icons you can use slots to extend input's functionality.</p>
<DemoTextfieldIconSlots />
</AppCardCode>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,169 @@
<script setup>
import Footer from '@/views/front-pages/front-page-footer.vue'
import Navbar from '@/views/front-pages/front-page-navbar.vue'
import AddressContent from '@/views/wizard-examples/checkout/Address.vue'
import CartContent from '@/views/wizard-examples/checkout/Cart.vue'
import ConfirmationContent from '@/views/wizard-examples/checkout/Confirmation.vue'
import PaymentContent from '@/views/wizard-examples/checkout/Payment.vue'
import googleHome from '@images/pages/google-home.png'
import iphone11 from '@images/pages/iphone-11.png'
import customAddress from '@images/svg/address.svg'
import customCart from '@images/svg/cart.svg'
import customPayment from '@images/svg/payment.svg'
import customTrending from '@images/svg/trending.svg'
import { useConfigStore } from '@core/stores/config'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const store = useConfigStore()
store.skin = 'default'
const checkoutSteps = [
{
title: 'Cart',
icon: customCart,
},
{
title: 'Address',
icon: customAddress,
},
{
title: 'Payment',
icon: customPayment,
},
{
title: 'Confirmation',
icon: customTrending,
},
]
const checkoutData = ref({
cartItems: [
{
id: 1,
name: 'Google - Google Home - White',
seller: 'Google',
inStock: true,
rating: 4,
price: 299,
discountPrice: 359,
image: googleHome,
quantity: 1,
estimatedDelivery: '18th Nov 2021',
},
{
id: 2,
name: 'Apple iPhone 11 (64GB, Black)',
seller: 'Apple',
inStock: true,
rating: 4,
price: 899,
discountPrice: 999,
image: iphone11,
quantity: 1,
estimatedDelivery: '20th Nov 2021',
},
],
promoCode: '',
orderAmount: 1198,
deliveryAddress: 'home',
deliverySpeed: 'free',
deliveryCharges: 0,
addresses: [
{
title: 'John Doe (Default)',
desc: '4135 Parkway Street, Los Angeles, CA, 90017',
subtitle: '1234567890',
value: 'home',
},
{
title: 'ACME Inc.',
desc: '87 Hoffman Avenue, New York, NY, 10016',
subtitle: '1234567890',
value: 'office',
},
],
})
const currentStep = ref(0)
</script>
<template>
<div class="checkout-page">
<Navbar />
<VContainer>
<div class="checkout-card">
<VCard>
<VCardText>
<!-- 👉 Stepper -->
<AppStepper
v-model:current-step="currentStep"
class="checkout-stepper"
:items="checkoutSteps"
:direction="$vuetify.display.mdAndUp ? 'horizontal' : 'vertical'"
align="center"
/>
</VCardText>
<VDivider />
<VCardText>
<!-- 👉 stepper content -->
<VWindow
v-model="currentStep"
class="disable-tab-transition"
:touch="false"
>
<VWindowItem>
<CartContent
v-model:current-step="currentStep"
v-model:checkout-data="checkoutData"
/>
</VWindowItem>
<VWindowItem>
<AddressContent
v-model:current-step="currentStep"
v-model:checkout-data="checkoutData"
/>
</VWindowItem>
<VWindowItem>
<PaymentContent
v-model:current-step="currentStep"
v-model:checkout-data="checkoutData"
/>
</VWindowItem>
<VWindowItem>
<ConfirmationContent v-model:checkout-data="checkoutData" />
</VWindowItem>
</VWindow>
</VCardText>
</VCard>
</div>
</VContainer>
<Footer />
</div>
</template>
<style lang="scss">
.checkout-card {
margin-block: 10.5rem 5.25rem;
}
@media (max-width: 960px) and (min-width: 600px) {
.checkout-page {
.v-container {
padding-inline: 2rem !important;
}
}
}
@media (max-width: 600px) {
.checkout-card {
margin-block-start: 6rem;
}
}
</style>

View File

@@ -0,0 +1,156 @@
<script setup>
import Footer from '@/views/front-pages/front-page-footer.vue'
import Navbar from '@/views/front-pages/front-page-navbar.vue'
import { useConfigStore } from '@core/stores/config'
const store = useConfigStore()
store.skin = 'default'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const articleData = ref()
setTimeout(async () => {
const { data, error } = await useApi('/pages/help-center/article')
if (error.value)
console.log(error.value)
else
articleData.value = data.value
}, 1000)
</script>
<template>
<!-- eslint-disable vue/no-v-html -->
<div class="bg-surface help-center-article">
<!-- 👉 Navbar -->
<Navbar />
<!-- 👉 Content -->
<VContainer>
<div
v-if="articleData && articleData?.title"
class="article-section"
>
<VRow>
<VCol
cols="12"
md="8"
>
<div>
<VBreadcrumbs
class="px-0 pb-2 pt-0 help-center-breadcrumbs"
:items="[{ title: 'Help Center', to: { name: 'front-pages-help-center' }, class: 'text-primary' }, { title: 'how to add product in cart' }]"
/>
<h4 class="text-h4 mb-2">
{{ articleData?.title }}
</h4>
<div class="text-body-1">
{{ articleData?.lastUpdated }}
</div>
</div>
<VDivider class="my-6" />
<!-- eslint-disable vue/no-v-html -->
<div
class="mb-6 text-body-1"
v-html="articleData?.productContent"
/>
<VImg
class="rounded-lg"
:src="articleData?.productImg"
/>
<p class="my-6 text-body-1">
{{ articleData?.checkoutContent }}
</p>
<VImg
class="rounded-lg"
:src="articleData?.checkoutImg"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VTextField
prepend-inner-icon="tabler-search"
placeholder="Search..."
class="mb-6"
/>
<div>
<!-- 👉 Article List -->
<h5
class="text-h5 px-6 py-2 mb-4 rounded"
style="background: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));"
>
Articles in this section
</h5>
<VList class="card-list">
<VListItem
v-for="(item, index) in articleData?.articleList"
:key="index"
link
class="text-disabled"
>
<template #append>
<VIcon
:icon="$vuetify.locale.isRtl ? 'tabler-chevron-left' : 'tabler-chevron-right'"
size="20"
/>
</template>
<div class="text-body-1 text-high-emphasis">
{{ item }}
</div>
</VListItem>
</VList>
</div>
</VCol>
</VRow>
</div>
</VContainer>
<!-- 👉 Footer -->
<Footer />
</div>
</template>
<style lang="scss" scoped>
.article-section {
margin-block: 10.5rem 5.25rem;
}
@media (max-width: 600px) {
.article-section {
margin-block-start: 6rem;
}
}
.card-list {
--v-card-list-gap: 1rem;
}
</style>
<style lang="scss">
@media (max-width: 960px) and (min-width: 600px) {
.help-center-article {
.v-container {
padding-inline: 2rem !important;
}
}
}
.help-center-breadcrumbs {
&.v-breadcrumbs {
.v-breadcrumbs-item {
padding: 0 !important;
&.v-breadcrumbs-item--disabled {
opacity: 0.9;
}
}
}
}
</style>

View File

@@ -0,0 +1,123 @@
<script setup>
import Footer from '@/views/front-pages/front-page-footer.vue'
import Navbar from '@/views/front-pages/front-page-navbar.vue'
import HelpCenterLandingArticlesOverview from '@/views/pages/help-center/HelpCenterLandingArticlesOverview.vue'
import HelpCenterLandingFooter from '@/views/pages/help-center/HelpCenterLandingFooter.vue'
import HelpCenterLandingKnowledgeBase from '@/views/pages/help-center/HelpCenterLandingKnowledgeBase.vue'
import { useConfigStore } from '@core/stores/config'
// fetching data from fake-api
const store = useConfigStore()
store.skin = 'default'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const apiData = ref()
// // Check if MSW service worker is registered and ready to intercept requests
setTimeout(async () => {
const faqData = await $api('/pages/help-center')
apiData.value = faqData
}, 1000)
</script>
<template>
<div class="help-center-page">
<Navbar />
<div v-if="apiData && apiData.allArticles.length">
<AppSearchHeader
subtitle="Common troubleshooting topics: eCommerce, Blogging to payment"
custom-class="rounded-0"
placeholder="Search"
>
<template #title>
<h4
class="text-h4 font-weight-medium"
style="color: rgba(var(--v-theme-primary), 1);"
>
Hello, how can we help?
</h4>
</template>
</AppSearchHeader>
<!-- 👉 Popular Articles -->
<div class="help-center-section bg-surface">
<VContainer>
<h4 class="text-h4 text-center mb-6">
Popular Articles
</h4>
<HelpCenterLandingArticlesOverview :articles="apiData.popularArticles" />
</VContainer>
</div>
<!-- 👉 Knowledge Base -->
<div class="help-center-section">
<VContainer>
<h4 class="text-h4 text-center mb-6">
Knowledge Base
</h4>
<HelpCenterLandingKnowledgeBase :categories="apiData.allArticles" />
</VContainer>
</div>
<!-- 👉 Keep Learning -->
<div class="help-center-section bg-surface">
<VContainer>
<h4 class="text-h4 text-center mb-6">
Keep Learning
</h4>
<HelpCenterLandingArticlesOverview :articles="apiData.keepLearning" />
</VContainer>
</div>
<!-- 👉 Still need help? -->
<div class="help-center-section">
<HelpCenterLandingFooter />
</div>
<div>
<Footer />
</div>
</div>
</div>
</template>
<style lang="scss">
.help-center-page {
.search-header {
background-size: cover !important;
padding-block: 9.25rem 4.75rem !important;
}
.help-center-section {
padding-block: 5.25rem;
}
}
@media (max-width: 960px) and (min-width: 600px) {
.help-center-page {
.v-container {
padding-inline: 2rem !important;
}
}
}
@media (max-width: 599px) {
.help-center-page {
.search-header {
padding-block: 7rem 2rem !important;
padding-inline: 2rem !important;
}
.help-center-section {
padding-block: 3.5rem;
}
}
}
</style>

View File

@@ -0,0 +1,98 @@
<script setup>
import Footer from '@/views/front-pages/front-page-footer.vue'
import Navbar from '@/views/front-pages/front-page-navbar.vue'
import Banner from '@/views/front-pages/landing-page/banner.vue'
import ContactUs from '@/views/front-pages/landing-page/contact-us.vue'
import CustomersReview from '@/views/front-pages/landing-page/customers-review.vue'
import FaqSection from '@/views/front-pages/landing-page/faq-section.vue'
import Features from '@/views/front-pages/landing-page/features.vue'
import HeroSection from '@/views/front-pages/landing-page/hero-section.vue'
import OurTeam from '@/views/front-pages/landing-page/our-team.vue'
import PricingPlans from '@/views/front-pages/landing-page/pricing-plans.vue'
import ProductStats from '@/views/front-pages/landing-page/product-stats.vue'
import { useConfigStore } from '@core/stores/config'
const store = useConfigStore()
store.skin = 'default'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const activeSectionId = ref()
const refHome = ref()
const refFeatures = ref()
const refTeam = ref()
const refContact = ref()
const refFaq = ref()
useIntersectionObserver([
refHome,
refFeatures,
refTeam,
refContact,
refFaq,
], ([{ isIntersecting, target }]) => {
if (isIntersecting)
activeSectionId.value = target.id
}, { threshold: 0.25 })
</script>
<template>
<div class="landing-page-wrapper">
<Navbar :active-id="activeSectionId" />
<!-- 👉 Hero Section -->
<HeroSection ref="refHome" />
<!-- 👉 Useful features -->
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
<Features ref="refFeatures" />
</div>
<!-- 👉 Customer Review -->
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
<CustomersReview />
</div>
<!-- 👉 Our Team -->
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
<OurTeam ref="refTeam" />
</div>
<!-- 👉 Pricing Plans -->
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
<PricingPlans />
</div>
<!-- 👉 Product stats -->
<ProductStats />
<!-- 👉 FAQ Section -->
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
<FaqSection ref="refFaq" />
</div>
<!-- 👉 Banner -->
<Banner />
<!-- 👉 Contact Us -->
<ContactUs ref="refContact" />
<!-- 👉 Footer -->
<Footer />
</div>
</template>
<style lang="scss">
@media (max-width: 960px) and (min-width: 600px) {
.landing-page-wrapper {
.v-container {
padding-inline: 2rem !important;
}
}
}
</style>

View File

@@ -0,0 +1,308 @@
<script setup>
import Footer from '@/views/front-pages/front-page-footer.vue'
import Navbar from '@/views/front-pages/front-page-navbar.vue'
import paypalDark from '@images/icons/payments/img/paypal-dark.png'
import paypalLight from '@images/icons/payments/img/paypal-light.png'
import visaDark from '@images/icons/payments/img/visa-dark.png'
import visaLight from '@images/icons/payments/img/visa-light.png'
import { useConfigStore } from '@core/stores/config'
const visa = useGenerateImageVariant(visaLight, visaDark)
const paypal = useGenerateImageVariant(paypalLight, paypalDark)
const store = useConfigStore()
store.skin = 'default'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const radioContent = [
{
title: 'Credit Card',
value: 'credit card',
images: visa.value,
},
{
title: 'PayPal',
value: 'paypal',
images: paypal.value,
},
]
const selectedRadio = ref('credit card')
const selectedCountry = ref('USA')
const isPricingPlanDialogVisible = ref(false)
</script>
<template>
<!-- eslint-disable vue/attribute-hyphenation -->
<div class="payment-page">
<!-- 👉 Navbar -->
<Navbar />
<!-- 👉 Payment card -->
<VContainer>
<div class="d-flex justify-center align-center payment-card">
<VCard width="100%">
<VRow>
<VCol
cols="12"
md="7"
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
>
<VCardText class="pa-8 pe-5">
<!-- Checkout header -->
<div>
<h4 class="text-h4 mb-2">
Checkout
</h4>
<div class="text-body-1">
All plans include 40+ advanced tools and features to boost your product. Choose the best plan to fit your needs.
</div>
</div>
<CustomRadios
v-model:selected-radio="selectedRadio"
:radio-content="radioContent"
:grid-column="{ cols: '12', sm: '6' }"
class="my-8"
>
<template #default="{ item }">
<div class="d-flex align-center gap-x-4 ms-3">
<img
:src="item.images"
height="34"
>
<h6 class="text-h6">
{{ item.title }}
</h6>
</div>
</template>
</CustomRadios>
<!-- billing Details -->
<div class="mb-8">
<h4 class="text-h4 mb-6">
Billing Details
</h4>
<VForm>
<VRow>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Email Address"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Password"
type="password"
placeholder="············"
autocomplete="on"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppSelect
v-model="selectedCountry"
label="Billing Country"
:items="['USA', 'Canada', 'UK', 'AUS']"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Billing Zip/Postal Code"
type="number"
placeholder="129211"
/>
</VCol>
</VRow>
</VForm>
</div>
<!-- Credit card info -->
<div
class="mb-8"
:class="selectedRadio === 'paypal' ? 'd-none' : 'd-block'"
>
<h4 class="text-h4 mb-6">
Credit Card Info
</h4>
<VRow>
<VCol cols="12">
<AppTextField
label="Card Number"
placeholder="8787 2345 3458"
type="number"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<AppTextField
label="Card Holder"
placeholder="John Doe"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<AppTextField
label="Exp. date"
placeholder="05/2020"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<AppTextField
label="CVV"
type="number"
placeholder="784"
/>
</VCol>
</VRow>
</div>
</VCardText>
</VCol>
<VCol
cols="12"
md="5"
>
<VCardText class="pa-8 ps-5">
<!-- order summary -->
<div class="mb-8">
<h4 class="text-h4 mb-2">
Order Summary
</h4>
<div class="text-body-1">
It can help you manage and service orders before, during, and after fulfillment.
</div>
</div>
<VCard
flat
color="rgba(var(--v-theme-on-surface), var(--v-hover-opacity))"
>
<VCardText>
<div class="text-body-1">
A simple start for everyone
</div>
<h1 class="text-h1 my-4">
$59.99<span class="text-body-1 font-weight-medium">/month</span>
</h1>
<VBtn
variant="tonal"
block
@click="isPricingPlanDialogVisible = !isPricingPlanDialogVisible"
>
Change Plan
</VBtn>
</VCardText>
</VCard>
<div class="my-5">
<div class="d-flex justify-space-between mb-2">
<span>Subscription</span>
<h6 class="text-h6">
$85.99
</h6>
</div>
<div class="d-flex justify-space-between">
<span>Tax</span>
<h6 class="text-h6">
$4.99
</h6>
</div>
<VDivider class="my-4" />
<div class="d-flex justify-space-between">
<span>Total</span>
<h6 class="text-h6">
$90.98
</h6>
</div>
</div>
<VBtn
block
color="success"
class="mb-8"
>
<template #append>
<VIcon
icon="tabler-arrow-right"
class="flip-in-rtl"
/>
</template>
Proceed With Payment
</VBtn>
<div class="text-body-1">
By continuing, you accept to our Terms of Services and Privacy Policy. Please note that payments are non-refundable.
</div>
</VCardText>
</VCol>
</VRow>
</VCard>
</div>
</VContainer>
<!-- 👉 Footer -->
<Footer />
<PricingPlanDialog v-model:is-dialog-visible="isPricingPlanDialogVisible" />
</div>
</template>
<style lang="scss" scoped>
.footer {
position: static !important;
inline-size: 100%;
inset-block-end: 0;
}
.payment-card {
margin-block: 10.5rem 5.25rem;
}
.payment-page {
@media (min-width: 600px) and (max-width: 960px) {
.v-container {
padding-inline: 2rem !important;
}
}
}
</style>
<style lang="scss">
.payment-card {
.custom-radio {
.v-radio {
margin-block-start: 0 !important;
}
}
}
</style>

View File

@@ -0,0 +1,406 @@
<script setup>
import Footer from '@/views/front-pages/front-page-footer.vue'
import Navbar from '@/views/front-pages/front-page-navbar.vue'
import { useConfigStore } from '@core/stores/config'
import laptopGirl from '@images/illustrations/laptop-girl.png'
const store = useConfigStore()
store.skin = 'default'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const features = [
{
feature: '14-days free trial',
starter: true,
pro: true,
enterprise: true,
addOnAvailable: {
starter: false,
pro: false,
enterprise: false,
},
},
{
feature: 'No user limit',
starter: false,
pro: false,
enterprise: true,
addOnAvailable: {
starter: false,
pro: false,
enterprise: false,
},
},
{
feature: 'Product Support',
starter: false,
pro: true,
enterprise: true,
addOnAvailable: {
starter: false,
pro: false,
enterprise: false,
},
},
{
feature: 'Email Support',
starter: false,
pro: false,
enterprise: true,
addOnAvailable: {
starter: false,
pro: true,
enterprise: false,
},
},
{
feature: 'Integrations',
starter: false,
pro: true,
enterprise: true,
addOnAvailable: {
starter: false,
pro: false,
enterprise: false,
},
},
{
feature: 'Removal of Front branding',
starter: false,
pro: false,
enterprise: true,
addOnAvailable: {
starter: false,
pro: true,
enterprise: false,
},
},
{
feature: 'Active maintenance & support',
starter: false,
pro: false,
enterprise: true,
addOnAvailable: {
starter: false,
pro: false,
enterprise: false,
},
},
{
feature: 'Data storage for 365 days',
starter: false,
pro: false,
enterprise: true,
addOnAvailable: {
starter: false,
pro: false,
enterprise: false,
},
},
]
const faqs = [
{
question: 'How do you process payments?',
answer: 'We accept Visa®, MasterCard®, American Express®, and PayPal®. So you can be confident that your credit card information will be kept safe and secure.',
},
{
question: 'What counts towards the 100 responses limit?',
answer: 'We count all responses submitted through all forms in a month.If you already received 100 responses this month, you won\'t be able to receive any more of them until next month when the counter resets.',
},
{
question: 'What payment methods do you accept?',
answer: 'Checkout accepts all type of credit and debit cards.',
},
]
</script>
<template>
<div class="pricing-page">
<Navbar />
<VCard class="pricing-card">
<!-- 👉 App Pricing components -->
<VContainer>
<AppPricing md="4" />
</VContainer>
<!-- 👉 Free trial Banner -->
<div class="page-pricing-free-trial-banner-bg">
<VContainer>
<div class="d-flex align-center flex-md-row flex-column position-relative">
<div class="text-center text-md-start pb-5 px-10 px-sm-0 pt-8">
<h4 class="text-h4 text-primary mb-2">
Still not convinced? Start with a 14-day FREE trial!
</h4>
<p class="text-body-1 mb-11">
You will get full access to all the features for 14 days.
</p>
<VBtn :to="{ name: 'front-pages-payment' }">
Start 14-day FREE trial
</VBtn>
</div>
<div class="free-trial-illustrator">
<VImg
:src="laptopGirl"
:width="238"
/>
</div>
</div>
</VContainer>
</div>
<!-- 👉 Plans -->
<VContainer>
<VCardText class="text-center py-16 pricing-section">
<h3 class="text-h3 mb-2">
Pick a plan that works best for you
</h3>
<p class="text-body-1">
Stay cool, we have a 48-hour money back guarantee!
</p>
<!-- 👉 Features & Tables -->
<VTable class="text-no-wrap border rounded pricing-table">
<!-- 👉 Table head -->
<thead>
<tr>
<th
scope="col"
class="py-4"
>
<div>
Features
</div>
<div class="text-body-2">
Native Font Features
</div>
</th>
<th
v-for="{ plan, price } in [
{ plan: 'Starter', price: 'Free' },
{ plan: 'Pro', price: '$7.5/Month' },
{ plan: 'Enterprise', price: '$16/Month' },
]"
:key="plan"
scope="col"
class="text-center py-4"
>
<div class="position-relative">
{{ plan }}
<VAvatar
v-if="plan === 'Pro'"
size="20"
class="ms-2 position-absolute"
variant="elevated"
color="primary"
style="inset-block-end: 7px;"
>
<VIcon
icon="tabler-star"
size="14"
color="white"
/>
</VAvatar>
</div>
<div class="text-body-2">
{{ price }}
</div>
</th>
</tr>
</thead>
<!-- 👉 Table Body -->
<tbody>
<tr
v-for="feature in features"
:key="feature.feature"
>
<td class="text-start text-body-1 text-high-emphasis">
{{ feature.feature }}
</td>
<td class="text-center">
<VAvatar
variant="tonal"
size="20"
:color="feature.starter ? 'primary' : 'secondary'"
>
<VIcon
v-if="!feature.addOnAvailable.starter"
:color="feature.starter ? 'primary' : 'secondary'"
size="14"
:icon="feature.starter ? 'tabler-check' : 'tabler-x'"
/>
</VAvatar>
<VChip
v-if="feature.addOnAvailable.starter"
color="primary"
size="small"
label
>
Add-On Available
</VChip>
</td>
<td class="text-center">
<VChip
v-if="feature.addOnAvailable.pro"
color="primary"
size="small"
label
>
Add-On Available
</VChip>
<VAvatar
v-else
size="20"
variant="tonal"
:color="feature.pro ? 'primary' : 'secondary'"
>
<VIcon
:color="feature.pro ? 'primary' : 'secondary'"
size="14"
:icon="feature.pro ? 'tabler-check' : 'tabler-x'"
/>
</VAvatar>
</td>
<td class="text-center">
<VChip
v-if="feature.addOnAvailable.enterprise"
label
color="primary"
size="small"
>
Add-On Available
</VChip>
<VAvatar
v-else
size="20"
variant="tonal"
:color="feature.enterprise ? 'primary' : 'disabled'"
>
<VIcon
:color="feature.enterprise ? 'primary' : 'disabled'"
size="14"
:icon="feature.enterprise ? 'tabler-check' : 'tabler-x'"
/>
</VAvatar>
</td>
</tr>
</tbody>
<!-- 👉 Table footer -->
<tfoot>
<tr>
<td class="py-2" />
<td class="text-center py-2">
<VBtn
variant="tonal"
:to="{ name: 'front-pages-payment' }"
>
Choose Plan
</VBtn>
</td>
<td class="text-center py-2">
<VBtn :to="{ name: 'front-pages-payment' }">
Choose Plan
</VBtn>
</td>
<td class="text-center py-2">
<VBtn
variant="tonal"
:to="{ name: 'front-pages-payment' }"
>
Choose Plan
</VBtn>
</td>
</tr>
</tfoot>
</VTable>
</VCardText>
</VContainer>
<!-- 👉 FAQ -->
<div style="background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));">
<VContainer>
<VCardText class="py-10 py-sm-16 pricing-section">
<div class="text-center">
<h4 class="text-h4 mb-2">
FAQ's
</h4>
<p class="text-body-1 mb-6">
Let us help answer the most common questions.
</p>
</div>
<div>
<VExpansionPanels>
<VExpansionPanel
v-for="(faq, index) in faqs"
:key="faq.question"
:title="faq.question"
:text="faq.answer"
:value="index"
/>
</VExpansionPanels>
</div>
</VCardText>
</VContainer>
</div>
<div style="background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));">
<Footer />
</div>
</VCard>
</div>
</template>
<style lang="scss" scoped>
.pricing-section {
padding-block: 5.25rem !important;
padding-inline: 0 !important;
}
.page-pricing-free-trial-banner-bg {
/* stylelint-disable-next-line color-function-notation */
background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity));
margin-block-start: 8.9375rem !important;
}
.pricing-card {
padding-block-start: 10.5rem !important;
}
@media screen and (min-width: 960px) {
.free-trial-illustrator {
position: absolute;
inset-block-end: -1rem !important;
inset-inline-end: 0%;
}
}
@media screen and (max-width: 959px) {
.free-trial-illustrator {
position: relative;
inset-block-end: -1rem !important;
}
}
.pricing-table {
tr:nth-child(even) {
background: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
}
}
</style>
<style lang="scss">
.pricing-page {
@media (min-width: 600px) and (max-width: 960px) {
.v-container {
padding-inline: 2rem !important;
}
}
}
</style>

View File

@@ -0,0 +1,243 @@
<!-- Errors in the form are set on line 60 -->
<script setup>
import { VForm } from 'vuetify/components/VForm'
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
import authV2LoginIllustrationDark from '@images/pages/auth-v2-login-illustration-dark.png'
import authV2LoginIllustrationLight from '@images/pages/auth-v2-login-illustration-light.png'
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
import authV2MaskLight from '@images/pages/misc-mask-light.png'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
definePage({
meta: {
layout: 'blank',
unauthenticatedOnly: true,
},
})
const isPasswordVisible = ref(false)
const route = useRoute()
const router = useRouter()
const ability = useAbility()
const errors = ref({
email: undefined,
password: undefined,
})
const refVForm = ref()
const credentials = ref({
email: 'admin@demo.com',
password: 'admin',
})
const rememberMe = ref(false)
const login = async () => {
try {
const res = await $api('/auth/login', {
method: 'POST',
body: {
email: credentials.value.email,
password: credentials.value.password,
},
onResponseError({ response }) {
errors.value = response._data.errors
},
})
const { accessToken, userData, userAbilityRules } = res
useCookie('userAbilityRules').value = userAbilityRules
ability.update(userAbilityRules)
useCookie('userData').value = userData
useCookie('accessToken').value = accessToken
await nextTick(() => {
router.replace(route.query.to ? String(route.query.to) : '/')
})
} catch (err) {
console.error(err)
}
}
const onSubmit = () => {
refVForm.value?.validate().then(({ valid: isValid }) => {
if (isValid)
login()
})
}
</script>
<template>
<RouterLink to="/">
<div class="auth-logo d-flex align-center gap-x-3">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="auth-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
<VRow
no-gutters
class="auth-wrapper bg-surface"
>
<VCol
md="8"
class="d-none d-md-flex"
>
<div class="position-relative bg-background w-100 me-0">
<div
class="d-flex align-center justify-center w-100 h-100"
style="padding-inline: 6.25rem;"
>
<VImg
max-width="613"
:src="authThemeImg"
class="auth-illustration mt-16 mb-2"
/>
</div>
<img
class="auth-footer-mask"
:src="authThemeMask"
alt="auth-footer-mask"
height="280"
width="100"
>
</div>
</VCol>
<VCol
cols="12"
md="4"
class="auth-card-v2 d-flex align-center justify-center"
>
<VCard
flat
:max-width="500"
class="mt-12 mt-sm-0 pa-4"
>
<VCardText>
<h4 class="text-h4 mb-1">
Welcome to <span class="text-capitalize"> {{ themeConfig.app.title }} </span>! 👋🏻
</h4>
<p class="mb-0">
Please sign-in to your account and start the adventure
</p>
</VCardText>
<VCardText>
<VAlert
color="primary"
variant="tonal"
>
<p class="text-sm mb-2">
Admin Email: <strong>admin@demo.com</strong> / Pass: <strong>admin</strong>
</p>
<p class="text-sm mb-0">
Client Email: <strong>client@demo.com</strong> / Pass: <strong>client</strong>
</p>
</VAlert>
</VCardText>
<VCardText>
<VForm
ref="refVForm"
@submit.prevent="onSubmit"
>
<VRow>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="credentials.email"
label="Email"
placeholder="johndoe@email.com"
type="email"
autofocus
:rules="[requiredValidator, emailValidator]"
:error-messages="errors.email"
/>
</VCol>
<!-- password -->
<VCol cols="12">
<AppTextField
v-model="credentials.password"
label="Password"
placeholder="············"
:rules="[requiredValidator]"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:error-messages="errors.password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
<div class="d-flex align-center flex-wrap justify-space-between my-6">
<VCheckbox
v-model="rememberMe"
label="Remember me"
/>
<RouterLink
class="text-primary ms-2 mb-1"
:to="{ name: 'forgot-password' }"
>
Forgot Password?
</RouterLink>
</div>
<VBtn
block
type="submit"
>
Login
</VBtn>
</VCol>
<!-- create account -->
<VCol
cols="12"
class="text-center"
>
<span>New on our platform?</span>
<RouterLink
class="text-primary ms-1"
:to="{ name: 'register' }"
>
Create an account
</RouterLink>
</VCol>
<VCol
cols="12"
class="d-flex align-center"
>
<VDivider />
<span class="mx-4">or</span>
<VDivider />
</VCol>
<!-- auth providers -->
<VCol
cols="12"
class="text-center"
>
<AuthProvider />
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

View File

@@ -0,0 +1,54 @@
<script setup>
import pages401 from '@images/pages/401.png'
import miscMaskDark from '@images/pages/misc-mask-dark.png'
import miscMaskLight from '@images/pages/misc-mask-light.png'
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
definePage({
alias: '/pages/misc/not-authorized',
meta: {
layout: 'blank',
public: true,
},
})
const authThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark)
</script>
<template>
<div class="misc-wrapper">
<ErrorHeader
status-code="401"
title="You are not authorized! 🔐"
description="You dont have permission to access this page. Go Home!."
/>
<VBtn
class="mb-11"
to="/"
>
Back To Home
</VBtn>
<!-- 👉 Image -->
<div class="misc-avatar w-100 text-center">
<VImg
:src="pages401"
alt="not autorized"
:max-height="$vuetify.display.smAndDown ? 350 : 500"
class="mx-auto"
/>
</div>
<img
class="misc-footer-img d-none d-md-block"
:src="authThemeMask"
alt="misc-footer-img"
height="320"
>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/misc.scss";
</style>

View File

@@ -0,0 +1,99 @@
<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'
const route = useRoute('pages-account-settings-tab')
const activeTab = computed({
get: () => route.params.tab,
set: () => route.params.tab,
})
// tabs
const tabs = [
{
title: 'Account',
icon: 'tabler-users',
tab: 'account',
},
{
title: 'Security',
icon: 'tabler-lock',
tab: 'security',
},
{
title: 'Billing & Plans',
icon: 'tabler-file-text',
tab: 'billing-plans',
},
{
title: 'Notifications',
icon: 'tabler-bell',
tab: 'notification',
},
{
title: 'Connections',
icon: 'tabler-link',
tab: 'connection',
},
]
definePage({ meta: { navActiveLink: 'pages-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: 'pages-account-settings-tab', params: { tab: item.tab } }"
>
<VIcon
size="20"
start
:icon="item.icon"
/>
{{ item.title }}
</VTab>
</VTabs>
<VWindow
v-model="activeTab"
class="mt-6 disable-tab-transition"
:touch="false"
>
<!-- Account -->
<VWindowItem value="account">
<AccountSettingsAccount />
</VWindowItem>
<!-- Security -->
<VWindowItem value="security">
<AccountSettingsSecurity />
</VWindowItem>
<!-- Billing -->
<VWindowItem value="billing-plans">
<AccountSettingsBillingAndPlans />
</VWindowItem>
<!-- Notification -->
<VWindowItem value="notification">
<AccountSettingsNotification />
</VWindowItem>
<!-- Connections -->
<VWindowItem value="connection">
<AccountSettingsConnections />
</VWindowItem>
</VWindow>
</div>
</template>

View File

@@ -0,0 +1,108 @@
<script setup>
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const form = ref({ email: '' })
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<div class="position-relative my-sm-16">
<!-- 👉 Top shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1TopShape })"
class="text-primary auth-v1-top-shape d-none d-sm-block"
/>
<!-- 👉 Bottom shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1BottomShape })"
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
/>
<!-- 👉 Auth card -->
<VCard
class="auth-card"
max-width="460"
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
>
<VCardItem class="justify-center">
<VCardTitle>
<RouterLink to="/">
<div class="app-logo">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="app-logo-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
</VCardTitle>
</VCardItem>
<VCardText>
<h4 class="text-h4 mb-1">
Forgot Password? 🔒
</h4>
<p class="mb-0">
Enter your email and we'll send you instructions to reset your password
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="form.email"
autofocus
label="Email"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- reset password -->
<VCol cols="12">
<VBtn
block
type="submit"
>
Send Reset Link
</VBtn>
</VCol>
<!-- back to login -->
<VCol cols="12">
<RouterLink
class="d-flex align-center justify-center"
:to="{ name: 'pages-authentication-login-v1' }"
>
<VIcon
icon="tabler-chevron-left"
size="20"
class="me-1 flip-in-rtl"
/>
<span>Back to login</span>
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

View File

@@ -0,0 +1,129 @@
<script setup>
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import authV2ForgotPasswordIllustrationDark from '@images/pages/auth-v2-forgot-password-illustration-dark.png'
import authV2ForgotPasswordIllustrationLight from '@images/pages/auth-v2-forgot-password-illustration-light.png'
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
import authV2MaskLight from '@images/pages/misc-mask-light.png'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const email = ref('')
const authThemeImg = useGenerateImageVariant(authV2ForgotPasswordIllustrationLight, authV2ForgotPasswordIllustrationDark)
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
</script>
<template>
<RouterLink to="/">
<div class="auth-logo d-flex align-center gap-x-3">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="auth-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
<VRow
class="auth-wrapper bg-surface"
no-gutters
>
<VCol
md="8"
class="d-none d-md-flex"
>
<div class="position-relative bg-background w-100 me-0">
<div
class="d-flex align-center justify-center w-100 h-100"
style="padding-inline: 150px;"
>
<VImg
max-width="468"
:src="authThemeImg"
class="auth-illustration mt-16 mb-2 flip-in-rtl"
/>
</div>
<img
class="auth-footer-mask flip-in-rtl"
:src="authThemeMask"
alt="auth-footer-mask"
height="280"
width="100"
>
</div>
</VCol>
<VCol
cols="12"
md="4"
class="auth-card-v2 d-flex align-center justify-center"
>
<VCard
flat
:max-width="500"
class="mt-12 mt-sm-0 pa-6"
>
<VCardText>
<h4 class="text-h4 mb-1">
Forgot Password? 🔒
</h4>
<p class="mb-0">
Enter your email and we'll send you instructions to reset your password
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="email"
autofocus
label="Email"
placeholder="johndoe@email.com"
type="email"
/>
</VCol>
<!-- Reset link -->
<VCol cols="12">
<VBtn
block
type="submit"
>
Send Reset Link
</VBtn>
</VCol>
<!-- back to login -->
<VCol cols="12">
<RouterLink
class="d-flex align-center justify-center"
:to="{ name: 'pages-authentication-login-v2' }"
>
<VIcon
icon="tabler-chevron-left"
size="20"
class="me-1 flip-in-rtl"
/>
<span>Back to login</span>
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth.scss";
</style>

View File

@@ -0,0 +1,159 @@
<script setup>
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const form = ref({
email: '',
password: '',
remember: false,
})
const isPasswordVisible = ref(false)
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<div class="position-relative my-sm-16">
<!-- 👉 Top shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1TopShape })"
class="text-primary auth-v1-top-shape d-none d-sm-block"
/>
<!-- 👉 Bottom shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1BottomShape })"
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
/>
<!-- 👉 Auth Card -->
<VCard
class="auth-card"
max-width="460"
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
>
<VCardItem class="justify-center">
<VCardTitle>
<RouterLink to="/">
<div class="app-logo">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="app-logo-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
</VCardTitle>
</VCardItem>
<VCardText>
<h4 class="text-h4 mb-1">
Welcome to <span class="text-capitalize">{{ themeConfig.app.title }}</span>! 👋🏻
</h4>
<p class="mb-0">
Please sign-in to your account and start the adventure
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="form.email"
autofocus
label="Email or Username"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- password -->
<VCol cols="12">
<AppTextField
v-model="form.password"
label="Password"
placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
<!-- remember me checkbox -->
<div class="d-flex align-center justify-space-between flex-wrap my-6">
<VCheckbox
v-model="form.remember"
label="Remember me"
/>
<RouterLink
class="text-primary"
:to="{ name: 'pages-authentication-forgot-password-v1' }"
>
Forgot Password?
</RouterLink>
</div>
<!-- login button -->
<VBtn
block
type="submit"
>
Login
</VBtn>
</VCol>
<!-- create account -->
<VCol
cols="12"
class="text-body-1 text-center"
>
<span class="d-inline-block">
New on our platform?
</span>
<RouterLink
class="text-primary ms-1 d-inline-block text-body-1"
:to="{ name: 'pages-authentication-register-v1' }"
>
Create an account
</RouterLink>
</VCol>
<VCol
cols="12"
class="d-flex align-center"
>
<VDivider />
<span class="mx-4 text-high-emphasis">or</span>
<VDivider />
</VCol>
<!-- auth providers -->
<VCol
cols="12"
class="text-center"
>
<AuthProvider />
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

View File

@@ -0,0 +1,178 @@
<script setup>
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
import authV2LoginIllustrationDark from '@images/pages/auth-v2-login-illustration-dark.png'
import authV2LoginIllustrationLight from '@images/pages/auth-v2-login-illustration-light.png'
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
import authV2MaskLight from '@images/pages/misc-mask-light.png'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const form = ref({
email: '',
password: '',
remember: false,
})
const isPasswordVisible = ref(false)
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
</script>
<template>
<RouterLink to="/">
<div class="auth-logo d-flex align-center gap-x-3">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="auth-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
<VRow
no-gutters
class="auth-wrapper bg-surface"
>
<VCol
md="8"
class="d-none d-md-flex"
>
<div class="position-relative bg-background w-100 me-0">
<div
class="d-flex align-center justify-center w-100 h-100"
style="padding-inline: 6.25rem;"
>
<VImg
max-width="613"
:src="authThemeImg"
class="auth-illustration mt-16 mb-2"
/>
</div>
<img
class="auth-footer-mask flip-in-rtl"
:src="authThemeMask"
alt="auth-footer-mask"
height="280"
width="100"
>
</div>
</VCol>
<VCol
cols="12"
md="4"
class="auth-card-v2 d-flex align-center justify-center"
>
<VCard
flat
:max-width="500"
class="mt-12 mt-sm-0 pa-6"
>
<VCardText>
<h4 class="text-h4 mb-1">
Welcome to <span class="text-capitalize">{{ themeConfig.app.title }}</span>! 👋🏻
</h4>
<p class="mb-0">
Please sign-in to your account and start the adventure
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="form.email"
autofocus
label="Email or Username"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- password -->
<VCol cols="12">
<AppTextField
v-model="form.password"
label="Password"
placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
<div class="d-flex align-center flex-wrap justify-space-between my-6">
<VCheckbox
v-model="form.remember"
label="Remember me"
/>
<RouterLink
class="text-primary"
:to="{ name: 'pages-authentication-forgot-password-v2' }"
>
Forgot Password?
</RouterLink>
</div>
<VBtn
block
type="submit"
>
Login
</VBtn>
</VCol>
<!-- create account -->
<VCol
cols="12"
class="text-body-1 text-center"
>
<span class="d-inline-block">
New on our platform?
</span>
<RouterLink
class="text-primary ms-1 d-inline-block text-body-1"
:to="{ name: 'pages-authentication-register-v2' }"
>
Create an account
</RouterLink>
</VCol>
<VCol
cols="12"
class="d-flex align-center"
>
<VDivider />
<span class="mx-4">or</span>
<VDivider />
</VCol>
<!-- auth providers -->
<VCol
cols="12"
class="text-center"
>
<AuthProvider />
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

View File

@@ -0,0 +1,454 @@
<script setup>
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
import registerMultiStepIllustrationDark from '@images/illustrations/register-multi-step-illustration-dark.png'
import registerMultiStepIllustrationLight from '@images/illustrations/register-multi-step-illustration-light.png'
import registerMultiStepBgDark from '@images/pages/register-multi-step-bg-dark.png'
import registerMultiStepBgLight from '@images/pages/register-multi-step-bg-light.png'
const registerMultiStepBg = useGenerateImageVariant(registerMultiStepBgLight, registerMultiStepBgDark)
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const currentStep = ref(0)
const isPasswordVisible = ref(false)
const isConfirmPasswordVisible = ref(false)
const registerMultiStepIllustration = useGenerateImageVariant(registerMultiStepIllustrationLight, registerMultiStepIllustrationDark)
const radioContent = [
{
title: 'Starter',
desc: 'A simple start for everyone.',
value: '0',
},
{
title: 'Standard',
desc: 'For small to medium businesses.',
value: '99',
},
{
title: 'Enterprise',
desc: 'Solution for big organizations.',
value: '499',
},
]
const items = [
{
title: 'Account',
subtitle: 'Account Details',
icon: 'tabler-file-analytics',
},
{
title: 'Personal',
subtitle: 'Enter Information',
icon: 'tabler-user',
},
{
title: 'Billing',
subtitle: 'Payment Details',
icon: 'tabler-credit-card',
},
]
const form = ref({
username: '',
email: '',
password: '',
confirmPassword: '',
link: '',
firstName: '',
lastName: '',
mobile: '',
pincode: '',
address: '',
landmark: '',
city: '',
state: null,
selectedPlan: '0',
cardNumber: '',
cardName: '',
expiryDate: '',
cvv: '',
})
const onSubmit = () => {
// eslint-disable-next-line no-alert
alert('Submitted..!!')
}
</script>
<template>
<RouterLink to="/">
<div class="auth-logo d-flex align-center gap-x-3">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="auth-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
<VRow
no-gutters
class="auth-wrapper"
>
<VCol
md="4"
class="d-none d-md-flex"
>
<!-- here your illustration -->
<div class="d-flex justify-center align-center w-100 position-relative">
<VImg
:src="registerMultiStepIllustration"
class="illustration-image flip-in-rtl"
/>
<img
class="bg-image position-absolute w-100 flip-in-rtl"
:src="registerMultiStepBg"
alt="register-multi-step-bg"
height="340"
>
</div>
</VCol>
<VCol
cols="12"
md="8"
class="auth-card-v2 d-flex align-center justify-center pa-10"
style="background-color: rgb(var(--v-theme-surface));"
>
<VCard
flat
class="mt-12 mt-sm-10"
>
<AppStepper
v-model:current-step="currentStep"
:items="items"
:direction="$vuetify.display.smAndUp ? 'horizontal' : 'vertical'"
icon-size="22"
class="stepper-icon-step-bg mb-12"
/>
<VWindow
v-model="currentStep"
class="disable-tab-transition"
style="max-inline-size: 681px;"
>
<VForm>
<VWindowItem>
<h4 class="text-h4">
Account Information
</h4>
<p class="text-body-1 mb-6">
Enter Your Account Details
</p>
<VRow>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.username"
label="Username"
placeholder="Johndoe"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.email"
label="Email"
placeholder="johndoe@email.com"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.password"
label="Password"
placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.confirmPassword"
label="Confirm Password"
autocomplete="confirm-password"
placeholder="············"
:type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isConfirmPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
/>
</VCol>
<VCol cols="12">
<AppTextField
v-model="form.link"
label="Profile Link"
placeholder="https://profile.com/johndoe"
type="url"
/>
</VCol>
</VRow>
</VWindowItem>
<VWindowItem>
<h4 class="text-h4">
Personal Information
</h4>
<p>
Enter Your Personal Information
</p>
<VRow>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.firstName"
label="First Name"
placeholder="John"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.lastName"
label="Last Name"
placeholder="Doe"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.mobile"
type="number"
label="Mobile"
placeholder="+1 123 456 7890"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.pincode"
type="number"
label="Pincode"
placeholder="123456"
/>
</VCol>
<VCol cols="12">
<AppTextField
v-model="form.address"
label="Address"
placeholder="1234 Main St, New York, NY 10001, USA"
/>
</VCol>
<VCol cols="12">
<AppTextField
v-model="form.landmark"
label="Landmark"
placeholder="Near Central Park"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.city"
label="City"
placeholder="New York"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppSelect
v-model="form.state"
label="State"
placeholder="Select State"
:items="['New York', 'California', 'Florida', 'Washington', 'Texas']"
/>
</VCol>
</VRow>
</VWindowItem>
<VWindowItem>
<h4 class="text-h4">
Select Plan
</h4>
<p class="text-body-1 mb-5">
Select plan as per your requirement
</p>
<CustomRadiosWithIcon
v-model:selected-radio="form.selectedPlan"
:radio-content="radioContent"
:grid-column="{ sm: '4', cols: '12' }"
>
<template #default="{ item }">
<div class="text-center">
<h5 class="text-h5 mb-2">
{{ item.title }}
</h5>
<p class="clamp-text mb-2">
{{ item.desc }}
</p>
<div class="d-flex align-center justify-center">
<span class="text-primary mb-2">$</span>
<span class="text-h4 text-primary">
{{ item.value }}
</span>
<span class="mt-2">/month</span>
</div>
</div>
</template>
</CustomRadiosWithIcon>
<h4 class="text-h4 mt-12">
Payment Information
</h4>
<p class="text-body-1 mb-6">
Enter your card information
</p>
<VRow>
<VCol cols="12">
<AppTextField
v-model="form.cardNumber"
type="number"
label="Card Number"
placeholder="1234 1234 1234 1234"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="form.cardName"
label="Name on Card"
placeholder="John Doe"
/>
</VCol>
<VCol
cols="6"
md="3"
>
<AppTextField
v-model="form.expiryDate"
label="Expiry"
placeholder="MM/YY"
/>
</VCol>
<VCol
cols="6"
md="3"
>
<AppTextField
v-model="form.cvv"
type="number"
label="CVV"
placeholder="123"
/>
</VCol>
</VRow>
</VWindowItem>
</VForm>
</VWindow>
<div class="d-flex flex-wrap justify-space-between gap-x-4 mt-6">
<VBtn
color="secondary"
:disabled="currentStep === 0"
variant="tonal"
@click="currentStep--"
>
<VIcon
icon="tabler-arrow-left"
start
class="flip-in-rtl"
/>
Previous
</VBtn>
<VBtn
v-if="items.length - 1 === currentStep"
color="success"
@click="onSubmit"
>
submit
</VBtn>
<VBtn
v-else
@click="currentStep++"
>
Next
<VIcon
icon="tabler-arrow-right"
end
class="flip-in-rtl"
/>
</VBtn>
</div>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth.scss";
.illustration-image {
block-size: 550px;
inline-size: 248px;
}
.bg-image {
inset-block-end: 0;
}
</style>

View File

@@ -0,0 +1,168 @@
<script setup>
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const form = ref({
username: '',
email: '',
password: '',
privacyPolicies: false,
})
const isPasswordVisible = ref(false)
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<div class="position-relative my-sm-16">
<!-- 👉 Top shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1TopShape })"
class="text-primary auth-v1-top-shape d-none d-sm-block"
/>
<!-- 👉 Bottom shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1BottomShape })"
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
/>
<!-- 👉 Auth card -->
<VCard
class="auth-card"
max-width="460"
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
>
<VCardItem class="justify-center">
<VCardTitle>
<RouterLink to="/">
<div class="app-logo">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="app-logo-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
</VCardTitle>
</VCardItem>
<VCardText>
<h4 class="text-h4 mb-1">
Adventure starts here 🚀
</h4>
<p class="mb-0">
Make your app management easy and fun!
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- Username -->
<VCol cols="12">
<AppTextField
v-model="form.username"
autofocus
label="Username"
placeholder="Johndoe"
/>
</VCol>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="form.email"
label="Email"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- password -->
<VCol cols="12">
<AppTextField
v-model="form.password"
label="Password"
placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
<div class="d-flex align-center my-6">
<VCheckbox
id="privacy-policy"
v-model="form.privacyPolicies"
inline
/>
<VLabel
for="privacy-policy"
style="opacity: 1;"
>
<span class="me-1 text-high-emphasis">I agree to</span>
<a
href="javascript:void(0)"
class="text-primary"
>privacy policy & terms</a>
</VLabel>
</div>
<VBtn
block
type="submit"
>
Sign up
</VBtn>
</VCol>
<!-- login instead -->
<VCol
cols="12"
class="text-center text-base"
>
<span>Already have an account?</span>
<RouterLink
class="text-primary ms-1"
:to="{ name: 'pages-authentication-login-v1' }"
>
Sign in instead
</RouterLink>
</VCol>
<VCol
cols="12"
class="d-flex align-center"
>
<VDivider />
<span class="mx-4">or</span>
<VDivider />
</VCol>
<!-- auth providers -->
<VCol
cols="12"
class="text-center"
>
<AuthProvider />
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

View File

@@ -0,0 +1,192 @@
<script setup>
import { VNodeRenderer } from '@/@layouts/components/VNodeRenderer'
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
import { themeConfig } from '@themeConfig'
import authV2RegisterIllustrationBorderedDark from '@images/pages/auth-v2-register-illustration-bordered-dark.png'
import authV2RegisterIllustrationBorderedLight from '@images/pages/auth-v2-register-illustration-bordered-light.png'
import authV2RegisterIllustrationDark from '@images/pages/auth-v2-register-illustration-dark.png'
import authV2RegisterIllustrationLight from '@images/pages/auth-v2-register-illustration-light.png'
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
import authV2MaskLight from '@images/pages/misc-mask-light.png'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const form = ref({
username: '',
email: '',
password: '',
privacyPolicies: false,
})
const imageVariant = useGenerateImageVariant(authV2RegisterIllustrationLight, authV2RegisterIllustrationDark, authV2RegisterIllustrationBorderedLight, authV2RegisterIllustrationBorderedDark, true)
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
const isPasswordVisible = ref(false)
</script>
<template>
<RouterLink to="/">
<div class="auth-logo d-flex align-center gap-x-3">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="auth-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
<VRow
no-gutters
class="auth-wrapper bg-surface"
>
<VCol
md="8"
class="d-none d-md-flex"
>
<div class="position-relative bg-background w-100 me-0">
<div
class="d-flex align-center justify-center w-100 h-100"
style="padding-inline: 100px;"
>
<VImg
max-width="500"
:src="imageVariant"
class="auth-illustration mt-16 mb-2"
/>
</div>
<img
class="auth-footer-mask flip-in-rtl"
:src="authThemeMask"
alt="auth-footer-mask"
height="280"
width="100"
>
</div>
</VCol>
<VCol
cols="12"
md="4"
class="auth-card-v2 d-flex align-center justify-center"
>
<VCard
flat
:max-width="500"
class="mt-12 pa-6"
>
<VCardText>
<h4 class="text-h4 mb-1">
Adventure starts here 🚀
</h4>
<p class="mb-0">
Make your app management easy and fun!
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- Username -->
<VCol cols="12">
<AppTextField
v-model="form.username"
autofocus
label="Username"
placeholder="Johndoe"
/>
</VCol>
<!-- email -->
<VCol cols="12">
<AppTextField
v-model="form.email"
label="Email"
type="email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- password -->
<VCol cols="12">
<AppTextField
v-model="form.password"
label="Password"
placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
<div class="d-flex align-center my-6">
<VCheckbox
id="privacy-policy"
v-model="form.privacyPolicies"
inline
/>
<VLabel
for="privacy-policy"
style="opacity: 1;"
>
<span class="me-1 text-high-emphasis">I agree to</span>
<a
href="javascript:void(0)"
class="text-primary"
>privacy policy & terms</a>
</VLabel>
</div>
<VBtn
block
type="submit"
>
Sign up
</VBtn>
</VCol>
<!-- create account -->
<VCol
cols="12"
class="text-center text-base"
>
<span class="d-inline-block">Already have an account?</span>
<RouterLink
class="text-primary ms-1 d-inline-block"
:to="{ name: 'pages-authentication-login-v2' }"
>
Sign in instead
</RouterLink>
</VCol>
<VCol
cols="12"
class="d-flex align-center"
>
<VDivider />
<span class="mx-4">or</span>
<VDivider />
</VCol>
<!-- auth providers -->
<VCol
cols="12"
class="text-center"
>
<AuthProvider />
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

View File

@@ -0,0 +1,130 @@
<script setup>
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
import { themeConfig } from '@themeConfig'
definePage({
meta: {
layout: 'blank',
public: true,
},
})
const form = ref({
newPassword: '',
confirmPassword: '',
})
const isPasswordVisible = ref(false)
const isConfirmPasswordVisible = ref(false)
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<div class="position-relative my-sm-16">
<!-- 👉 Top shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1TopShape })"
class="text-primary auth-v1-top-shape d-none d-sm-block"
/>
<!-- 👉 Bottom shape -->
<VNodeRenderer
:nodes="h('div', { innerHTML: authV1BottomShape })"
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
/>
<!-- 👉 Auth Card -->
<VCard
class="auth-card"
max-width="460"
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-2'"
>
<VCardItem class="justify-center">
<VCardTitle>
<RouterLink to="/">
<div class="app-logo">
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="app-logo-title">
{{ themeConfig.app.title }}
</h1>
</div>
</RouterLink>
</VCardTitle>
</VCardItem>
<VCardText>
<h4 class="text-h4 mb-1">
Reset Password 🔒
</h4>
<p class="mb-0">
Your new password must be different from previously used passwords
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- password -->
<VCol cols="12">
<AppTextField
v-model="form.newPassword"
autofocus
label="New Password"
placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
autocomplete="password"
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
</VCol>
<!-- Confirm Password -->
<VCol cols="12">
<AppTextField
v-model="form.confirmPassword"
label="Confirm Password"
autocomplete="confirm-password"
placeholder="············"
:type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isConfirmPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
/>
</VCol>
<!-- reset password -->
<VCol cols="12">
<VBtn
block
type="submit"
>
Set New Password
</VBtn>
</VCol>
<!-- back to login -->
<VCol cols="12">
<RouterLink
class="d-flex align-center justify-center"
:to="{ name: 'pages-authentication-login-v1' }"
>
<VIcon
icon="tabler-chevron-left"
size="20"
class="me-1 flip-in-rtl"
/>
<span>Back to login</span>
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/page-auth";
</style>

Some files were not shown because too many files have changed in this diff Show More