change color
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
Add: typeof import('./src/components/icons/Add.vue')['default']
|
Add: typeof import('./src/components/icons/Add.vue')['default']
|
||||||
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||||
|
ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||||
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||||
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||||
Button: typeof import('primevue/button')['default']
|
Button: typeof import('primevue/button')['default']
|
||||||
@@ -61,6 +62,7 @@ declare module 'vue' {
|
|||||||
declare global {
|
declare global {
|
||||||
const Add: typeof import('./src/components/icons/Add.vue')['default']
|
const Add: typeof import('./src/components/icons/Add.vue')['default']
|
||||||
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||||
|
const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||||
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||||
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||||
const Button: typeof import('primevue/button')['default']
|
const Button: typeof import('primevue/button')['default']
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
import VueHead from '@/components/VueHead';
|
|
||||||
|
|
||||||
interface Breadcrumb {
|
interface Breadcrumb {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -16,7 +15,7 @@ interface Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string | VNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
breadcrumbs?: Breadcrumb[];
|
breadcrumbs?: Breadcrumb[];
|
||||||
actions?: Action[];
|
actions?: Action[];
|
||||||
@@ -67,8 +66,8 @@ const getButtonClass = (variant?: string) => {
|
|||||||
<!-- Title & Actions -->
|
<!-- Title & Actions -->
|
||||||
<div class="flex items-start justify-between gap-4 flex-wrap">
|
<div class="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h1 class="text-3xl font-bold text-gray-900 mb-1">{{ title }}</h1>
|
<h1 v-if="typeof props.title == 'string'" class="text-3xl font-bold text-gray-900 mb-1">{{ title }}</h1>
|
||||||
<vue-head :input="{ title, meta: [{ name: 'description', content: description || '' }] }" />
|
<component v-else :is="title" />
|
||||||
<p v-if="description" class="text-gray-600">{{ description }}</p>
|
<p v-if="description" class="text-gray-600">{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
17
src/components/icons/ArrowDownTray.vue
Normal file
17
src/components/icons/ArrowDownTray.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||||
|
v-if="!filled">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||||
|
</svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" v-else>
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M12 2.25a.75.75 0 0 1 .75.75v11.69l3.22-3.22a.75.75 0 1 1 1.06 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0l-4.5-4.5a.75.75 0 1 1 1.06-1.06l3.22 3.22V3a.75.75 0 0 1 .75-.75Zm-9 13.5a.75.75 0 0 1 .75.75v2.25a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5V16.5a.75.75 0 0 1 1.5 0v2.25a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V16.5a.75.75 0 0 1 .75-.75Z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
filled?: boolean
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="tsx">
|
||||||
import { client, type ModelVideo } from '@/api/client';
|
import { client, type ModelVideo } from '@/api/client';
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
import NameGradient from './components/NameGradient.vue';
|
||||||
import QuickActions from './components/QuickActions.vue';
|
import QuickActions from './components/QuickActions.vue';
|
||||||
import RecentVideos from './components/RecentVideos.vue';
|
import RecentVideos from './components/RecentVideos.vue';
|
||||||
import StatsOverview from './components/StatsOverview.vue';
|
import StatsOverview from './components/StatsOverview.vue';
|
||||||
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const recentVideos = ref<ModelVideo[]>([]);
|
const recentVideos = ref<ModelVideo[]>([]);
|
||||||
|
|
||||||
@@ -53,14 +52,14 @@ const fetchDashboardData = async () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchDashboardData();
|
fetchDashboardData();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard-overview">
|
<div class="dashboard-overview">
|
||||||
<PageHeader :title="`Welcome back, ${auth.user?.username}! 👋`" description="Here's what's happening with your videos."
|
<PageHeader :title="NameGradient" description="Welcome back, Here's what's happening with your videos." :breadcrumbs="[
|
||||||
:breadcrumbs="[
|
{ label: 'Dashboard' }
|
||||||
{ label: 'Dashboard' }
|
]" />
|
||||||
]" />
|
|
||||||
|
|
||||||
<!-- Stats Grid -->
|
<!-- Stats Grid -->
|
||||||
<StatsOverview :loading="loading" :stats="stats" />
|
<StatsOverview :loading="loading" :stats="stats" />
|
||||||
|
|||||||
10
src/routes/overview/components/NameGradient.vue
Normal file
10
src/routes/overview/components/NameGradient.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-3xl font-bold text-gray-900 mb-1">
|
||||||
|
<span class=":uno: bg-[linear-gradient(130deg,#14a74b_0%,#22c55e_35%,#10b981_65%,#06b6d4_100%)] bg-clip-text text-transparent">Hello, {{ auth.user?.username }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
</script>
|
||||||
@@ -29,44 +29,64 @@ const getStatusSeverity = (status: string) => {
|
|||||||
return 'info';
|
return 'info';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import ArrowDownTray from '@/components/icons/ArrowDownTray.vue';
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const downloadInvoice = (item: PaymentHistoryItem) => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'info',
|
||||||
|
summary: 'Downloading',
|
||||||
|
detail: `Downloading invoice #${item.invoiceId}...`,
|
||||||
|
life: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate download delay
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Downloaded',
|
||||||
|
detail: `Invoice #${item.invoiceId} downloaded successfully`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-2xl font-bold mb-6 text-gray-900">Billing History</h2>
|
<h2 class="text-2xl font-bold mb-6 text-gray-900">Billing History</h2>
|
||||||
<div class="bg-white border border-gray-200 rounded-xl overflow-hidden shadow-sm">
|
<div class="bg-white border border-gray-200 rounded-xl overflow-hidden">
|
||||||
<DataTable :value="history" tableStyle="min-width: 50rem"
|
<DataTable :value="history" responsiveLayout="scroll" class="w-full">
|
||||||
:pt="{
|
<template #empty>
|
||||||
thead: { class: 'bg-gray-50 border-b border-gray-200' },
|
<div class="text-center py-8 text-gray-500">No payment history found.</div>
|
||||||
headerRow: { class: 'text-gray-500 text-xs font-semibold uppercase tracking-wider' },
|
|
||||||
bodyRow: { class: 'text-gray-700 hover:bg-gray-50/50' }
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #empty>
|
|
||||||
<div class="text-center py-8 text-gray-500">No payment history found.</div>
|
|
||||||
</template>
|
|
||||||
<Column field="date" header="Date" class="font-medium"></Column>
|
|
||||||
<Column field="amount" header="Amount">
|
|
||||||
<template #body="slotProps">
|
|
||||||
${{ slotProps.data.amount }}
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
<Column field="date" header="Date" class="font-medium"></Column>
|
||||||
<Column field="plan" header="Plan"></Column>
|
<Column field="amount" header="Amount">
|
||||||
<Column field="status" header="Status">
|
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<Tag
|
${{ slotProps.data.amount }}
|
||||||
:value="slotProps.data.status"
|
|
||||||
:severity="getStatusSeverity(slotProps.data.status)"
|
|
||||||
class="capitalize px-2 py-0.5 text-xs"
|
|
||||||
:rounded="true"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
<Column header="" style="width: 3rem">
|
|
||||||
<template #body>
|
|
||||||
<Button icon="i-heroicons-arrow-down-tray" text rounded severity="secondary" size="small" />
|
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
<Column field="plan" header="Plan"></Column>
|
||||||
|
<Column field="status" header="Status">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<Tag :value="slotProps.data.status" :severity="getStatusSeverity(slotProps.data.status)"
|
||||||
|
class="capitalize px-2 py-0.5 text-xs" :rounded="true" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<!-- <Column header="" style="width: 3rem">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<Button text rounded severity="secondary" size="small" @click="downloadInvoice(slotProps.data)"
|
||||||
|
v-tooltip="'Download Invoice'">
|
||||||
|
<template #icon>
|
||||||
|
<ArrowDownTray class="w-5 h-5" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Column> -->
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { formatBytes } from '@/lib/utils';
|
||||||
import ProgressBar from 'primevue/progressbar';
|
import ProgressBar from 'primevue/progressbar';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { formatBytes } from '@/lib/utils';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
storageUsed: number;
|
storageUsed: number;
|
||||||
@@ -15,7 +15,7 @@ const uploadsPercentage = computed(() => Math.min(Math.round((props.uploadsUsed
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-8 shadow-sm flex flex-col justify-center">
|
<div class="bg-white border border-gray-200 rounded-2xl p-8 flex flex-col justify-center">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-6">Usage Statistics</h3>
|
<h3 class="text-lg font-bold text-gray-900 mb-6">Usage Statistics</h3>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const formatBytes = (bytes: number) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-6 shadow-sm">
|
<div class="bg-white border border-gray-200 rounded-2xl p-6">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-4">Account Status</h3>
|
<h3 class="text-lg font-bold text-gray-900 mb-4">Account Status</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Tag from 'primevue/tag';
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-6 shadow-sm">
|
<div class="bg-white border border-gray-200 rounded-2xl p-6">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-4">Linked Accounts</h3>
|
<h3 class="text-lg font-bold text-gray-900 mb-4">Linked Accounts</h3>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex items-center justify-between p-3 rounded-lg border border-gray-100 hover:border-gray-200 transition-colors">
|
<div class="flex items-center justify-between p-3 rounded-lg border border-gray-100 hover:border-gray-200 transition-colors">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelUser } from '@/api/client';
|
import type { ModelUser } from '@/api/client';
|
||||||
import InputText from 'primevue/inputtext';
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: ModelUser | null;
|
user: ModelUser | null;
|
||||||
@@ -14,7 +14,7 @@ const emit = defineEmits<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-8 shadow-sm">
|
<div class="bg-white border border-gray-200 rounded-2xl p-8">
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h3 class="text-xl font-bold text-gray-900">Personal Information</h3>
|
<h3 class="text-xl font-bold text-gray-900">Personal Information</h3>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
|||||||
@@ -45,18 +45,18 @@ export default defineConfig({
|
|||||||
dark: "#14532d",
|
dark: "#14532d",
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "#6366f1",
|
DEFAULT: "#14a74b",
|
||||||
50: "#eef2ff",
|
50: "#ecfdf3",
|
||||||
100: "#e0e7ff",
|
100: "#d1fae5",
|
||||||
200: "#c7d2fe",
|
200: "#a7f3d0",
|
||||||
300: "#a5b4fc",
|
300: "#6ee7b7",
|
||||||
400: "#818cf8",
|
400: "#34d399",
|
||||||
500: "#6366f1",
|
500: "#14a74b",
|
||||||
600: "#4f46e5",
|
600: "#0f8a3d",
|
||||||
700: "#4338ca",
|
700: "#0c6f33",
|
||||||
800: "#3730a3",
|
800: "#095a2a",
|
||||||
900: "#312e81",
|
900: "#064622",
|
||||||
950: "#1e1b4b",
|
950: "#032814",
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
DEFAULT: "#22c55e",
|
DEFAULT: "#22c55e",
|
||||||
|
|||||||
Reference in New Issue
Block a user