211025/coding_UI
This commit is contained in:
parent
2414cad2d2
commit
232255cd84
@ -3,8 +3,8 @@ import { LayoutVue } from '@/components'
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: '#ff7789',
|
colorPrimary: '#007FCF',
|
||||||
borderRadius: 0,
|
borderRadius: 4,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</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');
|
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
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;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import '__base';
|
||||||
@import '__variables';
|
@import '__variables';
|
||||||
@import '__mixin';
|
@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>
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 {}
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
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'
|
||||||
@ -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>
|
||||||
|
|||||||
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'
|
||||||
@ -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)),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user