Merge pull request #2 from HiamQuan/feature/main_page
Feature/main page
This commit is contained in:
commit
c5b12065bf
@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { LayoutVue } from '@/components/common'
|
||||
import { LayoutVue } from '@/components'
|
||||
|
||||
const config = {
|
||||
token: {
|
||||
colorPrimary: '#ff7789',
|
||||
borderRadius: 0,
|
||||
colorPrimary: '#007FCF',
|
||||
borderRadius: 4,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
10
src/assets/scss/__animations.scss
Normal file
10
src/assets/scss/__animations.scss
Normal file
@ -0,0 +1,10 @@
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
|
||||
html {
|
||||
font-family: 'Noto Sans JP';
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Noto Sans JP';
|
||||
position: unset;
|
||||
}
|
||||
|
||||
img {
|
||||
@ -14,277 +18,16 @@ img {
|
||||
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;
|
||||
p {
|
||||
font-size: 1rem;
|
||||
margin: 1em;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
* {
|
||||
&::-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;
|
||||
h2 {
|
||||
color: var(--vt-c-main-color);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
27
src/assets/scss/__common.scss
Normal file
27
src/assets/scss/__common.scss
Normal 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);
|
||||
}
|
||||
@ -118,53 +118,40 @@ $fw: 100;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@mixin center_pos {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
@mixin btn-primary {
|
||||
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);
|
||||
|
||||
@mixin line_clamp($line) {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $line;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
@mixin position($pos: absolute, $left: 0, $right: 0, $top: 0, $bottom: 0) {
|
||||
position: $pos;
|
||||
top: $top;
|
||||
left: $left;
|
||||
right: $right;
|
||||
bottom: $bottom;
|
||||
}
|
||||
|
||||
// function
|
||||
@function sum($numbers...) {
|
||||
$sum: 0;
|
||||
@each $number in $numbers {
|
||||
$sum: $sum + $number;
|
||||
&:hover {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
@return $sum;
|
||||
}
|
||||
|
||||
@function calc_v2($size) {
|
||||
@return calc(100% - $size);
|
||||
}
|
||||
@mixin card-base {
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--card-radius);
|
||||
padding: var(--card-padding);
|
||||
box-shadow: var(--card-shadow);
|
||||
transition: var(--transition-all);
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
|
||||
@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;
|
||||
@media (max-width: 768px) {
|
||||
padding: var(--card-padding-mobile);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--card-shadow-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,107 +1,154 @@
|
||||
:root {
|
||||
--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;
|
||||
// ==================== COLORS ====================
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-v1: #5d6679;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
--vt-c-black-bold: #000000;
|
||||
--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;
|
||||
// Primary Colors
|
||||
--primary-color: #007FCF;
|
||||
--primary-hover: #0066A6;
|
||||
--primary-light: #E8F4FC;
|
||||
--primary-bg: #F5FBFF;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
// Secondary Colors
|
||||
--secondary-color: #FF9500;
|
||||
--secondary-hover: #E68600;
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
--vt-c-text-dark-3: rgba(0, 0, 0, 0.88);
|
||||
// Text Colors
|
||||
--text-primary: #2c3e50;
|
||||
--text-secondary: #5a6c7d;
|
||||
--text-muted: #8b95a5;
|
||||
--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);
|
||||
--vt-box-shadow-2: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
--vt-box-shadow-3: 0px 0px 5px 0px var(--vt-c-black-bold-v5) inset;
|
||||
// Border Colors
|
||||
--border-light: #E6F2FF;
|
||||
--border-color: #EBF5FF;
|
||||
--border-primary: #007FCF;
|
||||
|
||||
--vt-c-main: #ff7789;
|
||||
--vt-c-main-title: #519fb0;
|
||||
--vt-c-main-light: #6659ff;
|
||||
--vt-c-main-bg: #eff1f7;
|
||||
--vt-c-main-bg-v1: #f4f7ff;
|
||||
--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;
|
||||
// Status Colors
|
||||
--success-color: #10B981;
|
||||
--warning-color: #F59E0B;
|
||||
--error-color: #EF4444;
|
||||
--info-color: #007FCF;
|
||||
|
||||
--vt-c-violet: #5671fb;
|
||||
--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;
|
||||
// ==================== SPACING ====================
|
||||
|
||||
--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;
|
||||
--color-background-event: #3fb3ce;
|
||||
--color-background-sidebar: #e6e6e6;
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
// ==================== BORDER RADIUS ====================
|
||||
|
||||
--color-border: #b8b7b7;
|
||||
--color-border-v1: #cdd4e7;
|
||||
--color-border-shadow: #dfdfdf40;
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
// ==================== BOX SHADOWS ====================
|
||||
|
||||
--font-size: 14px;
|
||||
--vt-font-btn: 92px;
|
||||
--vt-br-btn: 3px;
|
||||
--section-gap: 160px;
|
||||
--shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
--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;
|
||||
--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;
|
||||
// ==================== TRANSITIONS ====================
|
||||
|
||||
--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);
|
||||
--vt-box-shadow-active: 0px 0px 4px 0px var(--vt-c-main);
|
||||
// ==================== TYPOGRAPHY ====================
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@import '__base';
|
||||
@import '__variables';
|
||||
@import '__mixin';
|
||||
@import '__base';
|
||||
@import '__animations';
|
||||
@import '__common';
|
||||
|
||||
235
src/components/WalletInfo.vue
Normal file
235
src/components/WalletInfo.vue
Normal 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>
|
||||
441
src/components/auth/CreateWalletComponent.vue
Normal file
441
src/components/auth/CreateWalletComponent.vue
Normal file
@ -0,0 +1,441 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ButtonCommon, FormCommon } from '@/components'
|
||||
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const passwordError = ref('')
|
||||
const confirmPasswordError = ref('')
|
||||
|
||||
// Password strength calculation
|
||||
const passwordStrength = computed(() => {
|
||||
if (!password.value) return { level: 0, text: '', color: '' }
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<!-- Header -->
|
||||
<div class="auth-card-header">
|
||||
<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 class="auth-card-content">
|
||||
<!-- Password Input -->
|
||||
<div class="form-group">
|
||||
<FormCommon
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="Create Password"
|
||||
placeholder="Enter your password"
|
||||
show-password-toggle
|
||||
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>
|
||||
|
||||
<!-- Confirm Password Input -->
|
||||
<div class="form-group">
|
||||
<FormCommon
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
label="Confirm Password"
|
||||
placeholder="Re-enter your password"
|
||||
show-password-toggle
|
||||
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>
|
||||
|
||||
<!-- 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">
|
||||
<ButtonCommon
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
:disabled="!canProceed"
|
||||
@click="handleNext"
|
||||
>
|
||||
Create Wallet
|
||||
</ButtonCommon>
|
||||
|
||||
<div class="secondary-actions">
|
||||
<button class="link-button" @click="handleIHaveWallet">
|
||||
Already have a wallet?
|
||||
</button>
|
||||
<span class="separator">•</span>
|
||||
<button class="link-button" @click="handleCancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
62
src/components/auth/OnboardingComponent.vue
Normal file
62
src/components/auth/OnboardingComponent.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonCommon } from '@/components'
|
||||
|
||||
const goToNewWallet = () => {
|
||||
window.open('https://kaspa-ng.org', '_blank')
|
||||
}
|
||||
|
||||
const goToLegacyWallet = () => {
|
||||
window.open('https://wallet.kaspanet.io', '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="welcome-page flex-center">
|
||||
<div class="welcome-card flex-center">
|
||||
<div class="welcome-box">
|
||||
<div class="header-section">
|
||||
<h2>Welcome to the New Wallet Experience</h2>
|
||||
<p>
|
||||
We've launched a new version of the Kaspa Wallet at
|
||||
<br />
|
||||
<span class="highlight"> https://kaspa-ng.org </span>
|
||||
</p>
|
||||
<ButtonCommon @click="goToNewWallet">
|
||||
Go to the new Kaspa NG Wallet
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<p>
|
||||
Already have funds on the old wallet?<br />
|
||||
You can still use <span class="highlight">https://wallet.kaspanet.io</span>
|
||||
</p>
|
||||
<ButtonCommon @click="goToLegacyWallet"> Continue on Legacy Wallet </ButtonCommon>
|
||||
<div class="note">Thank you for being a part of the Kaspa community!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.welcome-page {
|
||||
min-height: 100vh;
|
||||
background-color: var(--vt-c-white);
|
||||
position: relative;
|
||||
|
||||
.welcome-card {
|
||||
background-color: var(--vt-c-white);
|
||||
box-shadow: 0 0 15px var(--vt-c-badge-caption-shadow);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
.welcome-box {
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
src/components/auth/OpenWalletComponent.vue
Normal file
49
src/components/auth/OpenWalletComponent.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ButtonCommon, FormCommon } from '@/components'
|
||||
|
||||
const password = ref('')
|
||||
|
||||
const handleOpenWallet = () => {}
|
||||
|
||||
const handleNewWallet = () => {}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<div class="auth-card-header">
|
||||
<h2>Open Wallet</h2>
|
||||
</div>
|
||||
|
||||
<div class="auth-card-content">
|
||||
<div class="wallet-icon">
|
||||
<div class="icon-circle"></div>
|
||||
</div>
|
||||
|
||||
<div class="password-section">
|
||||
<FormCommon
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="Unlock the wallet with your password:"
|
||||
placeholder="Enter your password"
|
||||
show-password-toggle
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="auth-button-group">
|
||||
<ButtonCommon class="auth-btn secondary" @click="handleNewWallet">
|
||||
NEW WALLET
|
||||
</ButtonCommon>
|
||||
|
||||
<ButtonCommon class="auth-btn primary" @click="handleOpenWallet">
|
||||
OPEN WALLET
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
67
src/components/common/ButtonCommon.vue
Normal file
67
src/components/common/ButtonCommon.vue
Normal file
@ -0,0 +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>
|
||||
<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 />
|
||||
</Button>
|
||||
</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>
|
||||
152
src/components/common/FormCommon.vue
Normal file
152
src/components/common/FormCommon.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { IconCommon } from '../icon'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
type: { type: String, default: 'text' },
|
||||
placeholder: { type: String, default: '' },
|
||||
label: { type: String, default: '' },
|
||||
required: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
showPasswordToggle: { type: Boolean, default: false },
|
||||
error: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
||||
|
||||
const showPassword = ref(false)
|
||||
const isFocused = ref(false)
|
||||
|
||||
const inputType = computed(() => props.type)
|
||||
|
||||
const togglePassword = () => (showPassword.value = !showPassword.value)
|
||||
const handleInput = (e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
const handleFocus = (e: FocusEvent) => {
|
||||
isFocused.value = true
|
||||
emit('focus', e)
|
||||
}
|
||||
const handleBlur = (e: FocusEvent) => {
|
||||
isFocused.value = false
|
||||
emit('blur', e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-field">
|
||||
<label v-if="label" class="form-label">
|
||||
{{ label }}<span v-if="required" class="required-asterisk">*</span>
|
||||
</label>
|
||||
|
||||
<div class="input-container" :class="{ focused: isFocused, error, disabled }">
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
:type="inputType"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
class="form-input"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
|
||||
<button
|
||||
v-if="type === 'password' && showPasswordToggle"
|
||||
type="button"
|
||||
class="password-toggle flex-center"
|
||||
@click="togglePassword"
|
||||
>
|
||||
<IconCommon :size="20" :icon="showPassword ? 'eye-off' : 'eye'" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-field {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vt-c-black-bold);
|
||||
.required-asterisk {
|
||||
color: var(--vt-c-red-v3);
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-container {
|
||||
position: relative;
|
||||
border: 1px solid var(--vt-c-gray-6);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
&.focused {
|
||||
border-color: var(--vt-c-main-color);
|
||||
box-shadow: var(--vt-input-shadow-focus);
|
||||
}
|
||||
&.error {
|
||||
border-color: var(--vt-c-red-v3);
|
||||
background-color: var();
|
||||
}
|
||||
&.disabled {
|
||||
background-color: var(--vt-c-gray-4);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 12px 40px 12px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
color: var(--vt-c-black-bold);
|
||||
outline: none;
|
||||
&::placeholder {
|
||||
color: var(--vt-c-gray-8);
|
||||
}
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--vt-c-gray-8);
|
||||
}
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--vt-c-gray-8);
|
||||
padding: 4px;
|
||||
&:hover {
|
||||
color: var(--vt-c-black-bold);
|
||||
}
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--vt-c-red-v3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@ -1,3 +0,0 @@
|
||||
import LayoutVue from './LayoutVue.vue'
|
||||
|
||||
export { LayoutVue }
|
||||
31
src/components/icon/IconCommon.vue
Normal file
31
src/components/icon/IconCommon.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
EyeOutlined,
|
||||
EyeInvisibleOutlined,
|
||||
SearchOutlined,
|
||||
CloseOutlined,
|
||||
RightOutlined,
|
||||
LeftOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import type { IconProps } from '@/interface'
|
||||
|
||||
const props = withDefaults(defineProps<IconProps>(), {
|
||||
icon: '',
|
||||
})
|
||||
|
||||
const iconMap: Record<string, any> = {
|
||||
eye: EyeOutlined,
|
||||
'eye-off': EyeInvisibleOutlined,
|
||||
search: SearchOutlined,
|
||||
close: CloseOutlined,
|
||||
'arrow-right': RightOutlined,
|
||||
'arrow-left': LeftOutlined,
|
||||
}
|
||||
|
||||
const IconComponent = computed(() => iconMap[props.icon])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="IconComponent" :style="{ fontSize: size, color }" :class="props.class" />
|
||||
</template>
|
||||
1
src/components/icon/index.ts
Normal file
1
src/components/icon/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as IconCommon } from './IconCommon.vue'
|
||||
17
src/components/index.ts
Normal file
17
src/components/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import LayoutVue from './common/LayoutVue.vue'
|
||||
import ButtonCommon from './common/ButtonCommon.vue'
|
||||
import FormCommon from './common/FormCommon.vue'
|
||||
import OnboardingComponent from './auth/OnboardingComponent.vue'
|
||||
import OpenWalletComponent from './auth/OpenWalletComponent.vue'
|
||||
import CreateWalletComponent from './auth/CreateWalletComponent.vue'
|
||||
import { IconCommon } from './icon'
|
||||
|
||||
export {
|
||||
LayoutVue,
|
||||
ButtonCommon,
|
||||
FormCommon,
|
||||
OnboardingComponent,
|
||||
OpenWalletComponent,
|
||||
CreateWalletComponent,
|
||||
IconCommon,
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export * from './constants/code'
|
||||
export * from './constants/constants'
|
||||
export * from './constants/localStorage'
|
||||
20
src/interface/common.ts
Normal file
20
src/interface/common.ts
Normal 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
19
src/interface/home.ts
Normal 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 {}
|
||||
2
src/interface/index.ts
Normal file
2
src/interface/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './common'
|
||||
export * from './home'
|
||||
@ -1,5 +1,5 @@
|
||||
import * as Page from '@/views'
|
||||
import { getToken } from '@/helpers'
|
||||
import { getToken } from '@/utils'
|
||||
|
||||
const ifAuthenticated = (to: any, from: any, next: any) => {
|
||||
if (getToken()) {
|
||||
@ -20,18 +20,18 @@ const ifNotAuthenticated = (to: any, from: any, next: any) => {
|
||||
export const routes: any = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/home',
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
name: 'home',
|
||||
name: 'index',
|
||||
component: Page.Home,
|
||||
},
|
||||
{
|
||||
path: ':pathMatch(.*)*',
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: Page.Login,
|
||||
beforeEnter: ifNotAuthenticated,
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: Page.NotFound,
|
||||
name: 'page-not-found',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@ -10,7 +10,3 @@ export const CURRENT_YEAR = dayjs(new Date()).format('YYYY')
|
||||
export const MONTHS = Array.from({ length: 12 }, (item, i) => {
|
||||
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)
|
||||
}
|
||||
9
src/utils/helpers/format.ts
Normal file
9
src/utils/helpers/format.ts
Normal 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
4
src/utils/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './constants/code'
|
||||
export * from './constants/constants'
|
||||
export * from './helpers/format'
|
||||
export * from './helpers/localStorage'
|
||||
8
src/views/Auth/LoginView.vue
Normal file
8
src/views/Auth/LoginView.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { CreateWalletComponent } from '@/components'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div></div>
|
||||
<CreateWalletComponent />
|
||||
</template>
|
||||
@ -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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
96
src/views/Home/components/DebugTab.vue
Normal file
96
src/views/Home/components/DebugTab.vue
Normal 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>
|
||||
122
src/views/Home/components/NetworkTab.vue
Normal file
122
src/views/Home/components/NetworkTab.vue
Normal 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>
|
||||
41
src/views/Home/components/TransactionsTab.vue
Normal file
41
src/views/Home/components/TransactionsTab.vue
Normal 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>
|
||||
157
src/views/Home/components/WalletTab.vue
Normal file
157
src/views/Home/components/WalletTab.vue
Normal 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>
|
||||
4
src/views/Home/components/index.ts
Normal file
4
src/views/Home/components/index.ts
Normal 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'
|
||||
@ -1,2 +1,3 @@
|
||||
export const Home = () => import('@/views/Home/HomeView.vue')
|
||||
export const NotFound = () => import('@/views/NotFound/NotFoundView.vue')
|
||||
export const Login = () => import('@/views/Auth/LoginView.vue')
|
||||
|
||||
@ -4,21 +4,26 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 3008,
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
VueDevTools(),
|
||||
],
|
||||
},
|
||||
plugins: [vue(), vueJsx(), VueDevTools()],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `
|
||||
@import "@/assets/scss/__variables.scss";
|
||||
@import "@/assets/scss/__mixin.scss";
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user