Compare commits
No commits in common. "e24832fa1a28fdf62440eb4538c45a7ec21e4cd2" and "2414cad2d2586894f88c86b7d94117b07245ac7a" have entirely different histories.
e24832fa1a
...
2414cad2d2
@ -5,7 +5,7 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" href="/favicon.png" />
|
<link rel="apple-touch-icon" href="/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Neptune Web Wallet</title>
|
<title>Web Wallet</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "neptune-web-wallet",
|
"name": "webtoon-admin",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { LayoutVue } from '@/components'
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: '#007FCF',
|
colorPrimary: '#ff7789',
|
||||||
borderRadius: 4,
|
borderRadius: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { STATUS_CODE_SUCCESS, ACCESS_TOKEN, STATUS_CODE_UNAUTHORIZED } from '@/utils'
|
import { STATUS_CODE_SUCCESS, ACCESS_TOKEN, STATUS_CODE_UNAUTHORIZED } from '@/helpers'
|
||||||
|
|
||||||
axios.defaults.withCredentials = false
|
axios.defaults.withCredentials = false
|
||||||
|
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse-dot {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 0.8;
|
|
||||||
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +1,12 @@
|
|||||||
|
@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;
|
||||||
position: unset;
|
font-family: 'Noto Sans JP';
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@ -18,6 +14,303 @@ 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;
|
||||||
@ -31,3 +324,97 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
.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);
|
|
||||||
}
|
|
||||||
.highlight {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
@ -118,40 +118,53 @@ $fw: 100;
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin btn-primary {
|
@mixin center_pos {
|
||||||
background: var(--primary-color);
|
position: absolute;
|
||||||
border-color: var(--primary-color);
|
top: 50%;
|
||||||
font-weight: var(--font-semibold);
|
left: 50%;
|
||||||
height: auto;
|
transform: translate(-50%, -50%);
|
||||||
padding: var(--btn-padding-y) var(--btn-padding-x);
|
|
||||||
transition: var(--transition-all);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--primary-hover);
|
|
||||||
border-color: var(--primary-hover);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active,
|
@mixin line_clamp($line) {
|
||||||
&:focus {
|
overflow: hidden;
|
||||||
background: var(--primary-hover);
|
display: -webkit-box;
|
||||||
border-color: var(--primary-hover);
|
-webkit-line-clamp: $line;
|
||||||
}
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin card-base {
|
@mixin position($pos: absolute, $left: 0, $right: 0, $top: 0, $bottom: 0) {
|
||||||
background: var(--bg-white);
|
position: $pos;
|
||||||
border-radius: var(--card-radius);
|
top: $top;
|
||||||
padding: var(--card-padding);
|
left: $left;
|
||||||
box-shadow: var(--card-shadow);
|
right: $right;
|
||||||
transition: var(--transition-all);
|
bottom: $bottom;
|
||||||
animation: fadeIn 0.6s ease-out;
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
padding: var(--card-padding-mobile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
// function
|
||||||
transform: translateY(-4px);
|
@function sum($numbers...) {
|
||||||
box-shadow: var(--card-shadow-hover);
|
$sum: 0;
|
||||||
|
@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,154 +1,113 @@
|
|||||||
:root {
|
:root {
|
||||||
// ==================== COLORS ====================
|
--vt-btn-padding: 11px 8px;
|
||||||
|
--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;
|
||||||
|
|
||||||
// Primary Colors
|
--vt-c-black: #181818;
|
||||||
--primary-color: #007fcf;
|
--vt-c-black-v1: #5d6679;
|
||||||
--primary-hover: #0066a6;
|
--vt-c-black-soft: #222222;
|
||||||
--primary-light: #e8f4fc;
|
--vt-c-black-mute: #282828;
|
||||||
--primary-bg: #f5fbff;
|
--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;
|
||||||
|
|
||||||
// Secondary Colors
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
--secondary-color: #ff9500;
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
--secondary-hover: #e68600;
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
// Text Colors
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
--text-primary: #2c3e50;
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
--text-secondary: #5a6c7d;
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
--text-muted: #8b95a5;
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
--text-light: #ffffff;
|
--vt-c-text-dark-3: rgba(0, 0, 0, 0.88);
|
||||||
|
|
||||||
// Background Colors
|
--vt-c-background-dark-1: rgba(0, 0, 0, 0.04);
|
||||||
--bg-gradient-start: #f0f8ff;
|
|
||||||
--bg-gradient-end: #e6f2ff;
|
|
||||||
--bg-white: #ffffff;
|
|
||||||
--bg-light: #f8fcff;
|
|
||||||
--bg-hover: #e8f4fc;
|
|
||||||
|
|
||||||
// Border Colors
|
--vt-box-shadow-1: 0px 0px 8px rgba(78, 37, 0, 0.78);
|
||||||
--border-light: #e6f2ff;
|
--vt-box-shadow-2: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||||
--border-color: #ebf5ff;
|
--vt-box-shadow-3: 0px 0px 5px 0px var(--vt-c-black-bold-v5) inset;
|
||||||
--border-primary: #007fcf;
|
|
||||||
|
|
||||||
// Status Colors
|
--vt-c-main: #ff7789;
|
||||||
--success-color: #10b981;
|
--vt-c-main-title: #519fb0;
|
||||||
--warning-color: #f59e0b;
|
--vt-c-main-light: #6659ff;
|
||||||
--error-color: #ef4444;
|
--vt-c-main-bg: #eff1f7;
|
||||||
--info-color: #007fcf;
|
--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;
|
||||||
|
|
||||||
// ==================== SPACING ====================
|
--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-xs: 0.25rem; // 4px
|
--vt-c-turquoise: #3fb3ce;
|
||||||
--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
|
|
||||||
|
|
||||||
// ==================== BORDER RADIUS ====================
|
--color-background: #f5f7fb;
|
||||||
|
--color-background-event: #3fb3ce;
|
||||||
|
--color-background-sidebar: #e6e6e6;
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
|
||||||
--radius-sm: 8px;
|
--color-border: #b8b7b7;
|
||||||
--radius-md: 10px;
|
--color-border-v1: #cdd4e7;
|
||||||
--radius-lg: 12px;
|
--color-border-shadow: #dfdfdf40;
|
||||||
--radius-xl: 16px;
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
--radius-full: 9999px;
|
|
||||||
|
|
||||||
// ==================== BOX SHADOWS ====================
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
--shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.05);
|
--font-size: 14px;
|
||||||
--shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.05);
|
--vt-font-btn: 92px;
|
||||||
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08);
|
--vt-br-btn: 3px;
|
||||||
--shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
|
--section-gap: 160px;
|
||||||
--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);
|
|
||||||
|
|
||||||
// ==================== TRANSITIONS ====================
|
--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;
|
||||||
|
|
||||||
--transition-fast: 0.2s ease;
|
--vt-c-orange: #f3a964;
|
||||||
--transition-normal: 0.3s ease;
|
|
||||||
--transition-slow: 0.5s ease;
|
|
||||||
--transition-all: all 0.3s ease;
|
|
||||||
|
|
||||||
// ==================== TYPOGRAPHY ====================
|
--vt-box-shadow: 0px 0px 4px 0px var(--vt-c-gray-3);
|
||||||
|
--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,5 +1,3 @@
|
|||||||
@import '__base';
|
|
||||||
@import '__variables';
|
@import '__variables';
|
||||||
@import '__mixin';
|
@import '__mixin';
|
||||||
@import '__animations';
|
@import '__base';
|
||||||
@import '__common';
|
|
||||||
|
|||||||
@ -1,235 +0,0 @@
|
|||||||
<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,436 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, defineEmits, onMounted } from 'vue'
|
|
||||||
import { ButtonCommon } from '@/components'
|
|
||||||
import { useSeedStore } from '@/stores'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
next: []
|
|
||||||
back: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const seedStore = useSeedStore()
|
|
||||||
|
|
||||||
const seedWords = ref<string[]>([])
|
|
||||||
const currentQuestionIndex = ref(0)
|
|
||||||
const selectedAnswer = ref('')
|
|
||||||
const isCorrect = ref(false)
|
|
||||||
const showResult = ref(false)
|
|
||||||
|
|
||||||
const generateQuiz = (): {
|
|
||||||
position: number
|
|
||||||
correctWord: string
|
|
||||||
options: string[]
|
|
||||||
} | null => {
|
|
||||||
if (seedWords.value.length === 0) return null
|
|
||||||
|
|
||||||
const randomPosition = Math.floor(Math.random() * 12) + 1
|
|
||||||
currentQuestionIndex.value = randomPosition - 1
|
|
||||||
|
|
||||||
const correctWord = seedWords.value[randomPosition - 1]
|
|
||||||
const options = [correctWord]
|
|
||||||
|
|
||||||
const BIP39_WORDS = [
|
|
||||||
'abandon',
|
|
||||||
'ability',
|
|
||||||
'able',
|
|
||||||
'about',
|
|
||||||
'above',
|
|
||||||
'absent',
|
|
||||||
'absorb',
|
|
||||||
'abstract',
|
|
||||||
'absurd',
|
|
||||||
'abuse',
|
|
||||||
'access',
|
|
||||||
'accident',
|
|
||||||
'account',
|
|
||||||
'accuse',
|
|
||||||
'achieve',
|
|
||||||
'acid',
|
|
||||||
'acoustic',
|
|
||||||
'acquire',
|
|
||||||
'across',
|
|
||||||
'act',
|
|
||||||
'action',
|
|
||||||
'actor',
|
|
||||||
'actress',
|
|
||||||
'actual',
|
|
||||||
'adapt',
|
|
||||||
'add',
|
|
||||||
'addict',
|
|
||||||
'address',
|
|
||||||
'adjust',
|
|
||||||
'admit',
|
|
||||||
'adult',
|
|
||||||
'advance',
|
|
||||||
'advice',
|
|
||||||
'aerobic',
|
|
||||||
'affair',
|
|
||||||
'afford',
|
|
||||||
'afraid',
|
|
||||||
'again',
|
|
||||||
'age',
|
|
||||||
'agent',
|
|
||||||
'agree',
|
|
||||||
'ahead',
|
|
||||||
'aim',
|
|
||||||
'air',
|
|
||||||
'airport',
|
|
||||||
'aisle',
|
|
||||||
'alarm',
|
|
||||||
'album',
|
|
||||||
'alcohol',
|
|
||||||
'alert',
|
|
||||||
'alien',
|
|
||||||
'all',
|
|
||||||
'alley',
|
|
||||||
'allow',
|
|
||||||
'almost',
|
|
||||||
'alone',
|
|
||||||
'alpha',
|
|
||||||
'already',
|
|
||||||
'also',
|
|
||||||
'alter',
|
|
||||||
'always',
|
|
||||||
'amateur',
|
|
||||||
'amazing',
|
|
||||||
'among',
|
|
||||||
'amount',
|
|
||||||
'amused',
|
|
||||||
'analyst',
|
|
||||||
'anchor',
|
|
||||||
'ancient',
|
|
||||||
'anger',
|
|
||||||
'angle',
|
|
||||||
'angry',
|
|
||||||
'animal',
|
|
||||||
'ankle',
|
|
||||||
'announce',
|
|
||||||
'annual',
|
|
||||||
'another',
|
|
||||||
'answer',
|
|
||||||
'antenna',
|
|
||||||
'antique',
|
|
||||||
'anxiety',
|
|
||||||
'any',
|
|
||||||
'apart',
|
|
||||||
'apology',
|
|
||||||
'appear',
|
|
||||||
'apple',
|
|
||||||
'approve',
|
|
||||||
'april',
|
|
||||||
'arch',
|
|
||||||
'arctic',
|
|
||||||
'area',
|
|
||||||
'arena',
|
|
||||||
'argue',
|
|
||||||
'arm',
|
|
||||||
'armed',
|
|
||||||
'armor',
|
|
||||||
'army',
|
|
||||||
'around',
|
|
||||||
'arrange',
|
|
||||||
'arrest',
|
|
||||||
]
|
|
||||||
|
|
||||||
while (options.length < 4) {
|
|
||||||
const randomWord = BIP39_WORDS[Math.floor(Math.random() * BIP39_WORDS.length)]
|
|
||||||
if (!options.includes(randomWord)) {
|
|
||||||
options.push(randomWord)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.sort(() => Math.random() - 0.5)
|
|
||||||
|
|
||||||
return {
|
|
||||||
position: randomPosition,
|
|
||||||
correctWord,
|
|
||||||
options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const quizData = ref<{
|
|
||||||
position: number
|
|
||||||
correctWord: string
|
|
||||||
options: string[]
|
|
||||||
} | null>(null)
|
|
||||||
|
|
||||||
const handleAnswerSelect = (answer: string) => {
|
|
||||||
selectedAnswer.value = answer
|
|
||||||
isCorrect.value = answer === quizData.value?.correctWord
|
|
||||||
showResult.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
if (isCorrect.value) {
|
|
||||||
emit('next')
|
|
||||||
} else {
|
|
||||||
showResult.value = false
|
|
||||||
selectedAnswer.value = ''
|
|
||||||
const newQuiz = generateQuiz()
|
|
||||||
if (newQuiz) {
|
|
||||||
quizData.value = newQuiz
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const words = seedStore.getSeedWords()
|
|
||||||
if (words.length > 0) {
|
|
||||||
seedWords.value = words
|
|
||||||
const newQuiz = generateQuiz()
|
|
||||||
if (newQuiz) {
|
|
||||||
quizData.value = newQuiz
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const sampleWords = [
|
|
||||||
'abandon',
|
|
||||||
'ability',
|
|
||||||
'able',
|
|
||||||
'about',
|
|
||||||
'above',
|
|
||||||
'absent',
|
|
||||||
'absorb',
|
|
||||||
'abstract',
|
|
||||||
'absurd',
|
|
||||||
'abuse',
|
|
||||||
'access',
|
|
||||||
'accident',
|
|
||||||
]
|
|
||||||
seedWords.value = sampleWords
|
|
||||||
const newQuiz = generateQuiz()
|
|
||||||
if (newQuiz) {
|
|
||||||
quizData.value = newQuiz
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="confirm-container">
|
|
||||||
<div class="confirm-card">
|
|
||||||
<div class="confirm-header">
|
|
||||||
<h1 class="confirm-title">Recovery Seed</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="confirm-content">
|
|
||||||
<div class="progress-indicators">
|
|
||||||
<div class="progress-circle"></div>
|
|
||||||
<div class="progress-circle active"></div>
|
|
||||||
<div class="progress-circle"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="instruction-text">
|
|
||||||
<p>
|
|
||||||
Make sure you wrote the phrase down correctly by answering this quick
|
|
||||||
checkup.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quiz-section">
|
|
||||||
<h2 class="quiz-question">What is the {{ quizData?.position }}th word?</h2>
|
|
||||||
|
|
||||||
<div class="answer-options">
|
|
||||||
<button
|
|
||||||
v-for="(option, index) in quizData?.options"
|
|
||||||
:key="index"
|
|
||||||
class="answer-button"
|
|
||||||
:class="{
|
|
||||||
selected: selectedAnswer === option,
|
|
||||||
correct: showResult && option === quizData?.correctWord,
|
|
||||||
incorrect:
|
|
||||||
showResult &&
|
|
||||||
selectedAnswer === option &&
|
|
||||||
option !== quizData?.correctWord,
|
|
||||||
}"
|
|
||||||
@click="handleAnswerSelect(option)"
|
|
||||||
:disabled="showResult"
|
|
||||||
>
|
|
||||||
{{ option }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="showResult" class="result-message">
|
|
||||||
<p v-if="isCorrect" class="success-message">✓ Correct! You can proceed.</p>
|
|
||||||
<p v-else class="error-message">✗ Incorrect. Please try again.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="confirm-actions">
|
|
||||||
<ButtonCommon
|
|
||||||
v-if="showResult && isCorrect"
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
@click="handleNext"
|
|
||||||
>
|
|
||||||
CONTINUE
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.confirm-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
background: var(--bg-light);
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-card {
|
|
||||||
@include card-base;
|
|
||||||
max-width: 500px;
|
|
||||||
width: 100%;
|
|
||||||
border: 2px solid var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
padding-bottom: var(--spacing-xl);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
.confirm-title {
|
|
||||||
font-size: var(--font-2xl);
|
|
||||||
font-weight: var(--font-bold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-content {
|
|
||||||
.progress-indicators {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
|
|
||||||
.progress-circle {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--border-light);
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: var(--primary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.instruction-text {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
line-height: var(--leading-normal);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.quiz-section {
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
|
|
||||||
.quiz-question {
|
|
||||||
font-size: var(--font-lg);
|
|
||||||
font-weight: var(--font-bold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.answer-options {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
|
|
||||||
.answer-button {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
border: 2px solid var(--border-light);
|
|
||||||
background: var(--bg-white);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
font-weight: var(--font-medium);
|
|
||||||
color: var(--text-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
background: var(--primary-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
background: var(--primary-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.correct {
|
|
||||||
border-color: var(--success-color);
|
|
||||||
background: var(--success-light);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.incorrect {
|
|
||||||
border-color: var(--error-color);
|
|
||||||
background: var(--error-light);
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-message {
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.success-message {
|
|
||||||
color: var(--success-color);
|
|
||||||
font-weight: var(--font-medium);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: var(--error-color);
|
|
||||||
font-weight: var(--font-medium);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.confirm-container {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-card {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quiz-section {
|
|
||||||
.answer-options {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,561 +1,61 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineEmits } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ButtonCommon, FormCommon, KeystoreDownloadComponent } from '@/components'
|
import { ButtonCommon, FormCommon } from '@/components'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
navigateToOpenWallet: [event: Event]
|
|
||||||
navigateToRecoverySeed: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const step = ref(1)
|
|
||||||
|
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const confirmPassword = ref('')
|
const confirmPassword = ref('')
|
||||||
const passwordError = ref('')
|
|
||||||
const confirmPasswordError = ref('')
|
|
||||||
|
|
||||||
const passwordStrength = computed(() => {
|
const handleCancel = () => {}
|
||||||
if (!password.value) return { level: 0, text: '', color: '' }
|
|
||||||
|
|
||||||
let strength = 0
|
const handleIHaveWallet = () => {}
|
||||||
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(() => {
|
const handleNext = () => {}
|
||||||
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 handleIHaveWallet = (e: Event) => {
|
|
||||||
e.preventDefault()
|
|
||||||
emit('navigateToOpenWallet', e)
|
|
||||||
}
|
|
||||||
const handleNextPassword = () => {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
step.value = 2
|
|
||||||
}
|
|
||||||
function downloadKeystoreFile() {
|
|
||||||
// Giả lập nội dung keystore (có thể tuỳ chỉnh)
|
|
||||||
const data = {
|
|
||||||
account: 'kaspa-wallet',
|
|
||||||
version: 1,
|
|
||||||
enc: 'mock-data',
|
|
||||||
created: new Date().toISOString(),
|
|
||||||
note: 'Exported from web-wallet',
|
|
||||||
hint: 'Replace bằng file thực tế trong tích hợp thật.',
|
|
||||||
}
|
|
||||||
const file = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = URL.createObjectURL(file)
|
|
||||||
link.download = 'kaspa-wallet-keystore.json'
|
|
||||||
link.click()
|
|
||||||
setTimeout(() => URL.revokeObjectURL(link.href), 2300)
|
|
||||||
step.value = 3
|
|
||||||
}
|
|
||||||
function handleBack() {
|
|
||||||
if (step.value === 2) step.value = 1
|
|
||||||
else if (step.value === 3) step.value = 2
|
|
||||||
}
|
|
||||||
function resetAll() {
|
|
||||||
password.value = ''
|
|
||||||
confirmPassword.value = ''
|
|
||||||
passwordError.value = ''
|
|
||||||
confirmPasswordError.value = ''
|
|
||||||
step.value = 1
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-container">
|
<div class="auth-container">
|
||||||
<div class="auth-card">
|
<div class="auth-card">
|
||||||
<template v-if="step === 1">
|
|
||||||
<div class="auth-card-header">
|
<div class="auth-card-header">
|
||||||
<div class="logo-container">
|
<h2>Create Wallet</h2>
|
||||||
<div class="logo-circle">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
class="neptune-logo"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
|
|
||||||
|
|
||||||
<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)" />
|
|
||||||
|
|
||||||
<ellipse
|
|
||||||
cx="50"
|
|
||||||
cy="50"
|
|
||||||
rx="42"
|
|
||||||
ry="12"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#ringGradient)"
|
|
||||||
stroke-width="4"
|
|
||||||
opacity="0.8"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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="form-group">
|
<div class="password-section">
|
||||||
<FormCommon
|
<FormCommon
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
type="password"
|
||||||
label="Create Password"
|
label="Create a password for your new wallet"
|
||||||
placeholder="Enter your password"
|
placeholder="Password"
|
||||||
show-password-toggle
|
show-password-toggle
|
||||||
required
|
required
|
||||||
:error="passwordError"
|
|
||||||
@input="passwordError = ''"
|
|
||||||
/>
|
/>
|
||||||
<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>
|
</div>
|
||||||
<span
|
|
||||||
class="strength-text"
|
<div class="password-section">
|
||||||
:style="{ color: passwordStrength.color }"
|
|
||||||
>{{ passwordStrength.text }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<FormCommon
|
<FormCommon
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
type="password"
|
type="password"
|
||||||
label="Confirm Password"
|
label="Confirm password"
|
||||||
placeholder="Re-enter your password"
|
placeholder="Confirm Password"
|
||||||
show-password-toggle
|
show-password-toggle
|
||||||
required
|
required
|
||||||
:error="confirmPasswordError"
|
|
||||||
@input="confirmPasswordError = ''"
|
|
||||||
/>
|
/>
|
||||||
<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>
|
|
||||||
<p class="helper-text">
|
|
||||||
Password must be at least 8 characters with uppercase, lowercase, and
|
|
||||||
numbers.
|
|
||||||
</p>
|
|
||||||
<div class="auth-button-group">
|
<div class="auth-button-group">
|
||||||
<ButtonCommon
|
<ButtonCommon class="auth-btn secondary" @click="handleCancel">
|
||||||
type="primary"
|
Cancel
|
||||||
size="large"
|
</ButtonCommon>
|
||||||
class="auth-button"
|
|
||||||
block
|
<ButtonCommon class="auth-btn secondary" @click="handleIHaveWallet">
|
||||||
:disabled="!canProceed"
|
I have a wallet
|
||||||
@click="handleNextPassword"
|
</ButtonCommon>
|
||||||
>Create Wallet</ButtonCommon
|
|
||||||
>
|
<ButtonCommon class="auth-btn primary" @click="handleNext"> Next </ButtonCommon>
|
||||||
<div class="secondary-actions">
|
</div>
|
||||||
<button class="link-button" @click="handleIHaveWallet">
|
|
||||||
Already have a wallet?
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="step === 2">
|
|
||||||
<KeystoreDownloadComponent @download="downloadKeystoreFile" @back="handleBack" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="step === 3">
|
|
||||||
<div class="well-done-step">
|
|
||||||
<h2 class="done-main">You are done!</h2>
|
|
||||||
<p class="done-desc">
|
|
||||||
You are now ready to take advantage of all that your wallet has to offer!
|
|
||||||
Access with keystore file should only be used in an offline setting.
|
|
||||||
</p>
|
|
||||||
<div class="center-svg" style="margin: 14px auto 12px auto">
|
|
||||||
<svg width="180" height="95" viewBox="0 0 175 92" fill="none">
|
|
||||||
<rect x="111" y="37" width="64" height="33" rx="7" fill="#23B1EC" />
|
|
||||||
<rect
|
|
||||||
x="30.5"
|
|
||||||
y="37.5"
|
|
||||||
width="80"
|
|
||||||
height="46"
|
|
||||||
rx="7.5"
|
|
||||||
fill="#D6F9FE"
|
|
||||||
stroke="#AEEBF8"
|
|
||||||
stroke-width="5"
|
|
||||||
/>
|
|
||||||
<rect x="56" y="67" width="32" height="10" rx="3" fill="#B0F3A6" />
|
|
||||||
<rect x="46" y="49" width="52" height="12" rx="3" fill="#a2d2f5" />
|
|
||||||
<circle cx="155" cy="52" r="8" fill="#fff" />
|
|
||||||
<rect x="121" y="43" width="27" height="7" rx="1.5" fill="#5AE9D2" />
|
|
||||||
<rect x="128" y="59" width="17" height="4" rx="1.5" fill="#FCEBBA" />
|
|
||||||
<circle cx="40" cy="27" r="7" fill="#A2D2F5" />
|
|
||||||
<g>
|
|
||||||
<circle cx="128" cy="21" r="3" fill="#FF8585" />
|
|
||||||
<circle cx="57.5" cy="20.5" r="1.5" fill="#67DEFF" />
|
|
||||||
<rect x="95" y="18" width="7" height="5" rx="2" fill="#A2D2F5" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<ButtonCommon
|
|
||||||
class="done-btn"
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
style="margin-bottom: 0.3em"
|
|
||||||
@click="$router.push('/')"
|
|
||||||
>Access Wallet</ButtonCommon
|
|
||||||
>
|
|
||||||
<button class="done-link" type="button" @click="resetAll">
|
|
||||||
Create Another Wallet
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<slot> </slot>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
.auth-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
background: var(--bg-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card {
|
|
||||||
@include card-base;
|
|
||||||
max-width: 720px;
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
font-size: var(--font-xs);
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin: 0 0 var(--spacing-xl);
|
|
||||||
line-height: var(--leading-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button {
|
|
||||||
width: fit-content;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button-group {
|
|
||||||
margin-top: var(--spacing-2xl);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.well-done-step {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px 8px;
|
|
||||||
.done-title {
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.07em;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
.done-main {
|
|
||||||
font-size: 1.36rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.done-desc {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.11em;
|
|
||||||
max-width: 410px;
|
|
||||||
margin: 2px auto 15px auto;
|
|
||||||
}
|
|
||||||
.center-svg {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.btn-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 11px;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto 5px auto;
|
|
||||||
}
|
|
||||||
.done-btn {
|
|
||||||
margin-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
.done-link {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-size: 1em;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -1,270 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { ButtonCommon, FormCommon } from '@/components'
|
|
||||||
import { validateSeedPhrase18 } from '@/utils/helpers/seedPhrase'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(
|
|
||||||
e: 'import-success',
|
|
||||||
data: { type: 'seed' | 'privatekey'; value: string | string[]; passphrase?: string }
|
|
||||||
): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const tab = ref<'seedphrase' | 'privatekey'>('seedphrase')
|
|
||||||
const seedWords = ref<string[]>(Array(18).fill(''))
|
|
||||||
const seedError = ref('')
|
|
||||||
const passphrase = ref('')
|
|
||||||
const privateKey = ref('')
|
|
||||||
const privateKeyError = ref('')
|
|
||||||
|
|
||||||
const inputBoxFocus = (idx: number) => {
|
|
||||||
document.getElementById('input-' + idx)?.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateSeed = () => {
|
|
||||||
if (seedWords.value.some((w) => !w.trim())) {
|
|
||||||
seedError.value = 'Please enter all 18 words.'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!validateSeedPhrase18(seedWords.value)) {
|
|
||||||
seedError.value = 'One or more words are invalid.'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
seedError.value = ''
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateKey = () => {
|
|
||||||
if (!privateKey.value.trim()) {
|
|
||||||
privateKeyError.value = 'Please enter your private key.'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
privateKeyError.value = ''
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContinue = () => {
|
|
||||||
if (tab.value === 'seedphrase') {
|
|
||||||
if (validateSeed()) {
|
|
||||||
emit('import-success', {
|
|
||||||
type: 'seed',
|
|
||||||
value: seedWords.value,
|
|
||||||
passphrase: passphrase.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (validateKey()) {
|
|
||||||
emit('import-success', { type: 'privatekey', value: privateKey.value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="import-wallet dark-card">
|
|
||||||
<h2 class="title">Import Wallet</h2>
|
|
||||||
<div class="desc">Pick your import method</div>
|
|
||||||
<div class="tabs">
|
|
||||||
<button
|
|
||||||
:class="['tab-btn', tab === 'seedphrase' && 'active']"
|
|
||||||
@click="tab = 'seedphrase'"
|
|
||||||
>
|
|
||||||
Import by seed phrase
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:class="['tab-btn', tab === 'privatekey' && 'active']"
|
|
||||||
@click="tab = 'privatekey'"
|
|
||||||
>
|
|
||||||
Import by private key
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-if="tab === 'seedphrase'" class="tab-pane">
|
|
||||||
<div class="seed-row-radio">
|
|
||||||
<div class="radio active">18 words</div>
|
|
||||||
</div>
|
|
||||||
<div class="seed-inputs">
|
|
||||||
<div class="seed-input-grid">
|
|
||||||
<div v-for="(word, i) in seedWords" :key="i" class="seed-box">
|
|
||||||
<input
|
|
||||||
:id="'input-' + i"
|
|
||||||
type="text"
|
|
||||||
inputmode="text"
|
|
||||||
autocapitalize="off"
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false"
|
|
||||||
v-model="seedWords[i]"
|
|
||||||
:placeholder="i + 1 + '.'"
|
|
||||||
maxlength="24"
|
|
||||||
@keydown.enter="inputBoxFocus(i + 1)"
|
|
||||||
:class="{ error: seedError && !word.trim() }"
|
|
||||||
@focus="seedError = ''"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row mt-sm">
|
|
||||||
<FormCommon
|
|
||||||
v-model="passphrase"
|
|
||||||
:label="'Seed passphrase (optional)'"
|
|
||||||
placeholder="Enter seed passphrase"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="seedError" class="error-text">{{ seedError }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="tab-pane">
|
|
||||||
<div class="form-row mb-md">
|
|
||||||
<FormCommon
|
|
||||||
v-model="privateKey"
|
|
||||||
type="text"
|
|
||||||
label="Private key"
|
|
||||||
placeholder="Enter private key"
|
|
||||||
:error="privateKeyError"
|
|
||||||
@focus="privateKeyError = ''"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ButtonCommon
|
|
||||||
class="mt-lg"
|
|
||||||
type="primary"
|
|
||||||
block
|
|
||||||
size="large"
|
|
||||||
:disabled="
|
|
||||||
tab === 'seedphrase'
|
|
||||||
? !seedWords.every((w) => w) || !!seedError
|
|
||||||
: !privateKey || !!privateKeyError
|
|
||||||
"
|
|
||||||
@click="handleContinue"
|
|
||||||
>Continue</ButtonCommon
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.import-wallet {
|
|
||||||
max-width: 420px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 24px auto;
|
|
||||||
background: var(--bg-light);
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
box-shadow: var(--shadow-primary);
|
|
||||||
padding: 32px 28px 24px 28px;
|
|
||||||
color: var(--text-primary);
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.45rem;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
background: var(--text-primary);
|
|
||||||
border-radius: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 18px;
|
|
||||||
.tab-btn {
|
|
||||||
flex: 1;
|
|
||||||
padding: 13px;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.18s;
|
|
||||||
&.active {
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: var(--text-light);
|
|
||||||
}
|
|
||||||
&:hover:not(.active) {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tab-pane {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
.seed-row-radio {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
.radio {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
border-radius: 14px;
|
|
||||||
font-size: 1.08rem;
|
|
||||||
padding: 7px 24px 7px 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
&.active {
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: var(--text-light);
|
|
||||||
}
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.seed-inputs {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.seed-input-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.seed-box input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 9px;
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
outline: none;
|
|
||||||
font-size: 15px;
|
|
||||||
transition:
|
|
||||||
border 0.16s,
|
|
||||||
box-shadow 0.16s;
|
|
||||||
&:focus {
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
box-shadow: 0 2px 8px var(--shadow-primary);
|
|
||||||
}
|
|
||||||
&.error {
|
|
||||||
border-color: var(--error-color);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.form-row {
|
|
||||||
margin-top: 19px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.mt-sm {
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
.mb-md {
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
.mt-lg {
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
.error-text {
|
|
||||||
color: var(--error-color);
|
|
||||||
font-size: 0.97em;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.import-wallet {
|
|
||||||
padding: 16px 5px;
|
|
||||||
}
|
|
||||||
.seed-input-grid {
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,235 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'download'): void
|
|
||||||
(e: 'back'): void
|
|
||||||
}>()
|
|
||||||
function handleDownload() {
|
|
||||||
emit('download')
|
|
||||||
}
|
|
||||||
function handleBack() {
|
|
||||||
emit('back')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="keystore-step">
|
|
||||||
<div class="step-content">
|
|
||||||
<h2 class="title">Download keystore file</h2>
|
|
||||||
<div class="desc">Important things to know before downloading your keystore file.</div>
|
|
||||||
<div class="box-list">
|
|
||||||
<div class="box">
|
|
||||||
<div class="icn">
|
|
||||||
<svg width="44" height="44" viewBox="0 0 36 36">
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M15,29 L29,29 C30.1045695,29 31,28.1045695 31,27 L31,9 C31,7.8954305 30.1045695,7 29,7 L7,7 C5.8954305,7 5,7.8954305 5,9 L5,27 C5,28.1045695 5.8954305,29 7,29 L11,29"
|
|
||||||
fill="#d8f7fa"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12.5,20.5 L17.5,25.5 L27.5,15.5"
|
|
||||||
stroke="#51c7ce"
|
|
||||||
stroke-width="2.2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="box-title">Don't lose it</div>
|
|
||||||
<div class="box-desc">Be careful, it can not be recovered if you lose it.</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="icn">
|
|
||||||
<svg width="46" height="46" viewBox="0 0 28 28">
|
|
||||||
<g>
|
|
||||||
<circle cx="16" cy="16" r="12" fill="#e3fae5" />
|
|
||||||
<text
|
|
||||||
x="11"
|
|
||||||
y="21"
|
|
||||||
font-size="15"
|
|
||||||
font-family="Arial"
|
|
||||||
fill="#48b783"
|
|
||||||
font-weight="bold"
|
|
||||||
>
|
|
||||||
$
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="box-title">Don't share it</div>
|
|
||||||
<div class="box-desc">
|
|
||||||
Your funds will be stolen if you use this file on a malicious phishing site.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="icn">
|
|
||||||
<svg width="46" height="46" viewBox="0 0 28 28">
|
|
||||||
<g>
|
|
||||||
<rect x="5" y="7" width="18" height="16" rx="3" fill="#c6f1fc" />
|
|
||||||
<rect x="7" y="10" width="14" height="10" rx="2" fill="#96e2fc" />
|
|
||||||
<text
|
|
||||||
x="10"
|
|
||||||
y="19"
|
|
||||||
font-size="9"
|
|
||||||
font-family="monospace"
|
|
||||||
fill="#418aaf"
|
|
||||||
>
|
|
||||||
{ }
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="box-title">Make a backup</div>
|
|
||||||
<div class="box-desc">
|
|
||||||
Secure it like the millions of dollars it may one day be worth.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="back-btn" @click="handleBack">Back</button>
|
|
||||||
<button class="main-btn" @click="handleDownload">Acknowledge & Download</button>
|
|
||||||
</div>
|
|
||||||
<div class="not-recommended">
|
|
||||||
<span class="warn-icn">⚠</span>
|
|
||||||
<div>
|
|
||||||
<span class="strong">NOT RECOMMENDED</span><br />
|
|
||||||
This information is sensitive, and these options should only be used in offline
|
|
||||||
or secure environments.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.keystore-step {
|
|
||||||
max-width: 650px;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: 14px;
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
padding: 34px 16px 28px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-content {
|
|
||||||
padding: 6px 5px 0 5px;
|
|
||||||
}
|
|
||||||
.step-title {
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.07em;
|
|
||||||
font-size: 1.07rem;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.36rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.09rem;
|
|
||||||
margin-bottom: 23px;
|
|
||||||
}
|
|
||||||
.box-list {
|
|
||||||
display: flex;
|
|
||||||
gap: 21px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.box {
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
border-radius: 12px;
|
|
||||||
background: var(--bg-light);
|
|
||||||
padding: 21px 19px 17px 19px;
|
|
||||||
flex: 1 1 140px;
|
|
||||||
min-width: 196px;
|
|
||||||
max-width: 250px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
.icn {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.box-title {
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-size: 1.07em;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
.box-desc {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.99em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.btn-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 14px;
|
|
||||||
margin: 23px 0 0 0;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.back-btn {
|
|
||||||
padding: 10px 32px;
|
|
||||||
background: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.13s;
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.main-btn {
|
|
||||||
padding: 10px 32px;
|
|
||||||
background: var(--primary-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-light);
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.12s;
|
|
||||||
box-shadow: 0 4px 18px var(--shadow-primary);
|
|
||||||
&:hover {
|
|
||||||
background: var(--primary-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.not-recommended {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-radius: 10px;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
padding: 13px 17px;
|
|
||||||
margin-top: 28px;
|
|
||||||
font-size: 1.09em;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 11px;
|
|
||||||
.warn-icn {
|
|
||||||
font-size: 1.43em;
|
|
||||||
color: var(--error-color);
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
.strong {
|
|
||||||
font-weight: 700;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.steps-bar .step {
|
|
||||||
min-width: 90px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 650px) {
|
|
||||||
.keystore-step {
|
|
||||||
padding: 15px 3px 13px 3px;
|
|
||||||
}
|
|
||||||
.box {
|
|
||||||
padding: 12px 5px 10px 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,17 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonCommon } from '@/components'
|
import { ButtonCommon } from '@/components'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const goToNewWallet = () => {
|
||||||
goToCreate: []
|
window.open('https://kaspa-ng.org', '_blank')
|
||||||
goToLogin: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const handleGoToCreate = () => {
|
|
||||||
emit('goToCreate')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGoToLogin = () => {
|
const goToLegacyWallet = () => {
|
||||||
emit('goToLogin')
|
window.open('https://wallet.kaspanet.io', '_blank')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -21,19 +16,21 @@ const handleGoToLogin = () => {
|
|||||||
<div class="welcome-box">
|
<div class="welcome-box">
|
||||||
<div class="header-section">
|
<div class="header-section">
|
||||||
<h2>Welcome to the New Wallet Experience</h2>
|
<h2>Welcome to the New Wallet Experience</h2>
|
||||||
<p>Choose the next action:</p>
|
<p>
|
||||||
</div>
|
We've launched a new version of the Kaspa Wallet at
|
||||||
<div
|
<br />
|
||||||
class="button-group"
|
<span class="highlight"> https://kaspa-ng.org </span>
|
||||||
style="display: flex; flex-direction: column; gap: 1rem; margin: 2rem 0"
|
</p>
|
||||||
>
|
<ButtonCommon @click="goToNewWallet">
|
||||||
<ButtonCommon type="primary" size="large" @click="handleGoToCreate">
|
Go to the new Kaspa NG Wallet
|
||||||
Create new wallet
|
|
||||||
</ButtonCommon>
|
|
||||||
<ButtonCommon type="default" size="large" @click="handleGoToLogin">
|
|
||||||
Open existing wallet
|
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</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 class="note">Thank you for being a part of the Kaspa community!</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -43,15 +40,15 @@ const handleGoToLogin = () => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.welcome-page {
|
.welcome-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: var(--bg-light);
|
background-color: var(--vt-c-white);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.welcome-card {
|
.welcome-card {
|
||||||
background-color: var(--bg-white);
|
background-color: var(--vt-c-white);
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: 0 0 15px var(--vt-c-badge-caption-shadow);
|
||||||
border-radius: var(--radius-md);
|
border-radius: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 800px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
@ -62,15 +59,4 @@ const handleGoToLogin = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 1rem;
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
margin-top: 1rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,172 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { ButtonCommon, FormCommon } from '@/components'
|
import { ButtonCommon, FormCommon } from '@/components'
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const passwordError = ref('')
|
|
||||||
const isLoading = ref(false)
|
|
||||||
|
|
||||||
const handleOpenWallet = async () => {
|
const handleOpenWallet = () => {}
|
||||||
if (!password.value) {
|
|
||||||
passwordError.value = 'Please enter your password'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true
|
const handleNewWallet = () => {}
|
||||||
passwordError.value = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
router.push('/')
|
|
||||||
} catch (error) {
|
|
||||||
passwordError.value = 'Invalid password. Please try again.'
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
navigateToCreate: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const navigateToNewWallet = () => {
|
|
||||||
emit('navigateToCreate')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-container">
|
<div class="auth-container">
|
||||||
<div class="auth-card">
|
<div class="auth-card">
|
||||||
|
<div class="auth-card-header">
|
||||||
|
<h2>Open Wallet</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="auth-card-content">
|
<div class="auth-card-content">
|
||||||
<div class="wallet-icon">
|
<div class="wallet-icon">
|
||||||
<div class="logo-container">
|
<div class="icon-circle"></div>
|
||||||
<div class="logo-circle">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
class="neptune-logo"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
|
|
||||||
|
|
||||||
<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)" />
|
|
||||||
|
|
||||||
<ellipse
|
|
||||||
cx="50"
|
|
||||||
cy="50"
|
|
||||||
rx="42"
|
|
||||||
ry="12"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#ringGradient)"
|
|
||||||
stroke-width="4"
|
|
||||||
opacity="0.8"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="password-section">
|
||||||
<FormCommon
|
<FormCommon
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
type="password"
|
||||||
label="Enter your password"
|
label="Unlock the wallet with your password:"
|
||||||
placeholder="Password"
|
placeholder="Enter your password"
|
||||||
show-password-toggle
|
show-password-toggle
|
||||||
required
|
required
|
||||||
:error="passwordError"
|
|
||||||
:disabled="isLoading"
|
|
||||||
@input="passwordError = ''"
|
|
||||||
@keyup.enter="handleOpenWallet"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="security-notice">
|
|
||||||
<div class="notice-icon">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
||||||
<path
|
|
||||||
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M9 12l2 2 4-4"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span>Your password is encrypted and stored locally</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="auth-button-group">
|
<div class="auth-button-group">
|
||||||
<ButtonCommon
|
<ButtonCommon class="auth-btn secondary" @click="handleNewWallet">
|
||||||
type="primary"
|
NEW WALLET
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
:disabled="!password || isLoading"
|
|
||||||
:loading="isLoading"
|
|
||||||
@click="handleOpenWallet"
|
|
||||||
>
|
|
||||||
{{ isLoading ? 'Opening...' : 'Open Wallet' }}
|
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
<ButtonCommon type="default" size="large" block @click="navigateToNewWallet">
|
|
||||||
New Wallet
|
<ButtonCommon class="auth-btn primary" @click="handleOpenWallet">
|
||||||
|
OPEN WALLET
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -174,173 +46,4 @@ const navigateToNewWallet = () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
.auth-container {
|
|
||||||
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-content {
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.security-notice {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
background: var(--bg-light);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
|
|
||||||
.notice-icon {
|
|
||||||
color: var(--success-color);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: var(--font-xs);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
line-height: var(--leading-normal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-button-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help Links
|
|
||||||
.help-links {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding-top: var(--spacing-lg);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
.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:not(:disabled) {
|
|
||||||
color: var(--primary-hover);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: var(--text-muted);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-icon {
|
|
||||||
.icon-circle {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -1,207 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, defineEmits, onMounted } from 'vue'
|
|
||||||
import { ButtonCommon } from '@/components'
|
|
||||||
import { generateSeedPhrase } from '@/utils'
|
|
||||||
import { useSeedStore } from '@/stores'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
next: []
|
|
||||||
back: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const seedStore = useSeedStore()
|
|
||||||
|
|
||||||
const seedWords = ref<string[]>([])
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const words = generateSeedPhrase()
|
|
||||||
seedWords.value = words
|
|
||||||
seedStore.setSeedWords(words)
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
emit('next')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
emit('back')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="recovery-container">
|
|
||||||
<div class="recovery-card">
|
|
||||||
<div class="recovery-header">
|
|
||||||
<h1 class="recovery-title">Recovery Seed</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recovery-content">
|
|
||||||
<div class="instruction-text">
|
|
||||||
<p>
|
|
||||||
Your wallet is accessible by a seed phrase. The seed phrase is an ordered
|
|
||||||
12-word secret phrase.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Make sure no one is looking, as anyone with your seed phrase can access your
|
|
||||||
wallet your funds. Write it down and keep it safe.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="seed-words-container">
|
|
||||||
<div class="seed-words-grid">
|
|
||||||
<div v-for="(word, index) in seedWords" :key="index" class="seed-word-item">
|
|
||||||
<span class="word-number">{{ index + 1 }}</span>
|
|
||||||
<span class="word-text">{{ word }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cool-fact">
|
|
||||||
<p>
|
|
||||||
Cool fact: there are more 12-word phrase combinations than nanoseconds since
|
|
||||||
the big bang!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recovery-actions">
|
|
||||||
<ButtonCommon type="default" size="large" @click="handleBack">
|
|
||||||
BACK
|
|
||||||
</ButtonCommon>
|
|
||||||
<ButtonCommon type="primary" size="large" @click="handleNext">
|
|
||||||
NEXT
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.recovery-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
background: var(--bg-light);
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recovery-card {
|
|
||||||
@include card-base;
|
|
||||||
max-width: 500px;
|
|
||||||
width: 100%;
|
|
||||||
border: 2px solid var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recovery-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
padding-bottom: var(--spacing-xl);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
.recovery-title {
|
|
||||||
font-size: var(--font-2xl);
|
|
||||||
font-weight: var(--font-bold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.recovery-content {
|
|
||||||
.instruction-text {
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
line-height: var(--leading-normal);
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.seed-words-container {
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
background: var(--bg-hover);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
|
|
||||||
.seed-words-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
|
|
||||||
.seed-word-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding: var(--spacing-sm);
|
|
||||||
background: var(--bg-white);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
|
|
||||||
.word-number {
|
|
||||||
font-size: var(--font-xs);
|
|
||||||
font-weight: var(--font-bold);
|
|
||||||
color: var(--text-muted);
|
|
||||||
min-width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.word-text {
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
font-weight: var(--font-medium);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cool-fact {
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--font-xs);
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-style: italic;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.recovery-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsive Design
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.recovery-container {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recovery-card {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seed-words-container {
|
|
||||||
.seed-words-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
|
|
||||||
.seed-word-item {
|
|
||||||
padding: var(--spacing-xs);
|
|
||||||
|
|
||||||
.word-number {
|
|
||||||
min-width: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,67 +1,5 @@
|
|||||||
<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
|
<button class="btn-common">
|
||||||
: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);
|
|
||||||
transition: all 2s ease-in-out;
|
|
||||||
&: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>
|
|
||||||
|
|||||||
@ -89,19 +89,19 @@ const handleBlur = (e: FocusEvent) => {
|
|||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--vt-c-gray-6);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
&.focused {
|
&.focused {
|
||||||
border-color: var(--border-primary);
|
border-color: var(--vt-c-main-color);
|
||||||
box-shadow: var(--shadow-primary);
|
box-shadow: var(--vt-input-shadow-focus);
|
||||||
}
|
}
|
||||||
&.error {
|
&.error {
|
||||||
border-color: var(--error-color);
|
border-color: var(--vt-c-red-v3);
|
||||||
background-color: var();
|
background-color: var()
|
||||||
}
|
}
|
||||||
&.disabled {
|
&.disabled {
|
||||||
background-color: var(--bg-hover);
|
background-color: var(--vt-c-gray-4);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,18 +109,17 @@ const handleBlur = (e: FocusEvent) => {
|
|||||||
.form-input {
|
.form-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 40px 12px 12px;
|
padding: 12px 40px 12px 12px;
|
||||||
border: var(--border-color);
|
border: none;
|
||||||
background: var(--text-light);
|
background: transparent;
|
||||||
font-size: var(--font-base);
|
font-size: 14px;
|
||||||
color: var(--text-primary);
|
color: var(--vt-c-black-bold);
|
||||||
outline: none;
|
outline: none;
|
||||||
border-radius: var(--radius-md);
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: var(--text-muted);
|
color: var(--vt-c-gray-8);
|
||||||
}
|
}
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: var(--text-muted);
|
color: var(--vt-c-gray-8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,10 +131,10 @@ const handleBlur = (e: FocusEvent) => {
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--text-muted);
|
color: var(--vt-c-gray-8);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--text-primary);
|
color: var(--vt-c-black-bold);
|
||||||
}
|
}
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
@ -146,7 +145,7 @@ const handleBlur = (e: FocusEvent) => {
|
|||||||
.error-message {
|
.error-message {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--error-color);
|
color: var(--vt-c-red-v3);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,200 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IconProps } from '@/interface'
|
|
||||||
import { computed } from 'vue'
|
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>(), {
|
const props = withDefaults(defineProps<IconProps>(), {
|
||||||
size: 16,
|
icon: '',
|
||||||
color: 'currentColor',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const iconSize = computed(() => {
|
const iconMap: Record<string, any> = {
|
||||||
if (typeof props.size === 'number') {
|
eye: EyeOutlined,
|
||||||
return `${props.size}px`
|
'eye-off': EyeInvisibleOutlined,
|
||||||
|
search: SearchOutlined,
|
||||||
|
close: CloseOutlined,
|
||||||
|
'arrow-right': RightOutlined,
|
||||||
|
'arrow-left': LeftOutlined,
|
||||||
}
|
}
|
||||||
return props.size
|
|
||||||
})
|
const IconComponent = computed(() => iconMap[props.icon])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<component :is="IconComponent" :style="{ fontSize: size, color }" :class="props.class" />
|
||||||
:width="iconSize"
|
|
||||||
:height="iconSize"
|
|
||||||
:class="props.class"
|
|
||||||
:style="{ color: props.color }"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
>
|
|
||||||
<!-- Eye Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'eye'"
|
|
||||||
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
v-if="icon === 'eye'"
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="3"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Eye Off Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'eye-off'"
|
|
||||||
d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
v-if="icon === 'eye-off'"
|
|
||||||
x1="1"
|
|
||||||
y1="1"
|
|
||||||
x2="23"
|
|
||||||
y2="23"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Wallet Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'wallet'"
|
|
||||||
d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
v-if="icon === 'wallet'"
|
|
||||||
d="M9 12L11 14L15 10"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Shield Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'shield'"
|
|
||||||
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
v-if="icon === 'shield'"
|
|
||||||
d="M9 12l2 2 4-4"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Lock Icon -->
|
|
||||||
<rect
|
|
||||||
v-if="icon === 'lock'"
|
|
||||||
x="3"
|
|
||||||
y="11"
|
|
||||||
width="18"
|
|
||||||
height="11"
|
|
||||||
rx="2"
|
|
||||||
ry="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
v-if="icon === 'lock'"
|
|
||||||
d="M7 11V7a5 5 0 0 1 10 0v4"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Key Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'key'"
|
|
||||||
d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Plus Icon -->
|
|
||||||
<line
|
|
||||||
v-if="icon === 'plus'"
|
|
||||||
x1="12"
|
|
||||||
y1="5"
|
|
||||||
x2="12"
|
|
||||||
y2="19"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
v-if="icon === 'plus'"
|
|
||||||
x1="5"
|
|
||||||
y1="12"
|
|
||||||
x2="19"
|
|
||||||
y2="12"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Check Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'check'"
|
|
||||||
d="M20 6L9 17l-5-5"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- X Icon -->
|
|
||||||
<line
|
|
||||||
v-if="icon === 'x'"
|
|
||||||
x1="18"
|
|
||||||
y1="6"
|
|
||||||
x2="6"
|
|
||||||
y2="18"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
v-if="icon === 'x'"
|
|
||||||
x1="6"
|
|
||||||
y1="6"
|
|
||||||
x2="18"
|
|
||||||
y2="18"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Arrow Right Icon -->
|
|
||||||
<path
|
|
||||||
v-if="icon === 'arrow-right'"
|
|
||||||
d="M5 12h14m-7-7l7 7-7 7"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Copy Icon -->
|
|
||||||
<rect
|
|
||||||
v-if="icon === 'copy'"
|
|
||||||
x="9"
|
|
||||||
y="9"
|
|
||||||
width="13"
|
|
||||||
height="13"
|
|
||||||
rx="2"
|
|
||||||
ry="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
v-if="icon === 'copy'"
|
|
||||||
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -4,10 +4,6 @@ import FormCommon from './common/FormCommon.vue'
|
|||||||
import OnboardingComponent from './auth/OnboardingComponent.vue'
|
import OnboardingComponent from './auth/OnboardingComponent.vue'
|
||||||
import OpenWalletComponent from './auth/OpenWalletComponent.vue'
|
import OpenWalletComponent from './auth/OpenWalletComponent.vue'
|
||||||
import CreateWalletComponent from './auth/CreateWalletComponent.vue'
|
import CreateWalletComponent from './auth/CreateWalletComponent.vue'
|
||||||
import RecoverySeedComponent from './auth/RecoverySeedComponent.vue'
|
|
||||||
import ConfirmSeedComponent from './auth/ConfirmSeedComponent.vue'
|
|
||||||
import ImportWalletComponent from './auth/ImportWalletComponent.vue'
|
|
||||||
import KeystoreDownloadComponent from './auth/KeystoreDownloadComponent.vue'
|
|
||||||
import { IconCommon } from './icon'
|
import { IconCommon } from './icon'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -17,9 +13,5 @@ export {
|
|||||||
OnboardingComponent,
|
OnboardingComponent,
|
||||||
OpenWalletComponent,
|
OpenWalletComponent,
|
||||||
CreateWalletComponent,
|
CreateWalletComponent,
|
||||||
RecoverySeedComponent,
|
|
||||||
ConfirmSeedComponent,
|
|
||||||
IconCommon,
|
IconCommon,
|
||||||
ImportWalletComponent,
|
|
||||||
KeystoreDownloadComponent,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,3 +10,7 @@ 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)
|
||||||
|
}
|
||||||
3
src/helpers/index.ts
Normal file
3
src/helpers/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './constants/code'
|
||||||
|
export * from './constants/constants'
|
||||||
|
export * from './constants/localStorage'
|
||||||
@ -1,20 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
// 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,10 +0,0 @@
|
|||||||
export interface IconProps {
|
|
||||||
icon: string
|
|
||||||
size?: number | string
|
|
||||||
color?: string
|
|
||||||
class?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HighlightProps {
|
|
||||||
class?: string
|
|
||||||
}
|
|
||||||
@ -1,2 +1,9 @@
|
|||||||
export * from './common'
|
interface Props {
|
||||||
export * from './home'
|
name?: string
|
||||||
|
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 '@/utils'
|
import { getToken } from '@/helpers'
|
||||||
|
|
||||||
const ifAuthenticated = (to: any, from: any, next: any) => {
|
const ifAuthenticated = (to: any, from: any, next: any) => {
|
||||||
if (getToken()) {
|
if (getToken()) {
|
||||||
@ -20,30 +20,24 @@ const ifNotAuthenticated = (to: any, from: any, next: any) => {
|
|||||||
export const routes: any = [
|
export const routes: any = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'index',
|
redirect: '/home',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'home',
|
||||||
|
name: 'home',
|
||||||
component: Page.Home,
|
component: Page.Home,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: ':pathMatch(.*)*',
|
||||||
name: 'login',
|
|
||||||
component: Page.Auth,
|
|
||||||
beforeEnter: ifNotAuthenticated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/recovery-seed',
|
|
||||||
name: 'recovery-seed',
|
|
||||||
component: Page.Auth,
|
|
||||||
beforeEnter: ifNotAuthenticated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/confirm-seed',
|
|
||||||
name: 'confirm-seed',
|
|
||||||
component: Page.Auth,
|
|
||||||
beforeEnter: ifNotAuthenticated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/:pathMatch(.*)*',
|
|
||||||
component: Page.NotFound,
|
component: Page.NotFound,
|
||||||
name: 'page-not-found',
|
name: 'page-not-found',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/onboarding',
|
||||||
|
name: 'onboarding',
|
||||||
|
component: Page.Login,
|
||||||
|
beforeEnter: ifNotAuthenticated,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
// Auth flow states
|
|
||||||
export type AuthState = 'onboarding' | 'login' | 'create' | 'recovery' | 'confirm' | 'complete'
|
|
||||||
|
|
||||||
// Auth store to manage the flow
|
|
||||||
const currentState = ref<AuthState>('onboarding')
|
|
||||||
|
|
||||||
export const useAuthStore = () => {
|
|
||||||
const getCurrentState = () => currentState.value
|
|
||||||
|
|
||||||
const setState = (state: AuthState) => {
|
|
||||||
currentState.value = state
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextStep = () => {
|
|
||||||
switch (currentState.value) {
|
|
||||||
case 'onboarding':
|
|
||||||
setState('login')
|
|
||||||
break
|
|
||||||
case 'login':
|
|
||||||
// Stay in login, user chooses create or open
|
|
||||||
break
|
|
||||||
case 'create':
|
|
||||||
setState('recovery')
|
|
||||||
break
|
|
||||||
case 'recovery':
|
|
||||||
setState('confirm')
|
|
||||||
break
|
|
||||||
case 'confirm':
|
|
||||||
setState('complete')
|
|
||||||
break
|
|
||||||
case 'complete':
|
|
||||||
// Flow complete
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousStep = () => {
|
|
||||||
switch (currentState.value) {
|
|
||||||
case 'onboarding':
|
|
||||||
// Can't go back from onboarding
|
|
||||||
break
|
|
||||||
case 'login':
|
|
||||||
setState('onboarding')
|
|
||||||
break
|
|
||||||
case 'create':
|
|
||||||
setState('login')
|
|
||||||
break
|
|
||||||
case 'recovery':
|
|
||||||
setState('create')
|
|
||||||
break
|
|
||||||
case 'confirm':
|
|
||||||
setState('recovery')
|
|
||||||
break
|
|
||||||
case 'complete':
|
|
||||||
setState('confirm')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToCreate = () => {
|
|
||||||
setState('create')
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToLogin = () => {
|
|
||||||
setState('login')
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetFlow = () => {
|
|
||||||
setState('onboarding')
|
|
||||||
localStorage.removeItem('onboarding-completed')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentState: currentState.value,
|
|
||||||
getCurrentState,
|
|
||||||
setState,
|
|
||||||
nextStep,
|
|
||||||
previousStep,
|
|
||||||
goToCreate,
|
|
||||||
goToLogin,
|
|
||||||
resetFlow,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +1 @@
|
|||||||
export * from './seedStore'
|
export {}
|
||||||
export * from './authStore'
|
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const seedWords = ref<string[]>([])
|
|
||||||
const isSeedGenerated = ref(false)
|
|
||||||
|
|
||||||
export const useSeedStore = () => {
|
|
||||||
const setSeedWords = (words: string[]) => {
|
|
||||||
seedWords.value = words
|
|
||||||
isSeedGenerated.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSeedWords = () => {
|
|
||||||
return seedWords.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearSeedWords = () => {
|
|
||||||
seedWords.value = []
|
|
||||||
isSeedGenerated.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasSeedWords = () => {
|
|
||||||
return isSeedGenerated.value && seedWords.value.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
seedWords: seedWords.value,
|
|
||||||
isSeedGenerated: isSeedGenerated.value,
|
|
||||||
setSeedWords,
|
|
||||||
getSeedWords,
|
|
||||||
clearSeedWords,
|
|
||||||
hasSeedWords,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
// BIP39 English wordlist (first 100 words for demo)
|
|
||||||
const BIP39_WORDS = [
|
|
||||||
'abandon',
|
|
||||||
'ability',
|
|
||||||
'able',
|
|
||||||
'about',
|
|
||||||
'above',
|
|
||||||
'absent',
|
|
||||||
'absorb',
|
|
||||||
'abstract',
|
|
||||||
'absurd',
|
|
||||||
'abuse',
|
|
||||||
'access',
|
|
||||||
'accident',
|
|
||||||
'account',
|
|
||||||
'accuse',
|
|
||||||
'achieve',
|
|
||||||
'acid',
|
|
||||||
'acoustic',
|
|
||||||
'acquire',
|
|
||||||
'across',
|
|
||||||
'act',
|
|
||||||
'action',
|
|
||||||
'actor',
|
|
||||||
'actress',
|
|
||||||
'actual',
|
|
||||||
'adapt',
|
|
||||||
'add',
|
|
||||||
'addict',
|
|
||||||
'address',
|
|
||||||
'adjust',
|
|
||||||
'admit',
|
|
||||||
'adult',
|
|
||||||
'advance',
|
|
||||||
'advice',
|
|
||||||
'aerobic',
|
|
||||||
'affair',
|
|
||||||
'afford',
|
|
||||||
'afraid',
|
|
||||||
'again',
|
|
||||||
'age',
|
|
||||||
'agent',
|
|
||||||
'agree',
|
|
||||||
'ahead',
|
|
||||||
'aim',
|
|
||||||
'air',
|
|
||||||
'airport',
|
|
||||||
'aisle',
|
|
||||||
'alarm',
|
|
||||||
'album',
|
|
||||||
'alcohol',
|
|
||||||
'alert',
|
|
||||||
'alien',
|
|
||||||
'all',
|
|
||||||
'alley',
|
|
||||||
'allow',
|
|
||||||
'almost',
|
|
||||||
'alone',
|
|
||||||
'alpha',
|
|
||||||
'already',
|
|
||||||
'also',
|
|
||||||
'alter',
|
|
||||||
'always',
|
|
||||||
'amateur',
|
|
||||||
'amazing',
|
|
||||||
'among',
|
|
||||||
'amount',
|
|
||||||
'amused',
|
|
||||||
'analyst',
|
|
||||||
'anchor',
|
|
||||||
'ancient',
|
|
||||||
'anger',
|
|
||||||
'angle',
|
|
||||||
'angry',
|
|
||||||
'animal',
|
|
||||||
'ankle',
|
|
||||||
'announce',
|
|
||||||
'annual',
|
|
||||||
'another',
|
|
||||||
'answer',
|
|
||||||
'antenna',
|
|
||||||
'antique',
|
|
||||||
'anxiety',
|
|
||||||
'any',
|
|
||||||
'apart',
|
|
||||||
'apology',
|
|
||||||
'appear',
|
|
||||||
'apple',
|
|
||||||
'approve',
|
|
||||||
'april',
|
|
||||||
'arch',
|
|
||||||
'arctic',
|
|
||||||
'area',
|
|
||||||
'arena',
|
|
||||||
'argue',
|
|
||||||
'arm',
|
|
||||||
'armed',
|
|
||||||
'armor',
|
|
||||||
'army',
|
|
||||||
'around',
|
|
||||||
'arrange',
|
|
||||||
'arrest',
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random seed phrase with 12 words
|
|
||||||
* In a real application, you would use a proper BIP39 library
|
|
||||||
*/
|
|
||||||
export const generateSeedPhrase = (): string[] => {
|
|
||||||
const words: string[] = []
|
|
||||||
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
const randomIndex = Math.floor(Math.random() * BIP39_WORDS.length)
|
|
||||||
words.push(BIP39_WORDS[randomIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate if a seed phrase is valid (basic validation)
|
|
||||||
*/
|
|
||||||
export const validateSeedPhrase = (words: string[]): boolean => {
|
|
||||||
if (words.length !== 12) return false
|
|
||||||
|
|
||||||
// Check if all words are in the BIP39 wordlist
|
|
||||||
return words.every((word) => BIP39_WORDS.includes(word.toLowerCase()))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateSeedPhrase18 = (words: string[]): boolean => {
|
|
||||||
if (words.length !== 18) return false
|
|
||||||
return words.every((word) => BIP39_WORDS.includes(word.toLowerCase()))
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export * from './constants/code'
|
|
||||||
export * from './constants/constants'
|
|
||||||
export * from './helpers/format'
|
|
||||||
export * from './helpers/localStorage'
|
|
||||||
export * from './helpers/seedPhrase'
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { OnboardingComponent } from '@/components'
|
|
||||||
import { useAuthStore } from '@/stores'
|
|
||||||
import { LoginTab, CreateTab, RecoveryTab, ConfirmTab } from './components'
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
|
|
||||||
const currentState = computed(() => authStore.getCurrentState())
|
|
||||||
|
|
||||||
const handleOnboardingComplete = () => {
|
|
||||||
authStore.nextStep()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleGoToCreate = () => {
|
|
||||||
authStore.goToCreate()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleGoToLogin = () => {
|
|
||||||
authStore.goToLogin()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
authStore.nextStep()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
authStore.previousStep()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="auth-container">
|
|
||||||
<OnboardingComponent
|
|
||||||
v-if="currentState === 'onboarding'"
|
|
||||||
@go-to-create="handleGoToCreate"
|
|
||||||
@go-to-login="handleGoToLogin"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoginTab v-else-if="currentState === 'login'" @go-to-create="handleGoToCreate" />
|
|
||||||
|
|
||||||
<CreateTab
|
|
||||||
v-else-if="currentState === 'create'"
|
|
||||||
@go-to-login="handleGoToLogin"
|
|
||||||
@next="handleNext"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RecoveryTab
|
|
||||||
v-else-if="currentState === 'recovery'"
|
|
||||||
@back="handleBack"
|
|
||||||
@next="handleNext"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConfirmTab v-else-if="currentState === 'confirm'" @back="handleBack" @next="handleNext" />
|
|
||||||
|
|
||||||
<div v-else-if="currentState === 'complete'" class="complete-state">
|
|
||||||
<h2>Wallet Setup Complete!</h2>
|
|
||||||
<p>Your wallet has been successfully created.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.auth-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--bg-light);
|
|
||||||
font-family: var(--font-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.complete-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: var(--success-color);
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: var(--font-lg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
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,28 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ConfirmSeedComponent } from '@/components'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
next: []
|
|
||||||
back: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
emit('next')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
emit('back')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="confirm-tab">
|
|
||||||
<ConfirmSeedComponent @next="handleNext" @back="handleBack" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.confirm-tab {
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { CreateWalletComponent } from '@/components'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
goToLogin: []
|
|
||||||
next: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const handleNavigateToOpenWallet = () => {
|
|
||||||
emit('goToLogin')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNavigateToRecoverySeed = () => {
|
|
||||||
emit('next')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="create-tab">
|
|
||||||
<CreateWalletComponent
|
|
||||||
@navigateToOpenWallet="handleNavigateToOpenWallet"
|
|
||||||
@navigateToRecoverySeed="handleNavigateToRecoverySeed"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.create-tab {
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { ImportWalletComponent, OpenWalletComponent } from '@/components'
|
|
||||||
|
|
||||||
const emit = defineEmits<{ goToCreate: [] }>()
|
|
||||||
|
|
||||||
const stage = ref<'import' | 'password'>('import')
|
|
||||||
const importData = ref<any>(null)
|
|
||||||
|
|
||||||
const handleImported = (payload: {
|
|
||||||
type: 'seed' | 'privatekey'
|
|
||||||
value: string | string[]
|
|
||||||
passphrase?: string
|
|
||||||
}) => {
|
|
||||||
importData.value = payload
|
|
||||||
stage.value = 'password'
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNavigateToCreate = () => {
|
|
||||||
emit('goToCreate')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="login-tab">
|
|
||||||
<ImportWalletComponent v-if="stage === 'import'" @import-success="handleImported" />
|
|
||||||
<OpenWalletComponent v-else @navigateToCreate="handleNavigateToCreate" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.login-tab {
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { RecoverySeedComponent } from '@/components'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
next: []
|
|
||||||
back: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
emit('next')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
emit('back')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="recovery-tab">
|
|
||||||
<RecoverySeedComponent @next="handleNext" @back="handleBack" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.recovery-tab {
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export { default as LoginTab } from './LoginTab.vue'
|
|
||||||
export { default as CreateTab } from './CreateTab.vue'
|
|
||||||
export { default as RecoveryTab } from './RecoveryTab.vue'
|
|
||||||
export { default as ConfirmTab } from './ConfirmTab.vue'
|
|
||||||
@ -1,89 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts"></script>
|
||||||
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="home-container">
|
<div class="box">Home page</div>
|
||||||
<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 lang="scss" scoped></style>
|
||||||
.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>
|
|
||||||
|
|||||||
@ -1,96 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,325 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
|
||||||
import { formatNumberToLocaleString } from '@/utils'
|
|
||||||
import type { NetworkStatus } from '@/interface'
|
|
||||||
|
|
||||||
const networkStatus = ref<NetworkStatus>({
|
|
||||||
network: 'kaspa-mainnet',
|
|
||||||
daaScore: 0,
|
|
||||||
dagHeader: 0,
|
|
||||||
dagBlocks: 0,
|
|
||||||
difficulty: 0,
|
|
||||||
medianOffset: '00:00:00',
|
|
||||||
medianTimeUTC: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const loading = ref(true)
|
|
||||||
const error = ref('')
|
|
||||||
const isConnected = ref(false)
|
|
||||||
|
|
||||||
// Kaspa RPC Client instance (placeholder)
|
|
||||||
// You'll need to implement this using kaspa-wasm or kaspa RPC library
|
|
||||||
let rpcClient: any = null
|
|
||||||
let unsubscribe: (() => void) | null = null
|
|
||||||
|
|
||||||
// Initialize Kaspa RPC connection
|
|
||||||
const initializeKaspaRPC = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
error.value = ''
|
|
||||||
|
|
||||||
await simulateRPCConnection()
|
|
||||||
|
|
||||||
isConnected.value = true
|
|
||||||
loading.value = false
|
|
||||||
} catch (err) {
|
|
||||||
error.value = 'Failed to connect to Kaspa network'
|
|
||||||
loading.value = false
|
|
||||||
isConnected.value = false
|
|
||||||
|
|
||||||
useMockData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate RPC connection (mock for development)
|
|
||||||
const simulateRPCConnection = async (): Promise<void> => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Initial data
|
|
||||||
networkStatus.value = {
|
|
||||||
network: 'kaspa-mainnet',
|
|
||||||
daaScore: 256315320,
|
|
||||||
dagHeader: 1437265,
|
|
||||||
dagBlocks: 1437265,
|
|
||||||
difficulty: 33048964118340300.0,
|
|
||||||
medianOffset: '00:00:00',
|
|
||||||
medianTimeUTC: new Date().toISOString().replace('T', ' ').substring(0, 19),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate DAA score increment (real Kaspa ~1 block/sec)
|
|
||||||
const mockSubscription = setInterval(() => {
|
|
||||||
networkStatus.value.daaScore += 1
|
|
||||||
networkStatus.value.dagHeader += 1
|
|
||||||
networkStatus.value.dagBlocks += 1
|
|
||||||
updateMedianTime()
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
// Store cleanup function
|
|
||||||
unsubscribe = () => clearInterval(mockSubscription)
|
|
||||||
|
|
||||||
resolve()
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update median time
|
|
||||||
const updateMedianTime = () => {
|
|
||||||
networkStatus.value.medianTimeUTC = new Date().toISOString().replace('T', ' ').substring(0, 19)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use mock data fallback
|
|
||||||
const useMockData = () => {
|
|
||||||
networkStatus.value = {
|
|
||||||
network: 'kaspa-mainnet',
|
|
||||||
daaScore: 256315320,
|
|
||||||
dagHeader: 1437265,
|
|
||||||
dagBlocks: 1437265,
|
|
||||||
difficulty: 33048964118340300.0,
|
|
||||||
medianOffset: '00:00:00',
|
|
||||||
medianTimeUTC: new Date().toISOString().replace('T', ' ').substring(0, 19),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry connection
|
|
||||||
const retryConnection = () => {
|
|
||||||
initializeKaspaRPC()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
const cleanup = () => {
|
|
||||||
if (unsubscribe) {
|
|
||||||
unsubscribe()
|
|
||||||
unsubscribe = null
|
|
||||||
}
|
|
||||||
if (rpcClient) {
|
|
||||||
// TODO: Disconnect RPC client
|
|
||||||
rpcClient.disconnect()
|
|
||||||
rpcClient = null
|
|
||||||
}
|
|
||||||
isConnected.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initializeKaspaRPC()
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
cleanup()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="content-card">
|
|
||||||
<div class="network-status-container">
|
|
||||||
<h2 class="section-title">NETWORK STATUS</h2>
|
|
||||||
|
|
||||||
<!-- Loading State -->
|
|
||||||
<div v-if="loading && networkStatus.daaScore === 0" class="loading-state">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>Loading network data...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Error State -->
|
|
||||||
<div v-else-if="error" class="error-state">
|
|
||||||
<p>{{ error }}</p>
|
|
||||||
<button @click="retryConnection" class="retry-button">Retry Connection</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data Display -->
|
|
||||||
<div v-else 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>
|
|
||||||
|
|
||||||
<!-- Last Update Indicator -->
|
|
||||||
<div class="update-indicator">
|
|
||||||
<span class="update-dot" :class="{ connected: isConnected }"></span>
|
|
||||||
<span class="update-text">
|
|
||||||
{{ isConnected ? 'Connected - Live updates' : 'Connecting...' }}
|
|
||||||
</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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading State
|
|
||||||
.loading-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--spacing-4xl);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin: 0 auto var(--spacing-lg);
|
|
||||||
border: 4px solid var(--border-light);
|
|
||||||
border-top-color: var(--primary-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: var(--font-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error State
|
|
||||||
.error-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--spacing-4xl);
|
|
||||||
color: var(--error-color);
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
font-size: var(--font-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-button {
|
|
||||||
padding: var(--spacing-sm) var(--spacing-lg);
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: var(--text-light);
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
transition: var(--transition-all);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--primary-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Indicator
|
|
||||||
.update-indicator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
margin-top: var(--spacing-2xl);
|
|
||||||
padding-top: var(--spacing-lg);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
.update-dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: var(--text-muted);
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: var(--transition-all);
|
|
||||||
|
|
||||||
&.connected {
|
|
||||||
background: var(--success-color);
|
|
||||||
animation: pulse-dot 2s infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-text {
|
|
||||||
font-size: var(--font-xs);
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
<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 handleBackupFile = () => {
|
|
||||||
console.log('Backup File')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBackupSeed = () => {
|
|
||||||
console.log('Backup Seed')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRecoverFromSeed = () => {
|
|
||||||
console.log('Recover From Seed')
|
|
||||||
}
|
|
||||||
</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="handleBackupFile">
|
|
||||||
Backup File
|
|
||||||
</ButtonCommon>
|
|
||||||
<ButtonCommon type="primary" size="large" block @click="handleBackupSeed">
|
|
||||||
Backup Seed
|
|
||||||
</ButtonCommon>
|
|
||||||
<ButtonCommon type="primary" size="large" block @click="handleRecoverFromSeed">
|
|
||||||
Recover From Seed
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
</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>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
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,3 +1,3 @@
|
|||||||
export const Home = () => import('@/views/Home/HomeView.vue')
|
export const Home = () => import('@/views/Home/HomeView.vue')
|
||||||
export const NotFound = () => import('@/views/NotFound/NotFoundView.vue')
|
export const NotFound = () => import('@/views/NotFound/NotFoundView.vue')
|
||||||
export const Auth = () => import('@/views/Auth/AuthView.vue')
|
export const Login = () => import('@/views/Auth/LoginView.vue')
|
||||||
|
|||||||
@ -1,27 +1,24 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
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/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
port: 3008,
|
port: 3008,
|
||||||
},
|
},
|
||||||
plugins: [vue(), vueJsx(), VueDevTools()],
|
plugins: [
|
||||||
css: {
|
vue(),
|
||||||
preprocessorOptions: {
|
vueJsx(),
|
||||||
scss: {
|
VueDevTools(),
|
||||||
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