211025/coding_UI

This commit is contained in:
NguyenAnhQuan 2025-10-21 15:11:46 +07:00
parent 2414cad2d2
commit 232255cd84
28 changed files with 1503 additions and 607 deletions

View File

@ -3,8 +3,8 @@ import { LayoutVue } from '@/components'
const config = { const config = {
token: { token: {
colorPrimary: '#ff7789', colorPrimary: '#007FCF',
borderRadius: 0, borderRadius: 4,
}, },
} }
</script> </script>

View File

@ -0,0 +1,10 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@ -1,12 +1,16 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;200;300;400;500;600;700;800;900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;200;300;400;500;600;700;800;900&display=swap');
html {
font-family: 'Noto Sans JP';
font-size: 15px;
}
*, *,
*::before, *::before,
*::after { *::after {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'Noto Sans JP'; position: unset;
} }
img { img {
@ -14,303 +18,6 @@ img {
height: auto; height: auto;
} }
body {
min-height: 100vh;
color: var(--vt-c-gray-2);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family: 'Noto Sans JP';
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: var(--vt-c-main-gray-v1);
}
&::-webkit-scrollbar-thumb {
background-color: var(--vt-c-main-gray-v2);
border-radius: 10px;
transition: all 0.2s ease-in-out;
}
&::-webkit-scrollbar-track {
border-radius: 10px;
}
}
*,
*::before,
*::after {
position: unset;
}
.box {
// padding: 20px;
&.full {
min-height: calc(100% - 20px);
}
&.no-border {
border: none;
}
.box-head {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 66px;
padding: 0 20px;
background-color: var(--vt-c-white-mute);
.box-search {
display: flex;
gap: 35px;
align-items: center;
.ant-input-search {
width: 322px;
.ant-input {
border: none;
background: unset;
outline: unset;
box-shadow: unset;
}
.ant-input-group-addon {
background: unset;
.ant-input-search-button {
background: unset;
border: unset;
box-shadow: unset;
.anticon {
svg {
fill: var(--vt-c-gray-2);
}
}
}
}
}
}
.title {
font-weight: 900;
font-size: 18px;
line-height: 26px;
color: var(--vt-c-gray-2);
.title-page {
color: var(--vt-c-gray-v9);
}
&.manga {
color: var(--vt-c-gray-v9);
}
}
.box-btn {
@include center_flex;
width: 140px;
height: 40px;
border-radius: 6px;
color: var(--vt-c-main);
background-color: var(--vt-c-white);
box-shadow: var(--vt-box-shadow);
border: 1px solid var(--vt-c-main);
font-weight: 700;
font-size: 14px;
}
}
.box-body {
padding: 0 20px;
overflow-y: auto;
height: calc(100vh - 135px);
position: relative;
&.no-pagination {
height: calc(100vh - 66px);
}
&:has(.form-create-chap) {
.ant-tabs {
overflow-y: auto;
height: calc(100vh - 235px);
}
}
&.no_padding {
padding: 0;
}
.btn-save-setting {
&:disabled {
border-color: var(--vt-c-gray-1);
background-color: var(--vt-c-gray-1);
}
}
}
}
.btn-base {
margin-right: 12px;
&.btn-delete {
background-color: var(--vt-c-red-v3);
color: var(--vt-c-white);
&:hover {
border-color: unset !important;
color: var(--vt-c-white);
}
}
}
.ant-tag {
&.custom {
width: 72px;
height: 28px;
border: none;
@include center_flex;
}
}
.ant-picker {
width: 100%;
&.has-value {
border-color: var(--vt-c-main);
}
}
.rounded-btn {
border-radius: 100px !important;
}
.bold-label {
label {
font-weight: 700;
}
}
.ant-input-number {
width: 100%;
}
.full-width {
width: 100%;
}
.ant-input[disabled] {
background-color: var(--vt-c-background-dark-1);
color: var(--vt-c-text-dark-3);
}
.ant-tabs {
&.custom {
.ant-tabs-nav {
background-color: var(--vt-c-white-mute);
padding: 0 20px;
}
.ant-tabs-content-holder {
padding: 0 20px;
}
.ant-tabs-nav-list {
.ant-tabs-tab {
min-width: 68px;
justify-content: center;
padding: 0 16px 6px;
.ant-tabs-tab-btn {
font-size: 16px;
font-weight: 700;
line-height: 23.17px;
}
&:not(.ant-tabs-tab-active) {
color: var(--vt-c-gray-v9);
}
}
}
.ant-tabs-ink-bar {
height: 3px;
}
.ant-tabs-content {
&:has(.tab-report) {
height: calc(100vh - 180px);
overflow-y: scroll;
}
&:has(.tab-ranking) {
height: calc(100vh - 180px);
overflow-y: auto;
}
}
}
&.manga-tabs {
.ant-tabs-content-holder {
margin-bottom: 150px;
}
}
&.master-tabs {
.ant-tabs-content-holder {
margin-bottom: 70px;
}
}
}
.ant-form-item {
.ant-form-item-explain-error {
font-size: 12px;
}
}
.ant-spin-spinning {
width: 100%;
}
.flex-center {
@include center_flex;
}
.flex-col-center {
display: flex;
flex-direction: column;
align-items: center;
}
.btn-common {
@include baseBtn(
$bg: var(--vt-c-main-color),
$width: unset,
$height: unset,
$borderRadius: unset
);
margin: 0 auto 5px;
padding: var(--vt-btn-padding);
color: var(--vt-c-white);
font-weight: 500;
outline: none;
cursor: pointer;
border-radius: 8px;
border: 2px solid transparent;
transition: all 0.3s ease;
&:hover {
border: 2px solid var(--vt-c-black-bold);
color: var(--vt-c-black-bold);
}
}
p { p {
font-size: 1rem; font-size: 1rem;
margin: 1em; margin: 1em;
@ -324,97 +31,3 @@ h2 {
margin-bottom: 1rem; margin-bottom: 1rem;
text-align: center; text-align: center;
} }
.highlight {
color: var(--vt-c-main-color);
}
.spacer {
height: 1px;
width: 90%;
background: linear-gradient(to right, transparent, rgb(224, 224, 224), transparent);
margin: 2rem auto;
}
.note {
margin-top: 1rem;
font-size: 0.9rem;
color: --vt-c-gray-note;
}
.auth-container {
@include center_flex;
min-height: 100vh;
background-color: var(--vt-c-white);
padding: 20px;
}
.auth-card {
background-color: var(--vt-c-white);
border: 2px solid var(--vt-c-main-color);
width: 100%;
max-width: 600px;
padding: 0;
&-header {
padding: 5px 10px;
min-height: 30px;
display: flex;
align-items: center;
border-bottom: 2px solid var(--vt-c-main-color);
h2 {
font-size: 1rem;
margin: 0;
color: var(--vt-c-black-bold);
}
}
&-content {
padding: 20px;
text-align: center;
}
}
.wallet-icon {
margin: 20px 0 30px 0;
.icon-circle {
@include center_flex;
width: 60px;
height: 60px;
background-color: var(--vt-c-main-color);
border-radius: 50%;
margin: 0 auto;
}
}
.password-section {
margin-bottom: 20px;
}
.auth-button-group {
display: flex;
gap: 10px;
margin-top: 20px;
.auth-btn {
flex: 1;
border-radius: 4px;
padding: 12px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
&.secondary {
background-color: var(--vt-c-white);
color: var(--vt-c-black-bold);
border: 1px solid var(--vt-c-black-bold);
&:hover {
background-color: var(--vt-c-white-soft);
}
}
}
}

View File

@ -0,0 +1,27 @@
.flex-center {
@include center_flex;
}
.flex-col-center {
display: flex;
flex-direction: column;
align-items: center;
}
.card-base {
@include card-base;
}
// ==================== TEXT UTILITIES ====================
.text-primary {
color: var(--text-primary);
}
.text-secondary {
color: var(--text-secondary);
}
.text-muted {
color: var(--text-muted);
}

View File

@ -118,53 +118,40 @@ $fw: 100;
justify-content: center; justify-content: center;
} }
@mixin center_pos { @mixin btn-primary {
position: absolute; background: var(--primary-color);
top: 50%; border-color: var(--primary-color);
left: 50%; font-weight: var(--font-semibold);
transform: translate(-50%, -50%); height: auto;
padding: var(--btn-padding-y) var(--btn-padding-x);
transition: var(--transition-all);
&:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
} }
@mixin line_clamp($line) { &:active,
overflow: hidden; &:focus {
display: -webkit-box; background: var(--primary-hover);
-webkit-line-clamp: $line; border-color: var(--primary-hover);
-webkit-box-orient: vertical; }
} }
@mixin position($pos: absolute, $left: 0, $right: 0, $top: 0, $bottom: 0) { @mixin card-base {
position: $pos; background: var(--bg-white);
top: $top; border-radius: var(--card-radius);
left: $left; padding: var(--card-padding);
right: $right; box-shadow: var(--card-shadow);
bottom: $bottom; transition: var(--transition-all);
animation: fadeIn 0.6s ease-out;
@media (max-width: 768px) {
padding: var(--card-padding-mobile);
} }
// function &:hover {
@function sum($numbers...) { transform: translateY(-4px);
$sum: 0; box-shadow: var(--card-shadow-hover);
@each $number in $numbers {
$sum: $sum + $number;
} }
@return $sum;
}
@function calc_v2($size) {
@return calc(100% - $size);
}
@mixin baseBtn(
$bg: var(--vt-c-main),
$width: 92px,
$height: 36px,
$borderRadius: var(--vt-br-btn)
) {
display: flex;
justify-content: center;
align-items: center;
width: $width;
height: $height;
background-color: $bg;
border-radius: $borderRadius;
box-shadow: unset;
} }

View File

@ -1,113 +1,154 @@
:root { :root {
--vt-btn-padding: 11px 8px; // ==================== COLORS ====================
--vt-c-main-color: #009688;
--vt-c-badge-caption-shadow: rgba(0, 151, 115, 1);
--vt-input-shadow: 0 0 0 2px rgba(0, 150, 136, 0.1);
--vt-input-shadow-focus: 0 0 0 2px rgba(0, 150, 136, 0.1);
--vt-c-gray-note: rgb(85, 85, 85);
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-white-v2: #d9d9d9;
--vt-c-white-v5: #fafafa;
--vt-c-white-v7: #f0f5f8;
--vt-c-white-v8: #fbfbfb;
--vt-c-black: #181818; // Primary Colors
--vt-c-black-v1: #5d6679; --primary-color: #007FCF;
--vt-c-black-soft: #222222; --primary-hover: #0066A6;
--vt-c-black-mute: #282828; --primary-light: #E8F4FC;
--vt-c-black-bold: #000000; --primary-bg: #F5FBFF;
--vt-c-black-bold-v2: #131523;
--vt-c-black-bold-v3: #252c32;
--vt-c-black-bold-v3: #00000026;
--vt-c-black-bold-v4: #00000080;
--vt-c-black-bold-v5: #00000008;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29); // Secondary Colors
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12); --secondary-color: #FF9500;
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); --secondary-hover: #E68600;
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo); // Text Colors
--vt-c-text-light-2: rgba(60, 60, 60, 0.66); --text-primary: #2c3e50;
--vt-c-text-dark-1: var(--vt-c-white); --text-secondary: #5a6c7d;
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64); --text-muted: #8b95a5;
--vt-c-text-dark-3: rgba(0, 0, 0, 0.88); --text-light: #ffffff;
--vt-c-background-dark-1: rgba(0, 0, 0, 0.04); // Background Colors
--bg-gradient-start: #F0F8FF;
--bg-gradient-end: #E6F2FF;
--bg-white: #ffffff;
--bg-light: #F8FCFF;
--bg-hover: #E8F4FC;
--vt-box-shadow-1: 0px 0px 8px rgba(78, 37, 0, 0.78); // Border Colors
--vt-box-shadow-2: 0px 4px 4px rgba(0, 0, 0, 0.25); --border-light: #E6F2FF;
--vt-box-shadow-3: 0px 0px 5px 0px var(--vt-c-black-bold-v5) inset; --border-color: #EBF5FF;
--border-primary: #007FCF;
--vt-c-main: #ff7789; // Status Colors
--vt-c-main-title: #519fb0; --success-color: #10B981;
--vt-c-main-light: #6659ff; --warning-color: #F59E0B;
--vt-c-main-bg: #eff1f7; --error-color: #EF4444;
--vt-c-main-bg-v1: #f4f7ff; --info-color: #007FCF;
--vt-c-main-black: #051121;
--vt-c-main-clear: #e6e8ef;
--vt-c-main-link: #4335ef;
--vt-c-main-red: red;
--vt-c-main-gray: #7e84a3;
--vt-c-main-gray-v1: #fff3;
--vt-c-main-gray-v2: #0003;
--vt-c-gray-v4: #a9a5a3;
--vt-c-gray-v5: #666666;
--vt-c-gray-v6: #999999;
--vt-c-gray-v9: #888888;
--vt-c-gray-v10: #444444;
--vt-c-gray-v11: #6d6d6d;
--vt-c-gray-v12: #b9bdc7;
--vt-c-gray-v17: #e6e6e6;
--vt-c-blue: #3375f3;
--vt-c-blue-v3: #162dff;
--vt-c-blue-v4: #67abba;
--vt-c-blue-v5: #20aee5;
--vt-c-red-v3: #ff0f0f;
--vt-c-violet: #5671fb; // ==================== SPACING ====================
--vt-c-pink: #ffe6ea;
--vt-c-pink-2: #ff6d6d;
--vt-c-pink-3: #e05266;
--vt-c-green: #1e8e3e;
--vt-c-green-2: #93cb9c;
--vt-c-green-3: #429d7c;
--vt-c-turquoise: #3fb3ce; --spacing-xs: 0.25rem; // 4px
--spacing-sm: 0.5rem; // 8px
--spacing-md: 0.75rem; // 12px
--spacing-lg: 1rem; // 16px
--spacing-xl: 1.5rem; // 24px
--spacing-2xl: 2rem; // 32px
--spacing-3xl: 2.5rem; // 40px
--spacing-4xl: 3rem; // 48px
--color-background: #f5f7fb; // ==================== BORDER RADIUS ====================
--color-background-event: #3fb3ce;
--color-background-sidebar: #e6e6e6;
--color-background-soft: var(--vt-c-white-soft);
--color-border: #b8b7b7; --radius-sm: 8px;
--color-border-v1: #cdd4e7; --radius-md: 10px;
--color-border-shadow: #dfdfdf40; --radius-lg: 12px;
--color-border-hover: var(--vt-c-divider-light-1); --radius-xl: 16px;
--radius-full: 9999px;
--color-heading: var(--vt-c-text-light-1); // ==================== BOX SHADOWS ====================
--color-text: var(--vt-c-text-light-1);
--font-size: 14px; --shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.05);
--vt-font-btn: 92px; --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.05);
--vt-br-btn: 3px; --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08);
--section-gap: 160px; --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
--shadow-xl: 0 12px 40px rgba(0, 0, 0, 0.15);
--shadow-primary: 0 4px 12px rgba(0, 127, 207, 0.25);
--shadow-secondary: 0 4px 12px rgba(255, 149, 0, 0.25);
--vt-c-gray-1: #e9e9e9; // ==================== TRANSITIONS ====================
--vt-c-gray-2: #444444;
--vt-c-gray-3: #adadad66;
--vt-c-gray-4: #f9f9f9;
--vt-c-gray-5: #b4b4b4;
--vt-c-gray-6: #d9d9d9;
--vt-c-gray-7: #ececec;
--vt-c-gray-8: #636363;
--vt-c-gray-9: #aaaaaa;
--vt-c-green-dark: #435855;
--vt-c-orange: #f3a964; --transition-fast: 0.2s ease;
--transition-normal: 0.3s ease;
--transition-slow: 0.5s ease;
--transition-all: all 0.3s ease;
--vt-box-shadow: 0px 0px 4px 0px var(--vt-c-gray-3); // ==================== TYPOGRAPHY ====================
--vt-box-shadow-active: 0px 0px 4px 0px var(--vt-c-main);
// Font Families
--font-primary: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
--font-mono: 'Courier New', monospace;
--font-noto: 'Noto Sans JP';
// Font Sizes
--font-xs: 0.75rem; // 12px
--font-sm: 0.85rem; // 13.6px
--font-base: 0.9rem; // 14.4px
--font-md: 0.95rem; // 15.2px
--font-lg: 1rem; // 16px
--font-xl: 1.1rem; // 17.6px
--font-2xl: 1.2rem; // 19.2px
--font-3xl: 1.5rem; // 24px
--font-4xl: 3rem; // 48px
// Font Weights
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
// Line Heights
--leading-tight: 1.2;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
// Letter Spacing
--tracking-tight: -1px;
--tracking-normal: 0;
--tracking-wide: 0.5px;
--tracking-wider: 1px;
// ==================== Z-INDEX ====================
--z-base: 1;
--z-dropdown: 10;
--z-sticky: 20;
--z-fixed: 30;
--z-modal-backdrop: 40;
--z-modal: 50;
--z-popover: 60;
--z-tooltip: 70;
// ==================== BREAKPOINTS ====================
--breakpoint-xs: 480px;
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
// ==================== COMPONENTS SPECIFIC ====================
// Card
--card-padding: var(--spacing-2xl);
--card-padding-mobile: var(--spacing-xl);
--card-radius: var(--radius-xl);
--card-shadow: var(--shadow-md);
--card-shadow-hover: var(--shadow-lg);
// Button
--btn-padding-y: 0.75rem;
--btn-padding-x: 1rem;
--btn-radius: var(--radius-md);
--btn-transition: var(--transition-all);
// QR Code
--qr-size: 200px;
--qr-border: 3px solid var(--border-light);
--qr-radius: var(--radius-lg);
--qr-shadow: var(--shadow-sm);
// Tabs
--tabs-height: 3px;
--tabs-padding: 12px 20px;
--tabs-padding-mobile: 10px 16px;
} }

View File

@ -1,3 +1,5 @@
@import '__base';
@import '__variables'; @import '__variables';
@import '__mixin'; @import '__mixin';
@import '__base'; @import '__animations';
@import '__common';

View File

@ -0,0 +1,235 @@
<script setup lang="ts">
import { ref } from 'vue'
import ButtonCommon from '@/components/common/ButtonCommon.vue'
import { formatNumberToLocaleString } from '@/utils'
const availableBalance = ref(0)
const pendingBalance = ref(0)
const receiveAddress = ref('kaspa:qpn80v050r3jxv6mzt8tzss6dhvllc3rvcuuy86z6djgmvzx0napvhuj7ugh9')
const walletStatus = ref('Online')
const currentDaaScore = ref(255953336)
const copyAddress = () => {
navigator.clipboard.writeText(receiveAddress.value)
}
const handleSend = () => {
console.log('Send clicked')
}
const handleScanQR = () => {
console.log('Scan QR clicked')
}
</script>
<template>
<div class="wallet-info-container">
<!-- Balance Section -->
<div class="balance-section">
<div class="balance-label">Available</div>
<div class="balance-amount">{{ availableBalance }} KAS</div>
<div class="pending-section">
<span class="pending-label">Pending</span>
<span class="pending-amount">{{ pendingBalance }} KAS</span>
</div>
</div>
<!-- Receive Address Section -->
<div class="receive-section">
<div class="address-label">Receive Address:</div>
<div class="address-value" @click="copyAddress">
{{ receiveAddress }}
<svg
class="copy-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</div>
</div>
<!-- QR Code Section -->
<div class="qr-section">
<div class="qr-placeholder">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" fill="white" />
<rect x="5" y="5" width="35" height="35" fill="black" />
<rect x="10" y="10" width="25" height="25" fill="white" />
<rect x="15" y="15" width="15" height="15" fill="black" />
<rect x="60" y="5" width="35" height="35" fill="black" />
<rect x="65" y="10" width="25" height="25" fill="white" />
<rect x="70" y="15" width="15" height="15" fill="black" />
<rect x="5" y="60" width="35" height="35" fill="black" />
<rect x="10" y="65" width="25" height="25" fill="white" />
<rect x="15" y="70" width="15" height="15" fill="black" />
<rect x="45" y="45" width="10" height="10" fill="black" />
<rect x="60" y="60" width="10" height="10" fill="black" />
<rect x="70" y="70" width="10" height="10" fill="black" />
</svg>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<ButtonCommon type="primary" size="large" block @click="handleSend" class="btn-send">
SEND
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleScanQR" class="btn-scan">
Scan QR code
</ButtonCommon>
</div>
<!-- Wallet Status -->
<div class="wallet-status">
<span
>Wallet Status: <strong>{{ walletStatus }}</strong></span
>
<span
>DAA score: <strong>{{ formatNumberToLocaleString(currentDaaScore) }}</strong></span
>
</div>
</div>
</template>
<style lang="scss" scoped>
.wallet-info-container {
@include card-base;
}
.balance-section {
text-align: center;
margin-bottom: var(--spacing-3xl);
padding-bottom: var(--spacing-2xl);
border-bottom: 2px solid var(--border-color);
.balance-label {
color: var(--text-muted);
font-size: var(--font-base);
margin-bottom: var(--spacing-sm);
text-transform: uppercase;
letter-spacing: var(--tracking-wider);
}
.balance-amount {
font-size: var(--font-4xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
letter-spacing: var(--tracking-tight);
}
.pending-section {
display: flex;
justify-content: center;
align-items: center;
gap: var(--spacing-sm);
color: var(--text-secondary);
font-size: var(--font-md);
.pending-label {
font-weight: var(--font-medium);
}
.pending-amount {
font-weight: var(--font-semibold);
}
}
}
.receive-section {
margin-bottom: var(--spacing-2xl);
.address-label {
font-size: var(--font-base);
color: var(--text-secondary);
margin-bottom: var(--spacing-md);
font-weight: var(--font-semibold);
}
.address-value {
background: var(--bg-light);
padding: var(--spacing-lg);
border-radius: var(--radius-md);
word-break: break-all;
font-family: var(--font-mono);
font-size: var(--font-sm);
color: var(--primary-color);
cursor: pointer;
transition: var(--transition-all);
display: flex;
align-items: center;
gap: var(--spacing-sm);
border: 2px solid transparent;
&:hover {
background: var(--bg-hover);
border-color: var(--border-primary);
}
.copy-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
color: var(--primary-color);
}
}
}
.qr-section {
display: flex;
justify-content: center;
margin-bottom: var(--spacing-2xl);
.qr-placeholder {
width: var(--qr-size);
height: var(--qr-size);
background: var(--bg-white);
border: var(--qr-border);
border-radius: var(--qr-radius);
padding: var(--spacing-lg);
box-shadow: var(--qr-shadow);
transition: var(--transition-normal);
&:hover {
transform: scale(1.05);
}
svg {
width: 100%;
height: 100%;
}
}
}
.action-buttons {
display: flex;
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
:deep(.btn-send),
:deep(.btn-scan) {
letter-spacing: var(--tracking-wide);
}
}
.wallet-status {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
padding: 1.25rem;
background: var(--bg-light);
border-radius: var(--radius-md);
font-size: var(--font-base);
color: var(--text-secondary);
strong {
color: var(--text-primary);
font-weight: var(--font-semibold);
}
}
</style>

View File

@ -1,61 +1,441 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, computed } from 'vue'
import { ButtonCommon, FormCommon } from '@/components' import { ButtonCommon, FormCommon } from '@/components'
const password = ref('') const password = ref('')
const confirmPassword = ref('') const confirmPassword = ref('')
const passwordError = ref('')
const confirmPasswordError = ref('')
const handleCancel = () => {} // Password strength calculation
const passwordStrength = computed(() => {
if (!password.value) return { level: 0, text: '', color: '' }
const handleIHaveWallet = () => {} let strength = 0
const checks = {
length: password.value.length >= 8,
uppercase: /[A-Z]/.test(password.value),
lowercase: /[a-z]/.test(password.value),
number: /[0-9]/.test(password.value),
special: /[!@#$%^&*(),.?":{}|<>]/.test(password.value),
}
const handleNext = () => {} strength = Object.values(checks).filter(Boolean).length
if (strength <= 2) return { level: 1, text: 'Weak', color: 'var(--error-color)' }
if (strength <= 3) return { level: 2, text: 'Medium', color: 'var(--warning-color)' }
if (strength <= 4) return { level: 3, text: 'Good', color: 'var(--info-color)' }
return { level: 4, text: 'Strong', color: 'var(--success-color)' }
})
const isPasswordMatch = computed(() => {
if (!confirmPassword.value) return true
return password.value === confirmPassword.value
})
const canProceed = computed(() => {
return (
password.value.length >= 8 &&
confirmPassword.value.length >= 8 &&
isPasswordMatch.value &&
passwordStrength.value.level >= 2
)
})
const handleCancel = () => {
password.value = ''
confirmPassword.value = ''
passwordError.value = ''
confirmPasswordError.value = ''
}
const handleIHaveWallet = () => {
console.log('Navigate to open wallet')
}
const handleNext = () => {
if (!canProceed.value) {
if (password.value.length < 8) {
passwordError.value = 'Password must be at least 8 characters'
}
if (!isPasswordMatch.value) {
confirmPasswordError.value = 'Passwords do not match'
}
return
}
console.log('Proceed to next step')
}
</script> </script>
<template> <template>
<div class="auth-container"> <div class="auth-container">
<div class="auth-card"> <div class="auth-card">
<!-- Header -->
<div class="auth-card-header"> <div class="auth-card-header">
<h2>Create Wallet</h2> <div class="logo-container">
<div class="logo-circle">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
class="neptune-logo"
>
<!-- Neptune planet with ring -->
<defs>
<linearGradient
id="neptuneGradient"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop
offset="0%"
style="stop-color: #007fcf; stop-opacity: 1"
/>
<stop
offset="100%"
style="stop-color: #0066a6; stop-opacity: 1"
/>
</linearGradient>
<linearGradient id="ringGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop
offset="0%"
style="stop-color: #007fcf; stop-opacity: 0.3"
/>
<stop
offset="50%"
style="stop-color: #007fcf; stop-opacity: 0.6"
/>
<stop
offset="100%"
style="stop-color: #007fcf; stop-opacity: 0.3"
/>
</linearGradient>
</defs>
<!-- Planet -->
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
<!-- Surface details -->
<ellipse
cx="50"
cy="45"
rx="22"
ry="6"
fill="rgba(255, 255, 255, 0.1)"
/>
<ellipse cx="50" cy="55" rx="20" ry="5" fill="rgba(0, 0, 0, 0.1)" />
<!-- Ring -->
<ellipse
cx="50"
cy="50"
rx="42"
ry="12"
fill="none"
stroke="url(#ringGradient)"
stroke-width="4"
opacity="0.8"
/>
<!-- Highlight -->
<circle cx="42" cy="42" r="6" fill="rgba(255, 255, 255, 0.4)" />
</svg>
</div>
<div class="logo-text">
<span class="coin-name">Neptune</span>
<span class="coin-symbol">NPTUN</span>
</div>
</div>
<h1 class="auth-title">Create New Wallet</h1>
<p class="auth-subtitle">Secure your wallet with a strong password</p>
</div> </div>
<div class="auth-card-content"> <div class="auth-card-content">
<div class="password-section"> <!-- Password Input -->
<div class="form-group">
<FormCommon <FormCommon
v-model="password" v-model="password"
type="password" type="password"
label="Create a password for your new wallet" label="Create Password"
placeholder="Password" placeholder="Enter your password"
show-password-toggle show-password-toggle
required required
:error="passwordError"
@input="passwordError = ''"
/> />
<!-- Password Strength Indicator -->
<div v-if="password" class="password-strength">
<div class="strength-bar">
<div
class="strength-fill"
:style="{
width: `${(passwordStrength.level / 4) * 100}%`,
backgroundColor: passwordStrength.color,
}"
></div>
</div>
<span class="strength-text" :style="{ color: passwordStrength.color }">
{{ passwordStrength.text }}
</span>
</div>
</div> </div>
<div class="password-section"> <!-- Confirm Password Input -->
<div class="form-group">
<FormCommon <FormCommon
v-model="confirmPassword" v-model="confirmPassword"
type="password" type="password"
label="Confirm password" label="Confirm Password"
placeholder="Confirm Password" placeholder="Re-enter your password"
show-password-toggle show-password-toggle
required required
:error="confirmPasswordError"
@input="confirmPasswordError = ''"
/> />
<!-- Password Match Indicator -->
<div
v-if="confirmPassword"
class="password-match"
:class="{ match: isPasswordMatch }"
>
<span v-if="isPasswordMatch" class="match-text"> Passwords match </span>
<span v-else class="match-text error"> Passwords do not match </span>
</div>
</div> </div>
<!-- Helper Text -->
<p class="helper-text">
Password must be at least 8 characters with uppercase, lowercase, and numbers.
</p>
<!-- Action Buttons -->
<div class="auth-button-group"> <div class="auth-button-group">
<ButtonCommon class="auth-btn secondary" @click="handleCancel"> <ButtonCommon
Cancel type="primary"
size="large"
block
:disabled="!canProceed"
@click="handleNext"
>
Create Wallet
</ButtonCommon> </ButtonCommon>
<ButtonCommon class="auth-btn secondary" @click="handleIHaveWallet"> <div class="secondary-actions">
I have a wallet <button class="link-button" @click="handleIHaveWallet">
</ButtonCommon> Already have a wallet?
</button>
<ButtonCommon class="auth-btn primary" @click="handleNext"> Next </ButtonCommon> <span class="separator"></span>
<button class="link-button" @click="handleCancel">Cancel</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.auth-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
background: var(--bg-light);
}
.auth-card {
@include card-base;
max-width: 420px;
width: 100%;
@media (max-width: 640px) {
max-width: 100%;
}
}
.auth-card-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-xl);
border-bottom: 1px solid var(--border-color);
.logo-container {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
.logo-circle {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-light), var(--bg-white));
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-sm);
box-shadow: 0 2px 8px rgba(0, 127, 207, 0.15);
.neptune-logo {
width: 100%;
height: 100%;
}
}
.logo-text {
display: flex;
flex-direction: column;
align-items: flex-start;
.coin-name {
font-size: var(--font-lg);
font-weight: var(--font-bold);
color: var(--text-primary);
line-height: 1;
margin-bottom: 2px;
}
.coin-symbol {
font-size: var(--font-xs);
font-weight: var(--font-medium);
color: var(--primary-color);
background: var(--primary-light);
padding: 2px 8px;
border-radius: var(--radius-sm);
}
}
}
.auth-title {
font-size: var(--font-2xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.auth-subtitle {
font-size: var(--font-sm);
color: var(--text-secondary);
margin: 0;
}
}
.auth-card-content {
.form-group {
margin-bottom: var(--spacing-xl);
}
}
// Password Strength Indicator
.password-strength {
margin-top: var(--spacing-sm);
display: flex;
align-items: center;
gap: var(--spacing-md);
.strength-bar {
flex: 1;
height: 4px;
background: var(--border-light);
border-radius: var(--radius-full);
overflow: hidden;
.strength-fill {
height: 100%;
transition: all 0.3s ease;
}
}
.strength-text {
font-size: var(--font-xs);
font-weight: var(--font-medium);
min-width: 50px;
text-align: right;
}
}
// Password Match Indicator
.password-match {
margin-top: var(--spacing-sm);
font-size: var(--font-xs);
&.match .match-text {
color: var(--success-color);
}
.match-text.error {
color: var(--error-color);
}
}
// Helper Text
.helper-text {
font-size: var(--font-xs);
color: var(--text-muted);
margin: 0 0 var(--spacing-xl);
line-height: var(--leading-normal);
}
// Action Buttons
.auth-button-group {
margin-top: var(--spacing-2xl);
.secondary-actions {
display: flex;
justify-content: center;
align-items: center;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.link-button {
background: none;
border: none;
color: var(--primary-color);
font-size: var(--font-sm);
cursor: pointer;
transition: color 0.2s ease;
padding: 0;
&:hover {
color: var(--primary-hover);
text-decoration: underline;
}
}
.separator {
color: var(--text-muted);
font-size: var(--font-sm);
}
}
// Responsive Design
@media (max-width: 640px) {
.auth-container {
padding: var(--spacing-md);
}
.auth-card-header {
.logo-container {
.logo-circle {
width: 40px;
height: 40px;
}
.logo-text {
.coin-name {
font-size: var(--font-md);
}
}
}
.auth-title {
font-size: var(--font-xl);
}
}
}
</style>

View File

@ -1,5 +1,67 @@
<script setup lang="ts">
import { Button } from 'ant-design-vue'
import type { ButtonProps } from '@/interface'
const props = withDefaults(defineProps<ButtonProps>(), {
type: 'default',
size: 'large',
block: false,
disabled: false,
loading: false,
htmlType: 'button',
})
const emit = defineEmits(['click'])
const handleClick = () => {
if (!props.disabled && !props.loading) {
emit('click')
}
}
</script>
<template> <template>
<button class="btn-common"> <Button
:type="props.type"
:size="props.size"
:block="props.block"
:disabled="props.disabled"
:loading="props.loading"
:html-type="props.htmlType"
@click="handleClick"
class="btn-common"
>
<slot /> <slot />
</button> </Button>
</template> </template>
<style lang="scss" scoped>
.btn-common {
:deep(.ant-btn) {
background: var(--primary-color);
border-color: var(--primary-color);
font-weight: var(--font-semibold);
height: auto;
padding: var(--btn-padding-y) var(--btn-padding-x);
transition: var(--transition-all);
border-radius: var(--btn-radius);
letter-spacing: var(--tracking-wide);
&:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
&:active,
&:focus {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
}
</style>

View File

@ -98,7 +98,7 @@ const handleBlur = (e: FocusEvent) => {
} }
&.error { &.error {
border-color: var(--vt-c-red-v3); border-color: var(--vt-c-red-v3);
background-color: var() background-color: var();
} }
&.disabled { &.disabled {
background-color: var(--vt-c-gray-4); background-color: var(--vt-c-gray-4);

View File

@ -1,3 +0,0 @@
export * from './constants/code'
export * from './constants/constants'
export * from './constants/localStorage'

20
src/interface/common.ts Normal file
View File

@ -0,0 +1,20 @@
import type { ButtonType, ButtonSize } from 'ant-design-vue/es/button'
// Button Component Props
export interface ButtonProps {
type?: ButtonType
size?: ButtonSize
block?: boolean
disabled?: boolean
loading?: boolean
htmlType?: 'button' | 'submit' | 'reset'
}
// Icon Component Props
export interface IconProps {
name?: string
size?: number | string
color?: string
class?: string
icon: string
}

19
src/interface/home.ts Normal file
View File

@ -0,0 +1,19 @@
// Network Status Interface
export interface NetworkStatus {
network: string
daaScore: number
dagHeader: number
dagBlocks: number
difficulty: number
medianOffset: string
medianTimeUTC: string
}
// Wallet Tab Props
export interface WalletTabProps {
network: string
}
export interface DebugTabProps {}
export interface TransactionsTabProps {}

View File

@ -1,9 +1,2 @@
interface Props { export * from './common'
name?: string export * from './home'
size?: number | string
color?: string
class?: string
}
export interface IconProps extends Props {
icon: string
}

View File

@ -1,5 +1,5 @@
import * as Page from '@/views' import * as Page from '@/views'
import { getToken } from '@/helpers' import { getToken } from '@/utils'
const ifAuthenticated = (to: any, from: any, next: any) => { const ifAuthenticated = (to: any, from: any, next: any) => {
if (getToken()) { if (getToken()) {
@ -20,24 +20,18 @@ const ifNotAuthenticated = (to: any, from: any, next: any) => {
export const routes: any = [ export const routes: any = [
{ {
path: '/', path: '/',
redirect: '/home', name: 'index',
children: [
{
path: 'home',
name: 'home',
component: Page.Home, component: Page.Home,
}, },
{ {
path: ':pathMatch(.*)*', path: '/login',
component: Page.NotFound, name: 'login',
name: 'page-not-found',
},
],
},
{
path: '/onboarding',
name: 'onboarding',
component: Page.Login, component: Page.Login,
beforeEnter: ifNotAuthenticated, beforeEnter: ifNotAuthenticated,
}, },
{
path: '/:pathMatch(.*)*',
component: Page.NotFound,
name: 'page-not-found',
},
] ]

View File

@ -10,7 +10,3 @@ export const CURRENT_YEAR = dayjs(new Date()).format('YYYY')
export const MONTHS = Array.from({ length: 12 }, (item, i) => { export const MONTHS = Array.from({ length: 12 }, (item, i) => {
return dayjs(new Date(0, i)).format('MM') return dayjs(new Date(0, i)).format('MM')
}) })
export const FORMAT_DAY = (day: any, format = 'YYYY-MM-DD') => {
return dayjs(new Date(day)).format(format)
}

View File

@ -0,0 +1,9 @@
import dayjs from 'dayjs'
export const formatNumberToLocaleString = (num: number): string => {
return num.toLocaleString('en-US')
}
export const formatDate = (day: any, format = 'YYYY-MM-DD') => {
return dayjs(new Date(day)).format(format)
}

4
src/utils/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './constants/code'
export * from './constants/constants'
export * from './helpers/format'
export * from './helpers/localStorage'

View File

@ -1,7 +1,89 @@
<script setup lang="ts"></script> <script setup lang="ts">
import { ref } from 'vue'
import { Tabs, Row, Col } from 'ant-design-vue'
import WalletInfo from '@/components/WalletInfo.vue'
import { TransactionsTab, WalletTab, NetworkTab, DebugTab } from './components'
const activeTab = ref('WALLET')
const network = ref('kaspa-mainnet')
</script>
<template> <template>
<div class="box">Home page</div> <div class="home-container">
<Row :gutter="[24, 24]">
<!-- Left Column --->
<Col :xs="24" :lg="10">
<WalletInfo />
</Col>
<!-- Right Column - Tabs Content -->
<Col :xs="24" :lg="12">
<Tabs v-model:activeKey="activeTab" size="large" class="main-tabs">
<!-- TRANSACTIONS TAB -->
<Tabs.TabPane key="TRANSACTIONS" tab="TRANSACTIONS">
<TransactionsTab />
</Tabs.TabPane>
<!-- WALLET TAB -->
<Tabs.TabPane key="WALLET" tab="WALLET">
<WalletTab :network="network" />
</Tabs.TabPane>
<!-- NETWORK TAB -->
<Tabs.TabPane key="NETWORK" tab="NETWORK">
<NetworkTab />
</Tabs.TabPane>
<!-- DEBUG TAB -->
<Tabs.TabPane key="DEBUG" tab="DEBUG">
<DebugTab />
</Tabs.TabPane>
</Tabs>
</Col>
</Row>
</div>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.home-container {
min-height: 100vh;
background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
padding: var(--spacing-lg);
font-family: var(--font-primary);
@media (min-width: 768px) {
padding: var(--spacing-2xl);
}
}
:deep(.main-tabs) {
.ant-tabs-nav {
margin-bottom: var(--spacing-lg);
}
.ant-tabs-tab {
font-size: 14px;
font-weight: var(--font-semibold);
letter-spacing: var(--tracking-wide);
padding: 10px 16px;
@media (max-width: 768px) {
font-size: 12px;
padding: 8px 12px;
}
}
.ant-tabs-ink-bar {
background: var(--primary-color);
height: var(--tabs-height);
}
.ant-tabs-tab-active .ant-tabs-tab-btn {
color: var(--primary-color);
}
.ant-tabs-content {
padding-top: var(--spacing-lg);
}
}
</style>

View File

@ -0,0 +1,96 @@
<script setup lang="ts">
import { ref } from 'vue'
import { EditOutlined } from '@ant-design/icons-vue'
import ButtonCommon from '@/components/common/ButtonCommon.vue'
const inUseUtxosCount = ref(0)
const inUseUtxosAmount = ref(0)
const handleShowUTXOs = () => {
console.log('Show UTXOs')
}
const handleForceTransactionUpdate = () => {
console.log('Force transaction times update')
}
const handleScanMoreAddresses = () => {
console.log('Scan More Addresses')
}
</script>
<template>
<div class="content-card debug-card">
<div class="debug-header">
<h3 class="debug-title">
IN USE UTXOS
<EditOutlined style="margin-left: 8px; font-size: 16px" />
</h3>
<div class="debug-info">
<p><strong>COUNT</strong> {{ inUseUtxosCount }}</p>
<p><strong>AMOUNT</strong> {{ inUseUtxosAmount }} KAS</p>
</div>
</div>
<div class="debug-actions">
<ButtonCommon type="primary" size="large" block @click="handleShowUTXOs">
Show UTXOs
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleForceTransactionUpdate">
Force transaction times update
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleScanMoreAddresses">
Scan More Addresses
</ButtonCommon>
</div>
</div>
</template>
<style lang="scss" scoped>
.content-card {
@include card-base;
}
.debug-card {
.debug-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-xl);
border-bottom: 2px solid var(--border-color);
.debug-title {
font-size: var(--font-2xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
letter-spacing: var(--tracking-wide);
display: flex;
align-items: center;
justify-content: center;
}
.debug-info {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
p {
margin: 0;
font-size: var(--font-lg);
color: var(--text-secondary);
strong {
font-weight: var(--font-semibold);
margin-right: var(--spacing-sm);
}
}
}
}
.debug-actions {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
}
</style>

View File

@ -0,0 +1,122 @@
<script setup lang="ts">
import { ref } from 'vue'
import { formatNumberToLocaleString } from '@/utils'
import type { NetworkStatus } from '@/interface'
const networkStatus = ref<NetworkStatus>({
network: 'kaspa-mainnet',
daaScore: 255953336,
dagHeader: 1507472,
dagBlocks: 1507472,
difficulty: 32931885262483752.0,
medianOffset: '00:00:04',
medianTimeUTC: '2025-10-21 03:05:57',
})
</script>
<template>
<div class="content-card">
<div class="network-status-container">
<h2 class="section-title">NETWORK STATUS</h2>
<div class="status-grid">
<div class="status-item">
<span class="status-label">Network</span>
<span class="status-value">{{ networkStatus.network }}</span>
</div>
<div class="status-item">
<span class="status-label">DAA Score</span>
<span class="status-value">{{
formatNumberToLocaleString(networkStatus.daaScore)
}}</span>
</div>
<div class="status-item">
<span class="status-label">DAG Header</span>
<span class="status-value">{{
formatNumberToLocaleString(networkStatus.dagHeader)
}}</span>
</div>
<div class="status-item">
<span class="status-label">DAG Blocks</span>
<span class="status-value">{{
formatNumberToLocaleString(networkStatus.dagBlocks)
}}</span>
</div>
<div class="status-item">
<span class="status-label">Difficulty</span>
<span class="status-value">{{
formatNumberToLocaleString(networkStatus.difficulty)
}}</span>
</div>
<div class="status-item">
<span class="status-label">Median Offset</span>
<span class="status-value">{{ networkStatus.medianOffset }}</span>
</div>
<div class="status-item">
<span class="status-label">Median Time UTC</span>
<span class="status-value">{{ networkStatus.medianTimeUTC }}</span>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.content-card {
@include card-base;
}
.network-status-container {
.section-title {
font-size: var(--font-xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--spacing-2xl);
letter-spacing: var(--tracking-wider);
text-align: center;
padding-bottom: var(--spacing-lg);
border-bottom: 3px solid var(--primary-color);
}
.status-grid {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg) var(--spacing-xl);
background: var(--bg-light);
border-radius: var(--radius-md);
transition: var(--transition-all);
&:hover {
background: var(--bg-hover);
transform: translateX(5px);
}
.status-label {
font-weight: var(--font-semibold);
color: var(--text-secondary);
font-size: var(--font-md);
}
.status-value {
font-weight: var(--font-semibold);
color: var(--text-primary);
font-size: var(--font-lg);
text-align: right;
font-family: var(--font-mono);
}
}
}
</style>

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
// Component for Transactions Tab
</script>
<template>
<div class="content-card">
<div class="tab-content-header">
<h2>Transaction History</h2>
</div>
<div class="empty-state">
<p>No transactions yet</p>
</div>
</div>
</template>
<style lang="scss" scoped>
.content-card {
@include card-base;
}
.tab-content-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-lg);
border-bottom: 2px solid var(--border-color);
h2 {
font-size: 1.3rem;
font-weight: var(--font-bold);
color: var(--text-primary);
margin: 0;
}
}
.empty-state {
text-align: center;
padding: var(--spacing-4xl) var(--spacing-lg);
color: var(--text-muted);
font-size: var(--font-lg);
}
</style>

View File

@ -0,0 +1,157 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Divider } from 'ant-design-vue'
import ButtonCommon from '@/components/common/ButtonCommon.vue'
import type { WalletTabProps } from '@/interface'
const props = defineProps<WalletTabProps>()
const walletVersion = ref('1.1.38')
const walletStatus = ref('Online')
const networkName = computed(() => props.network.replace('-mainnet', ''))
const handleCompoundTransactions = () => {
console.log('Compound Transactions')
}
const handleExportCSV = () => {
console.log('Export transactions as CSV')
}
const handleUpdateTransactionTimes = () => {
console.log('Update transaction times')
}
const handleBackupSeed = () => {
console.log('Backup Seed')
}
const handleRecoverFromSeed = () => {
console.log('Recover From Seed')
}
const handleExportWalletSeed = () => {
console.log('Export Wallet Seed File (KPK)')
}
const handleImportWalletSeed = () => {
console.log('Import Wallet Seed File (KPK)')
}
</script>
<template>
<div class="content-card wallet-info-card">
<div class="wallet-header">
<h2 class="wallet-title">KASPA WALLET</h2>
<p class="wallet-version">Version {{ walletVersion }}</p>
<p class="wallet-status-text">
Status: <strong>{{ walletStatus }}</strong>
</p>
<p class="wallet-network">
Network: <strong>{{ networkName }}</strong>
</p>
</div>
<div class="wallet-actions">
<ButtonCommon type="primary" size="large" block @click="handleCompoundTransactions">
Compound Transactions
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleExportCSV">
Export transactions as CSV
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleUpdateTransactionTimes">
Update transaction times
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleBackupSeed">
Backup Seed
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleRecoverFromSeed">
Recover From Seed
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleExportWalletSeed">
Export Wallet Seed File (KPK)
</ButtonCommon>
<ButtonCommon type="primary" size="large" block @click="handleImportWalletSeed">
Import Wallet Seed File (KPK)
</ButtonCommon>
</div>
<Divider />
<div class="donations-section">
<h3 class="section-subtitle"><span style="margin-right: 8px"></span> DONATIONS</h3>
</div>
<Divider />
<div class="developer-section">
<h3 class="section-subtitle">
<span style="margin-right: 8px"></span> DEVELOPER INFO
</h3>
</div>
</div>
</template>
<style lang="scss" scoped>
.content-card {
@include card-base;
}
.wallet-info-card {
.wallet-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-xl);
border-bottom: 2px solid var(--border-color);
.wallet-title {
font-size: var(--font-3xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
letter-spacing: var(--tracking-wider);
}
.wallet-version {
font-size: var(--font-lg);
color: var(--text-secondary);
margin-bottom: var(--spacing-sm);
}
.wallet-status-text,
.wallet-network {
font-size: var(--font-md);
color: var(--text-secondary);
margin-bottom: var(--spacing-xs);
strong {
color: var(--text-primary);
}
}
}
.wallet-actions {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.donations-section,
.developer-section {
.section-subtitle {
font-size: var(--font-lg);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin: 0;
cursor: pointer;
transition: var(--transition-normal);
&:hover {
color: var(--primary-color);
}
}
}
}
</style>

View File

@ -0,0 +1,4 @@
export { default as TransactionsTab } from './TransactionsTab.vue'
export { default as WalletTab } from './WalletTab.vue'
export { default as NetworkTab } from './NetworkTab.vue'
export { default as DebugTab } from './DebugTab.vue'

View File

@ -4,21 +4,26 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx' import vueJsx from '@vitejs/plugin-vue-jsx'
import VueDevTools from 'vite-plugin-vue-devtools' import VueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
server: { server: {
port: 3008, port: 3008,
}, },
plugins: [ plugins: [vue(), vueJsx(), VueDevTools()],
vue(), css: {
vueJsx(), preprocessorOptions: {
VueDevTools(), scss: {
], additionalData: `
@import "@/assets/scss/__variables.scss";
@import "@/assets/scss/__mixin.scss";
`,
},
},
},
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url)),
} },
} },
}) })