第一次上传

This commit is contained in:
xxk
2026-06-11 10:31:24 +08:00
commit cfef094568
1523 changed files with 210650 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
<template>
<ThemeProvider>
<SidebarProvider>
<RouterView />
</SidebarProvider>
</ThemeProvider>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import ThemeProvider from './components/layout/ThemeProvider.vue'
import SidebarProvider from './components/layout/SidebarProvider.vue'
import { useSiteStore } from '@/stores/site'
const site = useSiteStore()
onMounted(() => {
site.loadSiteConfig()
})
</script>
+93
View File
@@ -0,0 +1,93 @@
import { useAuthStore } from '@/stores/auth'
export interface ApiResponse<T = unknown> {
code?: string | number
data?: T
msg?: string
message?: string
}
type QueryValue = string | number | boolean | null | undefined
interface RequestOptions extends RequestInit {
params?: Record<string, QueryValue>
}
function withQuery(url: string, params?: Record<string, QueryValue>) {
if (!params) return url
const search = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') search.set(key, String(value))
})
const query = search.toString()
if (!query) return url
return `${url}${url.includes('?') ? '&' : '?'}${query}`
}
async function request<T>(url: string, options: RequestOptions = {}): Promise<T> {
const auth = useAuthStore()
const headers = new Headers(options.headers)
const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData
if (!headers.has('Content-Type') && !isFormData) {
headers.set('Content-Type', 'application/json;charset=utf-8')
}
if (auth.accessToken) headers.set('Authorization', `Bearer ${auth.accessToken}`)
const response = await fetch(withQuery(url, options.params), {
...options,
headers,
})
const contentType = response.headers.get('content-type') || ''
const body = contentType.includes('application/json')
? ((await response.json()) as ApiResponse<T> | T)
: ((await response.text()) as T)
if (!response.ok) {
const apiBody = body as ApiResponse<T>
throw new Error(apiBody?.msg || apiBody?.message || response.statusText || '网络请求失败')
}
if (body && typeof body === 'object' && Object.prototype.hasOwnProperty.call(body, 'code')) {
const apiBody = body as ApiResponse<T>
const code = String(apiBody.code)
if (code === '00000' || code === '0' || code === '200') {
return apiBody.data as T
}
throw new Error(apiBody.msg || apiBody.message || '请求失败')
}
return body as T
}
export function get<T>(url: string, options?: RequestOptions) {
return request<T>(url, { ...options, method: 'GET' })
}
export function post<T>(url: string, data?: unknown, options?: RequestOptions) {
return request<T>(url, {
...options,
method: 'POST',
body: data === undefined ? undefined : JSON.stringify(data),
})
}
export function postForm<T>(url: string, data: FormData, options?: RequestOptions) {
return request<T>(url, {
...options,
method: 'POST',
body: data,
})
}
export function put<T>(url: string, data?: unknown, options?: RequestOptions) {
return request<T>(url, {
...options,
method: 'PUT',
body: data === undefined ? undefined : JSON.stringify(data),
})
}
export function del<T>(url: string, options?: RequestOptions) {
return request<T>(url, { ...options, method: 'DELETE' })
}
+685
View File
@@ -0,0 +1,685 @@
import { del, get, post, postForm, put } from '@/api/http'
export interface LoginPayload {
loginType?: string
account?: string
username?: string
mobile?: string
email?: string
password?: string
code?: string
}
export interface RegisterPayload {
registerType?: string
username?: string
password?: string
mobile?: string
email?: string
code?: string
nickname?: string
inviteCode?: string
}
export interface TokenPair {
accessToken: string
refreshToken?: string
expiresIn?: number
}
export interface CurrentMember {
userId?: number
id?: number
username?: string
nickname?: string
mobile?: string
email?: string
avatar?: string
inviteCode?: string
agentLevel?: number
commissionRate?: number
inviteCount?: number
verifyStatus?: number
}
export interface FileInfo {
name?: string
url?: string
}
export interface MemberVerifyFieldPolicy {
code?: string
label?: string
type?: 'TEXT' | 'IMAGE' | 'IMAGE_LIST' | string
enabled?: boolean
required?: boolean
readonly?: boolean
source?: string
maxCount?: number
sort?: number
placeholder?: string
}
export interface MemberVerifyPolicy {
enabled?: boolean
forceRequired?: boolean
forceScenes?: string[]
fields?: MemberVerifyFieldPolicy[]
}
export interface MemberVerifyCurrent {
memberUserId?: number
status?: number
verifyEnabled?: boolean
forceRequired?: boolean
forceScenes?: string[]
currentRecordId?: number
verifyNo?: string
mobile?: string
realName?: string
idCardNo?: string
idCardFrontUrl?: string
idCardBackUrl?: string
supportDocs?: string[]
submitRemark?: string
auditRemark?: string
submitTime?: string
auditTime?: string
verifiedTime?: string
policy?: MemberVerifyPolicy
}
export interface MemberVerifySubmitPayload {
realName?: string
idCardNo?: string
idCardFrontUrl?: string
idCardBackUrl?: string
supportDocs?: string[]
submitRemark?: string
}
export interface MemberAuthPublicConfig {
registerMethods?: string[]
loginMethods?: string[]
mobileVerificationEnabled?: boolean
emailVerificationEnabled?: boolean
}
export interface MemberSiteConfig {
siteName?: string
siteFaviconUrl?: string
siteLogoUrl?: string
customerServiceType?: 'QR_CODE' | 'URL'
customerServiceQrUrl?: string
customerServiceUrl?: string
homeFooterContent?: string
}
export interface CaptchaInfo {
captchaId?: string
captchaBase64?: string
}
export interface DurationOption {
durationDays?: number
durationUnit?: string
durationValue?: number
displayName?: string
multiplier?: number
}
export interface StaticRegion {
regionId?: number
regionCode?: string
regionName?: string
regionNameZh?: string
countryCode?: string
countryName?: string
basePrice?: number
iconUrl?: string
}
export interface QiYunNodeOption {
qiyunAreaId?: string
qiyunAreaName?: string
qiyunNodeId?: string
qiyunNodeName?: string
qiyunIsp?: string
basePrice?: number
priceType?: string
availableQuantity?: number
}
export interface StaticPackage {
productId?: number
productCode?: string
productName?: string
currency?: string
qiyunProductType?: string
upstreamProviderId?: number
qiyunProjectRequired?: boolean
defaultNodePrice?: number
durations?: DurationOption[]
regions?: StaticRegion[]
}
export interface PackageCatalog {
staticPackages?: StaticPackage[]
dynamicPackages?: unknown[]
durations?: unknown[]
}
export interface StaticPurchasePayload {
productId?: number
durationDays?: number
durationUnit?: string
durationValue?: number
purposeWeb?: string
qiyunPid?: string
qiyunProjectName?: string
qiyunAreaId?: string
qiyunAreaName?: string
qiyunNodeId?: string
qiyunNodeName?: string
qiyunIsp?: string
proxyAccountMode?: 'RANDOM' | 'CUSTOM'
proxyUsername?: string
proxyPassword?: string
proxyPortMode?: 'RANDOM' | 'CUSTOM'
proxyPort?: string
quantity?: number
items: Array<{
regionId?: number
qiyunAreaId?: string
qiyunAreaName?: string
qiyunNodeId?: string
qiyunNodeName?: string
qiyunIsp?: string
quantity?: number
}>
}
export interface OrderSubmit {
orderNo?: string
orderStatus?: string
payStatus?: string
openStatus?: string
saleAmount?: number
payAmount?: number
currency?: string
message?: string
}
export interface BatchOrderSubmit {
orderCount?: number
totalSaleAmount?: number
orders?: OrderSubmit[]
}
export interface PageResult<T> {
list: T[]
total: number
}
export interface WalletOverview {
balance?: number
frozenBalance?: number
totalRechargeAmount?: number
totalConsumeAmount?: number
totalRefundAmount?: number
}
export interface WalletFlow {
id?: number
flowNo?: string
userId?: number
memberUserId?: number
bizType?: string
changeType?: 'IN' | 'OUT' | string
changeAmount?: number
beforeBalance?: number
afterBalance?: number
relatedOrderNo?: string
remark?: string
operateBy?: number
createTime?: string
}
export interface WalletRechargePromotionRule {
rechargeAmount?: number
giftAmount?: number
}
export interface WalletRechargePromotionConfig {
enabled?: boolean
title?: string
minRechargeAmount?: number
quickAmounts?: number[]
rules?: WalletRechargePromotionRule[]
}
export interface WalletRechargePayload {
amount: number
paymentType: string
clientType?: string
returnUrl?: string
remark?: string
}
export interface WalletRechargeSubmit {
id?: number
rechargeNo?: string
payOrderNo?: string
payStatus?: string
paymentType?: string
paymentCompleted?: boolean
payAction?: string
payData?: string
payMessage?: string
gatewayOrderNo?: string
amount?: number
giftAmount?: number
creditedAmount?: number
promotionApplied?: boolean
promotionRuleDescription?: string
currency?: string
payableAmount?: number
payableCurrency?: string
exchangeRate?: number
exchangeRateFactor?: number
exchangeRateApplied?: number
exchangeRateSource?: string
}
export interface WalletRechargeOrder {
id?: number
rechargeNo?: string
payOrderNo?: string
memberUserId?: number
paymentType?: string
payStatus?: string
channelOrderNo?: string
clientType?: string
returnUrl?: string
amount?: number
giftAmount?: number
creditedAmount?: number
promotionRuleSnapshot?: string
currency?: string
paidTime?: string
remark?: string
createTime?: string
updateTime?: string
}
export interface DistributionAccount {
id?: number
userId?: number
memberUserId?: number
availableBalance?: number
frozenBalance?: number
totalEarnedAmount?: number
totalRevokedAmount?: number
totalTransferAmount?: number
totalWithdrawAmount?: number
withdrawThreshold?: number
withdrawFeeRate?: number
status?: number
createTime?: string
updateTime?: string
}
export interface DistributionCommission {
id?: number
orderNo?: string
levelNo?: number
rate?: number
baseAmount?: number
commissionAmount?: number
commissionStatus?: string
confirmTime?: string
invalidTime?: string
createTime?: string
remark?: string
}
export interface NoticeItem {
id?: number
title?: string
publishStatus?: number
type?: number
publisherName?: string
level?: string
publishTime?: string
isRead?: number
targetType?: number
createTime?: string
revokeTime?: string
}
export interface NoticeDetail extends NoticeItem {
content?: string
}
export interface OpenApiApplyCurrent {
id?: number
applyNo?: string
memberUserId?: number
status?: number
contactName?: string
contactMobile?: string
contactEmail?: string
companyName?: string
purpose?: string
scenarioDescription?: string
allowIpList?: string
submitRemark?: string
auditRemark?: string
submitTime?: string
auditTime?: string
openApiAppId?: number
}
export interface OpenApiApplyPayload {
contactName: string
contactMobile?: string
contactEmail?: string
companyName?: string
purpose: string
scenarioDescription: string
allowIpList?: string
submitRemark?: string
}
export interface OpenApiCredential {
id?: number
appId?: string
appName?: string
appSecret?: string
status?: number
allowIpList?: string
callbackUrl?: string
callbackSecret?: string
lastAuthTime?: string
lastAuthIp?: string
}
export interface OpenApiCallbackConfig {
openApiAppId?: number
callbackUrl?: string
callbackSecret?: string
}
export interface OpenApiAccount {
id?: number
openApiAppId?: number
memberUserId?: number
balance?: number
frozenBalance?: number
totalRechargeAmount?: number
totalConsumeAmount?: number
totalRefundAmount?: number
status?: number
createTime?: string
updateTime?: string
}
export interface OpenApiRechargePayload {
amount: number
paymentType: string
clientType?: string
returnUrl?: string
remark?: string
}
export interface OpenApiRechargeSubmit {
id?: number
rechargeNo?: string
payOrderNo?: string
payStatus?: string
paymentType?: string
paymentCompleted?: boolean
amount?: number
giftAmount?: number
creditedAmount?: number
currency?: string
payAction?: string
payData?: string
payMessage?: string
gatewayOrderNo?: string
payableAmount?: number
payableCurrency?: string
exchangeRate?: number
}
export interface OpenApiAccountFlow {
id?: number
flowNo?: string
bizType?: string
changeType?: string
changeAmount?: number
beforeBalance?: number
afterBalance?: number
relatedOrderNo?: string
remark?: string
createTime?: string
}
export interface OpenApiRechargeOrder {
id?: number
rechargeNo?: string
payOrderNo?: string
paymentType?: string
payStatus?: string
amount?: number
giftAmount?: number
creditedAmount?: number
currency?: string
paidTime?: string
remark?: string
createTime?: string
}
export interface OpenApiCallbackLog {
id?: number
orderNo?: string
callbackUrl?: string
eventType?: string
responseStatus?: number
deliveryStatus?: string
errorMessage?: string
attemptNo?: number
triggerTime?: string
finishTime?: string
createTime?: string
}
export const MemberAPI = {
authConfig() {
return get<MemberAuthPublicConfig>('/api/v1/member/auth/config')
},
siteConfig() {
return get<MemberSiteConfig>('/api/v1/member/site-config')
},
captcha(scene: 'LOGIN' | 'REGISTER') {
return get<CaptchaInfo>('/api/v1/member/auth/captcha', { params: { scene } })
},
sendMobileCode(params: {
mobile: string
scene: 'LOGIN' | 'REGISTER'
captchaId: string
captchaCode: string
}) {
return post<void>('/api/v1/member/auth/mobile/code', undefined, { params })
},
sendEmailCode(params: {
email: string
scene: 'LOGIN' | 'REGISTER'
captchaId: string
captchaCode: string
}) {
return post<void>('/api/v1/member/auth/email/code', undefined, { params })
},
login(data: LoginPayload) {
return post<TokenPair>('/api/v1/member/auth/login', data)
},
register(data: RegisterPayload) {
return post<TokenPair>('/api/v1/member/auth/register', data)
},
me() {
return get<CurrentMember>('/api/v1/member/auth/me')
},
logout() {
return del<void>('/api/v1/member/auth/logout')
},
publicCatalog() {
return get<PackageCatalog>('/api/v1/public/package-center/catalog')
},
memberCatalog() {
return get<PackageCatalog>('/api/v1/member/package-center/catalog')
},
qiyunProjects(productId: number) {
return get<Array<{ id?: string; value?: string; extra?: Record<string, unknown> }>>(
'/api/v1/member/package-center/qiyun/projects',
{ params: { productId } },
)
},
qiyunAreas(productId: number, pid?: string) {
return get<Array<{ id?: string; value?: string; extra?: Record<string, unknown> }>>(
'/api/v1/member/package-center/qiyun/areas',
{ params: { productId, pid } },
)
},
staticInventories(
productId: number,
params?: { purposeWeb?: string; qiyunPid?: string; qiyunAreaId?: string },
) {
return get<QiYunNodeOption[]>('/api/v1/member/package-center/static-inventory', {
params: { productId, ...params },
})
},
purchaseStaticPackage(data: StaticPurchasePayload) {
return post<BatchOrderSubmit>('/api/v1/member/package-center/static-orders', data)
},
payOrder(orderNo: string, data?: { paymentType?: string }) {
return post<OrderSubmit>(`/api/v1/member/orders/${orderNo}/pay`, data)
},
wallet() {
return get<WalletOverview>('/api/v1/member/wallet')
},
walletFlows(params?: Record<string, unknown>) {
return get<PageResult<WalletFlow>>('/api/v1/member/wallet/flows', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
walletRechargePromotionConfig() {
return get<WalletRechargePromotionConfig>('/api/v1/member/wallet/recharges/promotion-config')
},
previewWalletRecharge(data: WalletRechargePayload) {
return post<WalletRechargeSubmit>('/api/v1/member/wallet/recharges/preview', data)
},
createWalletRecharge(data: WalletRechargePayload) {
return post<WalletRechargeSubmit>('/api/v1/member/wallet/recharges', data)
},
walletRechargeOrders(params?: Record<string, unknown>) {
return get<PageResult<WalletRechargeOrder>>('/api/v1/member/wallet/recharges', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
walletRechargeDetail(rechargeNo: string) {
return get<WalletRechargeOrder>(`/api/v1/member/wallet/recharges/${rechargeNo}`)
},
retryWalletRechargePaymentStatus(rechargeNo: string) {
return post<WalletRechargeSubmit>(`/api/v1/member/wallet/recharges/${rechargeNo}/retry-payment-status`)
},
distributionAccount() {
return get<DistributionAccount>('/api/v1/member/distribution/account')
},
distributionCommissions(params?: Record<string, unknown>) {
return get<PageResult<DistributionCommission>>('/api/v1/member/distribution/commissions', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
orders(params?: Record<string, unknown>) {
return get<PageResult<unknown>>('/api/v1/member/orders', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
staticAssets(params?: Record<string, unknown>) {
return get<PageResult<unknown>>('/api/v1/member/static-assets', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
dynamicChannels(params?: Record<string, unknown>) {
return get<PageResult<unknown>>('/api/v1/member/dynamic-channels', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
verifyCurrent() {
return get<MemberVerifyCurrent>('/api/v1/member/verify/current')
},
verifyPolicy() {
return get<MemberVerifyPolicy>('/api/v1/member/verify/policy')
},
submitVerify(data: MemberVerifySubmitPayload) {
return post<void>('/api/v1/member/verify/submit', data)
},
uploadFile(file: File) {
const formData = new FormData()
formData.append('file', file)
return postForm<FileInfo>('/api/v1/files', formData)
},
openApiCurrent() {
return get<OpenApiApplyCurrent | null>('/api/v1/member/open-api/current')
},
openApiCredential() {
return get<OpenApiCredential | null>('/api/v1/member/open-api/credential')
},
submitOpenApiApply(data: OpenApiApplyPayload) {
return post<void>('/api/v1/member/open-api/submit', data)
},
openApiCallbackConfig() {
return get<OpenApiCallbackConfig>('/api/v1/member/open-api/callback-config')
},
updateOpenApiCallbackConfig(data: { callbackUrl?: string; callbackSecret?: string }) {
return put<OpenApiCallbackConfig>('/api/v1/member/open-api/callback-config', data)
},
openApiCallbackLogs(params?: Record<string, unknown>) {
return get<PageResult<OpenApiCallbackLog>>('/api/v1/member/open-api/callback-logs', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
retryOpenApiCallback(id: number) {
return post<void>(`/api/v1/member/open-api/callback-logs/${id}/retry`)
},
openApiAccount() {
return get<OpenApiAccount>('/api/v1/member/open-api/account')
},
openApiAccountFlows(params?: Record<string, unknown>) {
return get<PageResult<OpenApiAccountFlow>>('/api/v1/member/open-api/account/flows', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
previewOpenApiRecharge(data: OpenApiRechargePayload) {
return post<OpenApiRechargeSubmit>('/api/v1/member/open-api/account/recharges/preview', data)
},
createOpenApiRecharge(data: OpenApiRechargePayload) {
return post<OpenApiRechargeSubmit>('/api/v1/member/open-api/account/recharges', data)
},
openApiRechargeOrders(params?: Record<string, unknown>) {
return get<PageResult<OpenApiRechargeOrder>>('/api/v1/member/open-api/account/recharges', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
updateProfile(data: unknown) {
return put<CurrentMember>('/api/v1/member/profile', data)
},
notices(params?: Record<string, unknown>) {
return get<PageResult<NoticeItem>>('/api/v1/member/notices', {
params: params as Record<string, string | number | boolean | null | undefined>,
})
},
noticeDetail(id: number) {
return get<NoticeDetail>(`/api/v1/member/notices/${id}/detail`)
},
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,100 @@
<template>
<div class="max-w-full overflow-x-auto custom-scrollbar">
<div id="chartOne" class="-ml-5 min-w-[650px] xl:min-w-full pl-2">
<VueApexCharts type="bar" height="180" :options="chartOptions" :series="series" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import VueApexCharts from 'vue3-apexcharts'
const series = ref([
{
name: 'Sales',
data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
},
])
const chartOptions = ref({
colors: ['#465fff'],
chart: {
fontFamily: 'Outfit, sans-serif',
type: 'bar',
toolbar: {
show: false,
},
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '39%',
borderRadius: 5,
borderRadiusApplication: 'end',
},
},
dataLabels: {
enabled: false,
},
stroke: {
show: true,
width: 4,
colors: ['transparent'],
},
xaxis: {
categories: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
legend: {
show: true,
position: 'top',
horizontalAlign: 'left',
fontFamily: 'Outfit',
markers: {
radius: 99,
},
},
yaxis: {
title: false,
},
grid: {
yaxis: {
lines: {
show: true,
},
},
},
fill: {
opacity: 1,
},
tooltip: {
x: {
show: false,
},
y: {
formatter: function (val) {
return val.toString()
},
},
},
})
</script>
@@ -0,0 +1,117 @@
<template>
<div class="max-w-full overflow-x-auto custom-scrollbar">
<div id="chartThree" class="-ml-4 min-w-[1000px] xl:min-w-full pl-2">
<VueApexCharts type="area" height="310" :options="chartOptions" :series="series" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VueApexCharts from 'vue3-apexcharts'
const series = ref([
{
name: 'Sales',
data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
},
{
name: 'Revenue',
data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
},
])
const chartOptions = ref({
legend: {
show: false,
position: 'top',
horizontalAlign: 'left',
},
colors: ['#465FFF', '#9CB9FF'],
chart: {
fontFamily: 'Outfit, sans-serif',
type: 'area',
toolbar: {
show: false,
},
},
fill: {
gradient: {
enabled: true,
opacityFrom: 0.55,
opacityTo: 0,
},
},
stroke: {
curve: 'straight',
width: [2, 2],
},
markers: {
size: 0,
},
labels: {
show: false,
position: 'top',
},
grid: {
xaxis: {
lines: {
show: false,
},
},
yaxis: {
lines: {
show: true,
},
},
},
dataLabels: {
enabled: false,
},
tooltip: {
x: {
format: 'dd MMM yyyy',
},
},
xaxis: {
type: 'category',
categories: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
tooltip: {
enabled: false,
},
},
yaxis: {
title: {
style: {
fontSize: '0px',
},
},
},
})
</script>
<style scoped>
.area-chart {
width: 100%;
}
</style>
@@ -0,0 +1,10 @@
<template>
<div>
<div class="absolute right-0 top-0 -z-1 w-full max-w-[250px] xl:max-w-[450px]">
<img src="/images/shape/grid-01.svg" alt="grid" />
</div>
<div class="absolute bottom-0 left-0 -z-1 w-full max-w-[250px] rotate-180 xl:max-w-[450px]">
<img src="/images/shape/grid-01.svg" alt="grid" />
</div>
</div>
</template>
@@ -0,0 +1,37 @@
<template>
<div
:class="[
'rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]',
className,
]"
>
<!-- Card Header -->
<div class="px-6 py-5">
<h3 class="text-base font-medium text-gray-800 dark:text-white/90">
{{ title }}
</h3>
<p v-if="desc" class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ desc }}
</p>
</div>
<!-- Card Body -->
<div class="p-4 border-t border-gray-100 dark:border-gray-800 sm:p-6">
<div class="space-y-5">
<slot></slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
interface Props {
title: string
className?: string
desc?: string
}
defineProps<Props>()
</script>
@@ -0,0 +1,143 @@
<template>
<div class="mb-10">
<div
class="flex flex-wrap justify-center gap-1 mb-2 font-bold text-title-md text-brand-500 dark:text-brand-400 xl:text-title-lg"
>
<!-- timer days -->
<div v-for="(day, index) in daysArray" :key="index">
<div v-show="day.visible" class="timer-box">
<span>{{ day.value }}</span>
</div>
</div>
:
<!-- timer hours -->
<div v-for="(hour, index) in hoursArray" :key="index">
<div v-show="hour.visible" class="timer-box">
<span>{{ hour.value }}</span>
</div>
</div>
:
<!-- timer minutes -->
<div v-for="(minute, index) in minutesArray" :key="index">
<div v-show="minute.visible" class="timer-box">
<span>{{ minute.value }}</span>
</div>
</div>
:
<!-- timer seconds -->
<div v-for="(second, index) in secondsArray" :key="index">
<div v-show="second.visible" class="timer-box">
<span>{{ second.value }}</span>
</div>
</div>
</div>
<div class="text-base text-center text-gray-500 dark:text-gray-400">
<div class="flex justify-center gap-0.5">
<div v-for="(day, index) in daysArray" :key="index">
<span v-show="day.visible" class="inline-block timer-box">
<span class="inline-block">{{ day.value }}</span>
</span>
</div>
<div>days left</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const daysArray = ref([])
const hoursArray = ref([])
const minutesArray = ref([])
const secondsArray = ref([])
const endTime = new Date('December 20, 2025 23:59:59 GMT+0530').getTime()
const now = ref(new Date().getTime())
const timeLeft = ref(0)
let counter
const countdown = () => {
counter = setInterval(() => {
now.value = new Date().getTime()
timeLeft.value = (endTime - now.value) / 1000
updateTimeArrays()
if (timeLeft.value <= 0) {
clearInterval(counter)
resetTimeArrays()
}
}, 1000)
}
const format = (value) => {
if (value < 10) {
return '0' + Math.floor(value)
} else return Math.floor(value)
}
const updateTimeArrays = () => {
daysArray.value = getTimeArray(timeLeft.value / (60 * 60 * 24), 'days')
hoursArray.value = getTimeArray((timeLeft.value / (60 * 60)) % 24, 'hours')
minutesArray.value = getTimeArray((timeLeft.value / 60) % 60, 'minutes')
secondsArray.value = getTimeArray(timeLeft.value % 60, 'seconds')
}
const getMaxValueForUnit = (unit) => {
switch (unit) {
case 'days':
return 365
case 'hours':
return 24
case 'minutes':
return 60
case 'seconds':
return 60
default:
return 1
}
}
const getTimeArray = (value, unit) => {
let stringValue = format(value).toString()
let percentage = (value / getMaxValueForUnit(unit)) * 100
return stringValue.split('').map((digit) => ({
value: digit,
visible: true,
remainingPercentage: percentage,
}))
}
const calcOverlayHeight = () => {
if (daysArray.value.length > 0) {
let remainingDaysPercentage = daysArray.value[0].remainingPercentage
return `${remainingDaysPercentage}%`
}
return '0%'
}
const resetTimeArrays = () => {
daysArray.value = [{ value: '0', visible: true }]
hoursArray.value = [{ value: '0', visible: true }]
minutesArray.value = [{ value: '0', visible: true }]
secondsArray.value = [{ value: '0', visible: true }]
}
onMounted(() => {
countdown()
})
onUnmounted(() => {
if (counter) {
clearInterval(counter)
}
})
</script>
@@ -0,0 +1,102 @@
<template>
<div class="relative" v-click-outside="closeDropdown" ref="dropdown">
<!-- Dropdown Trigger Button -->
<button @click="toggleDropdown" :class="buttonClass">
<slot name="icon">
<!-- Default icon -->
<svg
class="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.99902 10.245C6.96552 10.245 7.74902 11.0285 7.74902 11.995V12.005C7.74902 12.9715 6.96552 13.755 5.99902 13.755C5.03253 13.755 4.24902 12.9715 4.24902 12.005V11.995C4.24902 11.0285 5.03253 10.245 5.99902 10.245ZM17.999 10.245C18.9655 10.245 19.749 11.0285 19.749 11.995V12.005C19.749 12.9715 18.9655 13.755 17.999 13.755C17.0325 13.755 16.249 12.9715 16.249 12.005V11.995C16.249 11.0285 17.0325 10.245 17.999 10.245ZM13.749 11.995C13.749 11.0285 12.9655 10.245 11.999 10.245C11.0325 10.245 10.249 11.0285 10.249 11.995V12.005C10.249 12.9715 11.0325 13.755 11.999 13.755C12.9655 13.755 13.749 12.9715 13.749 12.005V11.995Z"
fill="currentColor"
/>
</svg>
</slot>
</button>
<!-- Dropdown Menu -->
<div v-if="open" :class="menuClass">
<slot name="menu">
<!-- Default menu items -->
<template v-for="(item, index) in menuItems">
<router-link
v-if="item.to"
:key="`router-${index}`"
:to="item.to"
@click.native="handleMenuItemClick(item.onClick)"
:class="itemClass"
>
{{ item.label }}
</router-link>
<button
v-else
:key="`button-${index}`"
@click="handleMenuItemClick(item.onClick)"
:class="itemClass"
>
{{ item.label }}
</button>
</template>
</slot>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import vClickOutside from './v-click-outside.vue'
const props = defineProps({
menuItems: {
type: Array,
default: () => [],
},
buttonClass: {
type: String,
default: 'text-gray-500 dark:text-gray-400',
},
menuClass: {
type: String,
default:
'absolute right-0 z-40 w-40 p-2 space-y-1 bg-white border border-gray-200 top-full rounded-2xl shadow-lg dark:border-gray-800 dark:bg-gray-dark',
},
itemClass: {
type: String,
default:
'flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300',
},
})
const open = ref(false)
const toggleDropdown = () => {
open.value = !open.value
}
const closeDropdown = () => {
open.value = false
}
const handleMenuItemClick = (callback) => {
if (typeof callback === 'function') {
callback() // Execute the provided callback function
}
closeDropdown() // Close the dropdown after the item is clicked
}
</script>
<script>
export default {
directives: {
clickOutside: vClickOutside,
},
}
</script>
@@ -0,0 +1,17 @@
<template>
<div class="mb-6 flex flex-wrap items-center gap-3">
<h2 class="text-xl font-semibold text-gray-800 dark:text-white/90" x-text="pageTitle">
{{ pageTitle }}
</h2>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
interface BreadcrumbProps {
pageTitle: string
}
defineProps<BreadcrumbProps>()
</script>
@@ -0,0 +1,41 @@
<template>
<button
class="relative flex items-center justify-center text-gray-500 transition-colors bg-white border border-gray-200 rounded-full hover:text-dark-900 h-11 w-11 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
@click.prevent="toggleTheme"
>
<svg
class="hidden dark:block"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.99998 1.5415C10.4142 1.5415 10.75 1.87729 10.75 2.2915V3.5415C10.75 3.95572 10.4142 4.2915 9.99998 4.2915C9.58577 4.2915 9.24998 3.95572 9.24998 3.5415V2.2915C9.24998 1.87729 9.58577 1.5415 9.99998 1.5415ZM10.0009 6.79327C8.22978 6.79327 6.79402 8.22904 6.79402 10.0001C6.79402 11.7712 8.22978 13.207 10.0009 13.207C11.772 13.207 13.2078 11.7712 13.2078 10.0001C13.2078 8.22904 11.772 6.79327 10.0009 6.79327ZM5.29402 10.0001C5.29402 7.40061 7.40135 5.29327 10.0009 5.29327C12.6004 5.29327 14.7078 7.40061 14.7078 10.0001C14.7078 12.5997 12.6004 14.707 10.0009 14.707C7.40135 14.707 5.29402 12.5997 5.29402 10.0001ZM15.9813 5.08035C16.2742 4.78746 16.2742 4.31258 15.9813 4.01969C15.6884 3.7268 15.2135 3.7268 14.9207 4.01969L14.0368 4.90357C13.7439 5.19647 13.7439 5.67134 14.0368 5.96423C14.3297 6.25713 14.8045 6.25713 15.0974 5.96423L15.9813 5.08035ZM18.4577 10.0001C18.4577 10.4143 18.1219 10.7501 17.7077 10.7501H16.4577C16.0435 10.7501 15.7077 10.4143 15.7077 10.0001C15.7077 9.58592 16.0435 9.25013 16.4577 9.25013H17.7077C18.1219 9.25013 18.4577 9.58592 18.4577 10.0001ZM14.9207 15.9806C15.2135 16.2735 15.6884 16.2735 15.9813 15.9806C16.2742 15.6877 16.2742 15.2128 15.9813 14.9199L15.0974 14.036C14.8045 13.7431 14.3297 13.7431 14.0368 14.036C13.7439 14.3289 13.7439 14.8038 14.0368 15.0967L14.9207 15.9806ZM9.99998 15.7088C10.4142 15.7088 10.75 16.0445 10.75 16.4588V17.7088C10.75 18.123 10.4142 18.4588 9.99998 18.4588C9.58577 18.4588 9.24998 18.123 9.24998 17.7088V16.4588C9.24998 16.0445 9.58577 15.7088 9.99998 15.7088ZM5.96356 15.0972C6.25646 14.8043 6.25646 14.3295 5.96356 14.0366C5.67067 13.7437 5.1958 13.7437 4.9029 14.0366L4.01902 14.9204C3.72613 15.2133 3.72613 15.6882 4.01902 15.9811C4.31191 16.274 4.78679 16.274 5.07968 15.9811L5.96356 15.0972ZM4.29224 10.0001C4.29224 10.4143 3.95645 10.7501 3.54224 10.7501H2.29224C1.87802 10.7501 1.54224 10.4143 1.54224 10.0001C1.54224 9.58592 1.87802 9.25013 2.29224 9.25013H3.54224C3.95645 9.25013 4.29224 9.58592 4.29224 10.0001ZM4.9029 5.9637C5.1958 6.25659 5.67067 6.25659 5.96356 5.9637C6.25646 5.6708 6.25646 5.19593 5.96356 4.90303L5.07968 4.01915C4.78679 3.72626 4.31191 3.72626 4.01902 4.01915C3.72613 4.31204 3.72613 4.78692 4.01902 5.07981L4.9029 5.9637Z"
fill="currentColor"
/>
</svg>
<svg
class="dark:hidden"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.4547 11.97L18.1799 12.1611C18.265 11.8383 18.1265 11.4982 17.8401 11.3266C17.5538 11.1551 17.1885 11.1934 16.944 11.4207L17.4547 11.97ZM8.0306 2.5459L8.57989 3.05657C8.80718 2.81209 8.84554 2.44682 8.67398 2.16046C8.50243 1.8741 8.16227 1.73559 7.83948 1.82066L8.0306 2.5459ZM12.9154 13.0035C9.64678 13.0035 6.99707 10.3538 6.99707 7.08524H5.49707C5.49707 11.1823 8.81835 14.5035 12.9154 14.5035V13.0035ZM16.944 11.4207C15.8869 12.4035 14.4721 13.0035 12.9154 13.0035V14.5035C14.8657 14.5035 16.6418 13.7499 17.9654 12.5193L16.944 11.4207ZM16.7295 11.7789C15.9437 14.7607 13.2277 16.9586 10.0003 16.9586V18.4586C13.9257 18.4586 17.2249 15.7853 18.1799 12.1611L16.7295 11.7789ZM10.0003 16.9586C6.15734 16.9586 3.04199 13.8433 3.04199 10.0003H1.54199C1.54199 14.6717 5.32892 18.4586 10.0003 18.4586V16.9586ZM3.04199 10.0003C3.04199 6.77289 5.23988 4.05695 8.22173 3.27114L7.83948 1.82066C4.21532 2.77574 1.54199 6.07486 1.54199 10.0003H3.04199ZM6.99707 7.08524C6.99707 5.52854 7.5971 4.11366 8.57989 3.05657L7.48132 2.03522C6.25073 3.35885 5.49707 5.13487 5.49707 7.08524H6.99707Z"
fill="currentColor"
/>
</svg>
</button>
</template>
<script setup>
import { useTheme } from '../layout/ThemeProvider.vue'
const { toggleTheme } = useTheme()
</script>
@@ -0,0 +1,17 @@
<script>
import { onMounted, onUnmounted } from 'vue'
export default {
created(el, binding) {
el.clickOutsideEvent = (event) => {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event)
}
}
document.addEventListener('click', el.clickOutsideEvent)
},
unmounted(el) {
document.removeEventListener('click', el.clickOutsideEvent)
},
}
</script>
@@ -0,0 +1,139 @@
<template>
<div
class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] sm:p-6"
>
<div class="flex justify-between">
<div>
<h3 class="text-lg font-semibold text-gray-800 dark:text-white/90">
Customers Demographic
</h3>
<p class="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
Number of customer based on country
</p>
</div>
</div>
<div
class="px-4 py-6 my-6 overflow-hidden border border-gary-200 rounded-2xl bg-gray-50 dark:border-gray-800 dark:bg-gray-900 sm:px-6"
>
<div
ref="mapOneRef"
id="mapOne"
class="mapOne map-btn -mx-4 -my-6 h-[212px] w-[252px] 2xsm:w-[307px] xsm:w-[358px] sm:-mx-6 md:w-[668px] lg:w-[634px] xl:w-[393px] 2xl:w-[554px]"
></div>
</div>
<div class="space-y-5">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="items-center w-full rounded-full max-w-8">
<img src="/images/country/country-01.svg" alt="usa" />
</div>
<div>
<p class="font-semibold text-gray-800 text-theme-sm dark:text-white/90">USA</p>
<span class="block text-gray-500 text-theme-xs dark:text-gray-400">
2,379 Customers
</span>
</div>
</div>
<div class="flex w-full max-w-[140px] items-center gap-3">
<div class="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
<div
class="absolute left-0 top-0 flex h-full w-[79%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"
></div>
</div>
<p class="font-medium text-gray-800 text-theme-sm dark:text-white/90">79%</p>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="items-center w-full rounded-full max-w-8">
<img src="/images/country/country-02.svg" alt="france" />
</div>
<div>
<p class="font-semibold text-gray-800 text-theme-sm dark:text-white/90">France</p>
<span class="block text-gray-500 text-theme-xs dark:text-gray-400">
589 Customers
</span>
</div>
</div>
<div class="flex w-full max-w-[140px] items-center gap-3">
<div class="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
<div
class="absolute left-0 top-0 flex h-full w-[23%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"
></div>
</div>
<p class="font-medium text-gray-800 text-theme-sm dark:text-white/90">23%</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import jsVectorMap from 'jsvectormap'
import 'jsvectormap/dist/maps/world'
const mapOneRef = ref<HTMLElement | null>(null)
const mapInstance = ref<any>(null)
const initMap = () => {
if (mapOneRef.value) {
mapInstance.value = new jsVectorMap({
selector: mapOneRef.value,
map: 'world',
zoomButtons: false,
regionStyle: {
initial: {
fontFamily: 'Outfit',
fill: '#D9D9D9',
},
hover: {
fillOpacity: 1,
fill: '#465fff',
},
},
markers: [
{
name: 'Egypt',
coords: [26.8206, 30.8025],
},
{
name: 'United States',
coords: [55.3781, 3.436],
},
{
name: 'United States',
coords: [37.0902, -95.7129],
},
],
markerStyle: {
initial: {
strokeWidth: 1,
fill: '#465fff',
fillOpacity: 1,
r: 4,
},
hover: {
fill: '#465fff',
fillOpacity: 1,
},
selected: {},
selectedHover: {},
},
onRegionTooltipShow: function (event: MouseEvent, tooltip: any) {
const code = (event.target as HTMLElement).getAttribute('data-code')
if (code === 'EG') {
tooltip.setContent(tooltip.text() + ' (Hello Egypt)')
}
},
})
}
}
onMounted(() => {
initMap()
})
</script>
@@ -0,0 +1,109 @@
<template>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-6">
<div
class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6"
>
<div
class="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800"
>
<svg
class="fill-gray-800 dark:fill-white/90"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.80443 5.60156C7.59109 5.60156 6.60749 6.58517 6.60749 7.79851C6.60749 9.01185 7.59109 9.99545 8.80443 9.99545C10.0178 9.99545 11.0014 9.01185 11.0014 7.79851C11.0014 6.58517 10.0178 5.60156 8.80443 5.60156ZM5.10749 7.79851C5.10749 5.75674 6.76267 4.10156 8.80443 4.10156C10.8462 4.10156 12.5014 5.75674 12.5014 7.79851C12.5014 9.84027 10.8462 11.4955 8.80443 11.4955C6.76267 11.4955 5.10749 9.84027 5.10749 7.79851ZM4.86252 15.3208C4.08769 16.0881 3.70377 17.0608 3.51705 17.8611C3.48384 18.0034 3.5211 18.1175 3.60712 18.2112C3.70161 18.3141 3.86659 18.3987 4.07591 18.3987H13.4249C13.6343 18.3987 13.7992 18.3141 13.8937 18.2112C13.9797 18.1175 14.017 18.0034 13.9838 17.8611C13.7971 17.0608 13.4132 16.0881 12.6383 15.3208C11.8821 14.572 10.6899 13.955 8.75042 13.955C6.81096 13.955 5.61877 14.572 4.86252 15.3208ZM3.8071 14.2549C4.87163 13.2009 6.45602 12.455 8.75042 12.455C11.0448 12.455 12.6292 13.2009 13.6937 14.2549C14.7397 15.2906 15.2207 16.5607 15.4446 17.5202C15.7658 18.8971 14.6071 19.8987 13.4249 19.8987H4.07591C2.89369 19.8987 1.73504 18.8971 2.05628 17.5202C2.28015 16.5607 2.76117 15.2906 3.8071 14.2549ZM15.3042 11.4955C14.4702 11.4955 13.7006 11.2193 13.0821 10.7533C13.3742 10.3314 13.6054 9.86419 13.7632 9.36432C14.1597 9.75463 14.7039 9.99545 15.3042 9.99545C16.5176 9.99545 17.5012 9.01185 17.5012 7.79851C17.5012 6.58517 16.5176 5.60156 15.3042 5.60156C14.7039 5.60156 14.1597 5.84239 13.7632 6.23271C13.6054 5.73284 13.3741 5.26561 13.082 4.84371C13.7006 4.37777 14.4702 4.10156 15.3042 4.10156C17.346 4.10156 19.0012 5.75674 19.0012 7.79851C19.0012 9.84027 17.346 11.4955 15.3042 11.4955ZM19.9248 19.8987H16.3901C16.7014 19.4736 16.9159 18.969 16.9827 18.3987H19.9248C20.1341 18.3987 20.2991 18.3141 20.3936 18.2112C20.4796 18.1175 20.5169 18.0034 20.4837 17.861C20.2969 17.0607 19.913 16.088 19.1382 15.3208C18.4047 14.5945 17.261 13.9921 15.4231 13.9566C15.2232 13.6945 14.9995 13.437 14.7491 13.1891C14.5144 12.9566 14.262 12.7384 13.9916 12.5362C14.3853 12.4831 14.8044 12.4549 15.2503 12.4549C17.5447 12.4549 19.1291 13.2008 20.1936 14.2549C21.2395 15.2906 21.7206 16.5607 21.9444 17.5202C22.2657 18.8971 21.107 19.8987 19.9248 19.8987Z"
fill=""
/>
</svg>
</div>
<div class="flex items-end justify-between mt-5">
<div>
<span class="text-sm text-gray-500 dark:text-gray-400">Customers</span>
<h4 class="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">3,782</h4>
</div>
<span
class="flex items-center gap-1 rounded-full bg-success-50 py-0.5 pl-2 pr-2.5 text-sm font-medium text-success-600 dark:bg-success-500/15 dark:text-success-500"
>
<svg
class="fill-current"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.56462 1.62393C5.70193 1.47072 5.90135 1.37432 6.12329 1.37432C6.1236 1.37432 6.12391 1.37432 6.12422 1.37432C6.31631 1.37415 6.50845 1.44731 6.65505 1.59381L9.65514 4.5918C9.94814 4.88459 9.94831 5.35947 9.65552 5.65246C9.36273 5.94546 8.88785 5.94562 8.59486 5.65283L6.87329 3.93247L6.87329 10.125C6.87329 10.5392 6.53751 10.875 6.12329 10.875C5.70908 10.875 5.37329 10.5392 5.37329 10.125L5.37329 3.93578L3.65516 5.65282C3.36218 5.94562 2.8873 5.94547 2.5945 5.65248C2.3017 5.35949 2.30185 4.88462 2.59484 4.59182L5.56462 1.62393Z"
fill=""
/>
</svg>
11.01%
</span>
</div>
</div>
<div
class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6"
>
<div
class="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800"
>
<svg
class="fill-gray-800 dark:fill-white/90"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.665 3.75621C11.8762 3.65064 12.1247 3.65064 12.3358 3.75621L18.7807 6.97856L12.3358 10.2009C12.1247 10.3065 11.8762 10.3065 11.665 10.2009L5.22014 6.97856L11.665 3.75621ZM4.29297 8.19203V16.0946C4.29297 16.3787 4.45347 16.6384 4.70757 16.7654L11.25 20.0366V11.6513C11.1631 11.6205 11.0777 11.5843 10.9942 11.5426L4.29297 8.19203ZM12.75 20.037L19.2933 16.7654C19.5474 16.6384 19.7079 16.3787 19.7079 16.0946V8.19202L13.0066 11.5426C12.9229 11.5844 12.8372 11.6208 12.75 11.6516V20.037ZM13.0066 2.41456C12.3732 2.09786 11.6277 2.09786 10.9942 2.41456L4.03676 5.89319C3.27449 6.27432 2.79297 7.05342 2.79297 7.90566V16.0946C2.79297 16.9469 3.27448 17.726 4.03676 18.1071L10.9942 21.5857L11.3296 20.9149L10.9942 21.5857C11.6277 21.9024 12.3732 21.9024 13.0066 21.5857L19.9641 18.1071C20.7264 17.726 21.2079 16.9469 21.2079 16.0946V7.90566C21.2079 7.05342 20.7264 6.27432 19.9641 5.89319L13.0066 2.41456Z"
fill=""
/>
</svg>
</div>
<div class="flex items-end justify-between mt-5">
<div>
<span class="text-sm text-gray-500 dark:text-gray-400">Orders</span>
<h4 class="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">5,359</h4>
</div>
<span
class="flex items-center gap-1 rounded-full bg-error-50 py-0.5 pl-2 pr-2.5 text-sm font-medium text-error-600 dark:bg-error-500/15 dark:text-error-500"
>
<svg
class="fill-current"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.31462 10.3761C5.45194 10.5293 5.65136 10.6257 5.87329 10.6257C5.8736 10.6257 5.8739 10.6257 5.87421 10.6257C6.0663 10.6259 6.25845 10.5527 6.40505 10.4062L9.40514 7.4082C9.69814 7.11541 9.69831 6.64054 9.40552 6.34754C9.11273 6.05454 8.63785 6.05438 8.34486 6.34717L6.62329 8.06753L6.62329 1.875C6.62329 1.46079 6.28751 1.125 5.87329 1.125C5.45908 1.125 5.12329 1.46079 5.12329 1.875L5.12329 8.06422L3.40516 6.34719C3.11218 6.05439 2.6373 6.05454 2.3445 6.34752C2.0517 6.64051 2.05185 7.11538 2.34484 7.40818L5.31462 10.3761Z"
fill=""
/>
</svg>
9.05%
</span>
</div>
</div>
</div>
</template>
@@ -0,0 +1,139 @@
<template>
<div
class="overflow-hidden rounded-2xl border border-gray-200 bg-white px-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6"
>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-800 dark:text-white/90">Monthly Sales</h3>
<div class="relative h-fit">
<DropdownMenu :menu-items="menuItems">
<template #icon>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.2441 6C10.2441 5.0335 11.0276 4.25 11.9941 4.25H12.0041C12.9706 4.25 13.7541 5.0335 13.7541 6C13.7541 6.9665 12.9706 7.75 12.0041 7.75H11.9941C11.0276 7.75 10.2441 6.9665 10.2441 6ZM10.2441 18C10.2441 17.0335 11.0276 16.25 11.9941 16.25H12.0041C12.9706 16.25 13.7541 17.0335 13.7541 18C13.7541 18.9665 12.9706 19.75 12.0041 19.75H11.9941C11.0276 19.75 10.2441 18.9665 10.2441 18ZM11.9941 10.25C11.0276 10.25 10.2441 11.0335 10.2441 12C10.2441 12.9665 11.0276 13.75 11.9941 13.75H12.0041C12.9706 13.75 13.7541 12.9665 13.7541 12C13.7541 11.0335 12.9706 10.25 12.0041 10.25H11.9941Z"
fill="currentColor"
/>
</svg>
</template>
</DropdownMenu>
</div>
</div>
<div class="max-w-full overflow-x-auto custom-scrollbar">
<div id="chartOne" class="-ml-5 min-w-[650px] xl:min-w-full pl-2">
<VueApexCharts type="bar" height="180" :options="chartOptions" :series="series" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import DropdownMenu from '../common/DropdownMenu.vue'
const menuItems = [
{ label: 'View More', onClick: () => console.log('View More clicked') },
{ label: 'Delete', onClick: () => console.log('Delete clicked') },
]
import VueApexCharts from 'vue3-apexcharts'
const series = ref([
{
name: 'Sales',
data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
},
])
const chartOptions = ref({
colors: ['#465fff'],
chart: {
fontFamily: 'Outfit, sans-serif',
type: 'bar',
toolbar: {
show: false,
},
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '39%',
borderRadius: 5,
borderRadiusApplication: 'end',
},
},
dataLabels: {
enabled: false,
},
stroke: {
show: true,
width: 4,
colors: ['transparent'],
},
xaxis: {
categories: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
legend: {
show: true,
position: 'top',
horizontalAlign: 'left',
fontFamily: 'Outfit',
markers: {
radius: 99,
},
},
yaxis: {
title: false,
},
grid: {
yaxis: {
lines: {
show: true,
},
},
},
fill: {
opacity: 1,
},
tooltip: {
x: {
show: false,
},
y: {
formatter: function (val) {
return val.toString()
},
},
},
})
onMounted(() => {
// Any additional setup can be done here if needed
})
</script>
@@ -0,0 +1,206 @@
<template>
<div
class="rounded-2xl border border-gray-200 bg-gray-100 dark:border-gray-800 dark:bg-white/[0.03]"
>
<div
class="px-5 pt-5 bg-white shadow-default rounded-2xl pb-11 dark:bg-gray-900 sm:px-6 sm:pt-6"
>
<div class="flex justify-between">
<div>
<h3 class="text-lg font-semibold text-gray-800 dark:text-white/90">Monthly Target</h3>
<p class="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
Target youve set for each month
</p>
</div>
<div>
<DropdownMenu :menu-items="menuItems">
<template #icon>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.2441 6C10.2441 5.0335 11.0276 4.25 11.9941 4.25H12.0041C12.9706 4.25 13.7541 5.0335 13.7541 6C13.7541 6.9665 12.9706 7.75 12.0041 7.75H11.9941C11.0276 7.75 10.2441 6.9665 10.2441 6ZM10.2441 18C10.2441 17.0335 11.0276 16.25 11.9941 16.25H12.0041C12.9706 16.25 13.7541 17.0335 13.7541 18C13.7541 18.9665 12.9706 19.75 12.0041 19.75H11.9941C11.0276 19.75 10.2441 18.9665 10.2441 18ZM11.9941 10.25C11.0276 10.25 10.2441 11.0335 10.2441 12C10.2441 12.9665 11.0276 13.75 11.9941 13.75H12.0041C12.9706 13.75 13.7541 12.9665 13.7541 12C13.7541 11.0335 12.9706 10.25 12.0041 10.25H11.9941Z"
fill="currentColor"
/>
</svg>
</template>
</DropdownMenu>
</div>
</div>
<div class="relative max-h-[195px]">
<div id="chartTwo" class="h-full">
<div class="radial-bar-chart">
<VueApexCharts type="radialBar" height="330" :options="chartOptions" :series="series" />
</div>
</div>
<span
class="absolute left-1/2 top-[85%] -translate-x-1/2 -translate-y-[85%] rounded-full bg-success-50 px-3 py-1 text-xs font-medium text-success-600 dark:bg-success-500/15 dark:text-success-500"
>+10%</span
>
</div>
<p class="mx-auto mt-1.5 w-full max-w-[380px] text-center text-sm text-gray-500 sm:text-base">
You earn $3287 today, it's higher than last month. Keep up your good work!
</p>
</div>
<div class="flex items-center justify-center gap-5 px-6 py-3.5 sm:gap-8 sm:py-5">
<div>
<p class="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
Target
</p>
<p
class="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg"
>
$20K
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.26816 13.6632C7.4056 13.8192 7.60686 13.9176 7.8311 13.9176C7.83148 13.9176 7.83187 13.9176 7.83226 13.9176C8.02445 13.9178 8.21671 13.8447 8.36339 13.6981L12.3635 9.70076C12.6565 9.40797 12.6567 8.9331 12.3639 8.6401C12.0711 8.34711 11.5962 8.34694 11.3032 8.63973L8.5811 11.36L8.5811 2.5C8.5811 2.08579 8.24531 1.75 7.8311 1.75C7.41688 1.75 7.0811 2.08579 7.0811 2.5L7.0811 11.3556L4.36354 8.63975C4.07055 8.34695 3.59568 8.3471 3.30288 8.64009C3.01008 8.93307 3.01023 9.40794 3.30321 9.70075L7.26816 13.6632Z"
fill="#D92D20"
/>
</svg>
</p>
</div>
<div class="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
<div>
<p class="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
Revenue
</p>
<p
class="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg"
>
$20K
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
fill="#039855"
/>
</svg>
</p>
</div>
<div class="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
<div>
<p class="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
Today
</p>
<p
class="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg"
>
$20K
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
fill="#039855"
/>
</svg>
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import DropdownMenu from '../common/DropdownMenu.vue'
const menuItems = [
{ label: 'View More', onClick: () => console.log('View More clicked') },
{ label: 'Delete', onClick: () => console.log('Delete clicked') },
]
import VueApexCharts from 'vue3-apexcharts'
const props = defineProps({
value: {
type: Number,
default: 75.55,
},
})
const series = computed(() => [props.value])
const chartOptions = {
colors: ['#465FFF'],
chart: {
fontFamily: 'Outfit, sans-serif',
sparkline: {
enabled: true,
},
},
plotOptions: {
radialBar: {
startAngle: -90,
endAngle: 90,
hollow: {
size: '80%',
},
track: {
background: '#E4E7EC',
strokeWidth: '100%',
margin: 5,
},
dataLabels: {
name: {
show: false,
},
value: {
fontSize: '36px',
fontWeight: '600',
offsetY: 60,
color: '#1D2939',
formatter: function (val: number) {
return val.toFixed(2) + '%'
},
},
},
},
},
fill: {
type: 'solid',
colors: ['#465FFF'],
},
stroke: {
lineCap: 'round',
},
labels: ['Progress'],
}
</script>
<style scoped>
.radial-bar-chart {
width: 100%;
max-width: 330px;
margin: 0 auto;
}
</style>
@@ -0,0 +1,173 @@
<template>
<div
class="overflow-hidden rounded-2xl border border-gray-200 bg-white px-4 pb-3 pt-4 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6"
>
<div class="flex flex-col gap-2 mb-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 class="text-lg font-semibold text-gray-800 dark:text-white/90">Recent Orders</h3>
</div>
<div class="flex items-center gap-3">
<button
class="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-theme-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200"
>
<svg
class="stroke-current fill-white dark:fill-gray-800"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.29004 5.90393H17.7067"
stroke=""
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M17.7075 14.0961H2.29085"
stroke=""
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12.0826 3.33331C13.5024 3.33331 14.6534 4.48431 14.6534 5.90414C14.6534 7.32398 13.5024 8.47498 12.0826 8.47498C10.6627 8.47498 9.51172 7.32398 9.51172 5.90415C9.51172 4.48432 10.6627 3.33331 12.0826 3.33331Z"
fill=""
stroke=""
stroke-width="1.5"
/>
<path
d="M7.91745 11.525C6.49762 11.525 5.34662 12.676 5.34662 14.0959C5.34661 15.5157 6.49762 16.6667 7.91745 16.6667C9.33728 16.6667 10.4883 15.5157 10.4883 14.0959C10.4883 12.676 9.33728 11.525 7.91745 11.525Z"
fill=""
stroke=""
stroke-width="1.5"
/>
</svg>
Filter
</button>
<button
class="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-theme-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200"
>
See all
</button>
</div>
</div>
<div class="max-w-full overflow-x-auto custom-scrollbar">
<table class="min-w-full">
<thead>
<tr class="border-t border-gray-100 dark:border-gray-800">
<th class="py-3 text-left">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Products</p>
</th>
<th class="py-3 text-left">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Category</p>
</th>
<th class="py-3 text-left">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Price</p>
</th>
<th class="py-3 text-left">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Status</p>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(product, index) in products"
:key="index"
class="border-t border-gray-100 dark:border-gray-800"
>
<td class="py-3 whitespace-nowrap">
<div class="flex items-center gap-3">
<div class="h-[50px] w-[50px] overflow-hidden rounded-md">
<img :src="product.image" :alt="product.name" />
</div>
<div>
<p class="font-medium text-gray-800 text-theme-sm dark:text-white/90">
{{ product.name }}
</p>
<span class="text-gray-500 text-theme-xs dark:text-gray-400"
>{{ product.variants }} Variants</span
>
</div>
</div>
</td>
<td class="py-3 whitespace-nowrap">
<p class="text-gray-500 text-theme-sm dark:text-gray-400">{{ product.category }}</p>
</td>
<td class="py-3 whitespace-nowrap">
<p class="text-gray-500 text-theme-sm dark:text-gray-400">{{ product.price }}</p>
</td>
<td class="py-3 whitespace-nowrap">
<span
:class="{
'rounded-full px-2 py-0.5 text-theme-xs font-medium': true,
'bg-success-50 text-success-600 dark:bg-success-500/15 dark:text-success-500':
product.status === 'Delivered',
'bg-warning-50 text-warning-600 dark:bg-warning-500/15 dark:text-orange-400':
product.status === 'Pending',
'bg-error-50 text-error-600 dark:bg-error-500/15 dark:text-error-500':
product.status === 'Canceled',
}"
>
{{ product.status }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const products = ref([
{
name: 'Macbook pro 13"',
variants: 2,
image: '/images/product/product-01.jpg',
category: 'Laptop',
price: '$2399.00',
status: 'Delivered',
},
{
name: 'Apple Watch Ultra',
variants: 1,
image: '/images/product/product-02.jpg',
category: 'Watch',
price: '$879.00',
status: 'Pending',
},
{
name: 'iPhone 15 Pro Max',
variants: 2,
image: '/images/product/product-03.jpg',
category: 'SmartPhone',
price: '$1869.00',
status: 'Delivered',
},
{
name: 'iPad Pro 3rd Gen',
variants: 2,
image: '/images/product/product-04.jpg',
category: 'Electronics',
price: '$1699.00',
status: 'Canceled',
},
{
name: 'Airpods Pro 2nd Gen',
variants: 1,
image: '/images/product/product-05.jpg',
category: 'Accessories',
price: '$240.00',
status: 'Delivered',
},
])
</script>
@@ -0,0 +1,194 @@
<template>
<div
class="rounded-2xl border border-gray-200 bg-white px-5 pb-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6"
>
<div class="flex flex-col gap-5 mb-6 sm:flex-row sm:justify-between">
<div class="w-full">
<h3 class="text-lg font-semibold text-gray-800 dark:text-white/90">Statistics</h3>
<p class="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
Target youve set for each month
</p>
</div>
<div class="flex items-center gap-3">
<div class="relative">
<div
class="inline-flex items-center gap-0.5 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900"
>
<button
v-for="option in options"
:key="option.value"
@click="selected = option.value"
:class="[
selected === option.value
? 'shadow-theme-xs text-gray-900 dark:text-white bg-white dark:bg-gray-800'
: 'text-gray-500 dark:text-gray-400',
'px-3 py-2 font-medium rounded-md text-theme-sm hover:text-gray-900 hover:shadow-theme-xs dark:hover:bg-gray-800 dark:hover:text-white',
]"
>
{{ option.label }}
</button>
</div>
</div>
<div class="relative">
<flat-pickr
v-model="date"
:config="flatpickrConfig"
class="pl-3 sm:pl-9 dark:bg-dark-900 h-10 w-10 sm:w-40 rounded-lg border border-gray-200 bg-white text-transparent sm:text-theme-sm sm:text-gray-800 shadow-theme-xs placeholder:text-transparent sm:placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:bg-white/[0.03] dark:text-transparent sm:dark:text-gray-400 dark:placeholder:text-transparent sm:dark:placeholder:text-gray-400 dark:focus:border-brand-800"
placeholder="Select Date"
/>
<span
class="absolute text-gray-500 -translate-y-1/2 pointer-events-none left-1/2 -translate-x-1/2 sm:left-3 sm:translate-x-0 top-1/2 dark:text-gray-400"
>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.66659 1.5415C7.0808 1.5415 7.41658 1.87729 7.41658 2.2915V2.99984H12.5833V2.2915C12.5833 1.87729 12.919 1.5415 13.3333 1.5415C13.7475 1.5415 14.0833 1.87729 14.0833 2.2915V2.99984L15.4166 2.99984C16.5212 2.99984 17.4166 3.89527 17.4166 4.99984V7.49984V15.8332C17.4166 16.9377 16.5212 17.8332 15.4166 17.8332H4.58325C3.47868 17.8332 2.58325 16.9377 2.58325 15.8332V7.49984V4.99984C2.58325 3.89527 3.47868 2.99984 4.58325 2.99984L5.91659 2.99984V2.2915C5.91659 1.87729 6.25237 1.5415 6.66659 1.5415ZM6.66659 4.49984H4.58325C4.30711 4.49984 4.08325 4.7237 4.08325 4.99984V6.74984H15.9166V4.99984C15.9166 4.7237 15.6927 4.49984 15.4166 4.49984H13.3333H6.66659ZM15.9166 8.24984H4.08325V15.8332C4.08325 16.1093 4.30711 16.3332 4.58325 16.3332H15.4166C15.6927 16.3332 15.9166 16.1093 15.9166 15.8332V8.24984Z"
fill=""
/>
</svg>
</span>
</div>
</div>
</div>
<div class="max-w-full overflow-x-auto custom-scrollbar">
<div id="chartThree" class="-ml-4 min-w-[1000px] xl:min-w-full pl-2">
<VueApexCharts type="area" height="310" :options="chartOptions" :series="series" />
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import flatPickr from 'vue-flatpickr-component'
const options = [
{ value: 'optionOne', label: 'Monthly' },
{ value: 'optionTwo', label: 'Quarterly' },
{ value: 'optionThree', label: 'Annually' },
]
const selected = ref('optionOne')
const date = ref('')
const flatpickrConfig = {
mode: 'range',
dateFormat: 'M j',
defaultDate: [new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), new Date()],
}
import VueApexCharts from 'vue3-apexcharts'
const series = ref([
{
name: 'Sales',
data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
},
{
name: 'Revenue',
data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
},
])
const chartOptions = ref({
legend: {
show: false,
position: 'top',
horizontalAlign: 'left',
},
colors: ['#465FFF', '#9CB9FF'],
chart: {
fontFamily: 'Outfit, sans-serif',
type: 'area',
toolbar: {
show: false,
},
},
fill: {
gradient: {
enabled: true,
opacityFrom: 0.55,
opacityTo: 0,
},
},
stroke: {
curve: 'straight',
width: [2, 2],
},
markers: {
size: 0,
},
labels: {
show: false,
position: 'top',
},
grid: {
xaxis: {
lines: {
show: false,
},
},
yaxis: {
lines: {
show: true,
},
},
},
dataLabels: {
enabled: false,
},
tooltip: {
x: {
format: 'dd MMM yyyy',
},
},
xaxis: {
type: 'category',
categories: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
tooltip: {
enabled: false,
},
},
yaxis: {
title: {
style: {
fontSize: '0px',
},
},
},
})
</script>
<style scoped>
.area-chart {
width: 100%;
}
</style>
@@ -0,0 +1,137 @@
<template>
<div class="space-y-6">
<div class="flex flex-wrap items-center gap-8">
<!-- Default Checkbox -->
<div>
<label
for="checkboxLabelOne"
class="flex items-center text-sm font-medium text-gray-700 cursor-pointer select-none dark:text-gray-400"
>
<div class="relative">
<input type="checkbox" id="checkboxLabelOne" v-model="checkboxOne" class="sr-only" />
<div
:class="
checkboxOne
? 'border-brand-500 bg-brand-500'
: 'bg-transparent border-gray-300 dark:border-gray-700'
"
class="mr-3 flex h-5 w-5 items-center justify-center rounded-md border-[1.25px] hover:border-brand-500 dark:hover:border-brand-500"
>
<span :class="checkboxOne ? '' : 'opacity-0'">
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke="white"
stroke-width="1.94437"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
</div>
</div>
Default
</label>
</div>
<!-- Checked Checkbox -->
<div>
<label
for="checkboxLabelTwo"
class="flex items-center text-sm font-medium text-gray-700 cursor-pointer select-none dark:text-gray-400"
>
<div class="relative">
<input type="checkbox" id="checkboxLabelTwo" v-model="checkboxTwo" class="sr-only" />
<div
:class="
checkboxTwo
? 'border-brand-500 bg-brand-500'
: 'bg-transparent border-gray-300 dark:border-gray-700'
"
class="mr-3 flex h-5 w-5 items-center justify-center rounded-md border-[1.25px] hover:border-brand-500 dark:hover:border-brand-500"
>
<span :class="checkboxTwo ? '' : 'opacity-0'">
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke="white"
stroke-width="1.94437"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
</div>
</div>
Checked
</label>
</div>
<!-- Disabled Checkbox -->
<div>
<label
for="checkboxLabelThree"
class="flex items-center text-sm font-medium text-gray-300 cursor-pointer select-none dark:text-gray-700"
>
<div class="relative">
<input
type="checkbox"
id="checkboxLabelThree"
v-model="checkboxThree"
class="sr-only peer"
disabled
/>
<div
:class="
checkboxThree
? 'bg-transparent border-gray-200 dark:border-gray-800'
: 'border-brand-500 bg-brand-500'
"
class="mr-3 flex h-5 w-5 items-center justify-center rounded-md border-[1.25px]"
>
<span :class="checkboxThree ? '' : 'opacity-0'">
<svg
class="stroke-gray-200 dark:stroke-gray-800"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke=""
stroke-width="2.33333"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
</div>
</div>
Disabled
</label>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const checkboxOne = ref(false)
const checkboxTwo = ref(true)
const checkboxThree = ref(true)
</script>
@@ -0,0 +1,254 @@
<template>
<div class="space-y-6">
<!-- Text Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Input
</label>
<input
type="text"
v-model="formData.input"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<!-- Input with Placeholder -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Input with Placeholder
</label>
<input
type="text"
v-model="formData.inputWithPlaceholder"
placeholder="info@gmail.com"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<!-- Select Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Select Input
</label>
<div class="relative z-20 bg-transparent">
<select
v-model="formData.selectInput"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
:class="{ 'text-gray-800 dark:text-white/90': formData.selectInput }"
>
<option value="" disabled selected>Select Option</option>
<option value="marketing">Marketing</option>
<option value="template">Template</option>
<option value="development">Development</option>
</select>
<span
class="absolute z-30 text-gray-500 -translate-y-1/2 pointer-events-none right-4 top-1/2 dark:text-gray-400"
>
<svg
class="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke=""
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
</div>
</div>
<!-- Password Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Password Input
</label>
<div class="relative">
<input
:type="showPassword ? 'text' : 'password'"
v-model="formData.password"
placeholder="Enter your password"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
<span
@click="showPassword = !showPassword"
class="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"
>
<svg
v-if="!showPassword"
class="fill-gray-500 dark:fill-gray-400"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.0002 13.8619C7.23361 13.8619 4.86803 12.1372 3.92328 9.70241C4.86804 7.26761 7.23361 5.54297 10.0002 5.54297C12.7667 5.54297 15.1323 7.26762 16.0771 9.70243C15.1323 12.1372 12.7667 13.8619 10.0002 13.8619ZM10.0002 4.04297C6.48191 4.04297 3.49489 6.30917 2.4155 9.4593C2.3615 9.61687 2.3615 9.78794 2.41549 9.94552C3.49488 13.0957 6.48191 15.3619 10.0002 15.3619C13.5184 15.3619 16.5055 13.0957 17.5849 9.94555C17.6389 9.78797 17.6389 9.6169 17.5849 9.45932C16.5055 6.30919 13.5184 4.04297 10.0002 4.04297ZM9.99151 7.84413C8.96527 7.84413 8.13333 8.67606 8.13333 9.70231C8.13333 10.7286 8.96527 11.5605 9.99151 11.5605H10.0064C11.0326 11.5605 11.8646 10.7286 11.8646 9.70231C11.8646 8.67606 11.0326 7.84413 10.0064 7.84413H9.99151Z"
/>
</svg>
<svg
v-else
class="fill-gray-500 dark:fill-gray-400"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.63803 3.57709C4.34513 3.2842 3.87026 3.2842 3.57737 3.57709C3.28447 3.86999 3.28447 4.34486 3.57737 4.63775L4.85323 5.91362C3.74609 6.84199 2.89363 8.06395 2.4155 9.45936C2.3615 9.61694 2.3615 9.78801 2.41549 9.94558C3.49488 13.0957 6.48191 15.3619 10.0002 15.3619C11.255 15.3619 12.4422 15.0737 13.4994 14.5598L15.3625 16.4229C15.6554 16.7158 16.1302 16.7158 16.4231 16.4229C16.716 16.13 16.716 15.6551 16.4231 15.3622L4.63803 3.57709ZM12.3608 13.4212L10.4475 11.5079C10.3061 11.5423 10.1584 11.5606 10.0064 11.5606H9.99151C8.96527 11.5606 8.13333 10.7286 8.13333 9.70237C8.13333 9.5461 8.15262 9.39434 8.18895 9.24933L5.91885 6.97923C5.03505 7.69015 4.34057 8.62704 3.92328 9.70247C4.86803 12.1373 7.23361 13.8619 10.0002 13.8619C10.8326 13.8619 11.6287 13.7058 12.3608 13.4212ZM16.0771 9.70249C15.7843 10.4569 15.3552 11.1432 14.8199 11.7311L15.8813 12.7925C16.6329 11.9813 17.2187 11.0143 17.5849 9.94561C17.6389 9.78803 17.6389 9.61696 17.5849 9.45938C16.5055 6.30925 13.5184 4.04303 10.0002 4.04303C9.13525 4.04303 8.30244 4.17999 7.52218 4.43338L8.75139 5.66259C9.1556 5.58413 9.57311 5.54303 10.0002 5.54303C12.7667 5.54303 15.1323 7.26768 16.0771 9.70249Z"
/>
</svg>
</span>
</div>
</div>
<!-- Date Picker Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Date Picker Input
</label>
<div class="relative">
<flat-pickr
v-model="date"
:config="flatpickrConfig"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
placeholder="Select date"
/>
<span
class="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400"
>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.66659 1.5415C7.0808 1.5415 7.41658 1.87729 7.41658 2.2915V2.99984H12.5833V2.2915C12.5833 1.87729 12.919 1.5415 13.3333 1.5415C13.7475 1.5415 14.0833 1.87729 14.0833 2.2915V2.99984L15.4166 2.99984C16.5212 2.99984 17.4166 3.89527 17.4166 4.99984V7.49984V15.8332C17.4166 16.9377 16.5212 17.8332 15.4166 17.8332H4.58325C3.47868 17.8332 2.58325 16.9377 2.58325 15.8332V7.49984V4.99984C2.58325 3.89527 3.47868 2.99984 4.58325 2.99984L5.91659 2.99984V2.2915C5.91659 1.87729 6.25237 1.5415 6.66659 1.5415ZM6.66659 4.49984H4.58325C4.30711 4.49984 4.08325 4.7237 4.08325 4.99984V6.74984H15.9166V4.99984C15.9166 4.7237 15.6927 4.49984 15.4166 4.49984H13.3333H6.66659ZM15.9166 8.24984H4.08325V15.8332C4.08325 16.1093 4.30711 16.3332 4.58325 16.3332H15.4166C15.6927 16.3332 15.9166 16.1093 15.9166 15.8332V8.24984Z"
fill=""
/>
</svg>
</span>
</div>
</div>
<!-- Time Select Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Time Select Input
</label>
<div class="relative">
<flat-pickr
v-model="time"
:config="flatpickrTimeConfig"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
placeholder="Select time"
/>
<span class="absolute text-gray-500 -translate-y-1/2 right-3 top-1/2 dark:text-gray-400">
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.04175 9.99984C3.04175 6.15686 6.1571 3.0415 10.0001 3.0415C13.8431 3.0415 16.9584 6.15686 16.9584 9.99984C16.9584 13.8428 13.8431 16.9582 10.0001 16.9582C6.1571 16.9582 3.04175 13.8428 3.04175 9.99984ZM10.0001 1.5415C5.32867 1.5415 1.54175 5.32843 1.54175 9.99984C1.54175 14.6712 5.32867 18.4582 10.0001 18.4582C14.6715 18.4582 18.4584 14.6712 18.4584 9.99984C18.4584 5.32843 14.6715 1.5415 10.0001 1.5415ZM9.99998 10.7498C9.58577 10.7498 9.24998 10.4141 9.24998 9.99984V5.4165C9.24998 5.00229 9.58577 4.6665 9.99998 4.6665C10.4142 4.6665 10.75 5.00229 10.75 5.4165V9.24984H13.3334C13.7476 9.24984 14.0834 9.58562 14.0834 9.99984C14.0834 10.4141 13.7476 10.7498 13.3334 10.7498H10.0001H9.99998Z"
fill=""
/>
</svg>
</span>
</div>
</div>
<!-- Input with Payment -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Input with Payment
</label>
<div class="relative">
<input
type="text"
v-model="formData.cardNumber"
placeholder="Card number"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-[62px] text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
<span
class="absolute left-0 top-1/2 flex h-11 w-[46px] -translate-y-1/2 items-center justify-center border-r border-gray-200 dark:border-gray-800"
>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="6.25" cy="10" r="5.625" fill="#E80B26" />
<circle cx="13.75" cy="10" r="5.625" fill="#F59D31" />
<path
d="M10 14.1924C11.1508 13.1625 11.875 11.6657 11.875 9.99979C11.875 8.33383 11.1508 6.8371 10 5.80713C8.84918 6.8371 8.125 8.33383 8.125 9.99979C8.125 11.6657 8.84918 13.1625 10 14.1924Z"
fill="#FC6020"
/>
</svg>
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
const showPassword = ref(false)
const formData = reactive({
input: '',
inputWithPlaceholder: '',
selectInput: '',
password: '',
date: '',
time: '',
cardNumber: '',
})
const date = ref(null)
const flatpickrConfig = {
dateFormat: 'Y-m-d',
altInput: true,
altFormat: 'F j, Y',
wrap: true,
}
const flatpickrTimeConfig = {
enableTime: true,
noCalendar: true,
dateFormat: 'H:i',
time_24hr: false,
minuteIncrement: 1,
wrap: false,
}
const time = ref(null)
</script>
@@ -0,0 +1,134 @@
<template>
<div class="file-uploader">
<form
ref="dropzoneForm"
:id="dropzoneId"
:action="uploadUrl"
class="border-gray-300 border-dashed dropzone rounded-xl bg-gray-50 p-7 hover:border-brand-500 dark:border-gray-700 dark:bg-gray-900 dark:hover:border-brand-500 lg:p-10"
>
<div class="dz-message m-0!">
<div class="mb-[22px] flex justify-center">
<div
class="flex h-[68px] w-[68px] items-center justify-center rounded-full bg-gray-200 text-gray-700 dark:bg-gray-800 dark:text-gray-400"
>
<svg
class="fill-current"
width="29"
height="28"
viewBox="0 0 29 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.5019 3.91699C14.2852 3.91699 14.0899 4.00891 13.953 4.15589L8.57363 9.53186C8.28065 9.82466 8.2805 10.2995 8.5733 10.5925C8.8661 10.8855 9.34097 10.8857 9.63396 10.5929L13.7519 6.47752V18.667C13.7519 19.0812 14.0877 19.417 14.5019 19.417C14.9161 19.417 15.2519 19.0812 15.2519 18.667V6.48234L19.3653 10.5929C19.6583 10.8857 20.1332 10.8855 20.426 10.5925C20.7188 10.2995 20.7186 9.82463 20.4256 9.53184L15.0838 4.19378C14.9463 4.02488 14.7367 3.91699 14.5019 3.91699ZM5.91626 18.667C5.91626 18.2528 5.58047 17.917 5.16626 17.917C4.75205 17.917 4.41626 18.2528 4.41626 18.667V21.8337C4.41626 23.0763 5.42362 24.0837 6.66626 24.0837H22.3339C23.5766 24.0837 24.5839 23.0763 24.5839 21.8337V18.667C24.5839 18.2528 24.2482 17.917 23.8339 17.917C23.4197 17.917 23.0839 18.2528 23.0839 18.667V21.8337C23.0839 22.2479 22.7482 22.5837 22.3339 22.5837H6.66626C6.25205 22.5837 5.91626 22.2479 5.91626 21.8337V18.667Z"
fill=""
/>
</svg>
</div>
</div>
<h4 class="mb-3 font-semibold text-gray-800 text-theme-xl dark:text-white/90">
Drag & Drop File Here
</h4>
<span
class="mx-auto mb-5 block w-full max-w-[290px] text-sm text-gray-700 dark:text-gray-400"
>
Drag and drop your PNG, JPG, WebP, SVG images here or browse
</span>
<span class="font-medium underline cursor-pointer text-theme-sm text-brand-500">
Browse File
</span>
</div>
</form>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import Dropzone from 'dropzone'
import 'dropzone/dist/dropzone.css'
const props = defineProps({
uploadUrl: {
type: String,
default: '/upload',
},
})
const dropzoneForm = ref(null)
const dropzoneId = `dropzone-${Math.random().toString(36).substr(2, 9)}`
let dropzoneInstance = null
onMounted(() => {
Dropzone.autoDiscover = false
dropzoneInstance = new Dropzone(`#${dropzoneId}`, {
url: props.uploadUrl,
thumbnailWidth: 150,
maxFilesize: 0.5,
acceptedFiles: 'image/jpeg,image/png,image/gif,image/webp,image/svg+xml',
headers: { 'My-Awesome-Header': 'header value' },
dictDefaultMessage: '',
init: function () {
this.on('addedfile', (file) => {
console.log('A file has been added', file)
})
this.on('success', (file, response) => {
console.log('File successfully uploaded', file, response)
})
this.on('error', (file, error) => {
console.error('An error occurred during upload', file, error)
})
},
})
})
onBeforeUnmount(() => {
if (dropzoneInstance) {
dropzoneInstance.destroy()
}
})
</script>
<style>
.dropzone {
border: 1px dashed #d0d5dd;
transition: all 0.3s ease;
}
.dropzone:hover {
border-color: #465fff;
}
.dropzone .dz-preview {
margin: 10px;
}
.dropzone .dz-preview .dz-image {
border-radius: 8px;
}
.dropzone .dz-preview .dz-details {
padding: 1em;
}
.dropzone .dz-preview .dz-progress {
height: 10px;
}
.dropzone .dz-preview .dz-progress .dz-upload {
background: #4f46e5;
}
.dark .dropzone {
background-color: #111827;
border-color: #374151;
}
.dark .dropzone:hover {
border-color: #6366f1;
}
</style>
@@ -0,0 +1,14 @@
<template>
<div class="space-y-6">
<!-- Elements -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Upload file
</label>
<input
type="file"
class="focus:border-ring-brand-300 h-11 w-full overflow-hidden rounded-lg border border-gray-300 bg-transparent text-sm text-gray-500 shadow-theme-xs transition-colors file:mr-5 file:border-collapse file:cursor-pointer file:rounded-l-lg file:border-0 file:border-r file:border-solid file:border-gray-200 file:bg-gray-50 file:py-3 file:pl-3.5 file:pr-3 file:text-sm file:text-gray-700 placeholder:text-gray-400 hover:file:bg-gray-100 focus:outline-hidden focus:file:ring-brand-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400 dark:text-white/90 dark:file:border-gray-800 dark:file:bg-white/[0.03] dark:file:text-gray-400 dark:placeholder:text-gray-400"
/>
</div>
</div>
</template>
@@ -0,0 +1,217 @@
<template>
<div class="space-y-6">
<!-- Email Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Email
</label>
<div class="relative">
<span
class="absolute left-0 top-1/2 -translate-y-1/2 border-r border-gray-200 px-3.5 py-3 text-gray-500 dark:border-gray-800 dark:text-gray-400"
>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.04175 7.06206V14.375C3.04175 14.6511 3.26561 14.875 3.54175 14.875H16.4584C16.7346 14.875 16.9584 14.6511 16.9584 14.375V7.06245L11.1443 11.1168C10.457 11.5961 9.54373 11.5961 8.85638 11.1168L3.04175 7.06206ZM16.9584 5.19262C16.9584 5.19341 16.9584 5.1942 16.9584 5.19498V5.20026C16.9572 5.22216 16.946 5.24239 16.9279 5.25501L10.2864 9.88638C10.1145 10.0062 9.8862 10.0062 9.71437 9.88638L3.07255 5.25485C3.05342 5.24151 3.04202 5.21967 3.04202 5.19636C3.042 5.15695 3.07394 5.125 3.11335 5.125H16.8871C16.9253 5.125 16.9564 5.15494 16.9584 5.19262ZM18.4584 5.21428V14.375C18.4584 15.4796 17.563 16.375 16.4584 16.375H3.54175C2.43718 16.375 1.54175 15.4796 1.54175 14.375V5.19498C1.54175 5.1852 1.54194 5.17546 1.54231 5.16577C1.55858 4.31209 2.25571 3.625 3.11335 3.625H16.8871C17.7549 3.625 18.4584 4.32843 18.4585 5.19622C18.4585 5.20225 18.4585 5.20826 18.4584 5.21428Z"
fill="#667085"
/>
</svg>
</span>
<input
v-model="email"
type="text"
placeholder="info@gmail.com"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 pl-[62px] text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
<!-- Phone Input with Prepended Country Code -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Phone
</label>
<div class="relative">
<div class="absolute">
<select
v-model="selectedCountry"
@change="updatePhoneNumber"
class="appearance-none rounded-l-lg border-0 border-r border-gray-200 bg-transparent bg-none py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
>
<option v-for="(code, country) in countryCodes" :key="country" :value="country">
{{ country }}
</option>
</select>
<div
class="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none right-3 dark:text-gray-400"
>
<svg
class="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke=""
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</div>
<input
v-model="phoneNumber"
placeholder="+1 (555) 000-0000"
type="tel"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent py-3 pl-[84px] pr-4 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
<!-- Phone Input with Appended Country Code -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Phone
</label>
<div class="relative">
<div class="absolute right-0">
<select
v-model="selectedCountry2"
@change="updatePhoneNumber2"
class="appearance-none rounded-r-lg border-0 border-l border-gray-200 bg-transparent bg-none py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
>
<option v-for="(code, country) in countryCodes" :key="country" :value="country">
{{ country }}
</option>
</select>
<div
class="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none right-3 dark:text-gray-400"
>
<svg
class="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke=""
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</div>
<input
v-model="phoneNumber2"
placeholder="+1 (555) 000-0000"
type="tel"
class="dark:bg-dark-900 h-11 w-full p-3 rounded-lg border border-gray-300 bg-transparent py-3 pr-[84px] text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
<!-- URL Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"> URL </label>
<div class="relative">
<span
class="absolute left-0 top-1/2 inline-flex h-11 -translate-y-1/2 items-center justify-center border-r border-gray-200 py-3 pl-3.5 pr-3 text-gray-500 dark:border-gray-800 dark:text-gray-400"
>
http://
</span>
<input
v-model="url"
type="url"
placeholder="www.tailadmin.com"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 pl-[90px] text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
<!-- Website Input with Copy Button -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Website
</label>
<div class="relative">
<button
@click="copyWebsite"
class="absolute right-0 top-1/2 inline-flex -translate-y-1/2 cursor-pointer items-center gap-1 border-l border-gray-200 py-3 pl-3.5 pr-3 text-sm font-medium text-gray-700 dark:border-gray-800 dark:text-gray-400"
>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.58822 4.58398C6.58822 4.30784 6.81207 4.08398 7.08822 4.08398H15.4154C15.6915 4.08398 15.9154 4.30784 15.9154 4.58398L15.9154 12.9128C15.9154 13.189 15.6916 13.4128 15.4154 13.4128H7.08821C6.81207 13.4128 6.58822 13.189 6.58822 12.9128V4.58398ZM7.08822 2.58398C5.98365 2.58398 5.08822 3.47942 5.08822 4.58398V5.09416H4.58496C3.48039 5.09416 2.58496 5.98959 2.58496 7.09416V15.4161C2.58496 16.5207 3.48039 17.4161 4.58496 17.4161H12.9069C14.0115 17.4161 14.9069 16.5207 14.9069 15.4161L14.9069 14.9128H15.4154C16.52 14.9128 17.4154 14.0174 17.4154 12.9128L17.4154 4.58398C17.4154 3.47941 16.52 2.58398 15.4154 2.58398H7.08822ZM13.4069 14.9128H7.08821C5.98364 14.9128 5.08822 14.0174 5.08822 12.9128V6.59416H4.58496C4.30882 6.59416 4.08496 6.81801 4.08496 7.09416V15.4161C4.08496 15.6922 4.30882 15.9161 4.58496 15.9161H12.9069C13.183 15.9161 13.4069 15.6922 13.4069 15.4161L13.4069 14.9128Z"
fill=""
/>
</svg>
<div>{{ copyText }}</div>
</button>
<input
v-model="website"
type="url"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent py-3 pl-4 pr-[90px] text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const email = ref('')
const selectedCountry = ref('US')
const selectedCountry2 = ref('US')
const phoneNumber = ref('')
const phoneNumber2 = ref('')
const url = ref('')
const website = ref('www.tailadmin.com')
const copyText = ref('Copy')
const countryCodes = {
US: '+1',
GB: '+44',
CA: '+1',
AU: '+61',
}
const updatePhoneNumber = () => {
phoneNumber.value = countryCodes[selectedCountry.value as keyof typeof countryCodes]
}
const updatePhoneNumber2 = () => {
phoneNumber2.value = countryCodes[selectedCountry2.value as keyof typeof countryCodes]
}
const copyWebsite = () => {
navigator.clipboard.writeText(website.value)
copyText.value = 'Copied!'
setTimeout(() => {
copyText.value = 'Copy'
}, 2000)
}
</script>
@@ -0,0 +1,85 @@
<template>
<div class="space-y-6">
<!-- Error State Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Email
</label>
<div class="relative">
<input
type="text"
v-model="errorEmail"
class="dark:bg-dark-900 w-full rounded-lg border border-error-300 bg-transparent px-4 py-2.5 pr-10 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-error-300 focus:outline-hidden focus:ring-3 focus:ring-error-500/10 dark:border-error-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-error-800"
/>
<span class="absolute right-3.5 top-1/2 -translate-y-1/2">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.58325 7.99967C2.58325 5.00813 5.00838 2.58301 7.99992 2.58301C10.9915 2.58301 13.4166 5.00813 13.4166 7.99967C13.4166 10.9912 10.9915 13.4163 7.99992 13.4163C5.00838 13.4163 2.58325 10.9912 2.58325 7.99967ZM7.99992 1.08301C4.17995 1.08301 1.08325 4.17971 1.08325 7.99967C1.08325 11.8196 4.17995 14.9163 7.99992 14.9163C11.8199 14.9163 14.9166 11.8196 14.9166 7.99967C14.9166 4.17971 11.8199 1.08301 7.99992 1.08301ZM7.09932 5.01639C7.09932 5.51345 7.50227 5.91639 7.99932 5.91639H7.99999C8.49705 5.91639 8.89999 5.51345 8.89999 5.01639C8.89999 4.51933 8.49705 4.11639 7.99999 4.11639H7.99932C7.50227 4.11639 7.09932 4.51933 7.09932 5.01639ZM7.99998 11.8306C7.58576 11.8306 7.24998 11.4948 7.24998 11.0806V7.29627C7.24998 6.88206 7.58576 6.54627 7.99998 6.54627C8.41419 6.54627 8.74998 6.88206 8.74998 7.29627V11.0806C8.74998 11.4948 8.41419 11.8306 7.99998 11.8306Z"
fill="#F04438"
/>
</svg>
</span>
</div>
<p class="mt-1.5 text-theme-xs text-error-500">This is an error message.</p>
</div>
<!-- Success State Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Email
</label>
<div class="relative">
<input
type="text"
v-model="successEmail"
class="dark:bg-dark-900 w-full rounded-lg border border-success-300 bg-transparent px-4 py-2.5 pr-10 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-success-300 focus:outline-hidden focus:ring-3 focus:ring-success-500/10 dark:border-success-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-success-800"
/>
<span class="absolute right-3.5 top-1/2 -translate-y-1/2">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.61792 8.00034C2.61792 5.02784 5.0276 2.61816 8.00009 2.61816C10.9726 2.61816 13.3823 5.02784 13.3823 8.00034C13.3823 10.9728 10.9726 13.3825 8.00009 13.3825C5.0276 13.3825 2.61792 10.9728 2.61792 8.00034ZM8.00009 1.11816C4.19917 1.11816 1.11792 4.19942 1.11792 8.00034C1.11792 11.8013 4.19917 14.8825 8.00009 14.8825C11.801 14.8825 14.8823 11.8013 14.8823 8.00034C14.8823 4.19942 11.801 1.11816 8.00009 1.11816ZM10.5192 7.266C10.8121 6.97311 10.8121 6.49823 10.5192 6.20534C10.2264 5.91245 9.75148 5.91245 9.45858 6.20534L7.45958 8.20434L6.54162 7.28638C6.24873 6.99349 5.77385 6.99349 5.48096 7.28638C5.18807 7.57927 5.18807 8.05415 5.48096 8.34704L6.92925 9.79533C7.0699 9.93599 7.26067 10.015 7.45958 10.015C7.6585 10.015 7.84926 9.93599 7.98991 9.79533L10.5192 7.266Z"
fill="#12B76A"
/>
</svg>
</span>
</div>
<p class="mt-1.5 text-theme-xs text-success-500">This is a success message.</p>
</div>
<!-- Disabled State Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-300 dark:text-white/15">
Email
</label>
<input
type="text"
placeholder="info@gmail.com"
disabled
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:shadow-focus-ring focus:outline-hidden disabled:border-gray-100 disabled:placeholder:text-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-gray-400 dark:focus:border-brand-300 dark:disabled:border-gray-800 dark:disabled:placeholder:text-white/15"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const errorEmail = ref('demoemail')
const successEmail = ref('demoemail@gmail.com')
</script>
@@ -0,0 +1,165 @@
<template>
<div class="relative" ref="multiSelectRef">
<div
@click="toggleDropdown"
class="dark:bg-dark-900 h-11 flex items-center w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
:class="{ 'text-gray-800 dark:text-white/90': isOpen }"
>
<span v-if="selectedItems.length === 0" class="text-gray-400"> Select items... </span>
<div class="flex flex-wrap items-center flex-auto gap-2">
<div
v-for="item in selectedItems"
:key="item.value"
class="group flex items-center justify-center h-[30px] rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800"
>
<span>{{ item.label }}</span>
<button
@click.stop="removeItem(item)"
class="pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400"
aria-label="Remove item"
>
<svg
role="button"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.40717 4.46881C3.11428 4.17591 3.11428 3.70104 3.40717 3.40815C3.70006 3.11525 4.17494 3.11525 4.46783 3.40815L6.99943 5.93975L9.53095 3.40822C9.82385 3.11533 10.2987 3.11533 10.5916 3.40822C10.8845 3.70112 10.8845 4.17599 10.5916 4.46888L8.06009 7.00041L10.5916 9.53193C10.8845 9.82482 10.8845 10.2997 10.5916 10.5926C10.2987 10.8855 9.82385 10.8855 9.53095 10.5926L6.99943 8.06107L4.46783 10.5927C4.17494 10.8856 3.70006 10.8856 3.40717 10.5927C3.11428 10.2998 3.11428 9.8249 3.40717 9.53201L5.93877 7.00041L3.40717 4.46881Z"
fill="currentColor"
/>
</svg>
</button>
</div>
</div>
<svg
class="ml-auto"
:class="{ 'transform rotate-180': isOpen }"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.39551L10.0001 12.6038L15.2084 7.39551"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<div
v-if="isOpen"
class="absolute z-10 w-full mt-1 bg-white rounded-lg shadow-sm dark:bg-gray-900"
>
<ul
class="overflow-y-auto divide-y divide-gray-200 custom-scrollbar max-h-60 dark:divide-gray-800"
role="listbox"
aria-multiselectable="true"
>
<li
v-for="item in props.options"
:key="item.value"
@click="toggleItem(item)"
class="relative flex items-center w-full px-3 py-2 border-transparent cursor-pointer first:rounded-t-lg last:rounded-b-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-800"
:class="{ 'bg-gray-50 dark:bg-white/[0.03]': isSelected(item) }"
role="option"
:aria-selected="isSelected(item)"
>
<span class="grow">{{ item.label }}</span>
<svg
v-if="isSelected(item)"
class="w-5 h-5 text-gray-400 dark:text-gray-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
></path>
</svg>
</li>
</ul>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const props = defineProps({
options: {
type: Array,
required: true,
},
modelValue: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['update:modelValue'])
const isOpen = ref(false)
const selectedItems = ref(props.modelValue)
const multiSelectRef = ref(null)
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
const toggleItem = (item) => {
const index = selectedItems.value.findIndex((selected) => selected.value === item.value)
if (index === -1) {
selectedItems.value.push(item)
} else {
selectedItems.value.splice(index, 1)
}
emit('update:modelValue', selectedItems.value)
}
const removeItem = (item) => {
const index = selectedItems.value.findIndex((selected) => selected.value === item.value)
if (index !== -1) {
selectedItems.value.splice(index, 1)
emit('update:modelValue', selectedItems.value)
}
}
const isSelected = (item) => {
return selectedItems.value.some((selected) => selected.value === item.value)
}
const handleClickOutside = (event) => {
if (multiSelectRef.value && !multiSelectRef.value.contains(event.target)) {
isOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
@@ -0,0 +1,80 @@
<template>
<div class="space-y-6">
<!-- Single Select Input -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Select Input
</label>
<div class="relative z-20 bg-transparent">
<select
v-model="singleSelect"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
:class="{ 'text-gray-800 dark:text-white/90': singleSelect }"
>
<option value="" disabled>Select Option</option>
<option value="marketing" class="text-gray-700 dark:bg-gray-900 dark:text-gray-400">
Marketing
</option>
<option value="template" class="text-gray-700 dark:bg-gray-900 dark:text-gray-400">
Template
</option>
<option value="development" class="text-gray-700 dark:bg-gray-900 dark:text-gray-400">
Development
</option>
</select>
<span
class="absolute z-30 text-gray-700 -translate-y-1/2 pointer-events-none right-4 top-1/2 dark:text-gray-400"
>
<svg
class="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke=""
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
</div>
</div>
<!-- Multiple Select Input -->
<div>
<MultipleSelect v-model="selectedItems" :options="optionss" class="w-full" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import MultipleSelect from './MultipleSelect.vue'
const optionss = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'cherry', label: 'Cherry' },
{ value: 'date', label: 'Date' },
{ value: 'elderberry', label: 'Elderberry' },
{ value: 'graphs', label: 'Graphs' },
]
const selectedItems = ref([])
const singleSelect = ref('')
const options = ref([
{ text: 'Option 1', selected: false },
{ text: 'Option 2', selected: false },
{ text: 'Option 3', selected: false },
{ text: 'Option 4', selected: false },
])
const selected = computed(() => options.value.filter((option) => option.selected))
</script>
@@ -0,0 +1,52 @@
<template>
<div class="space-y-6">
<!-- Normal Textarea -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Description
</label>
<textarea
v-model="normalDescription"
placeholder="Enter a description..."
rows="6"
class="dark:bg-dark-900 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
></textarea>
</div>
<!-- Disabled Textarea -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-300 dark:text-white/15">
Description
</label>
<textarea
v-model="disabledDescription"
placeholder="Enter a description..."
rows="6"
disabled
class="dark:bg-dark-900 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:shadow-focus-ring focus:outline-hidden focus:ring-0 disabled:border-gray-100 disabled:bg-gray-50 disabled:placeholder:text-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 dark:disabled:border-gray-800 dark:disabled:bg-white/[0.03] dark:disabled:placeholder:text-white/15"
></textarea>
</div>
<!-- Error State Textarea -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Description
</label>
<textarea
v-model="errorDescription"
placeholder="Enter a description..."
rows="6"
class="dark:bg-dark-900 w-full rounded-lg border border-error-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-error-300 focus:outline-hidden focus:ring-3 focus:ring-error-500/10 dark:border-error-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-error-800"
></textarea>
<p class="mt-1.5 text-theme-xs text-error-500">Please enter a message in the textarea.</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const normalDescription = ref('')
const disabledDescription = ref('This textarea is disabled')
const errorDescription = ref('')
</script>
@@ -0,0 +1,54 @@
<template>
<div class="min-h-screen xl:flex">
<app-sidebar />
<Backdrop />
<div
class="flex min-h-screen flex-1 flex-col transition-all duration-300 ease-in-out"
:class="[isExpanded || isHovered ? 'lg:ml-[290px]' : 'lg:ml-[90px]']"
>
<app-header />
<div class="mx-auto w-full max-w-(--breakpoint-2xl) flex-1 p-4 md:p-6">
<slot></slot>
</div>
<footer class="min-h-18 border-t border-gray-200 bg-white px-4 py-5 text-sm text-gray-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 md:px-6">
<div v-if="site.homeFooterContent.value" class="site-footer-content mx-auto w-full max-w-(--breakpoint-2xl)" v-html="site.homeFooterContent.value"></div>
<div v-else class="mx-auto flex min-h-8 w-full max-w-(--breakpoint-2xl) flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<span>Copyright 2026 {{ site.siteName.value }}. 请在合法合规场景中使用代理服务</span>
</div>
</footer>
</div>
</div>
</template>
<script setup>
import AppSidebar from './AppSidebar.vue'
import AppHeader from './AppHeader.vue'
import { useSidebar } from '@/composables/useSidebar'
import Backdrop from './Backdrop.vue'
import { useSiteStore } from '@/stores/site'
const { isExpanded, isHovered } = useSidebar()
const site = useSiteStore()
</script>
<style scoped>
.site-footer-content {
overflow-wrap: anywhere;
line-height: 1.75;
}
.site-footer-content :deep(a) {
color: #0284c7;
font-weight: 500;
}
.site-footer-content :deep(img) {
display: inline-block;
max-height: 40px;
max-width: 100%;
vertical-align: middle;
}
.site-footer-content :deep(p) {
margin: 0;
}
</style>
@@ -0,0 +1,119 @@
<template>
<header
class="sticky top-0 flex w-full bg-white border-gray-200 z-99999 dark:border-gray-800 dark:bg-gray-900 lg:border-b"
>
<div class="flex flex-col items-center justify-between grow lg:flex-row lg:px-6">
<div
class="flex items-center justify-between w-full gap-2 px-3 py-3 border-b border-gray-200 dark:border-gray-800 sm:gap-4 lg:justify-normal lg:border-b-0 lg:px-0 lg:py-4"
>
<button
@click="handleToggle"
class="flex items-center justify-center w-10 h-10 text-gray-500 border-gray-200 rounded-lg z-99999 dark:border-gray-800 dark:text-gray-400 lg:h-11 lg:w-11 lg:border"
:class="[
isMobileOpen
? 'lg:bg-transparent dark:lg:bg-transparent bg-gray-100 dark:bg-gray-800'
: '',
]"
>
<svg
v-if="isMobileOpen"
class="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.21967 7.28131C5.92678 6.98841 5.92678 6.51354 6.21967 6.22065C6.51256 5.92775 6.98744 5.92775 7.28033 6.22065L11.999 10.9393L16.7176 6.22078C17.0105 5.92789 17.4854 5.92788 17.7782 6.22078C18.0711 6.51367 18.0711 6.98855 17.7782 7.28144L13.0597 12L17.7782 16.7186C18.0711 17.0115 18.0711 17.4863 17.7782 17.7792C17.4854 18.0721 17.0105 18.0721 16.7176 17.7792L11.999 13.0607L7.28033 17.7794C6.98744 18.0722 6.51256 18.0722 6.21967 17.7794C5.92678 17.4865 5.92678 17.0116 6.21967 16.7187L10.9384 12L6.21967 7.28131Z"
fill=""
/>
</svg>
<svg
v-else
width="16"
height="12"
viewBox="0 0 16 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.583252 1C0.583252 0.585788 0.919038 0.25 1.33325 0.25H14.6666C15.0808 0.25 15.4166 0.585786 15.4166 1C15.4166 1.41421 15.0808 1.75 14.6666 1.75L1.33325 1.75C0.919038 1.75 0.583252 1.41422 0.583252 1ZM0.583252 11C0.583252 10.5858 0.919038 10.25 1.33325 10.25L14.6666 10.25C15.0808 10.25 15.4166 10.5858 15.4166 11C15.4166 11.4142 15.0808 11.75 14.6666 11.75L1.33325 11.75C0.919038 11.75 0.583252 11.4142 0.583252 11ZM1.33325 5.25C0.919038 5.25 0.583252 5.58579 0.583252 6C0.583252 6.41421 0.919038 6.75 1.33325 6.75L7.99992 6.75C8.41413 6.75 8.74992 6.41421 8.74992 6C8.74992 5.58579 8.41413 5.25 7.99992 5.25L1.33325 5.25Z"
fill="currentColor"
/>
</svg>
</button>
<HeaderLogo />
<button
@click="toggleApplicationMenu"
class="flex items-center justify-center w-10 h-10 text-gray-700 rounded-lg z-99999 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 lg:hidden"
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.99902 10.4951C6.82745 10.4951 7.49902 11.1667 7.49902 11.9951V12.0051C7.49902 12.8335 6.82745 13.5051 5.99902 13.5051C5.1706 13.5051 4.49902 12.8335 4.49902 12.0051V11.9951C4.49902 11.1667 5.1706 10.4951 5.99902 10.4951ZM17.999 10.4951C18.8275 10.4951 19.499 11.1667 19.499 11.9951V12.0051C19.499 12.8335 18.8275 13.5051 17.999 13.5051C17.1706 13.5051 16.499 12.8335 16.499 12.0051V11.9951C16.499 11.1667 17.1706 10.4951 17.999 10.4951ZM13.499 11.9951C13.499 11.1667 12.8275 10.4951 11.999 10.4951C11.1706 10.4951 10.499 11.1667 10.499 11.9951V12.0051C10.499 12.8335 11.1706 13.5051 11.999 13.5051C12.8275 13.5051 13.499 12.8335 13.499 12.0051V11.9951Z"
fill="currentColor"
/>
</svg>
</button>
<SearchBar />
</div>
<div
:class="[isApplicationMenuOpen ? 'flex' : 'hidden']"
class="items-center justify-between w-full gap-4 px-5 py-4 shadow-theme-md lg:flex lg:justify-end lg:px-0 lg:shadow-none"
>
<div class="flex items-center gap-2 2xsm:gap-3">
<ThemeToggler />
<NotificationMenu />
</div>
<UserMenu />
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useSidebar } from '@/composables/useSidebar'
import ThemeToggler from '../common/ThemeToggler.vue'
import SearchBar from './header/SearchBar.vue'
import HeaderLogo from './header/HeaderLogo.vue'
import NotificationMenu from './header/NotificationMenu.vue'
import UserMenu from './header/UserMenu.vue'
const { toggleSidebar, toggleMobileSidebar, isMobileOpen } = useSidebar()
const handleToggle = () => {
if (window.innerWidth >= 1024) {
toggleSidebar()
} else {
toggleMobileSidebar()
}
}
const dropdownOpen = ref(false)
const notifying = ref(false)
const toggleDropdown = () => {
dropdownOpen.value = !dropdownOpen.value
notifying.value = false
}
const isApplicationMenuOpen = ref(false)
const toggleApplicationMenu = () => {
isApplicationMenuOpen.value = !isApplicationMenuOpen.value
}
</script>
@@ -0,0 +1,144 @@
<template>
<aside
:class="[
'fixed mt-16 flex flex-col lg:mt-0 top-0 px-5 left-0 bg-white dark:bg-gray-900 dark:border-gray-800 text-gray-900 h-screen transition-all duration-300 ease-in-out z-99999 border-r border-gray-200',
{
'lg:w-[290px]': isExpanded || isMobileOpen || isHovered,
'lg:w-[90px]': !isExpanded && !isHovered,
'translate-x-0 w-[290px]': isMobileOpen,
'-translate-x-full': !isMobileOpen,
'lg:translate-x-0': true,
},
]"
@mouseenter="!isExpanded && (isHovered = true)"
@mouseleave="isHovered = false"
>
<div :class="['py-8 flex', !isExpanded && !isHovered ? 'lg:justify-center' : 'justify-start']">
<router-link to="/" class="flex items-center gap-3">
<img v-if="site.siteLogoUrl.value" :src="site.siteLogoUrl.value" :alt="site.siteName.value" class="h-10 w-10 shrink-0 rounded-xl object-contain shadow-theme-sm" />
<span v-else class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-500 text-base font-bold text-white shadow-theme-sm">{{ siteInitial }}</span>
<span v-if="isExpanded || isHovered || isMobileOpen">
<span class="block text-lg font-semibold text-gray-900 dark:text-white">{{ site.siteName.value }}</span>
</span>
</router-link>
</div>
<div class="flex flex-col overflow-y-auto duration-300 ease-linear no-scrollbar">
<nav class="mb-6">
<div class="flex flex-col gap-4">
<div v-for="menuGroup in menuGroups" :key="menuGroup.title">
<h2
:class="[
'mb-4 text-xs uppercase flex leading-[20px] text-gray-400',
!isExpanded && !isHovered ? 'lg:justify-center' : 'justify-start',
]"
>
<template v-if="isExpanded || isHovered || isMobileOpen">{{ menuGroup.title }}</template>
<HorizontalDots v-else />
</h2>
<ul class="flex flex-col gap-2">
<li v-for="item in menuGroup.items" :key="item.name">
<router-link
:to="item.path"
:class="[
'menu-item group',
{
'menu-item-active': isActive(item),
'menu-item-inactive': !isActive(item),
},
!isExpanded && !isHovered ? 'lg:justify-center' : 'lg:justify-start',
]"
>
<span :class="['menu-item-icon', isActive(item) ? 'menu-item-icon-active' : 'menu-item-icon-inactive']">
<component :is="item.icon" />
</span>
<span v-if="isExpanded || isHovered || isMobileOpen" class="menu-item-text">{{ item.name }}</span>
</router-link>
</li>
</ul>
</div>
</div>
</nav>
</div>
</aside>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import {
BoxCubeIcon,
Calendar2Line,
DedicatedProxyIcon,
GridIcon,
HorizontalDots,
HomeResidentialIcon,
LayoutDashboardIcon,
PageIcon,
PlugInIcon,
StaticAIcon,
StaticBIcon,
SupportIcon,
TableIcon,
UserCircleIcon,
UserGroupIcon,
} from '@/icons'
import { useSidebar } from '@/composables/useSidebar'
import { useSiteStore } from '@/stores/site'
const route = useRoute()
const { isExpanded, isMobileOpen, isHovered } = useSidebar()
const site = useSiteStore()
const siteInitial = computed(() => site.siteName.value.slice(0, 1) || '站')
const menuGroups = [
{
title: '控制台',
items: [
{ icon: LayoutDashboardIcon, name: '总览', path: '/console/dashboard' },
{ icon: BoxCubeIcon, name: '购买套餐', path: '/console/buy' },
{ icon: TableIcon, name: '我的订单', path: '/console/orders' },
{ icon: Calendar2Line, name: '我的钱包', path: '/console/wallet' },
],
},
{
title: '购买产品',
items: [
{ icon: StaticAIcon, name: '静态长效A (高带宽)', path: '/console/buy?type=staticA', productType: 'staticA' },
{ icon: StaticBIcon, name: '静态长效B(特惠)', path: '/console/buy?type=staticB', productType: 'staticB' },
{ icon: HomeResidentialIcon, name: '住宅长效', path: '/console/buy?type=home', productType: 'home' },
{ icon: DedicatedProxyIcon, name: '独享长效', path: '/console/buy?type=custom', productType: 'custom' },
],
},
{
title: '资源',
items: [
{ icon: GridIcon, name: '我的代理', path: '/console/static-assets' },
{ icon: PlugInIcon, name: '开放 API', path: '/console/open-api' },
{ icon: UserGroupIcon, name: '推广中心', path: '/console/promotion' },
],
},
{
title: '账户',
items: [
{ icon: SupportIcon, name: '实名认证', path: '/console/verify' },
{ icon: UserCircleIcon, name: '账户资料', path: '/console/profile' },
{ icon: PageIcon, name: '返回官网', path: '/' },
],
},
]
function isActive(item: { path: string; productType?: string }) {
if (item.productType) {
return route.path === '/console/buy' && route.query.type === item.productType
}
if (item.path === '/console/buy') {
return route.path === '/console/buy' && !route.query.type
}
if (item.path === '/console/open-api') {
return route.path === '/console/open-api' || route.path === '/console/open-api/docs'
}
return route.path === item.path
}
</script>
@@ -0,0 +1,12 @@
<template>
<div
v-if="isMobileOpen"
class="fixed inset-0 bg-gray-900/50 z-9999 lg:hidden"
@click="toggleMobileSidebar"
></div>
</template>
<script setup lang="ts">
import { useSidebar } from '@/composables/useSidebar'
const { toggleMobileSidebar, isMobileOpen } = useSidebar()
</script>
@@ -0,0 +1,9 @@
<template>
<div class="min-h-screen">
<main>
<slot></slot>
</main>
</div>
</template>
<script setup lang="ts"></script>
@@ -0,0 +1,9 @@
<template>
<slot></slot>
</template>
<script setup lang="ts">
import { useSidebarProvider } from '@/composables/useSidebar'
useSidebarProvider()
</script>
@@ -0,0 +1,18 @@
<template>
<div
class="mx-auto mb-10 w-full max-w-60 rounded-2xl bg-gray-50 px-4 py-5 text-center dark:bg-white/[0.03]"
>
<h3 class="mb-2 font-semibold text-gray-900 dark:text-white">#1 Tailwind CSS Dashboard</h3>
<p class="mb-4 text-gray-500 text-theme-sm dark:text-gray-400">
Leading Tailwind CSS Admin Template with 400+ UI Component and Pages.
</p>
<a
href="https://tailadmin.com/pricing"
target="_blank"
rel="nofollow"
class="flex items-center justify-center p-3 font-medium text-white rounded-lg bg-brand-500 text-theme-sm hover:bg-brand-600"
>
Purchase Plan
</a>
</div>
</template>
@@ -0,0 +1,54 @@
<template>
<slot></slot>
</template>
<script setup lang="ts">
import { ref, provide, onMounted, watch, computed } from 'vue'
type Theme = 'light' | 'dark'
const theme = ref<Theme>('light')
const isInitialized = ref(false)
const isDarkMode = computed(() => theme.value === 'dark')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
onMounted(() => {
const savedTheme = localStorage.getItem('theme') as Theme | null
const initialTheme = savedTheme || 'light' // Default to light theme
theme.value = initialTheme
isInitialized.value = true
})
watch([theme, isInitialized], ([newTheme, newIsInitialized]) => {
if (newIsInitialized) {
localStorage.setItem('theme', newTheme)
if (newTheme === 'dark') {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
})
provide('theme', {
isDarkMode,
toggleTheme,
})
</script>
<script lang="ts">
import { inject } from 'vue'
export function useTheme() {
const theme = inject('theme')
if (!theme) {
throw new Error('useTheme must be used within a ThemeProvider')
}
return theme
}
</script>
@@ -0,0 +1,16 @@
<template>
<router-link to="/" class="flex items-center gap-2 lg:hidden">
<img v-if="site.siteLogoUrl.value" :src="site.siteLogoUrl.value" :alt="site.siteName.value" class="h-9 w-9 rounded-xl object-contain" />
<span v-else class="flex h-9 w-9 items-center justify-center rounded-xl bg-brand-500 text-sm font-bold text-white">{{ siteInitial }}</span>
<span class="text-base font-semibold text-gray-900 dark:text-white">{{ site.siteName.value }}</span>
</router-link>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
import { useSiteStore } from '@/stores/site'
const site = useSiteStore()
const siteInitial = computed(() => site.siteName.value.slice(0, 1) || '站')
</script>
@@ -0,0 +1,183 @@
<template>
<div class="relative" ref="dropdownRef">
<button
class="relative flex h-11 w-11 items-center justify-center rounded-full border border-gray-200 bg-white text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
aria-label="查看公告消息"
@click="toggleDropdown"
>
<span v-if="notices.length" class="absolute right-0 top-0.5 z-1 h-2 w-2 rounded-full bg-brand-400"></span>
<BellIcon />
</button>
<div
v-if="dropdownOpen"
class="absolute -right-[180px] mt-[17px] flex w-[330px] flex-col rounded-2xl border border-gray-200 bg-white p-3 shadow-theme-lg dark:border-gray-800 dark:bg-gray-dark sm:w-[380px] lg:right-0"
>
<div class="mb-3 flex items-center justify-between border-b border-gray-100 pb-3 dark:border-gray-800">
<div>
<h3 class="text-base font-semibold text-gray-900 dark:text-white">公告消息</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">平台公告与服务提醒</p>
</div>
<button class="text-xs font-medium text-brand-500" :disabled="loading" @click="refreshNotices">
{{ loading ? '刷新中' : '刷新' }}
</button>
</div>
<div v-if="loading" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">正在加载公告...</div>
<ul v-else-if="notices.length" class="max-h-[360px] overflow-y-auto custom-scrollbar">
<li v-for="notice in notices" :key="notice.id">
<button
class="block w-full rounded-xl px-3 py-3 text-left transition hover:bg-gray-50 dark:hover:bg-white/[0.04]"
@click="openDetail(notice)"
>
<div class="mb-1 flex items-center gap-2">
<span class="rounded-full px-2 py-0.5 text-[11px] font-medium" :class="levelClass(notice.level)">
{{ levelText(notice.level) }}
</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ notice.publishTime || notice.createTime || '-' }}</span>
</div>
<p class="line-clamp-2 text-sm font-medium leading-6 text-gray-800 dark:text-white/90">{{ notice.title || '公告消息' }}</p>
</button>
</li>
</ul>
<div v-else class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">暂无公告消息</div>
</div>
<div v-if="detailVisible" class="fixed inset-0 z-999999 grid place-items-center bg-gray-950/50 p-5" @click.self="closeDetail">
<div class="w-full max-w-2xl rounded-2xl bg-white p-6 shadow-theme-xl dark:bg-gray-900">
<div class="mb-4 flex items-start justify-between gap-4">
<div>
<p class="text-xs font-medium text-brand-500">公告详情</p>
<h3 class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ detail?.title || '-' }}</h3>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ detail?.publishTime || '-' }}</p>
</div>
<button class="rounded-lg border border-gray-200 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50 dark:border-gray-800 dark:text-gray-300 dark:hover:bg-white/[0.04]" @click="closeDetail">
关闭
</button>
</div>
<div class="max-h-[55vh] overflow-y-auto rounded-xl bg-gray-50 p-4 text-sm leading-7 text-gray-700 dark:bg-white/[0.03] dark:text-gray-300" v-html="detailContent"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { BellIcon } from '@/icons'
import { MemberAPI, type NoticeDetail, type NoticeItem } from '@/api/member'
const AUTO_POPUP_SEEN_KEY = 'member_notice_auto_popup_seen_ids'
const MAX_CACHED_NOTICE_IDS = 200
const dropdownOpen = ref(false)
const loading = ref(false)
const notices = ref<NoticeItem[]>([])
const detail = ref<NoticeDetail | null>(null)
const detailVisible = ref(false)
const dropdownRef = ref<HTMLElement | null>(null)
const detailContent = computed(() => detail.value?.content || '<p>暂无内容</p>')
onMounted(() => {
document.addEventListener('click', handleClickOutside)
loadNotices({ autoPopup: true })
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
function toggleDropdown() {
dropdownOpen.value = !dropdownOpen.value
if (dropdownOpen.value && !notices.value.length) loadNotices()
}
function refreshNotices() {
void loadNotices()
}
async function loadNotices(options: { autoPopup?: boolean } = {}) {
loading.value = true
try {
const page = await MemberAPI.notices({ pageNum: 1, pageSize: 8 })
notices.value = page.list || []
} finally {
loading.value = false
}
if (options.autoPopup) await maybeShowInitialNoticePopup()
}
async function openDetail(notice: NoticeItem) {
if (!notice.id) return false
detail.value = await MemberAPI.noticeDetail(notice.id)
detailVisible.value = true
dropdownOpen.value = false
return true
}
function closeDetail() {
detailVisible.value = false
detail.value = null
}
function handleClickOutside(event: MouseEvent) {
if (detailVisible.value) return
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
dropdownOpen.value = false
}
}
function levelText(level?: string) {
if (level === 'H') return '重要'
if (level === 'M') return '提醒'
return '普通'
}
function levelClass(level?: string) {
if (level === 'H') return 'bg-error-50 text-error-600 dark:bg-error-500/15 dark:text-error-400'
if (level === 'M') return 'bg-warning-50 text-warning-700 dark:bg-warning-500/15 dark:text-orange-400'
return 'bg-brand-50 text-brand-600 dark:bg-brand-500/15 dark:text-brand-300'
}
async function maybeShowInitialNoticePopup() {
if (!notices.value.length || detailVisible.value) return
const seenIds = getAutoPopupSeenIds()
const firstUnseen = notices.value.find((notice) => {
const key = getNoticeIdKey(notice)
return key && !seenIds.has(key)
})
if (!firstUnseen) return
try {
const opened = await openDetail(firstUnseen)
if (opened) rememberAutoPopupSeen(notices.value)
} catch {
// Ignore auto-popup failures; the notice list can still be opened manually.
}
}
function rememberAutoPopupSeen(items: NoticeItem[]) {
const seenIds = getAutoPopupSeenIds()
items.forEach((notice) => {
const key = getNoticeIdKey(notice)
if (key) seenIds.add(key)
})
const cachedIds = Array.from(seenIds).slice(-MAX_CACHED_NOTICE_IDS)
localStorage.setItem(AUTO_POPUP_SEEN_KEY, JSON.stringify(cachedIds))
}
function getAutoPopupSeenIds() {
try {
const value = localStorage.getItem(AUTO_POPUP_SEEN_KEY)
const parsed = value ? JSON.parse(value) : []
return new Set(Array.isArray(parsed) ? parsed.map(String) : [])
} catch {
return new Set<string>()
}
}
function getNoticeIdKey(notice: NoticeItem) {
return notice.id ? String(notice.id) : ''
}
</script>
@@ -0,0 +1,258 @@
<template>
<div class="hidden lg:block">
<div ref="rootRef" class="relative">
<span class="absolute -translate-y-1/2 left-4 top-1/2 text-gray-500 dark:text-gray-400">
<Search class="h-5 w-5" />
</span>
<input
v-model="keyword"
type="text"
placeholder="搜索订单、代理地址或节点..."
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-200 bg-transparent py-2.5 pl-12 pr-4 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 xl:w-[430px]"
@focus="openPanel"
@keydown.enter.prevent="submitSearch"
@keydown.esc="closePanel"
/>
<span v-if="loading" class="absolute -translate-y-1/2 right-4 top-1/2 text-gray-400">
<Loader2 class="h-4 w-4 animate-spin" />
</span>
<div
v-if="panelOpen"
class="absolute left-0 right-0 top-full z-999 mt-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-theme-lg dark:border-gray-800 dark:bg-gray-900"
>
<div class="custom-scrollbar max-h-96 overflow-y-auto p-2">
<template v-if="quickResults.length">
<p class="px-3 py-2 text-xs font-medium text-gray-400">功能入口</p>
<button
v-for="item in quickResults"
:key="item.path"
type="button"
class="flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-gray-50 dark:hover:bg-white/[0.05]"
@mousedown.prevent="goToPath(item.path)"
>
<span class="min-w-0">
<span class="block truncate text-sm font-medium text-gray-800 dark:text-white/90">{{ item.title }}</span>
<span class="mt-0.5 block truncate text-xs text-gray-500 dark:text-gray-400">{{ item.description }}</span>
</span>
<ArrowUpRight class="h-4 w-4 shrink-0 text-gray-400" />
</button>
</template>
<template v-if="orderResults.length">
<p class="px-3 py-2 text-xs font-medium text-gray-400">订单</p>
<button
v-for="item in orderResults"
:key="`order-${resultText(item, 'orderNo')}`"
type="button"
class="flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-gray-50 dark:hover:bg-white/[0.05]"
@mousedown.prevent="goToOrders(item)"
>
<span class="min-w-0">
<span class="block truncate text-sm font-medium text-gray-800 dark:text-white/90">{{ resultText(item, 'orderNo') || '订单' }}</span>
<span class="mt-0.5 block truncate text-xs text-gray-500 dark:text-gray-400">{{ orderMeta(item) }}</span>
</span>
<ArrowUpRight class="h-4 w-4 shrink-0 text-gray-400" />
</button>
</template>
<template v-if="assetResults.length">
<p class="px-3 py-2 text-xs font-medium text-gray-400">我的代理</p>
<button
v-for="item in assetResults"
:key="`asset-${resultText(item, 'id') || proxyEndpoint(item)}`"
type="button"
class="flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-gray-50 dark:hover:bg-white/[0.05]"
@mousedown.prevent="goToAssets(item)"
>
<span class="min-w-0">
<span class="block truncate text-sm font-medium text-gray-800 dark:text-white/90">{{ proxyEndpoint(item) }}</span>
<span class="mt-0.5 block truncate text-xs text-gray-500 dark:text-gray-400">{{ assetMeta(item) }}</span>
</span>
<ArrowUpRight class="h-4 w-4 shrink-0 text-gray-400" />
</button>
</template>
<div v-if="normalizedKeyword && !loading && !hasResults" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
没有匹配结果
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ArrowUpRight, Loader2, Search } from 'lucide-vue-next'
import { MemberAPI } from '@/api/member'
type SearchEntry = {
title: string
description: string
path: string
keywords: string[]
}
const router = useRouter()
const rootRef = ref<HTMLElement | null>(null)
const keyword = ref('')
const panelOpen = ref(false)
const loading = ref(false)
const orderResults = ref<unknown[]>([])
const assetResults = ref<unknown[]>([])
let searchTimer: number | undefined
const entries: SearchEntry[] = [
{ title: '控制台总览', description: '账户余额、订单和资源概览', path: '/console/dashboard', keywords: ['首页', '总览', '控制台'] },
{ title: '购买代理', description: '选择商品、节点、时长和数量', path: '/console/buy', keywords: ['购买', '套餐', '下单'] },
{ title: '静态长效A (高带宽)', description: '进入高带宽产品购买页', path: '/console/buy?type=staticA', keywords: ['静态长效A', '高带宽', 'staticA'] },
{ title: '静态长效B(特惠)', description: '进入特惠产品购买页', path: '/console/buy?type=staticB', keywords: ['静态长效B', '特惠', 'staticB'] },
{ title: '住宅长效', description: '进入住宅长效产品购买页', path: '/console/buy?type=home', keywords: ['住宅', 'home'] },
{ title: '独享长效', description: '进入独享长效产品购买页', path: '/console/buy?type=custom', keywords: ['独享', 'custom'] },
{ title: '我的订单', description: '查看购买、支付和开通状态', path: '/console/orders', keywords: ['订单', '支付', '开通'] },
{ title: '我的代理', description: '查看代理地址、账号和到期时间', path: '/console/static-assets', keywords: ['代理', '节点', '资产', 'IP'] },
{ title: '我的钱包', description: '查看余额和资金流水', path: '/console/wallet', keywords: ['钱包', '余额', '流水'] },
{ title: '开放 API', description: '查看开放能力和密钥信息', path: '/console/open-api', keywords: ['API', '接口', '开放'] },
{ title: '推广中心', description: '查看推广账户和佣金', path: '/console/promotion', keywords: ['推广', '佣金', '邀请'] },
{ title: '实名认证', description: '完善实名信息,保障账户使用', path: '/console/verify', keywords: ['实名', '认证'] },
{ title: '账户资料', description: '查看账户信息和认证状态', path: '/console/profile', keywords: ['资料', '账号', '账户'] },
]
const normalizedKeyword = computed(() => keyword.value.trim())
const quickResults = computed(() => {
const term = normalizedKeyword.value.toLowerCase()
const source = term
? entries.filter((entry) =>
[entry.title, entry.description, ...entry.keywords].some((text) => text.toLowerCase().includes(term)),
)
: entries.slice(0, 6)
return source.slice(0, 6)
})
const hasResults = computed(() => quickResults.value.length > 0 || orderResults.value.length > 0 || assetResults.value.length > 0)
watch(normalizedKeyword, (value) => {
window.clearTimeout(searchTimer)
orderResults.value = []
assetResults.value = []
if (!value) {
loading.value = false
return
}
loading.value = true
searchTimer = window.setTimeout(() => {
searchRemote(value)
}, 250)
})
onMounted(() => {
document.addEventListener('mousedown', handleDocumentMouseDown)
})
onBeforeUnmount(() => {
window.clearTimeout(searchTimer)
document.removeEventListener('mousedown', handleDocumentMouseDown)
})
function openPanel() {
panelOpen.value = true
}
function closePanel() {
panelOpen.value = false
}
function handleDocumentMouseDown(event: MouseEvent) {
const target = event.target as Node | null
if (target && rootRef.value?.contains(target)) return
closePanel()
}
async function searchRemote(term: string) {
try {
const [orders, assets] = await Promise.allSettled([
MemberAPI.orders({ pageNum: 1, pageSize: 5, keywords: term }),
MemberAPI.staticAssets({ pageNum: 1, pageSize: 5, keywords: term }),
])
if (normalizedKeyword.value !== term) return
orderResults.value = orders.status === 'fulfilled' ? orders.value.list || [] : []
assetResults.value = assets.status === 'fulfilled' ? assets.value.list || [] : []
} finally {
if (normalizedKeyword.value === term) loading.value = false
}
}
function submitSearch() {
if (!normalizedKeyword.value) return
const firstEntry = quickResults.value[0]
if (firstEntry) {
goToPath(firstEntry.path)
return
}
const firstOrder = orderResults.value[0]
if (firstOrder) {
goToOrders(firstOrder)
return
}
const firstAsset = assetResults.value[0]
if (firstAsset) {
goToAssets(firstAsset)
return
}
if (looksLikeProxyKeyword(normalizedKeyword.value)) {
goToSearch('/console/static-assets', normalizedKeyword.value)
return
}
goToSearch('/console/orders', normalizedKeyword.value)
}
function goToPath(path: string) {
closePanel()
router.push(path)
}
function goToOrders(row: unknown) {
goToSearch('/console/orders', resultText(row, 'orderNo') || normalizedKeyword.value)
}
function goToAssets(row: unknown) {
goToSearch('/console/static-assets', resultText(row, 'proxyAddress') || normalizedKeyword.value)
}
function goToSearch(path: string, value: string) {
closePanel()
router.push({ path, query: { keywords: value } })
}
function resultText(row: unknown, key: string) {
if (!row || typeof row !== 'object') return ''
const value = (row as Record<string, unknown>)[key]
return value == null ? '' : String(value)
}
function orderMeta(row: unknown) {
return [resultText(row, 'orderStatus') || resultText(row, 'status') || '处理中', resultText(row, 'createTime')]
.filter(Boolean)
.join(' / ')
}
function proxyEndpoint(row: unknown) {
const address = resultText(row, 'proxyAddress')
const port = resultText(row, 'port')
return address && port ? `${address}:${port}` : address || '代理节点'
}
function assetMeta(row: unknown) {
return [
resultText(row, 'cityName') || resultText(row, 'countryName') || resultText(row, 'countryCode'),
resultText(row, 'expiredAt') || resultText(row, 'expireTime'),
]
.filter(Boolean)
.join(' / ')
}
function looksLikeProxyKeyword(value: string) {
return /^[\d.:]+$/.test(value) || value.includes('.')
}
</script>
@@ -0,0 +1,92 @@
<template>
<div class="relative" ref="dropdownRef">
<button class="flex items-center text-gray-700 dark:text-gray-400" @click.prevent="toggleDropdown">
<span class="mr-3 flex h-11 w-11 items-center justify-center overflow-hidden rounded-full bg-brand-50 text-sm font-semibold text-brand-500 dark:bg-brand-500/15 dark:text-brand-300">
{{ displayInitial }}
</span>
<span class="mr-1 hidden font-medium text-theme-sm sm:block">{{ auth.displayName.value }}</span>
<ChevronDownIcon :class="{ 'rotate-180': dropdownOpen }" />
</button>
<div
v-if="dropdownOpen"
class="absolute right-0 mt-[17px] flex w-[260px] flex-col rounded-2xl border border-gray-200 bg-white p-3 shadow-theme-lg dark:border-gray-800 dark:bg-gray-dark"
>
<div>
<span class="block font-medium text-gray-700 text-theme-sm dark:text-gray-400">
{{ auth.displayName.value }}
</span>
<span class="mt-0.5 block text-theme-xs text-gray-500 dark:text-gray-400">
ID {{ auth.user?.userId || auth.user?.id || '-' }}
</span>
</div>
<ul class="flex flex-col gap-1 border-b border-gray-200 pb-3 pt-4 dark:border-gray-800">
<li v-for="item in menuItems" :key="item.href">
<router-link
:to="item.href"
class="flex items-center gap-3 rounded-lg px-3 py-2 font-medium text-gray-700 group text-theme-sm hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
@click="closeDropdown"
>
<component :is="item.icon" class="text-gray-500 group-hover:text-gray-700 dark:group-hover:text-gray-300" />
{{ item.text }}
</router-link>
</li>
</ul>
<button
class="mt-3 flex items-center gap-3 rounded-lg px-3 py-2 font-medium text-gray-700 group text-theme-sm hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
@click="signOut"
>
<LogoutIcon class="text-gray-500 group-hover:text-gray-700 dark:group-hover:text-gray-300" />
退出登录
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ChevronDownIcon, InfoCircleIcon, LogoutIcon, SettingsIcon, UserCircleIcon } from '@/icons'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const auth = useAuthStore()
const dropdownOpen = ref(false)
const dropdownRef = ref<HTMLElement | null>(null)
const displayInitial = computed(() => auth.displayName.value.slice(0, 1))
const menuItems = [
{ href: '/console/profile', icon: UserCircleIcon, text: '账户资料' },
{ href: '/console/verify', icon: SettingsIcon, text: '实名认证' },
{ href: '/console/open-api', icon: InfoCircleIcon, text: '开放 API' },
]
function toggleDropdown() {
dropdownOpen.value = !dropdownOpen.value
}
function closeDropdown() {
dropdownOpen.value = false
}
async function signOut() {
await auth.logout()
closeDropdown()
router.push('/login')
}
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
closeDropdown()
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
@@ -0,0 +1,307 @@
<template>
<div class="relative bg-white py-4 dark:bg-gray-950">
<div class="text-center">
<h2 class="text-title-sm font-semibold text-gray-950 dark:text-white lg:text-title-md">全国IP分布热点图</h2>
<p v-if="currentId !== initialMapId" class="mt-3 text-sm text-sky-600 dark:text-sky-300">{{ currentName }}</p>
</div>
<div class="mt-10 grid items-center gap-8 lg:grid-cols-[290px_1fr_220px]">
<div class="lg:pt-4">
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-200">节点遍布全国150+城市</h3>
<ul class="mt-6 grid gap-3 text-base leading-7 text-gray-600 dark:text-gray-300">
<li v-for="item in summaryItems" :key="item" class="flex gap-2">
<span class="mt-3 h-1.5 w-1.5 shrink-0 rounded-full bg-gray-500 dark:bg-gray-400"></span>
<span>{{ item }}</span>
</li>
</ul>
</div>
<div>
<div class="relative min-h-[420px]">
<svg class="h-full min-h-[420px] w-full drop-shadow-[0_20px_36px_rgba(14,165,233,0.16)]" :viewBox="`0 0 ${svgWidth} ${svgHeight}`" role="img" aria-label="IP 节点热力地图">
<path
v-for="region in regions"
:key="region.key"
:d="region.path"
:fill="region.fill"
class="cursor-pointer stroke-white stroke-[1.3] transition hover:opacity-80 dark:stroke-gray-950"
@click="drillDown(region)"
/>
<text
v-for="region in labelRegions"
:key="`${region.key}-label`"
:x="region.labelX"
:y="region.labelY"
class="pointer-events-none select-none fill-gray-700 text-[11px] font-semibold dark:fill-gray-200"
text-anchor="middle"
>
{{ region.name }}
</text>
</svg>
<div v-if="loading" class="absolute inset-0 grid place-items-center bg-white/70 text-sm font-semibold text-sky-700 backdrop-blur dark:bg-gray-950/70 dark:text-sky-200">
正在加载地图...
</div>
<div v-if="error" class="absolute inset-x-4 bottom-4 rounded-xl bg-error-50 px-4 py-3 text-sm text-error-600 dark:bg-error-500/15 dark:text-error-300">
{{ error }}
</div>
</div>
<div class="mt-6 flex flex-wrap items-center justify-center gap-5 text-sm text-gray-700 dark:text-gray-300">
<span class="inline-flex items-center gap-2">
<i class="h-4 w-6 rounded bg-[#1f7df2]"></i>
已开通
</span>
<span class="inline-flex items-center gap-2">
<i class="h-4 w-6 rounded bg-[#d8edff]"></i>
暂未开通
</span>
</div>
</div>
<div class="flex h-full flex-col justify-end gap-4 pb-12">
<strong class="text-xl font-semibold text-gray-600 dark:text-gray-300">全国IP分布情况</strong>
<span class="text-sm text-gray-400 dark:text-gray-500">点击地图区域可进入下一级</span>
<button
v-if="currentId !== initialMapId"
type="button"
class="h-10 w-fit rounded-lg border border-sky-200 bg-white px-4 text-sm font-semibold text-sky-700 transition hover:bg-sky-50 dark:border-sky-500/20 dark:bg-white/[0.06] dark:text-sky-200"
@click="resetMap"
>
返回全国
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
type Position = [number, number]
type Ring = Position[]
type PolygonCoordinates = Ring[]
type MultiPolygonCoordinates = PolygonCoordinates[]
type GeoFeature = {
type: 'Feature'
properties?: {
name?: string
adcode?: number | string
center?: Position
centroid?: Position
childrenNum?: number
}
geometry?: {
type: 'Polygon' | 'MultiPolygon'
coordinates: PolygonCoordinates | MultiPolygonCoordinates
}
}
type GeoJson = {
type: 'FeatureCollection'
features: GeoFeature[]
}
type RegionPath = {
key: string
name: string
adcode: string
path: string
fill: string
labelX: number
labelY: number
}
const initialMapId = '100000_full'
const svgWidth = 760
const svgHeight = 500
const padding = 24
const openedColor = '#1f7df2'
const unopenedColor = '#d8edff'
const openedProvinceCodes = new Set([
'110000',
'120000',
'130000',
'140000',
'210000',
'220000',
'230000',
'310000',
'320000',
'330000',
'340000',
'350000',
'360000',
'370000',
'410000',
'420000',
'430000',
'440000',
'450000',
'460000',
'500000',
'510000',
'520000',
'530000',
])
const summaryItems = ['日更IP量超300万+', '可用率95%以上', '支持HTTP/HTTPS']
const currentId = ref(initialMapId)
const currentName = ref('全国 IP 节点热力图')
const geoJson = ref<GeoJson | null>(null)
const loading = ref(false)
const error = ref('')
const regions = computed<RegionPath[]>(() => {
if (!geoJson.value?.features?.length) return []
return buildRegionPaths(geoJson.value.features)
})
const labelRegions = computed(() => {
if (currentId.value === initialMapId) return []
return regions.value.length <= 45 ? regions.value : regions.value.slice(0, 45)
})
onMounted(() => {
void loadMap(initialMapId, '全国 IP 节点热力图', false)
})
async function drillDown(region: RegionPath) {
if (!region.adcode) {
await resetMap()
return
}
await loadMap(`${region.adcode}_full`, `${region.name} IP 节点热力图`, true)
}
async function resetMap() {
await loadMap(initialMapId, '全国 IP 节点热力图', false)
}
async function loadMap(id: string, name: string, fallbackToRoot: boolean) {
loading.value = true
error.value = ''
try {
const response = await fetch(mapUrl(id))
if (!response.ok) throw new Error(`地图数据请求失败:${response.status}`)
const payload = await response.json()
const data = normalizeGeoJson(payload)
if (!data?.features?.length) throw new Error('暂无下级地图数据')
geoJson.value = data
currentId.value = id
currentName.value = name
} catch (err) {
if (fallbackToRoot) {
await loadMap(initialMapId, '全国 IP 节点热力图', false)
error.value = '当前区域暂无下级地图,已返回全国。'
return
}
error.value = err instanceof Error ? err.message : '地图数据加载失败'
} finally {
loading.value = false
}
}
function mapUrl(id: string) {
const normalizedId = id.endsWith('_full') ? id : `${id}_full`
const base = window.location.hostname.endsWith('qiyunip.com') ? '' : 'https://www.qiyunip.com'
return `${base}/api/map/getjson?id=${encodeURIComponent(normalizedId)}`
}
function normalizeGeoJson(payload: unknown): GeoJson | null {
const value = payload as { type?: string; features?: GeoFeature[]; data?: unknown }
if (value?.type === 'FeatureCollection' && Array.isArray(value.features)) return value as GeoJson
const data = value?.data as { type?: string; features?: GeoFeature[] } | undefined
if (data?.type === 'FeatureCollection' && Array.isArray(data.features)) return data as GeoJson
return null
}
function buildRegionPaths(features: GeoFeature[]) {
const bbox = getFeatureBounds(features)
const scale = Math.min(
(svgWidth - padding * 2) / Math.max(bbox.maxX - bbox.minX, 1),
(svgHeight - padding * 2) / Math.max(bbox.maxY - bbox.minY, 1),
)
const offsetX = (svgWidth - (bbox.maxX - bbox.minX) * scale) / 2
const offsetY = (svgHeight - (bbox.maxY - bbox.minY) * scale) / 2
const project = ([lng, lat]: Position) => ({
x: offsetX + (lng - bbox.minX) * scale,
y: offsetY + (bbox.maxY - lat) * scale,
})
return features
.map((feature, index) => {
const name = feature.properties?.name || '未知区域'
const adcode = feature.properties?.adcode ? String(feature.properties.adcode) : ''
const center = feature.properties?.centroid || feature.properties?.center || getFeatureCenter(feature)
const label = project(center)
return {
key: `${adcode || name}-${index}`,
name,
adcode,
path: geometryToPath(feature, project),
fill: isOpenedRegion(adcode, index) ? openedColor : unopenedColor,
labelX: label.x,
labelY: label.y,
}
})
.filter((item) => item.path)
}
function geometryToPath(feature: GeoFeature, project: (position: Position) => { x: number; y: number }) {
const geometry = feature.geometry
if (!geometry) return ''
if (geometry.type === 'Polygon') return polygonToPath(geometry.coordinates as PolygonCoordinates, project)
if (geometry.type === 'MultiPolygon') {
return (geometry.coordinates as MultiPolygonCoordinates).map((polygon) => polygonToPath(polygon, project)).join(' ')
}
return ''
}
function polygonToPath(polygon: PolygonCoordinates, project: (position: Position) => { x: number; y: number }) {
return polygon
.map((ring) =>
ring
.map((position, index) => {
const point = project(position)
return `${index === 0 ? 'M' : 'L'}${point.x.toFixed(2)},${point.y.toFixed(2)}`
})
.join(' ') + ' Z',
)
.join(' ')
}
function getFeatureBounds(features: GeoFeature[]) {
const positions = features.flatMap((feature) => getFeaturePositions(feature))
return positions.reduce(
(bbox, [lng, lat]) => ({
minX: Math.min(bbox.minX, lng),
minY: Math.min(bbox.minY, lat),
maxX: Math.max(bbox.maxX, lng),
maxY: Math.max(bbox.maxY, lat),
}),
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
)
}
function getFeaturePositions(feature: GeoFeature) {
const geometry = feature.geometry
if (!geometry) return []
if (geometry.type === 'Polygon') return (geometry.coordinates as PolygonCoordinates).flat()
return (geometry.coordinates as MultiPolygonCoordinates).flat(2)
}
function getFeatureCenter(feature: GeoFeature): Position {
const positions = getFeaturePositions(feature)
if (!positions.length) return [105, 35]
const sum = positions.reduce((acc, [lng, lat]) => ({ lng: acc.lng + lng, lat: acc.lat + lat }), { lng: 0, lat: 0 })
return [sum.lng / positions.length, sum.lat / positions.length]
}
function isOpenedRegion(adcode: string, index: number) {
if (!adcode) return false
if (currentId.value === initialMapId) return openedProvinceCodes.has(adcode)
return index % 3 !== 0
}
</script>
@@ -0,0 +1,18 @@
<template>
<div class="flex flex-col items-center justify-center rounded-2xl border border-dashed border-gray-200 bg-gray-50 px-6 py-10 text-center dark:border-gray-800 dark:bg-white/[0.03]">
<div class="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-brand-50 text-brand-500 dark:bg-brand-500/15 dark:text-brand-400">
<BoxIcon />
</div>
<h3 class="text-base font-semibold text-gray-800 dark:text-white/90">{{ title }}</h3>
<p class="mt-2 max-w-md text-sm leading-6 text-gray-500 dark:text-gray-400">{{ description }}</p>
</div>
</template>
<script setup lang="ts">
import { BoxIcon } from '@/icons'
defineProps<{
title: string
description: string
}>()
</script>
@@ -0,0 +1,168 @@
<template>
<div ref="rootRef" class="relative">
<button
type="button"
class="app-input flex items-center justify-between gap-3 text-left"
:class="[
{ 'border-brand-300 ring-3 ring-brand-500/10 dark:border-brand-800': open },
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer',
]"
:disabled="disabled"
@click="toggle"
@keydown.down.prevent="openDropdown"
@keydown.enter.prevent="toggle"
@keydown.esc.prevent="close"
>
<span class="min-w-0 truncate" :class="selectedLabel ? 'text-gray-800 dark:text-white/90' : 'text-gray-400 dark:text-white/30'">
{{ selectedLabel || placeholder }}
</span>
<ChevronDownIcon class="h-5 w-5 shrink-0 text-gray-400 transition-transform" :class="{ 'rotate-180': open }" />
</button>
<div
v-if="open"
class="absolute left-0 right-0 top-full z-999 mt-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-theme-lg dark:border-gray-800 dark:bg-gray-900"
>
<div v-if="searchable" class="border-b border-gray-100 p-2 dark:border-gray-800">
<input
ref="searchRef"
v-model.trim="keyword"
class="h-10 w-full rounded-lg border border-gray-200 bg-gray-50 px-3 text-sm text-gray-800 outline-none transition placeholder:text-gray-400 focus:border-brand-300 focus:bg-white focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-950 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 dark:focus:bg-gray-900"
:placeholder="searchPlaceholder"
@keydown.down.prevent="focusNext"
@keydown.up.prevent="focusPrevious"
@keydown.enter.prevent="selectFocused"
@keydown.esc.prevent="close"
/>
</div>
<div class="custom-scrollbar max-h-72 overflow-y-auto p-1">
<button
v-for="(item, index) in filteredOptions"
:key="item.value || `empty-${index}`"
type="button"
class="flex min-h-10 w-full items-center justify-between gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors"
:class="[
item.value === modelValue
? 'bg-brand-50 text-brand-600 dark:bg-brand-500/15 dark:text-brand-300'
: 'text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-white/[0.05]',
index === focusedIndex ? 'bg-gray-50 dark:bg-white/[0.05]' : '',
]"
@mouseenter="focusedIndex = index"
@click="selectOption(item)"
>
<span class="min-w-0 truncate">{{ item.label }}</span>
<CheckIcon v-if="item.value === modelValue" class="h-3.5 w-3.5 shrink-0 rounded-full bg-brand-500 p-0.5 text-white" />
</button>
<div v-if="!filteredOptions.length" class="px-3 py-6 text-center text-sm text-gray-500 dark:text-gray-400">
暂无匹配选项
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { CheckIcon, ChevronDownIcon } from '@/icons'
type SelectOption = {
label: string
value: string
}
const props = withDefaults(
defineProps<{
modelValue: string
options: SelectOption[]
placeholder?: string
searchPlaceholder?: string
searchable?: boolean
disabled?: boolean
}>(),
{
placeholder: '请选择',
searchPlaceholder: '搜索选项',
searchable: true,
disabled: false,
},
)
const emit = defineEmits<{
'update:modelValue': [value: string]
change: []
}>()
const open = ref(false)
const keyword = ref('')
const focusedIndex = ref(0)
const rootRef = ref<HTMLElement | null>(null)
const searchRef = ref<HTMLInputElement | null>(null)
const selectedLabel = computed(() => props.options.find((item) => item.value === props.modelValue)?.label || '')
const filteredOptions = computed(() => {
const value = keyword.value.trim().toLowerCase()
if (!value) return props.options
return props.options.filter((item) => item.label.toLowerCase().includes(value) || item.value.toLowerCase().includes(value))
})
watch(open, async (value) => {
if (!value) return
keyword.value = ''
focusedIndex.value = Math.max(
props.options.findIndex((item) => item.value === props.modelValue),
0,
)
await nextTick()
if (props.searchable) searchRef.value?.focus()
})
watch(filteredOptions, () => {
if (focusedIndex.value >= filteredOptions.value.length) {
focusedIndex.value = Math.max(filteredOptions.value.length - 1, 0)
}
})
function toggle() {
if (props.disabled) return
open.value ? close() : openDropdown()
}
function openDropdown() {
if (props.disabled) return
open.value = true
}
function close() {
open.value = false
}
function focusNext() {
if (!filteredOptions.value.length) return
focusedIndex.value = (focusedIndex.value + 1) % filteredOptions.value.length
}
function focusPrevious() {
if (!filteredOptions.value.length) return
focusedIndex.value = (focusedIndex.value - 1 + filteredOptions.value.length) % filteredOptions.value.length
}
function selectFocused() {
const item = filteredOptions.value[focusedIndex.value]
if (item) selectOption(item)
}
function selectOption(item: SelectOption) {
emit('update:modelValue', item.value)
emit('change')
close()
}
function handleDocumentMouseDown(event: MouseEvent) {
if (!rootRef.value?.contains(event.target as Node)) close()
}
document.addEventListener('mousedown', handleDocumentMouseDown)
onBeforeUnmount(() => {
document.removeEventListener('mousedown', handleDocumentMouseDown)
})
</script>
@@ -0,0 +1,176 @@
<template>
<div>
<div class="p-5 border border-gray-200 rounded-2xl dark:border-gray-800 lg:p-6">
<div class="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between">
<div>
<h4 class="text-lg font-semibold text-gray-800 dark:text-white/90 lg:mb-6">Address</h4>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-7 2xl:gap-x-32">
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">Country</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">United States</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">City/State</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">
Phoenix, United States
</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">
Postal Code
</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">ERT 2489</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">TAX ID</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">AS4568384</p>
</div>
</div>
</div>
<button
@click="isProfileAddressModal = true"
class="flex w-full items-center justify-center gap-2 rounded-full border border-gray-300 bg-white px-4 py-3 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200 lg:inline-flex lg:w-auto"
>
<svg
class="fill-current"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.0911 2.78206C14.2125 1.90338 12.7878 1.90338 11.9092 2.78206L4.57524 10.116C4.26682 10.4244 4.0547 10.8158 3.96468 11.2426L3.31231 14.3352C3.25997 14.5833 3.33653 14.841 3.51583 15.0203C3.69512 15.1996 3.95286 15.2761 4.20096 15.2238L7.29355 14.5714C7.72031 14.4814 8.11172 14.2693 8.42013 13.9609L15.7541 6.62695C16.6327 5.74827 16.6327 4.32365 15.7541 3.44497L15.0911 2.78206ZM12.9698 3.84272C13.2627 3.54982 13.7376 3.54982 14.0305 3.84272L14.6934 4.50563C14.9863 4.79852 14.9863 5.2734 14.6934 5.56629L14.044 6.21573L12.3204 4.49215L12.9698 3.84272ZM11.2597 5.55281L5.6359 11.1766C5.53309 11.2794 5.46238 11.4099 5.43238 11.5522L5.01758 13.5185L6.98394 13.1037C7.1262 13.0737 7.25666 13.003 7.35947 12.9002L12.9833 7.27639L11.2597 5.55281Z"
fill=""
/>
</svg>
Edit
</button>
</div>
</div>
<Modal v-if="isProfileAddressModal" @close="isProfileAddressModal = false">
<template #body>
<div
class="no-scrollbar relative w-full max-w-[700px] overflow-y-auto rounded-3xl bg-white p-4 dark:bg-gray-900 lg:p-11"
>
<!-- close btn -->
<button
@click="isProfileAddressModal = false"
class="transition-color absolute right-5 top-5 z-999 flex h-11 w-11 items-center justify-center rounded-full bg-gray-100 text-gray-400 hover:bg-gray-200 hover:text-gray-600 dark:bg-gray-700 dark:bg-white/[0.05] dark:text-gray-400 dark:hover:bg-white/[0.07] dark:hover:text-gray-300"
>
<svg
class="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.04289 16.5418C5.65237 16.9323 5.65237 17.5655 6.04289 17.956C6.43342 18.3465 7.06658 18.3465 7.45711 17.956L11.9987 13.4144L16.5408 17.9565C16.9313 18.347 17.5645 18.347 17.955 17.9565C18.3455 17.566 18.3455 16.9328 17.955 16.5423L13.4129 12.0002L17.955 7.45808C18.3455 7.06756 18.3455 6.43439 17.955 6.04387C17.5645 5.65335 16.9313 5.65335 16.5408 6.04387L11.9987 10.586L7.45711 6.04439C7.06658 5.65386 6.43342 5.65386 6.04289 6.04439C5.65237 6.43491 5.65237 7.06808 6.04289 7.4586L10.5845 12.0002L6.04289 16.5418Z"
fill=""
/>
</svg>
</button>
<div class="px-2 pr-14">
<h4 class="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90">
Edit Address
</h4>
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400 lg:mb-7">
Update your details to keep your profile up-to-date.
</p>
</div>
<form class="flex flex-col">
<div class="px-2 overflow-y-auto custom-scrollbar">
<div class="grid grid-cols-1 gap-x-6 gap-y-5 lg:grid-cols-2">
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Country
</label>
<input
type="text"
value="United States"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
City/State
</label>
<input
type="text"
value="Poenix, Arizona, United States"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Postal Code
</label>
<input
type="text"
value="ERT 2489"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
TAX ID
</label>
<input
type="text"
value="AS4568384"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
<div class="flex items-center gap-3 mt-6 lg:justify-end">
<button
@click="isProfileAddressModal = false"
type="button"
class="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
>
Close
</button>
<button
@click="saveProfile"
type="button"
class="flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
>
Save Changes
</button>
</div>
</form>
</div>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const isProfileAddressModal = ref(false)
const saveProfile = () => {
// Implement save profile logic here
console.log('Profile saved')
isProfileInfoModal.value = false
}
</script>
<style></style>
@@ -0,0 +1,14 @@
<template>
<div class="fixed inset-0 flex items-center justify-center overflow-y-auto modal z-99999">
<div
class="fixed inset-0 h-full w-full bg-gray-400/50 backdrop-blur-[32px]"
aria-hidden="true"
@click="$emit('close')"
></div>
<slot name="body"></slot>
</div>
</template>
<script setup>
// No additional setup needed
</script>
@@ -0,0 +1,264 @@
<template>
<div>
<div class="p-5 mb-6 border border-gray-200 rounded-2xl dark:border-gray-800 lg:p-6">
<div class="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between">
<div>
<h4 class="text-lg font-semibold text-gray-800 dark:text-white/90 lg:mb-6">
Personal Information
</h4>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-7 2xl:gap-x-32">
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">First Name</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">Musharof</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">Last Name</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">Chowdhury</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">
Email address
</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">
randomuser@pimjo.com
</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">Phone</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">+09 363 398 46</p>
</div>
<div>
<p class="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">Bio</p>
<p class="text-sm font-medium text-gray-800 dark:text-white/90">Team Manager</p>
</div>
</div>
</div>
<button class="edit-button" @click="isProfileInfoModal = true">
<svg
class="fill-current"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.0911 2.78206C14.2125 1.90338 12.7878 1.90338 11.9092 2.78206L4.57524 10.116C4.26682 10.4244 4.0547 10.8158 3.96468 11.2426L3.31231 14.3352C3.25997 14.5833 3.33653 14.841 3.51583 15.0203C3.69512 15.1996 3.95286 15.2761 4.20096 15.2238L7.29355 14.5714C7.72031 14.4814 8.11172 14.2693 8.42013 13.9609L15.7541 6.62695C16.6327 5.74827 16.6327 4.32365 15.7541 3.44497L15.0911 2.78206ZM12.9698 3.84272C13.2627 3.54982 13.7376 3.54982 14.0305 3.84272L14.6934 4.50563C14.9863 4.79852 14.9863 5.2734 14.6934 5.56629L14.044 6.21573L12.3204 4.49215L12.9698 3.84272ZM11.2597 5.55281L5.6359 11.1766C5.53309 11.2794 5.46238 11.4099 5.43238 11.5522L5.01758 13.5185L6.98394 13.1037C7.1262 13.0737 7.25666 13.003 7.35947 12.9002L12.9833 7.27639L11.2597 5.55281Z"
fill=""
/>
</svg>
Edit
</button>
</div>
</div>
<Modal v-if="isProfileInfoModal" @close="isProfileInfoModal = false">
<template #body>
<div
class="no-scrollbar relative w-full max-w-[700px] overflow-y-auto rounded-3xl bg-white p-4 dark:bg-gray-900 lg:p-11"
>
<!-- close btn -->
<button
@click="isProfileInfoModal = false"
class="transition-color absolute right-5 top-5 z-999 flex h-11 w-11 items-center justify-center rounded-full bg-gray-100 text-gray-400 hover:bg-gray-200 hover:text-gray-600 dark:bg-gray-700 dark:bg-white/[0.05] dark:text-gray-400 dark:hover:bg-white/[0.07] dark:hover:text-gray-300"
>
<svg
class="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.04289 16.5418C5.65237 16.9323 5.65237 17.5655 6.04289 17.956C6.43342 18.3465 7.06658 18.3465 7.45711 17.956L11.9987 13.4144L16.5408 17.9565C16.9313 18.347 17.5645 18.347 17.955 17.9565C18.3455 17.566 18.3455 16.9328 17.955 16.5423L13.4129 12.0002L17.955 7.45808C18.3455 7.06756 18.3455 6.43439 17.955 6.04387C17.5645 5.65335 16.9313 5.65335 16.5408 6.04387L11.9987 10.586L7.45711 6.04439C7.06658 5.65386 6.43342 5.65386 6.04289 6.04439C5.65237 6.43491 5.65237 7.06808 6.04289 7.4586L10.5845 12.0002L6.04289 16.5418Z"
fill=""
/>
</svg>
</button>
<div class="px-2 pr-14">
<h4 class="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90">
Edit Personal Information
</h4>
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400 lg:mb-7">
Update your details to keep your profile up-to-date.
</p>
</div>
<form class="flex flex-col">
<div class="custom-scrollbar h-[458px] overflow-y-auto p-2">
<div>
<h5 class="mb-5 text-lg font-medium text-gray-800 dark:text-white/90 lg:mb-6">
Social Links
</h5>
<div class="grid grid-cols-1 gap-x-6 gap-y-5 lg:grid-cols-2">
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Facebook
</label>
<input
type="text"
value="https://www.facebook.com/PimjoHQ"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
X.com
</label>
<input
type="text"
value="https://x.com/PimjoHQ"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Linkedin
</label>
<input
type="text"
value="https://www.linkedin.com/company/pimjo/posts/?feedView=all"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Instagram
</label>
<input
type="text"
value="https://instagram.com/PimjoHQ"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
<div class="mt-7">
<h5 class="mb-5 text-lg font-medium text-gray-800 dark:text-white/90 lg:mb-6">
Personal Information
</h5>
<div class="grid grid-cols-1 gap-x-6 gap-y-5 lg:grid-cols-2">
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
First Name
</label>
<input
type="text"
value="Musharof"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Last Name
</label>
<input
type="text"
value="Chowdhury"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Email Address
</label>
<input
type="text"
value="emirhanboruch55@gmail.com"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Phone
</label>
<input
type="text"
value="+09 363 398 46"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Bio
</label>
<input
type="text"
value="Team Manager"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
</div>
<div class="flex items-center gap-3 px-2 mt-6 lg:justify-end">
<button
@click="isProfileInfoModal = false"
type="button"
class="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
>
Close
</button>
<button
@click="saveProfile"
type="button"
class="flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
>
Save Changes
</button>
</div>
</form>
</div>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const isProfileInfoModal = ref(false)
const saveProfile = () => {
// Implement save profile logic here
console.log('Profile saved')
isProfileInfoModal.value = false
}
</script>
@@ -0,0 +1,325 @@
<template>
<div>
<div class="p-5 mb-6 border border-gray-200 rounded-2xl dark:border-gray-800 lg:p-6">
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
<div class="flex flex-col items-center w-full gap-6 xl:flex-row">
<div
class="w-20 h-20 overflow-hidden border border-gray-200 rounded-full dark:border-gray-800"
>
<img src="/images/user/owner.jpg" alt="user" />
</div>
<div class="order-3 xl:order-2">
<h4
class="mb-2 text-lg font-semibold text-center text-gray-800 dark:text-white/90 xl:text-left"
>
Musharof Chowdhury
</h4>
<div
class="flex flex-col items-center gap-1 text-center xl:flex-row xl:gap-3 xl:text-left"
>
<p class="text-sm text-gray-500 dark:text-gray-400">Team Manager</p>
<div class="hidden h-3.5 w-px bg-gray-300 dark:bg-gray-700 xl:block"></div>
<p class="text-sm text-gray-500 dark:text-gray-400">Arizona, United States</p>
</div>
</div>
<div class="flex items-center order-2 gap-2 grow xl:order-3 xl:justify-end">
<a
href="https://www.facebook.com/PimjoHQ"
target="_blank"
rel="noopener"
class="social-button"
>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6666 11.2503H13.7499L14.5833 7.91699H11.6666V6.25033C11.6666 5.39251 11.6666 4.58366 13.3333 4.58366H14.5833V1.78374C14.3118 1.7477 13.2858 1.66699 12.2023 1.66699C9.94025 1.66699 8.33325 3.04771 8.33325 5.58342V7.91699H5.83325V11.2503H8.33325V18.3337H11.6666V11.2503Z"
fill=""
/>
</svg>
</a>
<a href="https://x.com/PimjoHQ" target="_blank" rel="noopener" class="social-button">
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.1708 1.875H17.9274L11.9049 8.75833L18.9899 18.125H13.4424L9.09742 12.4442L4.12578 18.125H1.36745L7.80912 10.7625L1.01245 1.875H6.70078L10.6283 7.0675L15.1708 1.875ZM14.2033 16.475H15.7308L5.87078 3.43833H4.23162L14.2033 16.475Z"
fill=""
/>
</svg>
</a>
<a
href="https://www.linkedin.com/company/pimjo/"
target="_blank"
rel="noopener"
class="social-button"
>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.78381 4.16645C5.78351 4.84504 5.37181 5.45569 4.74286 5.71045C4.11391 5.96521 3.39331 5.81321 2.92083 5.32613C2.44836 4.83904 2.31837 4.11413 2.59216 3.49323C2.86596 2.87233 3.48886 2.47942 4.16715 2.49978C5.06804 2.52682 5.78422 3.26515 5.78381 4.16645ZM5.83381 7.06645H2.50048V17.4998H5.83381V7.06645ZM11.1005 7.06645H7.78381V17.4998H11.0672V12.0248C11.0672 8.97475 15.0422 8.69142 15.0422 12.0248V17.4998H18.3338V10.8914C18.3338 5.74978 12.4505 5.94145 11.0672 8.46642L11.1005 7.06645Z"
fill=""
/>
</svg>
</a>
<a
href="https://www.instagram.com/PimjoHQ"
target="_blank"
rel="noopener"
class="social-button"
>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.8567 1.66699C11.7946 1.66854 12.2698 1.67351 12.6805 1.68573L12.8422 1.69102C13.0291 1.69766 13.2134 1.70599 13.4357 1.71641C14.3224 1.75738 14.9273 1.89766 15.4586 2.10391C16.0078 2.31572 16.4717 2.60183 16.9349 3.06503C17.3974 3.52822 17.6836 3.99349 17.8961 4.54141C18.1016 5.07197 18.2419 5.67753 18.2836 6.56433C18.2935 6.78655 18.3015 6.97088 18.3081 7.15775L18.3133 7.31949C18.3255 7.73011 18.3311 8.20543 18.3328 9.1433L18.3335 9.76463C18.3336 9.84055 18.3336 9.91888 18.3336 9.99972L18.3335 10.2348L18.333 10.8562C18.3314 11.794 18.3265 12.2694 18.3142 12.68L18.3089 12.8417C18.3023 13.0286 18.294 13.213 18.2836 13.4351C18.2426 14.322 18.1016 14.9268 17.8961 15.458C17.6842 16.0074 17.3974 16.4713 16.9349 16.9345C16.4717 17.397 16.0057 17.6831 15.4586 17.8955C14.9273 18.1011 14.3224 18.2414 13.4357 18.2831C13.2134 18.293 13.0291 18.3011 12.8422 18.3076L12.6805 18.3128C12.2698 18.3251 11.7946 18.3306 10.8567 18.3324L10.2353 18.333C10.1594 18.333 10.0811 18.333 10.0002 18.333H9.76516L9.14375 18.3325C8.20591 18.331 7.7306 18.326 7.31997 18.3137L7.15824 18.3085C6.97136 18.3018 6.78703 18.2935 6.56481 18.2831C5.67801 18.2421 5.07384 18.1011 4.5419 17.8955C3.99328 17.6838 3.5287 17.397 3.06551 16.9345C2.60231 16.4713 2.3169 16.0053 2.1044 15.458C1.89815 14.9268 1.75856 14.322 1.7169 13.4351C1.707 13.213 1.69892 13.0286 1.69238 12.8417L1.68714 12.68C1.67495 12.2694 1.66939 11.794 1.66759 10.8562L1.66748 9.1433C1.66903 8.20543 1.67399 7.73011 1.68621 7.31949L1.69151 7.15775C1.69815 6.97088 1.70648 6.78655 1.7169 6.56433C1.75786 5.67683 1.89815 5.07266 2.1044 4.54141C2.3162 3.9928 2.60231 3.52822 3.06551 3.06503C3.5287 2.60183 3.99398 2.31641 4.5419 2.10391C5.07315 1.89766 5.67731 1.75808 6.56481 1.71641C6.78703 1.70652 6.97136 1.69844 7.15824 1.6919L7.31997 1.68666C7.7306 1.67446 8.20591 1.6689 9.14375 1.6671L10.8567 1.66699ZM10.0002 5.83308C7.69781 5.83308 5.83356 7.69935 5.83356 9.99972C5.83356 12.3021 7.69984 14.1664 10.0002 14.1664C12.3027 14.1664 14.1669 12.3001 14.1669 9.99972C14.1669 7.69732 12.3006 5.83308 10.0002 5.83308ZM10.0002 7.49974C11.381 7.49974 12.5002 8.61863 12.5002 9.99972C12.5002 11.3805 11.3813 12.4997 10.0002 12.4997C8.6195 12.4997 7.50023 11.3809 7.50023 9.99972C7.50023 8.61897 8.61908 7.49974 10.0002 7.49974ZM14.3752 4.58308C13.8008 4.58308 13.3336 5.04967 13.3336 5.62403C13.3336 6.19841 13.8002 6.66572 14.3752 6.66572C14.9496 6.66572 15.4169 6.19913 15.4169 5.62403C15.4169 5.04967 14.9488 4.58236 14.3752 4.58308Z"
fill=""
/>
</svg>
</a>
</div>
</div>
<button @click="isProfileInfoModal = true" class="edit-button">
<svg
class="fill-current"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.0911 2.78206C14.2125 1.90338 12.7878 1.90338 11.9092 2.78206L4.57524 10.116C4.26682 10.4244 4.0547 10.8158 3.96468 11.2426L3.31231 14.3352C3.25997 14.5833 3.33653 14.841 3.51583 15.0203C3.69512 15.1996 3.95286 15.2761 4.20096 15.2238L7.29355 14.5714C7.72031 14.4814 8.11172 14.2693 8.42013 13.9609L15.7541 6.62695C16.6327 5.74827 16.6327 4.32365 15.7541 3.44497L15.0911 2.78206ZM12.9698 3.84272C13.2627 3.54982 13.7376 3.54982 14.0305 3.84272L14.6934 4.50563C14.9863 4.79852 14.9863 5.2734 14.6934 5.56629L14.044 6.21573L12.3204 4.49215L12.9698 3.84272ZM11.2597 5.55281L5.6359 11.1766C5.53309 11.2794 5.46238 11.4099 5.43238 11.5522L5.01758 13.5185L6.98394 13.1037C7.1262 13.0737 7.25666 13.003 7.35947 12.9002L12.9833 7.27639L11.2597 5.55281Z"
fill=""
/>
</svg>
Edit
</button>
</div>
</div>
<Modal v-if="isProfileInfoModal" @close="isProfileInfoModal = false">
<template #body>
<div
class="no-scrollbar relative w-full max-w-[700px] overflow-y-auto rounded-3xl bg-white p-4 dark:bg-gray-900 lg:p-11"
>
<!-- close btn -->
<button
@click="isProfileInfoModal = false"
class="transition-color absolute right-5 top-5 z-999 flex h-11 w-11 items-center justify-center rounded-full bg-gray-100 text-gray-400 hover:bg-gray-200 hover:text-gray-600 dark:bg-gray-700 dark:bg-white/[0.05] dark:text-gray-400 dark:hover:bg-white/[0.07] dark:hover:text-gray-300"
>
<svg
class="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.04289 16.5418C5.65237 16.9323 5.65237 17.5655 6.04289 17.956C6.43342 18.3465 7.06658 18.3465 7.45711 17.956L11.9987 13.4144L16.5408 17.9565C16.9313 18.347 17.5645 18.347 17.955 17.9565C18.3455 17.566 18.3455 16.9328 17.955 16.5423L13.4129 12.0002L17.955 7.45808C18.3455 7.06756 18.3455 6.43439 17.955 6.04387C17.5645 5.65335 16.9313 5.65335 16.5408 6.04387L11.9987 10.586L7.45711 6.04439C7.06658 5.65386 6.43342 5.65386 6.04289 6.04439C5.65237 6.43491 5.65237 7.06808 6.04289 7.4586L10.5845 12.0002L6.04289 16.5418Z"
fill=""
/>
</svg>
</button>
<div class="px-2 pr-14">
<h4 class="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90">
Edit Personal Information
</h4>
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400 lg:mb-7">
Update your details to keep your profile up-to-date.
</p>
</div>
<form class="flex flex-col">
<div class="custom-scrollbar h-[458px] overflow-y-auto p-2">
<div>
<h5 class="mb-5 text-lg font-medium text-gray-800 dark:text-white/90 lg:mb-6">
Social Links
</h5>
<div class="grid grid-cols-1 gap-x-6 gap-y-5 lg:grid-cols-2">
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Facebook
</label>
<input
type="text"
value="https://www.facebook.com/PimjoHQ"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
X.com
</label>
<input
type="text"
value="https://x.com/PimjoHQ"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Linkedin
</label>
<input
type="text"
value="https://www.linkedin.com/company/pimjo/posts/?feedView=all"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div>
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Instagram
</label>
<input
type="text"
value="https://instagram.com/PimjoHQ"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
<div class="mt-7">
<h5 class="mb-5 text-lg font-medium text-gray-800 dark:text-white/90 lg:mb-6">
Personal Information
</h5>
<div class="grid grid-cols-1 gap-x-6 gap-y-5 lg:grid-cols-2">
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
First Name
</label>
<input
type="text"
value="Musharof"
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Last Name
</label>
<input
type="text"
value="Chowdhury"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Email Address
</label>
<input
type="text"
value="randomuser@pimjo.com"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2 lg:col-span-1">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Phone
</label>
<input
type="text"
value="+09 363 398 46"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
<div class="col-span-2">
<label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
>
Bio
</label>
<input
type="text"
value="Team Manager"
class="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
</div>
<div class="flex items-center gap-3 px-2 mt-6 lg:justify-end">
<button
@click="isProfileInfoModal = false"
type="button"
class="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
>
Close
</button>
<button
@click="saveProfile"
type="button"
class="flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
>
Save Changes
</button>
</div>
</form>
</div>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const isProfileInfoModal = ref(false)
const saveProfile = () => {
// Implement save profile logic here
console.log('Profile saved')
isProfileInfoModal.value = false
}
</script>
@@ -0,0 +1,142 @@
<template>
<div
class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]"
>
<div class="max-w-full overflow-x-auto custom-scrollbar">
<table class="min-w-full">
<thead>
<tr class="border-b border-gray-200 dark:border-gray-700">
<th class="px-5 py-3 text-left w-3/11 sm:px-6">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">User</p>
</th>
<th class="px-5 py-3 text-left w-2/11 sm:px-6">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Project Name</p>
</th>
<th class="px-5 py-3 text-left w-2/11 sm:px-6">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Team</p>
</th>
<th class="px-5 py-3 text-left w-2/11 sm:px-6">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Status</p>
</th>
<th class="px-5 py-3 text-left w-2/11 sm:px-6">
<p class="font-medium text-gray-500 text-theme-xs dark:text-gray-400">Budget</p>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tr
v-for="(user, index) in users"
:key="index"
class="border-t border-gray-100 dark:border-gray-800"
>
<td class="px-5 py-4 sm:px-6">
<div class="flex items-center gap-3">
<div class="w-10 h-10 overflow-hidden rounded-full">
<img :src="user.avatar" :alt="user.name" />
</div>
<div>
<span class="block font-medium text-gray-800 text-theme-sm dark:text-white/90">
{{ user.name }}
</span>
<span class="block text-gray-500 text-theme-xs dark:text-gray-400">
{{ user.role }}
</span>
</div>
</div>
</td>
<td class="px-5 py-4 sm:px-6">
<p class="text-gray-500 text-theme-sm dark:text-gray-400">{{ user.project }}</p>
</td>
<td class="px-5 py-4 sm:px-6">
<div class="flex -space-x-2">
<div
v-for="(member, memberIndex) in user.team"
:key="memberIndex"
class="w-6 h-6 overflow-hidden border-2 border-white rounded-full dark:border-gray-900"
>
<img :src="member" alt="team member" />
</div>
</div>
</td>
<td class="px-5 py-4 sm:px-6">
<span
:class="[
'rounded-full px-2 py-0.5 text-theme-xs font-medium',
{
'bg-success-50 text-success-700 dark:bg-success-500/15 dark:text-success-500':
user.status === 'Active',
'bg-warning-50 text-warning-700 dark:bg-warning-500/15 dark:text-warning-400':
user.status === 'Pending',
'bg-error-50 text-error-700 dark:bg-error-500/15 dark:text-error-500':
user.status === 'Cancel',
},
]"
>
{{ user.status }}
</span>
</td>
<td class="px-5 py-4 sm:px-6">
<p class="text-gray-500 text-theme-sm dark:text-gray-400">{{ user.budget }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const users = ref([
{
name: 'Lindsey Curtis',
role: 'Web Designer',
avatar: '/images/user/user-17.jpg',
project: 'Agency Website',
team: ['/images/user/user-22.jpg', '/images/user/user-23.jpg', '/images/user/user-24.jpg'],
status: 'Active',
budget: '3.9K',
},
{
name: 'Kaiya George',
role: 'Project Manager',
avatar: '/images/user/user-18.jpg',
project: 'Technology',
team: ['/images/user/user-25.jpg', '/images/user/user-26.jpg'],
status: 'Pending',
budget: '24.9K',
},
{
name: 'Zain Geidt',
role: 'Content Writer',
avatar: '/images/user/user-19.jpg',
project: 'Blog Writing',
team: ['/images/user/user-27.jpg'],
status: 'Active',
budget: '12.7K',
},
{
name: 'Abram Schleifer',
role: 'Digital Marketer',
avatar: '/images/user/user-20.jpg',
project: 'Social Media',
team: ['/images/user/user-28.jpg', '/images/user/user-29.jpg', '/images/user/user-30.jpg'],
status: 'Cancel',
budget: '2.8K',
},
{
name: 'Carla George',
role: 'Front-end Developer',
avatar: '/images/user/user-21.jpg',
project: 'Website',
team: ['/images/user/user-31.jpg', '/images/user/user-32.jpg', '/images/user/user-33.jpg'],
status: 'Active',
budget: '4.5K',
},
])
</script>
<style scoped>
/* Add any additional styles here if needed */
</style>
+72
View File
@@ -0,0 +1,72 @@
<template>
<div :class="['rounded-xl border p-4', variantClasses[variant].container]">
<div class="flex items-start gap-3">
<div :class="['-mt-0.5', variantClasses[variant].icon]">
<component :is="icons[variant]" />
</div>
<div>
<h4 class="mb-1 text-sm font-semibold text-gray-800 dark:text-white/90">
{{ title }}
</h4>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ message }}</p>
<router-link
v-if="showLink"
:to="linkHref"
class="inline-block mt-3 text-sm font-medium text-gray-500 underline dark:text-gray-400"
>
{{ linkText }}
</router-link>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { SuccessIcon, ErrorIcon, WarningIcon, InfoCircleIcon } from '@/icons'
import { computed } from 'vue'
interface AlertProps {
variant: 'success' | 'error' | 'warning' | 'info'
title: string
message: string
showLink?: boolean
linkHref?: string
linkText?: string
}
const props = withDefaults(defineProps<AlertProps>(), {
showLink: false,
linkHref: '#',
linkText: 'Learn more',
})
const variantClasses = {
success: {
container: 'border-success-500 bg-success-50 dark:border-success-500/30 dark:bg-success-500/15',
icon: 'text-success-500',
},
error: {
container: 'border-error-500 bg-error-50 dark:border-error-500/30 dark:bg-error-500/15',
icon: 'text-error-500',
},
warning: {
container: 'border-warning-500 bg-warning-50 dark:border-warning-500/30 dark:bg-warning-500/15',
icon: 'text-warning-500',
},
info: {
container:
'border-blue-light-500 bg-blue-light-50 dark:border-blue-light-500/30 dark:bg-blue-light-500/15',
icon: 'text-blue-light-500',
},
}
const icons = {
success: SuccessIcon,
error: ErrorIcon,
warning: WarningIcon,
info: InfoCircleIcon,
}
</script>
@@ -0,0 +1,52 @@
<template>
<div :class="['relative rounded-full', sizeClasses[size]]">
<img :src="src" :alt="alt" class="object-cover rounded-full" />
<span
v-if="status !== 'none'"
:class="[
'absolute bottom-0 right-0 rounded-full border-[1.5px] border-white dark:border-gray-900',
statusSizeClasses[size],
statusColorClasses[status] || '',
]"
></span>
</div>
</template>
<script setup lang="ts">
interface AvatarProps {
src: string
alt?: string
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'
status?: 'online' | 'offline' | 'busy' | 'none'
}
const props = withDefaults(defineProps<AvatarProps>(), {
alt: 'User Avatar',
size: 'medium',
status: 'none',
})
const sizeClasses = {
xsmall: 'h-6 w-6 max-w-6',
small: 'h-8 w-8 max-w-8',
medium: 'h-10 w-10 max-w-10',
large: 'h-12 w-12 max-w-12',
xlarge: 'h-14 w-14 max-w-14',
xxlarge: 'h-16 w-16 max-w-16',
}
const statusSizeClasses = {
xsmall: 'h-1.5 w-1.5 max-w-1.5',
small: 'h-2 w-2 max-w-2',
medium: 'h-2.5 w-2.5 max-w-2.5',
large: 'h-3 w-3 max-w-3',
xlarge: 'h-3.5 w-3.5 max-w-3.5',
xxlarge: 'h-4 w-4 max-w-4',
}
const statusColorClasses = {
online: 'bg-success-500',
offline: 'bg-error-400',
busy: 'bg-warning-500',
}
</script>
+65
View File
@@ -0,0 +1,65 @@
<template>
<span :class="[baseStyles, sizeClass, colorStyles]">
<span v-if="startIcon" class="mr-1">
<component :is="startIcon" />
</span>
<slot></slot>
<span v-if="endIcon" class="ml-1">
<component :is="endIcon" />
</span>
</span>
</template>
<script setup lang="ts">
import { computed } from 'vue'
type BadgeVariant = 'light' | 'solid'
type BadgeSize = 'sm' | 'md'
type BadgeColor = 'primary' | 'success' | 'error' | 'warning' | 'info' | 'light' | 'dark'
interface BadgeProps {
variant?: BadgeVariant
size?: BadgeSize
color?: BadgeColor
startIcon?: object
endIcon?: object
}
const props = withDefaults(defineProps<BadgeProps>(), {
variant: 'light',
color: 'primary',
size: 'md',
})
const baseStyles =
'inline-flex items-center px-2.5 py-0.5 justify-center gap-1 rounded-full font-medium capitalize'
const sizeStyles = {
sm: 'text-theme-xs',
md: 'text-sm',
}
const variants = {
light: {
primary: 'bg-brand-50 text-brand-500 dark:bg-brand-500/15 dark:text-brand-400',
success: 'bg-success-50 text-success-600 dark:bg-success-500/15 dark:text-success-500',
error: 'bg-error-50 text-error-600 dark:bg-error-500/15 dark:text-error-500',
warning: 'bg-warning-50 text-warning-600 dark:bg-warning-500/15 dark:text-orange-400',
info: 'bg-blue-light-50 text-blue-light-500 dark:bg-blue-light-500/15 dark:text-blue-light-500',
light: 'bg-gray-100 text-gray-700 dark:bg-white/5 dark:text-white/80',
dark: 'bg-gray-500 text-white dark:bg-white/5 dark:text-white',
},
solid: {
primary: 'bg-brand-500 text-white dark:text-white',
success: 'bg-success-500 text-white dark:text-white',
error: 'bg-error-500 text-white dark:text-white',
warning: 'bg-warning-500 text-white dark:text-white',
info: 'bg-blue-light-500 text-white dark:text-white',
light: 'bg-gray-400 dark:bg-white/5 text-white dark:text-white/80',
dark: 'bg-gray-700 text-white dark:text-white',
},
}
const sizeClass = computed(() => sizeStyles[props.size])
const colorStyles = computed(() => variants[props.variant][props.color])
</script>
@@ -0,0 +1,59 @@
<template>
<button
:class="[
'inline-flex items-center justify-center font-medium gap-2 rounded-lg transition',
sizeClasses[size],
variantClasses[variant],
className,
{ 'cursor-not-allowed opacity-50': disabled },
]"
@click="onClick"
:disabled="disabled"
>
<span v-if="startIcon" class="flex items-center">
<component :is="startIcon" />
</span>
<slot></slot>
<span v-if="endIcon" class="flex items-center">
<component :is="endIcon" />
</span>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
interface ButtonProps {
size?: 'sm' | 'md'
variant?: 'primary' | 'outline'
startIcon?: object
endIcon?: object
onClick?: () => void
className?: string
disabled?: boolean
}
const props = withDefaults(defineProps<ButtonProps>(), {
size: 'md',
variant: 'primary',
className: '',
disabled: false,
})
const sizeClasses = {
sm: 'px-4 py-3 text-sm',
md: 'px-5 py-3.5 text-sm',
}
const variantClasses = {
primary: 'bg-brand-500 text-white shadow-theme-xs hover:bg-brand-600 disabled:bg-brand-300',
outline:
'bg-white text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-400 dark:ring-gray-700 dark:hover:bg-white/[0.03] dark:hover:text-gray-300',
}
const onClick = () => {
if (!props.disabled && props.onClick) {
props.onClick()
}
}
</script>
+20
View File
@@ -0,0 +1,20 @@
<template>
<div class="fixed inset-0 flex items-center justify-center overflow-y-auto z-99999">
<div
v-if="fullScreenBackdrop"
class="fixed inset-0 h-full w-full bg-gray-400/50 backdrop-blur-[32px]"
aria-hidden="true"
@click="$emit('close')"
></div>
<slot name="body"></slot>
</div>
</template>
<script setup lang="ts">
interface ModalProps {
fullScreenBackdrop?: boolean
}
defineProps<ModalProps>()
defineEmits(['close'])
</script>
@@ -0,0 +1,41 @@
<template>
<div :class="['overflow-hidden rounded-lg', aspectRatioClass, className]">
<iframe
:src="`https://www.youtube.com/embed/${videoId}`"
:title="title"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
class="w-full h-full"
></iframe>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
type AspectRatio = '16:9' | '4:3' | '21:9' | '1:1'
interface Props {
videoId: string
aspectRatio?: AspectRatio
title?: string
className?: string
}
const props = withDefaults(defineProps<Props>(), {
aspectRatio: '16:9',
title: 'YouTube video',
className: '',
})
const aspectRatioClass = computed(() => {
const aspectRatioClasses = {
'16:9': 'aspect-video',
'4:3': 'aspect-4/3',
'21:9': 'aspect-21/9',
'1:1': 'aspect-square',
}
return aspectRatioClasses[props.aspectRatio]
})
</script>
@@ -0,0 +1,12 @@
<template>
<div class="relative">
<div id="pane" class="overflow-hidden">
<img
src="/images/grid-image/image-01.png"
alt="Cover"
class="w-full border border-gray-200 rounded-xl dark:border-gray-800"
/>
</div>
<div id="ghostpane" class="absolute top-0 left-0 duration-300 ease-in-out"></div>
</div>
</template>
@@ -0,0 +1,19 @@
<template>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-3">
<div v-for="(image, index) in images" :key="index">
<img
:src="image.src"
:alt="image.alt"
class="w-full border border-gray-200 rounded-xl dark:border-gray-800"
/>
</div>
</div>
</template>
<script setup>
const images = [
{ src: '/images/grid-image/image-04.png', alt: 'Grid image 1' },
{ src: '/images/grid-image/image-05.png', alt: 'Grid image 2' },
{ src: '/images/grid-image/image-06.png', alt: 'Grid image 3' },
]
</script>
@@ -0,0 +1,19 @@
<template>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<img
src="/images/grid-image/image-02.png"
alt="grid"
class="w-full border border-gray-200 rounded-xl dark:border-gray-800"
/>
</div>
<div>
<img
src="/images/grid-image/image-03.png"
alt="grid"
class="w-full border border-gray-200 rounded-xl dark:border-gray-800"
/>
</div>
</div>
</template>
+116
View File
@@ -0,0 +1,116 @@
// import { ref } from 'vue'
// export function useSidebar() {
// const isMobileOpen = ref(false)
// const isDesktopOpen = ref(true)
// const toggleSidebar = () => {
// isDesktopOpen.value = !isDesktopOpen.value
// }
// const toggleMobileSidebar = () => {
// isMobileOpen.value = !isMobileOpen.value
// }
// return {
// isMobileOpen,
// isDesktopOpen,
// toggleSidebar,
// toggleMobileSidebar,
// }
// }
import { ref, computed, onMounted, onUnmounted, provide, inject } from 'vue'
import type { Ref } from 'vue' //
interface SidebarContextType {
isExpanded: Ref<boolean>
isMobileOpen: Ref<boolean>
isHovered: Ref<boolean>
activeItem: Ref<string | null>
openSubmenu: Ref<string | null>
toggleSidebar: () => void
toggleMobileSidebar: () => void
setIsHovered: (isHovered: boolean) => void
setActiveItem: (item: string | null) => void
toggleSubmenu: (item: string) => void
}
const SidebarSymbol = Symbol()
export function useSidebarProvider() {
const isExpanded = ref(true)
const isMobileOpen = ref(false)
const isMobile = ref(false)
const isHovered = ref(false)
const activeItem = ref<string | null>(null)
const openSubmenu = ref<string | null>(null)
const handleResize = () => {
const mobile = window.innerWidth < 768
isMobile.value = mobile
if (!mobile) {
isMobileOpen.value = false
}
}
onMounted(() => {
handleResize()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
const toggleSidebar = () => {
if (isMobile.value) {
isMobileOpen.value = !isMobileOpen.value
} else {
isExpanded.value = !isExpanded.value
}
}
const toggleMobileSidebar = () => {
isMobileOpen.value = !isMobileOpen.value
}
const setIsHovered = (value: boolean) => {
isHovered.value = value
}
const setActiveItem = (item: string | null) => {
activeItem.value = item
}
const toggleSubmenu = (item: string) => {
openSubmenu.value = openSubmenu.value === item ? null : item
}
const context: SidebarContextType = {
isExpanded: computed(() => (isMobile.value ? false : isExpanded.value)),
isMobileOpen,
isHovered,
activeItem,
openSubmenu,
toggleSidebar,
toggleMobileSidebar,
setIsHovered,
setActiveItem,
toggleSubmenu,
}
provide(SidebarSymbol, context)
return context
}
export function useSidebar(): SidebarContextType {
const context = inject<SidebarContextType>(SidebarSymbol)
if (!context) {
throw new Error(
'useSidebar must be used within a component that has SidebarProvider as an ancestor',
)
}
return context
}
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.54175 4.8335C1.54175 3.59085 2.54911 2.5835 3.79175 2.5835H16.2084C17.4511 2.5835 18.4584 3.59085 18.4584 4.8335V5.16683C18.4584 5.96477 18.0431 6.66569 17.4167 7.06517V15.1668C17.4167 16.4095 16.4094 17.4168 15.1667 17.4168H4.83341C3.59077 17.4168 2.58341 16.4095 2.58341 15.1668V7.06517C1.95711 6.66568 1.54175 5.96476 1.54175 5.16683V4.8335ZM4.08341 7.41683H15.9167V15.1668C15.9167 15.581 15.581 15.9168 15.1667 15.9168H4.83341C4.4192 15.9168 4.08341 15.581 4.08341 15.1668V7.41683ZM16.9584 5.16683C16.9584 5.58104 16.6226 5.91683 16.2084 5.91683H3.79175C3.37753 5.91683 3.04175 5.58104 3.04175 5.16683V4.8335C3.04175 4.41928 3.37753 4.0835 3.79175 4.0835H16.2084C16.6226 4.0835 16.9584 4.41928 16.9584 4.8335V5.16683ZM8.33341 9.04183C7.9192 9.04183 7.58341 9.37762 7.58341 9.79183C7.58341 10.206 7.9192 10.5418 8.33341 10.5418H11.6667C12.081 10.5418 12.4167 10.206 12.4167 9.79183C12.4167 9.37762 12.081 9.04183 11.6667 9.04183H8.33341Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.85954 4.0835C9.5834 4.0835 9.35954 4.30735 9.35954 4.5835V15.4161C9.35954 15.6922 9.5834 15.9161 9.85954 15.9161H10.1373C10.4135 15.9161 10.6373 15.6922 10.6373 15.4161V4.5835C10.6373 4.30735 10.4135 4.0835 10.1373 4.0835H9.85954ZM7.85954 4.5835C7.85954 3.47893 8.75497 2.5835 9.85954 2.5835H10.1373C11.2419 2.5835 12.1373 3.47893 12.1373 4.5835V15.4161C12.1373 16.5206 11.2419 17.4161 10.1373 17.4161H9.85954C8.75497 17.4161 7.85954 16.5206 7.85954 15.4161V4.5835ZM4.58203 8.9598C4.30589 8.9598 4.08203 9.18366 4.08203 9.4598V15.4168C4.08203 15.693 4.30589 15.9168 4.58203 15.9168H4.85981C5.13595 15.9168 5.35981 15.693 5.35981 15.4168V9.4598C5.35981 9.18366 5.13595 8.9598 4.85981 8.9598H4.58203ZM2.58203 9.4598C2.58203 8.35523 3.47746 7.4598 4.58203 7.4598H4.85981C5.96438 7.4598 6.85981 8.35523 6.85981 9.4598V15.4168C6.85981 16.5214 5.96438 17.4168 4.85981 17.4168H4.58203C3.47746 17.4168 2.58203 16.5214 2.58203 15.4168V9.4598ZM14.637 12.435C14.637 12.1589 14.8609 11.935 15.137 11.935H15.4148C15.691 11.935 15.9148 12.1589 15.9148 12.435V15.4168C15.9148 15.693 15.691 15.9168 15.4148 15.9168H15.137C14.8609 15.9168 14.637 15.693 14.637 15.4168V12.435ZM15.137 10.435C14.0325 10.435 13.137 11.3304 13.137 12.435V15.4168C13.137 16.5214 14.0325 17.4168 15.137 17.4168H15.4148C16.5194 17.4168 17.4148 16.5214 17.4148 15.4168V12.435C17.4148 11.3304 16.5194 10.435 15.4148 10.435H15.137Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.7487 2.29248C10.7487 1.87827 10.4129 1.54248 9.9987 1.54248C9.58448 1.54248 9.2487 1.87827 9.2487 2.29248V2.83613C6.08132 3.20733 3.6237 5.9004 3.6237 9.16748V14.4591H3.33203C2.91782 14.4591 2.58203 14.7949 2.58203 15.2091C2.58203 15.6234 2.91782 15.9591 3.33203 15.9591H4.3737H15.6237H16.6654C17.0796 15.9591 17.4154 15.6234 17.4154 15.2091C17.4154 14.7949 17.0796 14.4591 16.6654 14.4591H16.3737V9.16748C16.3737 5.9004 13.9161 3.20733 10.7487 2.83613V2.29248ZM14.8737 14.4591V9.16748C14.8737 6.47509 12.6911 4.29248 9.9987 4.29248C7.30631 4.29248 5.1237 6.47509 5.1237 9.16748V14.4591H14.8737ZM7.9987 17.7085C7.9987 18.1228 8.33448 18.4585 8.7487 18.4585H11.2487C11.6629 18.4585 11.9987 18.1228 11.9987 17.7085C11.9987 17.2943 11.6629 16.9585 11.2487 16.9585H8.7487C8.33448 16.9585 7.9987 17.2943 7.9987 17.7085Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.665 3.75618C11.8762 3.65061 12.1247 3.65061 12.3358 3.75618L18.7807 6.97853L12.3358 10.2009C12.1247 10.3064 11.8762 10.3064 11.665 10.2009L5.22014 6.97853L11.665 3.75618ZM4.29297 8.19199V16.0946C4.29297 16.3787 4.45347 16.6384 4.70757 16.7654L11.25 20.0365V11.6512C11.1631 11.6205 11.0777 11.5843 10.9942 11.5425L4.29297 8.19199ZM12.75 20.037L19.2933 16.7654C19.5474 16.6384 19.7079 16.3787 19.7079 16.0946V8.19199L13.0066 11.5425C12.9229 11.5844 12.8372 11.6207 12.75 11.6515V20.037ZM13.0066 2.41453C12.3732 2.09783 11.6277 2.09783 10.9942 2.41453L4.03676 5.89316C3.27449 6.27429 2.79297 7.05339 2.79297 7.90563V16.0946C2.79297 16.9468 3.27448 17.7259 4.03676 18.1071L10.9942 21.5857L11.3296 20.9149L10.9942 21.5857C11.6277 21.9024 12.3732 21.9024 13.0066 21.5857L19.9641 18.1071C20.7264 17.7259 21.2079 16.9468 21.2079 16.0946V7.90563C21.2079 7.05339 20.7264 6.27429 19.9641 5.89316L13.0066 2.41453Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.77692 3.24224C9.91768 3.17186 10.0834 3.17186 10.2241 3.24224L15.3713 5.81573L10.3359 8.33331C10.1248 8.43888 9.87626 8.43888 9.66512 8.33331L4.6298 5.81573L9.77692 3.24224ZM3.70264 7.0292V13.4124C3.70264 13.6018 3.80964 13.775 3.97903 13.8597L9.25016 16.4952L9.25016 9.7837C9.16327 9.75296 9.07782 9.71671 8.99432 9.67496L3.70264 7.0292ZM10.7502 16.4955V9.78396C10.8373 9.75316 10.923 9.71683 11.0067 9.67496L16.2984 7.0292V13.4124C16.2984 13.6018 16.1914 13.775 16.022 13.8597L10.7502 16.4955ZM9.41463 17.4831L9.10612 18.1002C9.66916 18.3817 10.3319 18.3817 10.8949 18.1002L16.6928 15.2013C17.3704 14.8625 17.7984 14.17 17.7984 13.4124V6.58831C17.7984 5.83076 17.3704 5.13823 16.6928 4.79945L10.8949 1.90059C10.3319 1.61908 9.66916 1.61907 9.10612 1.90059L9.44152 2.57141L9.10612 1.90059L3.30823 4.79945C2.63065 5.13823 2.20264 5.83076 2.20264 6.58831V13.4124C2.20264 14.17 2.63065 14.8625 3.30823 15.2013L9.10612 18.1002L9.41463 17.4831Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 2C8.41421 2 8.75 2.33579 8.75 2.75V3.75H15.25V2.75C15.25 2.33579 15.5858 2 16 2C16.4142 2 16.75 2.33579 16.75 2.75V3.75H18.5C19.7426 3.75 20.75 4.75736 20.75 6V9V19C20.75 20.2426 19.7426 21.25 18.5 21.25H5.5C4.25736 21.25 3.25 20.2426 3.25 19V9V6C3.25 4.75736 4.25736 3.75 5.5 3.75H7.25V2.75C7.25 2.33579 7.58579 2 8 2ZM8 5.25H5.5C5.08579 5.25 4.75 5.58579 4.75 6V8.25H19.25V6C19.25 5.58579 18.9142 5.25 18.5 5.25H16H8ZM19.25 9.75H4.75V19C4.75 19.4142 5.08579 19.75 5.5 19.75H18.5C18.9142 19.75 19.25 19.4142 19.25 19V9.75Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.00002 12.0957C4.00002 7.67742 7.58174 4.0957 12 4.0957C16.4183 4.0957 20 7.67742 20 12.0957C20 16.514 16.4183 20.0957 12 20.0957H5.06068L6.34317 18.8132C6.48382 18.6726 6.56284 18.4818 6.56284 18.2829C6.56284 18.084 6.48382 17.8932 6.34317 17.7526C4.89463 16.304 4.00002 14.305 4.00002 12.0957ZM12 2.5957C6.75332 2.5957 2.50002 6.849 2.50002 12.0957C2.50002 14.4488 3.35633 16.603 4.77303 18.262L2.71969 20.3154C2.50519 20.5299 2.44103 20.8525 2.55711 21.1327C2.6732 21.413 2.94668 21.5957 3.25002 21.5957H12C17.2467 21.5957 21.5 17.3424 21.5 12.0957C21.5 6.849 17.2467 2.5957 12 2.5957ZM7.62502 10.8467C6.93467 10.8467 6.37502 11.4063 6.37502 12.0967C6.37502 12.787 6.93467 13.3467 7.62502 13.3467H7.62512C8.31548 13.3467 8.87512 12.787 8.87512 12.0967C8.87512 11.4063 8.31548 10.8467 7.62512 10.8467H7.62502ZM10.75 12.0967C10.75 11.4063 11.3097 10.8467 12 10.8467H12.0001C12.6905 10.8467 13.2501 11.4063 13.2501 12.0967C13.2501 12.787 12.6905 13.3467 12.0001 13.3467H12C11.3097 13.3467 10.75 12.787 10.75 12.0967ZM16.375 10.8467C15.6847 10.8467 15.125 11.4063 15.125 12.0967C15.125 12.787 15.6847 13.3467 16.375 13.3467H16.3751C17.0655 13.3467 17.6251 12.787 17.6251 12.0967C17.6251 11.4063 17.0655 10.8467 16.3751 10.8467H16.375Z"
fill="currentColor"
/>
</svg>
</template>
+11
View File
@@ -0,0 +1,11 @@
<template>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.6668 3.5L5.25016 9.91667L2.3335 7"
stroke="white"
stroke-width="1.94437"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,11 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,11 @@
<template>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.83333 12.6665L10 8.49984L5.83333 4.33317"
stroke="currentColor"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.7365 2.82264C11.9086 2.75882 12.0914 2.75882 12.2635 2.82264L18.7635 5.2344C19.0574 5.34346 19.25 5.6239 19.25 5.9375V11.623C19.25 15.7497 16.6168 19.4217 12.7053 20.7314L12.2379 20.8879C12.0834 20.9396 11.9166 20.9396 11.7621 20.8879L11.2947 20.7314C7.38319 19.4217 4.75 15.7497 4.75 11.623V5.9375C4.75 5.6239 4.94261 5.34346 5.23652 5.2344L11.7365 2.82264ZM6.25 6.45899V11.623C6.25 15.1048 8.47238 18.2029 11.7711 19.3073L12 19.3839L12.2289 19.3073C15.5276 18.2029 17.75 15.1048 17.75 11.623V6.45899L12 4.32664L6.25 6.45899Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 8.25C11.1716 8.25 10.5 8.92157 10.5 9.75C10.5 10.5784 11.1716 11.25 12 11.25C12.8284 11.25 13.5 10.5784 13.5 9.75C13.5 8.92157 12.8284 8.25 12 8.25ZM9 9.75C9 8.09315 10.3431 6.75 12 6.75C13.6569 6.75 15 8.09315 15 9.75C15 11.1512 14.0399 12.3282 12.7422 12.6597L14.4571 14.3746C14.75 14.6675 14.75 15.1424 14.4571 15.4353C14.1642 15.7282 13.6893 15.7282 13.3964 15.4353L12 14.0388L10.6036 15.4353C10.3107 15.7282 9.83579 15.7282 9.54289 15.4353C9.25 15.1424 9.25 14.6675 9.54289 14.3746L11.2578 12.6597C9.96006 12.3282 9 11.1512 9 9.75Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M19.5 19.75C19.5 20.9926 18.4926 22 17.25 22H6.75C5.50736 22 4.5 20.9926 4.5 19.75V9.62105C4.5 9.02455 4.73686 8.45247 5.15851 8.03055L10.5262 2.65951C10.9482 2.23725 11.5207 2 12.1177 2H17.25C18.4926 2 19.5 3.00736 19.5 4.25V19.75ZM17.25 20.5C17.6642 20.5 18 20.1642 18 19.75V4.25C18 3.83579 17.6642 3.5 17.25 3.5H12.248L12.2509 7.49913C12.2518 8.7424 11.2442 9.75073 10.0009 9.75073H6V19.75C6 20.1642 6.33579 20.5 6.75 20.5H17.25ZM7.05913 8.25073L10.7488 4.55876L10.7509 7.5002C10.7512 7.91462 10.4153 8.25073 10.0009 8.25073H7.05913ZM8.25 14.5C8.25 14.0858 8.58579 13.75 9 13.75H15C15.4142 13.75 15.75 14.0858 15.75 14.5C15.75 14.9142 15.4142 15.25 15 15.25H9C8.58579 15.25 8.25 14.9142 8.25 14.5ZM8.25 17.5C8.25 17.0858 8.58579 16.75 9 16.75H12C12.4142 16.75 12.75 17.0858 12.75 17.5C12.75 17.9142 12.4142 18.25 12 18.25H9C8.58579 18.25 8.25 17.9142 8.25 17.5Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.04175 7.06206V14.375C3.04175 14.6511 3.26561 14.875 3.54175 14.875H16.4584C16.7346 14.875 16.9584 14.6511 16.9584 14.375V7.06245L11.1443 11.1168C10.457 11.5961 9.54373 11.5961 8.85638 11.1168L3.04175 7.06206ZM16.9584 5.19262C16.9584 5.19341 16.9584 5.1942 16.9584 5.19498V5.20026C16.9572 5.22216 16.946 5.24239 16.9279 5.25501L10.2864 9.88638C10.1145 10.0062 9.8862 10.0062 9.71437 9.88638L3.07255 5.25485C3.05342 5.24151 3.04202 5.21967 3.04202 5.19636C3.042 5.15695 3.07394 5.125 3.11335 5.125H16.8871C16.9253 5.125 16.9564 5.15494 16.9584 5.19262ZM18.4584 5.21428V14.375C18.4584 15.4796 17.563 16.375 16.4584 16.375H3.54175C2.43718 16.375 1.54175 15.4796 1.54175 14.375V5.19498C1.54175 5.1852 1.54194 5.17546 1.54231 5.16577C1.55858 4.31209 2.25571 3.625 3.11335 3.625H16.8871C17.7549 3.625 18.4584 4.32843 18.4585 5.19622C18.4585 5.20225 18.4585 5.20826 18.4584 5.21428Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.12454 4.53906L15.8736 4.53906C16.1416 4.53906 16.3892 4.68201 16.5231 4.91406L20.3977 11.625C20.5317 11.857 20.5317 12.1429 20.3977 12.375L16.5231 19.0859C16.3892 19.3179 16.1416 19.4609 15.8736 19.4609H8.12454C7.85659 19.4609 7.609 19.3179 7.47502 19.0859L3.60048 12.375C3.46651 12.1429 3.46651 11.857 3.60048 11.625L7.47502 4.91406C7.609 4.68201 7.85659 4.53906 8.12454 4.53906ZM15.8736 3.03906H8.12454C7.3207 3.03906 6.57791 3.46791 6.17599 4.16406L2.30144 10.875C1.89952 11.5711 1.89952 12.4288 2.30144 13.125L6.17599 19.8359C6.57791 20.532 7.32069 20.9609 8.12454 20.9609H15.8736C16.6775 20.9609 17.4203 20.532 17.8222 19.8359L21.6967 13.125C22.0987 12.4288 22.0987 11.5711 21.6967 10.875L17.8222 4.16406C17.4203 3.46791 16.6775 3.03906 15.8736 3.03906ZM12.0007 7.81075C12.4149 7.81075 12.7507 8.14653 12.7507 8.56075V12.7803C12.7507 13.1945 12.4149 13.5303 12.0007 13.5303C11.5865 13.5303 11.2507 13.1945 11.2507 12.7803V8.56075C11.2507 8.14653 11.5865 7.81075 12.0007 7.81075ZM10.9998 15.3303C10.9998 14.778 11.4475 14.3303 11.9998 14.3303H12.0005C12.5528 14.3303 13.0005 14.778 13.0005 15.3303C13.0005 15.8826 12.5528 16.3303 12.0005 16.3303H11.9998C11.4475 16.3303 10.9998 15.8826 10.9998 15.3303Z"
fill="currentColor"
/>
</svg>
</template>
+17
View File
@@ -0,0 +1,17 @@
<template>
<svg
className="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M20.3499 12.0004C20.3499 16.612 16.6115 20.3504 11.9999 20.3504C7.38832 20.3504 3.6499 16.612 3.6499 12.0004C3.6499 7.38881 7.38833 3.65039 11.9999 3.65039C16.6115 3.65039 20.3499 7.38881 20.3499 12.0004ZM11.9999 22.1504C17.6056 22.1504 22.1499 17.6061 22.1499 12.0004C22.1499 6.3947 17.6056 1.85039 11.9999 1.85039C6.39421 1.85039 1.8499 6.3947 1.8499 12.0004C1.8499 17.6061 6.39421 22.1504 11.9999 22.1504ZM13.0008 16.4753C13.0008 15.923 12.5531 15.4753 12.0008 15.4753L11.9998 15.4753C11.4475 15.4753 10.9998 15.923 10.9998 16.4753C10.9998 17.0276 11.4475 17.4753 11.9998 17.4753L12.0008 17.4753C12.5531 17.4753 13.0008 17.0276 13.0008 16.4753ZM11.9998 6.62898C12.414 6.62898 12.7498 6.96476 12.7498 7.37898L12.7498 13.0555C12.7498 13.4697 12.414 13.8055 11.9998 13.8055C11.5856 13.8055 11.2498 13.4697 11.2498 13.0555L11.2498 7.37898C11.2498 6.96476 11.5856 6.62898 11.9998 6.62898Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.2996 1.12891C11.4713 1.12891 10.7998 1.80033 10.7996 2.62867L10.7996 3.1264V3.12659L10.7997 4.87507H6.14591C3.6031 4.87507 1.54175 6.93642 1.54175 9.47923V14.3207C1.54175 15.4553 2.46151 16.3751 3.5961 16.3751H6.14591H10.0001H16.2084C17.4511 16.3751 18.4584 15.3677 18.4584 14.1251V10.1251C18.4584 7.22557 16.1079 4.87507 13.2084 4.87507H12.2997L12.2996 3.87651H13.7511C14.5097 3.87651 15.1248 3.26157 15.1249 2.50293C15.125 1.74411 14.5099 1.12891 13.7511 1.12891H12.2996ZM3.04175 9.47923C3.04175 7.76485 4.43153 6.37507 6.14591 6.37507C7.8603 6.37507 9.25008 7.76485 9.25008 9.47923V14.8751H6.14591H3.5961C3.28994 14.8751 3.04175 14.6269 3.04175 14.3207V9.47923ZM10.7501 9.47923V14.8751H16.2084C16.6226 14.8751 16.9584 14.5393 16.9584 14.1251V10.1251C16.9584 8.054 15.2795 6.37507 13.2084 6.37507H9.54632C10.294 7.19366 10.7501 8.28319 10.7501 9.47923Z"
fill="currentColor"
/>
</svg>
</template>
+8
View File
@@ -0,0 +1,8 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.05 6.9L10.45 7.35L11.05 6.9ZM4.25 5.25H8.5V3.75H4.25V5.25ZM3.5 18V6H2V18H3.5ZM19.75 18.75H4.25V20.25H19.75V18.75ZM20.5 9V18H22V9H20.5ZM19.75 6.75H12.25V8.25H19.75V6.75ZM11.65 6.45L10.3 4.65L9.1 5.55L10.45 7.35L11.65 6.45ZM12.25 6.75C12.0139 6.75 11.7916 6.63885 11.65 6.45L10.45 7.35C10.8749 7.91656 11.5418 8.25 12.25 8.25V6.75ZM22 9C22 7.75736 20.9926 6.75 19.75 6.75V8.25C20.1642 8.25 20.5 8.58579 20.5 9H22ZM19.75 20.25C20.9926 20.25 22 19.2426 22 18H20.5C20.5 18.4142 20.1642 18.75 19.75 18.75V20.25ZM2 18C2 19.2426 3.00736 20.25 4.25 20.25V18.75C3.83579 18.75 3.5 18.4142 3.5 18H2ZM8.5 5.25C8.73607 5.25 8.95836 5.36115 9.1 5.55L10.3 4.65C9.87508 4.08344 9.2082 3.75 8.5 3.75V5.25ZM4.25 3.75C3.00736 3.75 2 4.75736 2 6H3.5C3.5 5.58579 3.83579 5.25 4.25 5.25V3.75Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.5 3.25C4.25736 3.25 3.25 4.25736 3.25 5.5V8.99998C3.25 10.2426 4.25736 11.25 5.5 11.25H9C10.2426 11.25 11.25 10.2426 11.25 8.99998V5.5C11.25 4.25736 10.2426 3.25 9 3.25H5.5ZM4.75 5.5C4.75 5.08579 5.08579 4.75 5.5 4.75H9C9.41421 4.75 9.75 5.08579 9.75 5.5V8.99998C9.75 9.41419 9.41421 9.74998 9 9.74998H5.5C5.08579 9.74998 4.75 9.41419 4.75 8.99998V5.5ZM5.5 12.75C4.25736 12.75 3.25 13.7574 3.25 15V18.5C3.25 19.7426 4.25736 20.75 5.5 20.75H9C10.2426 20.75 11.25 19.7427 11.25 18.5V15C11.25 13.7574 10.2426 12.75 9 12.75H5.5ZM4.75 15C4.75 14.5858 5.08579 14.25 5.5 14.25H9C9.41421 14.25 9.75 14.5858 9.75 15V18.5C9.75 18.9142 9.41421 19.25 9 19.25H5.5C5.08579 19.25 4.75 18.9142 4.75 18.5V15ZM12.75 5.5C12.75 4.25736 13.7574 3.25 15 3.25H18.5C19.7426 3.25 20.75 4.25736 20.75 5.5V8.99998C20.75 10.2426 19.7426 11.25 18.5 11.25H15C13.7574 11.25 12.75 10.2426 12.75 8.99998V5.5ZM15 4.75C14.5858 4.75 14.25 5.08579 14.25 5.5V8.99998C14.25 9.41419 14.5858 9.74998 15 9.74998H18.5C18.9142 9.74998 19.25 9.41419 19.25 8.99998V5.5C19.25 5.08579 18.9142 4.75 18.5 4.75H15ZM15 12.75C13.7574 12.75 12.75 13.7574 12.75 15V18.5C12.75 19.7426 13.7574 20.75 15 20.75H18.5C19.7426 20.75 20.75 19.7427 20.75 18.5V15C20.75 13.7574 19.7426 12.75 18.5 12.75H15ZM14.25 15C14.25 14.5858 14.5858 14.25 15 14.25H18.5C18.9142 14.25 19.25 14.5858 19.25 15V18.5C19.25 18.9142 18.9142 19.25 18.5 19.25H15C14.5858 19.25 14.25 18.9142 14.25 18.5V15Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.48994 3.61404C7.79216 3.38738 8.20771 3.38738 8.50993 3.61404L12.3433 6.48904C12.5573 6.64957 12.6833 6.9015 12.6833 7.16904V11.8333C12.6833 12.3028 12.3027 12.6833 11.8333 12.6833H8.64993V10.8333C8.64993 10.4744 8.35892 10.1833 7.99993 10.1833C7.64095 10.1833 7.34993 10.4744 7.34993 10.8333V12.6833H4.1666C3.69716 12.6833 3.3166 12.3028 3.3166 11.8333V7.16904C3.3166 6.9015 3.44257 6.64957 3.6566 6.48904L7.48994 3.61404ZM7.99478 13.9833H4.1666C2.97919 13.9833 2.0166 13.0207 2.0166 11.8333V7.16904C2.0166 6.49231 2.33522 5.85508 2.8766 5.44904L6.70994 2.57404C7.47438 2.00071 8.52549 2.00071 9.28993 2.57404L13.1233 5.44904C13.6647 5.85508 13.9833 6.49232 13.9833 7.16904V11.8333C13.9833 13.0207 13.0207 13.9833 11.8333 13.9833H8.00509C8.00337 13.9833 8.00166 13.9833 7.99993 13.9833C7.99821 13.9833 7.9965 13.9833 7.99478 13.9833Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.2929 3.29289C11.6834 2.90237 12.3166 2.90237 12.7071 3.29289L20.4571 11.0429C20.75 11.3358 20.75 11.8107 20.4571 12.1036C20.1642 12.3964 19.6893 12.3964 19.3964 12.1036L18.75 11.4571V18.25C18.75 19.4926 17.7426 20.5 16.5 20.5H14.75C14.3358 20.5 14 20.1642 14 19.75V15.25C14 14.9739 13.7761 14.75 13.5 14.75H10.5C10.2239 14.75 10 14.9739 10 15.25V19.75C10 20.1642 9.66421 20.5 9.25 20.5H7.5C6.25736 20.5 5.25 19.4926 5.25 18.25V11.4571L4.60355 12.1036C4.31066 12.3964 3.83579 12.3964 3.54289 12.1036C3.25 11.8107 3.25 11.3358 3.54289 11.0429L11.2929 3.29289ZM6.75 9.95711V18.25C6.75 18.6642 7.08579 19 7.5 19H8.5V15.25C8.5 14.1454 9.39543 13.25 10.5 13.25H13.5C14.6046 13.25 15.5 14.1454 15.5 15.25V19H16.5C16.9142 19 17.25 18.6642 17.25 18.25V9.95711L12 4.70711L6.75 9.95711Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.25 8.75C9.25 8.33579 9.58579 8 10 8H14C14.4142 8 14.75 8.33579 14.75 8.75C14.75 9.16421 14.4142 9.5 14 9.5H10C9.58579 9.5 9.25 9.16421 9.25 8.75ZM10.75 11.25C10.75 10.8358 11.0858 10.5 11.5 10.5H12.5C12.9142 10.5 13.25 10.8358 13.25 11.25C13.25 11.6642 12.9142 12 12.5 12H11.5C11.0858 12 10.75 11.6642 10.75 11.25Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.99915 10.2451C6.96564 10.2451 7.74915 11.0286 7.74915 11.9951V12.0051C7.74915 12.9716 6.96564 13.7551 5.99915 13.7551C5.03265 13.7551 4.24915 12.9716 4.24915 12.0051V11.9951C4.24915 11.0286 5.03265 10.2451 5.99915 10.2451ZM17.9991 10.2451C18.9656 10.2451 19.7491 11.0286 19.7491 11.9951V12.0051C19.7491 12.9716 18.9656 13.7551 17.9991 13.7551C17.0326 13.7551 16.2491 12.9716 16.2491 12.0051V11.9951C16.2491 11.0286 17.0326 10.2451 17.9991 10.2451ZM13.7491 11.9951C13.7491 11.0286 12.9656 10.2451 11.9991 10.2451C11.0326 10.2451 10.2491 11.0286 10.2491 11.9951V12.0051C10.2491 12.9716 11.0326 13.7551 11.9991 13.7551C12.9656 13.7551 13.7491 12.9716 13.7491 12.0051V11.9951Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.5 12C3.5 7.30558 7.30558 3.5 12 3.5C16.6944 3.5 20.5 7.30558 20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12ZM12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM11.0991 7.52507C11.0991 8.02213 11.5021 8.42507 11.9991 8.42507H12.0001C12.4972 8.42507 12.9001 8.02213 12.9001 7.52507C12.9001 7.02802 12.4972 6.62507 12.0001 6.62507H11.9991C11.5021 6.62507 11.0991 7.02802 11.0991 7.52507ZM12.0001 17.3714C11.5859 17.3714 11.2501 17.0356 11.2501 16.6214V10.9449C11.2501 10.5307 11.5859 10.1949 12.0001 10.1949C12.4143 10.1949 12.7501 10.5307 12.7501 10.9449V16.6214C12.7501 17.0356 12.4143 17.3714 12.0001 17.3714Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.6501 11.9996C3.6501 7.38803 7.38852 3.64961 12.0001 3.64961C16.6117 3.64961 20.3501 7.38803 20.3501 11.9996C20.3501 16.6112 16.6117 20.3496 12.0001 20.3496C7.38852 20.3496 3.6501 16.6112 3.6501 11.9996ZM12.0001 1.84961C6.39441 1.84961 1.8501 6.39392 1.8501 11.9996C1.8501 17.6053 6.39441 22.1496 12.0001 22.1496C17.6058 22.1496 22.1501 17.6053 22.1501 11.9996C22.1501 6.39392 17.6058 1.84961 12.0001 1.84961ZM10.9992 7.52468C10.9992 8.07697 11.4469 8.52468 11.9992 8.52468H12.0002C12.5525 8.52468 13.0002 8.07697 13.0002 7.52468C13.0002 6.9724 12.5525 6.52468 12.0002 6.52468H11.9992C11.4469 6.52468 10.9992 6.9724 10.9992 7.52468ZM12.0002 17.371C11.586 17.371 11.2502 17.0352 11.2502 16.621V10.9445C11.2502 10.5303 11.586 10.1945 12.0002 10.1945C12.4144 10.1945 12.7502 10.5303 12.7502 10.9445V16.621C12.7502 17.0352 12.4144 17.371 12.0002 17.371Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.83203 2.5835C3.58939 2.5835 2.58203 3.59085 2.58203 4.83349V7.25015C2.58203 8.49279 3.58939 9.50015 4.83203 9.50015H7.2487C8.49134 9.50015 9.4987 8.49279 9.4987 7.25015V4.8335C9.4987 3.59086 8.49134 2.5835 7.2487 2.5835H4.83203ZM4.08203 4.83349C4.08203 4.41928 4.41782 4.0835 4.83203 4.0835H7.2487C7.66291 4.0835 7.9987 4.41928 7.9987 4.8335V7.25015C7.9987 7.66436 7.66291 8.00015 7.2487 8.00015H4.83203C4.41782 8.00015 4.08203 7.66436 4.08203 7.25015V4.83349ZM4.83203 10.5002C3.58939 10.5002 2.58203 11.5075 2.58203 12.7502V15.1668C2.58203 16.4095 3.58939 17.4168 4.83203 17.4168H7.2487C8.49134 17.4168 9.4987 16.4095 9.4987 15.1668V12.7502C9.4987 11.5075 8.49134 10.5002 7.2487 10.5002H4.83203ZM4.08203 12.7502C4.08203 12.336 4.41782 12.0002 4.83203 12.0002H7.2487C7.66291 12.0002 7.9987 12.336 7.9987 12.7502V15.1668C7.9987 15.5811 7.66291 15.9168 7.2487 15.9168H4.83203C4.41782 15.9168 4.08203 15.5811 4.08203 15.1668V12.7502ZM10.4987 4.83349C10.4987 3.59085 11.5061 2.5835 12.7487 2.5835H15.1654C16.408 2.5835 17.4154 3.59086 17.4154 4.8335V7.25015C17.4154 8.49279 16.408 9.50015 15.1654 9.50015H12.7487C11.5061 9.50015 10.4987 8.49279 10.4987 7.25015V4.83349ZM12.7487 4.0835C12.3345 4.0835 11.9987 4.41928 11.9987 4.83349V7.25015C11.9987 7.66436 12.3345 8.00015 12.7487 8.00015H15.1654C15.5796 8.00015 15.9154 7.66436 15.9154 7.25015V4.8335C15.9154 4.41928 15.5796 4.0835 15.1654 4.0835H12.7487ZM12.7487 10.5002C11.5061 10.5002 10.4987 11.5075 10.4987 12.7502V15.1668C10.4987 16.4095 11.5061 17.4168 12.7487 17.4168H15.1654C16.408 17.4168 17.4154 16.4095 17.4154 15.1668V12.7502C17.4154 11.5075 16.408 10.5002 15.1654 10.5002H12.7487ZM11.9987 12.7502C11.9987 12.336 12.3345 12.0002 12.7487 12.0002H15.1654C15.5796 12.0002 15.9154 12.336 15.9154 12.7502V15.1668C15.9154 15.5811 15.5796 15.9168 15.1654 15.9168H12.7487C12.3345 15.9168 11.9987 15.5811 11.9987 15.1668V12.7502Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.5 3.25C4.25736 3.25 3.25 4.25736 3.25 5.5V18.5C3.25 19.7426 4.25736 20.75 5.5 20.75H18.5001C19.7427 20.75 20.7501 19.7426 20.7501 18.5V5.5C20.7501 4.25736 19.7427 3.25 18.5001 3.25H5.5ZM4.75 5.5C4.75 5.08579 5.08579 4.75 5.5 4.75H18.5001C18.9143 4.75 19.2501 5.08579 19.2501 5.5V18.5C19.2501 18.9142 18.9143 19.25 18.5001 19.25H5.5C5.08579 19.25 4.75 18.9142 4.75 18.5V5.5ZM6.25005 9.7143C6.25005 9.30008 6.58583 8.9643 7.00005 8.9643L17 8.96429C17.4143 8.96429 17.75 9.30008 17.75 9.71429C17.75 10.1285 17.4143 10.4643 17 10.4643L7.00005 10.4643C6.58583 10.4643 6.25005 10.1285 6.25005 9.7143ZM6.25005 14.2857C6.25005 13.8715 6.58583 13.5357 7.00005 13.5357H17C17.4143 13.5357 17.75 13.8715 17.75 14.2857C17.75 14.6999 17.4143 15.0357 17 15.0357H7.00005C6.58583 15.0357 6.25005 14.6999 6.25005 14.2857Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.1007 19.247C14.6865 19.247 14.3507 18.9112 14.3507 18.497L14.3507 14.245H12.8507V18.497C12.8507 19.7396 13.8581 20.747 15.1007 20.747H18.5007C19.7434 20.747 20.7507 19.7396 20.7507 18.497L20.7507 5.49609C20.7507 4.25345 19.7433 3.24609 18.5007 3.24609H15.1007C13.8581 3.24609 12.8507 4.25345 12.8507 5.49609V9.74501L14.3507 9.74501V5.49609C14.3507 5.08188 14.6865 4.74609 15.1007 4.74609L18.5007 4.74609C18.9149 4.74609 19.2507 5.08188 19.2507 5.49609L19.2507 18.497C19.2507 18.9112 18.9149 19.247 18.5007 19.247H15.1007ZM3.25073 11.9984C3.25073 12.2144 3.34204 12.4091 3.48817 12.546L8.09483 17.1556C8.38763 17.4485 8.86251 17.4487 9.15549 17.1559C9.44848 16.8631 9.44863 16.3882 9.15583 16.0952L5.81116 12.7484L16.0007 12.7484C16.4149 12.7484 16.7507 12.4127 16.7507 11.9984C16.7507 11.5842 16.4149 11.2484 16.0007 11.2484L5.81528 11.2484L9.15585 7.90554C9.44864 7.61255 9.44847 7.13767 9.15547 6.84488C8.86248 6.55209 8.3876 6.55226 8.09481 6.84525L3.52309 11.4202C3.35673 11.5577 3.25073 11.7657 3.25073 11.9984Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.2996 1.12891C11.4713 1.12891 10.7998 1.80033 10.7996 2.62867L10.7996 3.1264V3.12659L10.7997 4.87507H6.14591C3.6031 4.87507 1.54175 6.93642 1.54175 9.47923V14.3207C1.54175 15.4553 2.46151 16.3751 3.5961 16.3751H6.14591H10.0001H16.2084C17.4511 16.3751 18.4584 15.3677 18.4584 14.1251V10.1251C18.4584 7.22557 16.1079 4.87507 13.2084 4.87507H12.2997L12.2996 3.87651H13.7511C14.5097 3.87651 15.1248 3.26157 15.1249 2.50293C15.125 1.74411 14.5099 1.12891 13.7511 1.12891H12.2996ZM3.04175 9.47923C3.04175 7.76485 4.43153 6.37507 6.14591 6.37507C7.8603 6.37507 9.25008 7.76485 9.25008 9.47923V14.8751H6.14591H3.5961C3.28994 14.8751 3.04175 14.6269 3.04175 14.3207V9.47923ZM10.7501 9.47923V14.8751H16.2084C16.6226 14.8751 16.9584 14.5393 16.9584 14.1251V10.1251C16.9584 8.054 15.2795 6.37507 13.2084 6.37507H9.54632C10.294 7.19366 10.7501 8.28319 10.7501 9.47923Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.5 8.187V17.25C3.5 17.6642 3.83579 18 4.25 18H19.75C20.1642 18 20.5 17.6642 20.5 17.25V8.18747L13.2873 13.2171C12.5141 13.7563 11.4866 13.7563 10.7134 13.2171L3.5 8.187ZM20.5 6.2286C20.5 6.23039 20.5 6.23218 20.5 6.23398V6.24336C20.4976 6.31753 20.4604 6.38643 20.3992 6.42905L12.4293 11.9867C12.1716 12.1664 11.8291 12.1664 11.5713 11.9867L3.60116 6.42885C3.538 6.38481 3.50035 6.31268 3.50032 6.23568C3.50028 6.10553 3.60577 6 3.73592 6H20.2644C20.3922 6 20.4963 6.10171 20.5 6.2286ZM22 6.25648V17.25C22 18.4926 20.9926 19.5 19.75 19.5H4.25C3.00736 19.5 2 18.4926 2 17.25V6.23398C2 6.22371 2.00021 6.2135 2.00061 6.20333C2.01781 5.25971 2.78812 4.5 3.73592 4.5H20.2644C21.2229 4.5 22 5.27697 22.0001 6.23549C22.0001 6.24249 22.0001 6.24949 22 6.25648Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linejoin="round"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.50391 4.25C8.50391 3.83579 8.83969 3.5 9.25391 3.5H15.2777C15.4766 3.5 15.6674 3.57902 15.8081 3.71967L18.2807 6.19234C18.4214 6.333 18.5004 6.52376 18.5004 6.72268V16.75C18.5004 17.1642 18.1646 17.5 17.7504 17.5H16.248V17.4993H14.748V17.5H9.25391C8.83969 17.5 8.50391 17.1642 8.50391 16.75V4.25ZM14.748 19H9.25391C8.01126 19 7.00391 17.9926 7.00391 16.75V6.49854H6.24805C5.83383 6.49854 5.49805 6.83432 5.49805 7.24854V19.75C5.49805 20.1642 5.83383 20.5 6.24805 20.5H13.998C14.4123 20.5 14.748 20.1642 14.748 19.75L14.748 19ZM7.00391 4.99854V4.25C7.00391 3.00736 8.01127 2 9.25391 2H15.2777C15.8745 2 16.4468 2.23705 16.8687 2.659L19.3414 5.13168C19.7634 5.55364 20.0004 6.12594 20.0004 6.72268V16.75C20.0004 17.9926 18.9931 19 17.7504 19H16.248L16.248 19.75C16.248 20.9926 15.2407 22 13.998 22H6.24805C5.00541 22 3.99805 20.9926 3.99805 19.75V7.24854C3.99805 6.00589 5.00541 4.99854 6.24805 4.99854H7.00391Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 2C11.5858 2 11.25 2.33579 11.25 2.75V12C11.25 12.4142 11.5858 12.75 12 12.75H21.25C21.6642 12.75 22 12.4142 22 12C22 6.47715 17.5228 2 12 2ZM12.75 11.25V3.53263C13.2645 3.57761 13.7659 3.66843 14.25 3.80098V3.80099C15.6929 4.19606 16.9827 4.96184 18.0104 5.98959C19.0382 7.01734 19.8039 8.30707 20.199 9.75C20.3316 10.2341 20.4224 10.7355 20.4674 11.25H12.75ZM2 12C2 7.25083 5.31065 3.27489 9.75 2.25415V3.80099C6.14748 4.78734 3.5 8.0845 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5C15.9155 20.5 19.2127 17.8525 20.199 14.25H21.7459C20.7251 18.6894 16.7492 22 12 22C6.47715 22 2 17.5229 2 12Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 2.75C14 2.33579 14.3358 2 14.75 2C15.1642 2 15.5 2.33579 15.5 2.75V5.73291L17.75 5.73291H19C19.4142 5.73291 19.75 6.0687 19.75 6.48291C19.75 6.89712 19.4142 7.23291 19 7.23291H18.5L18.5 12.2329C18.5 15.5691 15.9866 18.3183 12.75 18.6901V21.25C12.75 21.6642 12.4142 22 12 22C11.5858 22 11.25 21.6642 11.25 21.25V18.6901C8.01342 18.3183 5.5 15.5691 5.5 12.2329L5.5 7.23291H5C4.58579 7.23291 4.25 6.89712 4.25 6.48291C4.25 6.0687 4.58579 5.73291 5 5.73291L6.25 5.73291L8.5 5.73291L8.5 2.75C8.5 2.33579 8.83579 2 9.25 2C9.66421 2 10 2.33579 10 2.75L10 5.73291L14 5.73291V2.75ZM7 7.23291L7 12.2329C7 14.9943 9.23858 17.2329 12 17.2329C14.7614 17.2329 17 14.9943 17 12.2329L17 7.23291L7 7.23291Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.72763 4.33443C7.92401 3.6437 9.30836 3.34945 10.6823 3.49385C12.0562 3.63826 13.3491 4.2139 14.3757 5.13828C15.0468 5.74252 15.5815 6.4755 15.9517 7.28815L13.6069 6.49282C13.2147 6.35977 12.7888 6.5699 12.6557 6.96216C12.5227 7.35443 12.7328 7.78028 13.1251 7.91333L16.8227 9.16752C16.8668 9.18743 16.9129 9.20314 16.9605 9.21426L17.0868 9.25712C17.2752 9.32101 17.4813 9.30746 17.6597 9.21943C17.838 9.1314 17.9741 8.97611 18.038 8.78772L19.3816 4.82561C19.5147 4.43334 19.3045 4.0075 18.9122 3.87447C18.52 3.74145 18.0941 3.95161 17.9611 4.34388L17.2335 6.48938C16.783 5.5609 16.1553 4.72223 15.3794 4.02356C14.1174 2.88722 12.528 2.17958 10.839 2.00207C9.15012 1.82455 7.44834 2.18628 5.97763 3.03539C4.50692 3.88451 3.34277 5.17743 2.65203 6.72884C1.9613 8.28025 1.77944 10.0105 2.13252 11.6716C2.4856 13.3328 3.3555 14.8395 4.61753 15.9758C5.87957 17.1121 7.46894 17.8198 9.15788 17.9973C10.8468 18.1748 12.5486 17.8131 14.0193 16.964C14.378 16.7569 14.5009 16.2982 14.2938 15.9395C14.0867 15.5807 13.628 15.4578 13.2693 15.6649C12.0729 16.3557 10.6886 16.6499 9.31467 16.5055C7.94077 16.3611 6.64786 15.7855 5.62123 14.8611C4.5946 13.9367 3.88697 12.711 3.59974 11.3598C3.31252 10.0085 3.46046 8.60098 4.02235 7.33894C4.58424 6.07691 5.53125 5.02516 6.72763 4.33443Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.98481 2.44399C3.11333 1.57147 1.15325 3.46979 1.96543 5.36824L3.82086 9.70527C3.90146 9.89367 3.90146 10.1069 3.82086 10.2953L1.96543 14.6323C1.15326 16.5307 3.11332 18.4291 4.98481 17.5565L16.8184 12.0395C18.5508 11.2319 18.5508 8.76865 16.8184 7.961L4.98481 2.44399ZM3.34453 4.77824C3.0738 4.14543 3.72716 3.51266 4.35099 3.80349L16.1846 9.32051C16.762 9.58973 16.762 10.4108 16.1846 10.68L4.35098 16.197C3.72716 16.4879 3.0738 15.8551 3.34453 15.2223L5.19996 10.8853C5.21944 10.8397 5.23735 10.7937 5.2537 10.7473L9.11784 10.7473C9.53206 10.7473 9.86784 10.4115 9.86784 9.99726C9.86784 9.58304 9.53206 9.24726 9.11784 9.24726L5.25157 9.24726C5.2358 9.20287 5.2186 9.15885 5.19996 9.11528L3.34453 4.77824Z"
fill="currentColor"
/>
</svg>
</template>
+10
View File
@@ -0,0 +1,10 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.4858 3.5L13.5182 3.5C13.9233 3.5 14.2518 3.82851 14.2518 4.23377C14.2518 5.9529 16.1129 7.02795 17.602 6.1682C17.9528 5.96567 18.4014 6.08586 18.6039 6.43667L20.1203 9.0631C20.3229 9.41407 20.2027 9.86286 19.8517 10.0655C18.3625 10.9253 18.3625 13.0747 19.8517 13.9345C20.2026 14.1372 20.3229 14.5859 20.1203 14.9369L18.6039 17.5634C18.4013 17.9142 17.9528 18.0344 17.602 17.8318C16.1129 16.9721 14.2518 18.0471 14.2518 19.7663C14.2518 20.1715 13.9233 20.5 13.5182 20.5H10.4858C10.0804 20.5 9.75182 20.1714 9.75182 19.766C9.75182 18.0461 7.88983 16.9717 6.40067 17.8314C6.04945 18.0342 5.60037 17.9139 5.39767 17.5628L3.88167 14.937C3.67903 14.586 3.79928 14.1372 4.15026 13.9346C5.63949 13.0748 5.63946 10.9253 4.15025 10.0655C3.79926 9.86282 3.67901 9.41401 3.88165 9.06303L5.39764 6.43725C5.60034 6.08617 6.04943 5.96581 6.40065 6.16858C7.88982 7.02836 9.75182 5.9539 9.75182 4.23399C9.75182 3.82862 10.0804 3.5 10.4858 3.5ZM13.5182 2L10.4858 2C9.25201 2 8.25182 3.00019 8.25182 4.23399C8.25182 4.79884 7.64013 5.15215 7.15065 4.86955C6.08213 4.25263 4.71559 4.61859 4.0986 5.68725L2.58261 8.31303C1.96575 9.38146 2.33183 10.7477 3.40025 11.3645C3.88948 11.647 3.88947 12.3531 3.40026 12.6355C2.33184 13.2524 1.96578 14.6186 2.58263 15.687L4.09863 18.3128C4.71562 19.3814 6.08215 19.7474 7.15067 19.1305C7.64015 18.8479 8.25182 19.2012 8.25182 19.766C8.25182 20.9998 9.25201 22 10.4858 22H13.5182C14.7519 22 15.7518 20.9998 15.7518 19.7663C15.7518 19.2015 16.3632 18.8487 16.852 19.1309C17.9202 19.7476 19.2862 19.3816 19.9029 18.3134L21.4193 15.6869C22.0361 14.6185 21.6701 13.2523 20.6017 12.6355C20.1125 12.3531 20.1125 11.647 20.6017 11.3645C21.6701 10.7477 22.0362 9.38152 21.4193 8.3131L19.903 5.68667C19.2862 4.61842 17.9202 4.25241 16.852 4.86917C16.3632 5.15138 15.7518 4.79856 15.7518 4.23377C15.7518 3.00024 14.7519 2 13.5182 2ZM9.6659 11.9999C9.6659 10.7103 10.7113 9.66493 12.0009 9.66493C13.2905 9.66493 14.3359 10.7103 14.3359 11.9999C14.3359 13.2895 13.2905 14.3349 12.0009 14.3349C10.7113 14.3349 9.6659 13.2895 9.6659 11.9999ZM12.0009 8.16493C9.88289 8.16493 8.1659 9.88191 8.1659 11.9999C8.1659 14.1179 9.88289 15.8349 12.0009 15.8349C14.1189 15.8349 15.8359 14.1179 15.8359 11.9999C15.8359 9.88191 14.1189 8.16493 12.0009 8.16493Z"
fill="currentColor"
/>
</svg>
</template>
+17
View File
@@ -0,0 +1,17 @@
<template>
<svg
class="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.99993 2.375C10.2854 2.375 10.5461 2.53707 10.6725 2.79308L12.7318 6.96563L17.3365 7.63473C17.619 7.67578 17.8537 7.87367 17.9419 8.14517C18.0301 8.41668 17.9565 8.71473 17.7521 8.914L14.4201 12.1619L15.2067 16.748C15.255 17.0293 15.1393 17.3137 14.9083 17.4815C14.6774 17.6493 14.3712 17.6714 14.1185 17.5386L9.99993 15.3733L5.88137 17.5386C5.62869 17.6714 5.32249 17.6493 5.09153 17.4815C4.86057 17.3137 4.7449 17.0293 4.79316 16.748L5.57974 12.1619L2.24775 8.914C2.04332 8.71473 1.96975 8.41668 2.05797 8.14517C2.14619 7.87367 2.3809 7.67578 2.66341 7.63473L7.2681 6.96563L9.32738 2.79308C9.45373 2.53707 9.71445 2.375 9.99993 2.375ZM9.99993 4.81966L8.4387 7.98306C8.32946 8.20442 8.11828 8.35785 7.874 8.39334L4.38298 8.90062L6.90911 11.363C7.08587 11.5353 7.16653 11.7835 7.1248 12.0268L6.52847 15.5037L9.65093 13.8622C9.86942 13.7473 10.1304 13.7473 10.3489 13.8622L13.4714 15.5037L12.8751 12.0268C12.8333 11.7835 12.914 11.5353 13.0908 11.363L15.6169 8.90062L12.1259 8.39334C11.8816 8.35785 11.6704 8.20442 11.5612 7.98306L9.99993 4.81966Z"
fill=""
/>
</svg>
</template>
+14
View File
@@ -0,0 +1,14 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.665 3.75618C11.8762 3.65061 12.1247 3.65061 12.3358 3.75618L18.7807 6.97853L12.3358 10.2009C12.1247 10.3064 11.8762 10.3064 11.665 10.2009L5.22014 6.97853L11.665 3.75618ZM4.29297 8.19199V16.0946C4.29297 16.3787 4.45347 16.6384 4.70757 16.7654L11.25 20.0365V11.6512C11.1631 11.6205 11.0777 11.5843 10.9942 11.5425L4.29297 8.19199ZM12.75 20.037L19.2933 16.7654C19.5474 16.6384 19.7079 16.3787 19.7079 16.0946V8.19199L13.0066 11.5425C12.9229 11.5844 12.8372 11.6207 12.75 11.6515V20.037ZM13.0066 2.41453C12.3732 2.09783 11.6277 2.09783 10.9942 2.41453L4.03676 5.89316C3.27449 6.27429 2.79297 7.05339 2.79297 7.90563V16.0946C2.79297 16.9468 3.27448 17.7259 4.03676 18.1071L10.9942 21.5857C11.6277 21.9024 12.3732 21.9024 13.0066 21.5857L19.9641 18.1071C20.7264 17.7259 21.2079 16.9468 21.2079 16.0946V7.90563C21.2079 7.05339 20.7264 6.27429 19.9641 5.89316L13.0066 2.41453Z"
fill="currentColor"
/>
<path
d="M9.20312 16.75L11.3594 11.25H12.6406L14.7969 16.75H13.5703L13.1484 15.625H10.8438L10.4297 16.75H9.20312ZM11.1953 14.6562H12.7969L12.0078 12.5234H11.9844L11.1953 14.6562Z"
fill="currentColor"
/>
</svg>
</template>

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