feat: 241025/login_by_seed_phrase
This commit is contained in:
parent
ecbcc08209
commit
065b77db84
@ -3,6 +3,9 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
@ -13,6 +16,7 @@
|
|||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@neptune/wasm": "file:./packages/neptune-wasm",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
|
|||||||
@ -1,41 +1,22 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import router from '@/router'
|
|
||||||
import { STATUS_CODE_SUCCESS, ACCESS_TOKEN, STATUS_CODE_UNAUTHORIZED } from '@/utils'
|
|
||||||
|
|
||||||
axios.defaults.withCredentials = false
|
axios.defaults.withCredentials = false
|
||||||
|
|
||||||
export const API_URL = import.meta.env.VITE_APP_API
|
export const API_URL = import.meta.env.VITE_APP_API
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
})
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
instance.interceptors.request.use(
|
|
||||||
function (config: any) {
|
|
||||||
try {
|
|
||||||
const token = localStorage.getItem(ACCESS_TOKEN)
|
|
||||||
if (token) {
|
|
||||||
config.headers['Authorization'] = `Bearer ${token}`
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw Error('')
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
},
|
},
|
||||||
function (error) {
|
method: 'POST',
|
||||||
return Promise.reject(error)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
instance.interceptors.response.use(
|
instance.interceptors.response.use(
|
||||||
function (response) {
|
function (response) {
|
||||||
if (response?.status !== STATUS_CODE_SUCCESS) return Promise.reject(response?.data)
|
// if (response?.status !== STATUS_CODE_SUCCESS) return Promise.reject(response?.data)
|
||||||
return response.data
|
return response
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
if (error?.response?.status === STATUS_CODE_UNAUTHORIZED || error.code === 'ERR_NETWORK') {
|
|
||||||
localStorage.clear()
|
|
||||||
return router.push({ name: 'login' })
|
|
||||||
}
|
|
||||||
if (error?.response?.data) {
|
if (error?.response?.data) {
|
||||||
return Promise.reject(error?.response?.data)
|
return Promise.reject(error?.response?.data)
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/api/neptuneApi.ts
Normal file
53
src/api/neptuneApi.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { callJsonRpc } from '@/api/request'
|
||||||
|
|
||||||
|
export const getUtxosFromViewKey = (
|
||||||
|
viewKey: string,
|
||||||
|
startBlock: number = 0,
|
||||||
|
endBlock: number | null = null,
|
||||||
|
maxSearchDepth: number = 1000
|
||||||
|
) => {
|
||||||
|
const params = {
|
||||||
|
viewKey,
|
||||||
|
startBlock,
|
||||||
|
endBlock,
|
||||||
|
maxSearchDepth,
|
||||||
|
}
|
||||||
|
return callJsonRpc('wallet_getUtxosFromViewKey', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBalance = async (): Promise<any> => {
|
||||||
|
return await callJsonRpc('wallet_balance', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBlockHeight = async (): Promise<any> => {
|
||||||
|
return await callJsonRpc('chain_height', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNetworkInfo = async (): Promise<any> => {
|
||||||
|
return await callJsonRpc('node_network', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWalletAddress = async (): Promise<any> => {
|
||||||
|
return await callJsonRpc('wallet_address', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendTransaction = async (
|
||||||
|
toAddress: string,
|
||||||
|
amount: string,
|
||||||
|
fee: string
|
||||||
|
): Promise<any> => {
|
||||||
|
const params = [toAddress, amount, fee]
|
||||||
|
return await callJsonRpc('wallet_send', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const broadcastSignedTransaction = async (signedTxData: any): Promise<any> => {
|
||||||
|
return await callJsonRpc('wallet_broadcastSignedTransaction', signedTxData)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTransaction = async (txHash: string): Promise<any> => {
|
||||||
|
return await callJsonRpc('chain_transaction', [txHash])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMempoolInfo = async (): Promise<any> => {
|
||||||
|
return await callJsonRpc('chain_mempool', [])
|
||||||
|
}
|
||||||
14
src/api/request.ts
Normal file
14
src/api/request.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import request from '@/api/config'
|
||||||
|
|
||||||
|
export const callJsonRpc = (method: string, params: any = []) => {
|
||||||
|
const requestData = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
id: 1,
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
method: 'POST',
|
||||||
|
data: requestData,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,32 +1,108 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import ButtonCommon from '@/components/common/ButtonCommon.vue'
|
|
||||||
import { formatNumberToLocaleString } from '@/utils'
|
import { formatNumberToLocaleString } from '@/utils'
|
||||||
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { ButtonCommon, SpinnerCommon } from '@/components'
|
||||||
|
|
||||||
|
const neptuneStore = useNeptuneStore()
|
||||||
|
const { getBalance, getBlockHeight } = useNeptuneWallet()
|
||||||
|
|
||||||
const availableBalance = ref(0)
|
const availableBalance = ref(0)
|
||||||
const pendingBalance = ref(0)
|
const pendingBalance = ref(0)
|
||||||
const receiveAddress = ref('kaspa:qpn80v050r3jxv6mzt8tzss6dhvllc3rvcuuy86z6djgmvzx0napvhuj7ugh9')
|
const currentDaaScore = ref(0)
|
||||||
const walletStatus = ref('Online')
|
const isLoadingData = ref(false)
|
||||||
const currentDaaScore = ref(255953336)
|
|
||||||
|
|
||||||
const copyAddress = () => {
|
const receiveAddress = computed(() => neptuneStore.getWallet?.address || '')
|
||||||
navigator.clipboard.writeText(receiveAddress.value)
|
const walletStatus = computed(() => {
|
||||||
|
if (neptuneStore.getLoading) return 'Loading...'
|
||||||
|
if (neptuneStore.getError) return 'Error'
|
||||||
|
if (neptuneStore.getWallet?.address) return 'Online'
|
||||||
|
return 'Offline'
|
||||||
|
})
|
||||||
|
|
||||||
|
const copyAddress = async () => {
|
||||||
|
if (!receiveAddress.value) {
|
||||||
|
message.error('No address available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(receiveAddress.value)
|
||||||
|
message.success('Address copied to clipboard!')
|
||||||
|
} catch (err) {
|
||||||
|
message.error('Failed to copy address')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
console.log('Send clicked')
|
// TODO: Implement send transaction functionality
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadWalletData = async () => {
|
||||||
|
if (!receiveAddress.value) return
|
||||||
|
|
||||||
|
isLoadingData.value = true
|
||||||
|
try {
|
||||||
|
const [balanceResult, blockHeightResult] = await Promise.all([
|
||||||
|
getBalance(),
|
||||||
|
getBlockHeight(),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (balanceResult) {
|
||||||
|
if (typeof balanceResult === 'number') {
|
||||||
|
availableBalance.value = balanceResult
|
||||||
|
} else if (balanceResult.confirmed !== undefined) {
|
||||||
|
availableBalance.value = balanceResult.confirmed || 0
|
||||||
|
pendingBalance.value = balanceResult.unconfirmed || 0
|
||||||
|
} else if (balanceResult.balance !== undefined) {
|
||||||
|
availableBalance.value = parseFloat(balanceResult.balance) || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockHeightResult) {
|
||||||
|
currentDaaScore.value =
|
||||||
|
typeof blockHeightResult === 'number'
|
||||||
|
? blockHeightResult
|
||||||
|
: blockHeightResult.height || 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Failed to load wallet data')
|
||||||
|
} finally {
|
||||||
|
isLoadingData.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadWalletData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="wallet-info-container">
|
<div class="wallet-info-container">
|
||||||
|
<div v-if="isLoadingData && !receiveAddress" class="loading-state">
|
||||||
|
<SpinnerCommon size="medium" />
|
||||||
|
<p>Loading wallet data...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!receiveAddress" class="empty-state">
|
||||||
|
<p>No wallet found. Please create or import a wallet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
<!-- Balance Section -->
|
<!-- Balance Section -->
|
||||||
<div class="balance-section">
|
<div class="balance-section">
|
||||||
<div class="balance-label">Available</div>
|
<div class="balance-label">Available</div>
|
||||||
<div class="balance-amount">{{ availableBalance }} KAS</div>
|
<div class="balance-amount">
|
||||||
|
<span v-if="isLoadingData">Loading...</span>
|
||||||
|
<span v-else>{{ formatNumberToLocaleString(availableBalance) }} NEPT</span>
|
||||||
|
</div>
|
||||||
<div class="pending-section">
|
<div class="pending-section">
|
||||||
<span class="pending-label">Pending</span>
|
<span class="pending-label">Pending</span>
|
||||||
<span class="pending-amount">{{ pendingBalance }} KAS</span>
|
<span class="pending-amount">
|
||||||
|
{{ isLoadingData ? '...' : formatNumberToLocaleString(pendingBalance) }}
|
||||||
|
NEPT
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -34,7 +110,7 @@ const handleSend = () => {
|
|||||||
<div class="receive-section">
|
<div class="receive-section">
|
||||||
<div class="address-label">Receive Address:</div>
|
<div class="address-label">Receive Address:</div>
|
||||||
<div class="address-value" @click="copyAddress">
|
<div class="address-value" @click="copyAddress">
|
||||||
{{ receiveAddress }}
|
{{ receiveAddress || 'No address available' }}
|
||||||
<svg
|
<svg
|
||||||
class="copy-icon"
|
class="copy-icon"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -50,7 +126,13 @@ const handleSend = () => {
|
|||||||
</div>
|
</div>
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<ButtonCommon type="primary" size="large" block @click="handleSend" class="btn-send">
|
<ButtonCommon
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
@click="handleSend"
|
||||||
|
class="btn-send"
|
||||||
|
>
|
||||||
SEND
|
SEND
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
@ -61,9 +143,13 @@ const handleSend = () => {
|
|||||||
>Wallet Status: <strong>{{ walletStatus }}</strong></span
|
>Wallet Status: <strong>{{ walletStatus }}</strong></span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>DAA score: <strong>{{ formatNumberToLocaleString(currentDaaScore) }}</strong></span
|
>DAA Score:
|
||||||
|
<strong>{{
|
||||||
|
isLoadingData ? '...' : formatNumberToLocaleString(currentDaaScore)
|
||||||
|
}}</strong></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -72,6 +158,18 @@ const handleSend = () => {
|
|||||||
@include card-base;
|
@include card-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-state,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--spacing-3xl);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: var(--spacing-lg) 0 0;
|
||||||
|
font-size: var(--font-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.balance-section {
|
.balance-section {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: var(--spacing-3xl);
|
margin-bottom: var(--spacing-3xl);
|
||||||
|
|||||||
@ -1,639 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, defineEmits } from 'vue'
|
|
||||||
import {
|
|
||||||
ButtonCommon,
|
|
||||||
FormCommon,
|
|
||||||
KeystoreDownloadComponent,
|
|
||||||
RecoverySeedComponent,
|
|
||||||
} from '@/components'
|
|
||||||
import { generateSeedPhrase18 } from '@/utils/helpers/seedPhrase'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
navigateToOpenWallet: [event: Event]
|
|
||||||
navigateToRecoverySeed: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const step = ref(1)
|
|
||||||
const backupMethod = ref<'none' | 'seed' | 'keystore'>('none')
|
|
||||||
const generatedSeed = ref<string[] | null>(null)
|
|
||||||
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
const passwordError = ref('')
|
|
||||||
const confirmPasswordError = ref('')
|
|
||||||
|
|
||||||
const passwordStrength = computed(() => {
|
|
||||||
if (!password.value) return { level: 0, text: '', color: '' }
|
|
||||||
|
|
||||||
let strength = 0
|
|
||||||
const checks = {
|
|
||||||
length: password.value.length >= 8,
|
|
||||||
uppercase: /[A-Z]/.test(password.value),
|
|
||||||
lowercase: /[a-z]/.test(password.value),
|
|
||||||
number: /[0-9]/.test(password.value),
|
|
||||||
special: /[!@#$%^&*(),.?":{}|<>]/.test(password.value),
|
|
||||||
}
|
|
||||||
strength = Object.values(checks).filter(Boolean).length
|
|
||||||
if (strength <= 2) return { level: 1, text: 'Weak', color: 'var(--error-color)' }
|
|
||||||
if (strength <= 3) return { level: 2, text: 'Medium', color: 'var(--warning-color)' }
|
|
||||||
if (strength <= 4) return { level: 3, text: 'Good', color: 'var(--info-color)' }
|
|
||||||
return { level: 4, text: 'Strong', color: 'var(--success-color)' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const isPasswordMatch = computed(() => {
|
|
||||||
if (!confirmPassword.value) return true
|
|
||||||
return password.value === confirmPassword.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const canProceed = computed(() => {
|
|
||||||
return (
|
|
||||||
password.value.length >= 8 &&
|
|
||||||
confirmPassword.value.length >= 8 &&
|
|
||||||
isPasswordMatch.value &&
|
|
||||||
passwordStrength.value.level >= 2
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const 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
|
|
||||||
}
|
|
||||||
const handleChooseMethod = (method: 'seed' | 'keystore'): void => {
|
|
||||||
backupMethod.value = method
|
|
||||||
if (method === 'seed') {
|
|
||||||
generatedSeed.value = generateSeedPhrase18()
|
|
||||||
}
|
|
||||||
step.value = 3
|
|
||||||
}
|
|
||||||
const handleNextRecovery = () => {
|
|
||||||
step.value = 4
|
|
||||||
}
|
|
||||||
const handleBackChoose = () => {
|
|
||||||
backupMethod.value = 'none'
|
|
||||||
generatedSeed.value = null
|
|
||||||
step.value = 2
|
|
||||||
}
|
|
||||||
function handleBack() {
|
|
||||||
if (step.value === 2) step.value = 1
|
|
||||||
else if (step.value === 3) {
|
|
||||||
backupMethod.value = 'none'
|
|
||||||
step.value = 2
|
|
||||||
} else if (step.value === 4) step.value = 2
|
|
||||||
}
|
|
||||||
function resetAll() {
|
|
||||||
password.value = ''
|
|
||||||
confirmPassword.value = ''
|
|
||||||
passwordError.value = ''
|
|
||||||
confirmPasswordError.value = ''
|
|
||||||
step.value = 1
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="auth-container">
|
|
||||||
<div class="auth-card">
|
|
||||||
<template v-if="step === 1">
|
|
||||||
<div class="auth-card-header">
|
|
||||||
<div class="logo-container">
|
|
||||||
<div class="logo-circle">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
class="neptune-logo"
|
|
||||||
>
|
|
||||||
<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 class="auth-card-content">
|
|
||||||
<div class="form-group">
|
|
||||||
<FormCommon
|
|
||||||
v-model="password"
|
|
||||||
type="password"
|
|
||||||
label="Create Password"
|
|
||||||
placeholder="Enter your password"
|
|
||||||
show-password-toggle
|
|
||||||
required
|
|
||||||
:error="passwordError"
|
|
||||||
@input="passwordError = ''"
|
|
||||||
/>
|
|
||||||
<div v-if="password" class="password-strength">
|
|
||||||
<div class="strength-bar">
|
|
||||||
<div
|
|
||||||
class="strength-fill"
|
|
||||||
:style="{
|
|
||||||
width: `${(passwordStrength.level / 4) * 100}%`,
|
|
||||||
backgroundColor: passwordStrength.color,
|
|
||||||
}"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="strength-text"
|
|
||||||
:style="{ color: passwordStrength.color }"
|
|
||||||
>{{ passwordStrength.text }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<FormCommon
|
|
||||||
v-model="confirmPassword"
|
|
||||||
type="password"
|
|
||||||
label="Confirm Password"
|
|
||||||
placeholder="Re-enter your password"
|
|
||||||
show-password-toggle
|
|
||||||
required
|
|
||||||
:error="confirmPasswordError"
|
|
||||||
@input="confirmPasswordError = ''"
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
<p class="helper-text">
|
|
||||||
Password must be at least 8 characters with uppercase, lowercase, and
|
|
||||||
numbers.
|
|
||||||
</p>
|
|
||||||
<div class="auth-button-group">
|
|
||||||
<ButtonCommon
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
class="auth-button"
|
|
||||||
block
|
|
||||||
:disabled="!canProceed"
|
|
||||||
@click="handleNextPassword"
|
|
||||||
>Create Wallet</ButtonCommon
|
|
||||||
>
|
|
||||||
<div class="secondary-actions">
|
|
||||||
<button class="link-button" @click="handleIHaveWallet">
|
|
||||||
Already have a wallet?
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="step === 2">
|
|
||||||
<div class="choose-backup-method">
|
|
||||||
<h2>Choose your backup method</h2>
|
|
||||||
<div class="choose-btns">
|
|
||||||
<ButtonCommon
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
@click="() => handleChooseMethod('seed')"
|
|
||||||
>
|
|
||||||
Mnemonic Phrase
|
|
||||||
</ButtonCommon>
|
|
||||||
<ButtonCommon
|
|
||||||
type="default"
|
|
||||||
size="large"
|
|
||||||
@click="() => handleChooseMethod('keystore')"
|
|
||||||
>
|
|
||||||
Keystore File
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else-if="step === 3">
|
|
||||||
<template v-if="backupMethod === 'seed'">
|
|
||||||
<RecoverySeedComponent
|
|
||||||
:seed-words="generatedSeed"
|
|
||||||
@next="handleNextRecovery"
|
|
||||||
@back="handleBackChoose"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<KeystoreDownloadComponent
|
|
||||||
@download="downloadKeystoreFile"
|
|
||||||
@back="handleBackChoose"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else-if="step === 4">
|
|
||||||
<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>
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.choose-backup-method {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px 8px;
|
|
||||||
h2 {
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.07em;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
.choose-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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>
|
|
||||||
52
src/components/common/SpinnerCommon.vue
Normal file
52
src/components/common/SpinnerCommon.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
size: 'medium',
|
||||||
|
color: 'var(--primary-color)',
|
||||||
|
})
|
||||||
|
|
||||||
|
const spinnerSize = computed(() => {
|
||||||
|
const sizes = {
|
||||||
|
small: '24px',
|
||||||
|
medium: '40px',
|
||||||
|
large: '60px',
|
||||||
|
}
|
||||||
|
return sizes[props.size]
|
||||||
|
})
|
||||||
|
|
||||||
|
const borderWidth = computed(() => {
|
||||||
|
const widths = {
|
||||||
|
small: '3px',
|
||||||
|
medium: '4px',
|
||||||
|
large: '5px',
|
||||||
|
}
|
||||||
|
return widths[props.size]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="spinner"
|
||||||
|
:style="{
|
||||||
|
width: spinnerSize,
|
||||||
|
height: spinnerSize,
|
||||||
|
borderWidth: borderWidth,
|
||||||
|
borderTopColor: color,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid var(--border-light);
|
||||||
|
border-top-color: var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,25 +1,7 @@
|
|||||||
import LayoutVue from './common/LayoutVue.vue'
|
import LayoutVue from './common/LayoutVue.vue'
|
||||||
import ButtonCommon from './common/ButtonCommon.vue'
|
import ButtonCommon from './common/ButtonCommon.vue'
|
||||||
import FormCommon from './common/FormCommon.vue'
|
import FormCommon from './common/FormCommon.vue'
|
||||||
import OnboardingComponent from './auth/OnboardingComponent.vue'
|
import SpinnerCommon from './common/SpinnerCommon.vue'
|
||||||
import OpenWalletComponent from './auth/OpenWalletComponent.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 { LayoutVue, ButtonCommon, FormCommon, SpinnerCommon, IconCommon }
|
||||||
LayoutVue,
|
|
||||||
ButtonCommon,
|
|
||||||
FormCommon,
|
|
||||||
OnboardingComponent,
|
|
||||||
OpenWalletComponent,
|
|
||||||
CreateWalletComponent,
|
|
||||||
RecoverySeedComponent,
|
|
||||||
ConfirmSeedComponent,
|
|
||||||
IconCommon,
|
|
||||||
ImportWalletComponent,
|
|
||||||
KeystoreDownloadComponent,
|
|
||||||
}
|
|
||||||
|
|||||||
292
src/composables/useNeptuneWallet.ts
Normal file
292
src/composables/useNeptuneWallet.ts
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import * as API from '@/api/neptuneApi'
|
||||||
|
import type { GenerateSeedResult, ViewKeyResult } from '@/interface'
|
||||||
|
import initWasm, {
|
||||||
|
generate_seed,
|
||||||
|
get_viewkey,
|
||||||
|
address_from_seed,
|
||||||
|
validate_seed_phrase,
|
||||||
|
decode_viewkey,
|
||||||
|
} from '@neptune/wasm'
|
||||||
|
|
||||||
|
let wasmInitialized = false
|
||||||
|
let initPromise: Promise<void> | null = null
|
||||||
|
|
||||||
|
export function useNeptuneWallet() {
|
||||||
|
const store = useNeptuneStore()
|
||||||
|
|
||||||
|
// ===== WASM METHODS =====
|
||||||
|
const ensureWasmInitialized = async (): Promise<void> => {
|
||||||
|
if (wasmInitialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initPromise) {
|
||||||
|
return initPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
initPromise = (async () => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
await initWasm()
|
||||||
|
|
||||||
|
wasmInitialized = true
|
||||||
|
} catch (err) {
|
||||||
|
wasmInitialized = false
|
||||||
|
const errorMsg = 'Failed to initialize Neptune WASM'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
console.error('WASM init error:', err)
|
||||||
|
throw new Error(errorMsg)
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return initPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateWallet = async (): Promise<GenerateSeedResult> => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
await ensureWasmInitialized()
|
||||||
|
|
||||||
|
const resultJson = generate_seed()
|
||||||
|
const result: GenerateSeedResult = JSON.parse(resultJson)
|
||||||
|
|
||||||
|
store.setSeedPhrase(result.seed_phrase)
|
||||||
|
store.setReceiverId(result.receiver_identifier)
|
||||||
|
|
||||||
|
const viewKeyResult = await getViewKeyFromSeed(result.seed_phrase, store.getNetwork)
|
||||||
|
|
||||||
|
store.setViewKey(viewKeyResult.view_key)
|
||||||
|
store.setAddress(viewKeyResult.address)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to generate wallet'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getViewKeyFromSeed = async (
|
||||||
|
seedPhrase: string[],
|
||||||
|
network: 'mainnet' | 'testnet'
|
||||||
|
): Promise<ViewKeyResult> => {
|
||||||
|
await ensureWasmInitialized()
|
||||||
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
||||||
|
const resultJson = get_viewkey(seedPhraseJson, network)
|
||||||
|
return JSON.parse(resultJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
const importWallet = async (
|
||||||
|
seedPhrase: string[],
|
||||||
|
network: 'mainnet' | 'testnet' = 'testnet'
|
||||||
|
): Promise<ViewKeyResult> => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
const isValid = await validateSeedPhrase(seedPhrase)
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error('Invalid seed phrase')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await getViewKeyFromSeed(seedPhrase, network)
|
||||||
|
|
||||||
|
store.setSeedPhrase(seedPhrase)
|
||||||
|
store.setReceiverId(result.receiver_identifier)
|
||||||
|
store.setViewKey(result.view_key)
|
||||||
|
store.setAddress(result.address)
|
||||||
|
store.setNetwork(network)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to import wallet'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAddressFromSeed = async (
|
||||||
|
seedPhrase: string[],
|
||||||
|
network: 'mainnet' | 'testnet'
|
||||||
|
): Promise<string> => {
|
||||||
|
await ensureWasmInitialized()
|
||||||
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
||||||
|
return address_from_seed(seedPhraseJson, network)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateSeedPhrase = async (seedPhrase: string[]): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await ensureWasmInitialized()
|
||||||
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
||||||
|
return validate_seed_phrase(seedPhraseJson)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Validation error:', err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodeViewKey = async (viewKeyHex: string): Promise<{ receiver_identifier: string }> => {
|
||||||
|
await ensureWasmInitialized()
|
||||||
|
const resultJson = decode_viewkey(viewKeyHex)
|
||||||
|
return JSON.parse(resultJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== API METHODS =====
|
||||||
|
|
||||||
|
const getUtxos = async (
|
||||||
|
startBlock: number = 0,
|
||||||
|
endBlock: number | null = null,
|
||||||
|
maxSearchDepth: number = 1000
|
||||||
|
): Promise<any> => {
|
||||||
|
try {
|
||||||
|
if (!store.getViewKey) {
|
||||||
|
throw new Error('No view key available. Please import or generate a wallet first.')
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
const response = await API.getUtxosFromViewKey(
|
||||||
|
store.getViewKey,
|
||||||
|
startBlock,
|
||||||
|
endBlock,
|
||||||
|
maxSearchDepth
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.data?.result || response.data
|
||||||
|
store.setUtxos(result.utxos || result || [])
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get UTXOs'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBalance = async (): Promise<any> => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
const response = await API.getBalance()
|
||||||
|
const result = response.data?.result || response.data
|
||||||
|
store.setBalance(result.balance || result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get balance'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBlockHeight = async (): Promise<any> => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
const response = await API.getBlockHeight()
|
||||||
|
const result = response.data?.result || response.data
|
||||||
|
return result.height || result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get block height'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNetworkInfo = async (): Promise<any> => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
const response = await API.getNetworkInfo()
|
||||||
|
const result = response.data?.result || response.data
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get network info'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendTransaction = async (
|
||||||
|
toAddress: string,
|
||||||
|
amount: string,
|
||||||
|
fee: string
|
||||||
|
): Promise<any> => {
|
||||||
|
try {
|
||||||
|
store.setLoading(true)
|
||||||
|
store.setError(null)
|
||||||
|
|
||||||
|
const response = await API.sendTransaction(toAddress, amount, fee)
|
||||||
|
const result = response.data?.result || response.data
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to send transaction'
|
||||||
|
store.setError(errorMsg)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
store.setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setNetwork = async (network: 'mainnet' | 'testnet') => {
|
||||||
|
store.setNetwork(network)
|
||||||
|
|
||||||
|
if (store.getSeedPhrase) {
|
||||||
|
const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase, network)
|
||||||
|
store.setAddress(viewKeyResult.address)
|
||||||
|
store.setViewKey(viewKeyResult.view_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== UTILITY METHODS =====
|
||||||
|
const clearWallet = () => {
|
||||||
|
store.clearWallet()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
walletState: computed(() => store.getWallet),
|
||||||
|
isLoading: computed(() => store.getLoading),
|
||||||
|
error: computed(() => store.getError),
|
||||||
|
hasWallet: computed(() => store.hasWallet),
|
||||||
|
|
||||||
|
initWasm: ensureWasmInitialized,
|
||||||
|
generateWallet,
|
||||||
|
importWallet,
|
||||||
|
getViewKeyFromSeed,
|
||||||
|
getAddressFromSeed,
|
||||||
|
validateSeedPhrase,
|
||||||
|
decodeViewKey,
|
||||||
|
|
||||||
|
getUtxos,
|
||||||
|
getBalance,
|
||||||
|
getBlockHeight,
|
||||||
|
getNetworkInfo,
|
||||||
|
sendTransaction,
|
||||||
|
|
||||||
|
clearWallet,
|
||||||
|
setNetwork,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './common'
|
export * from './common'
|
||||||
export * from './home'
|
export * from './home'
|
||||||
|
export * from './neptune'
|
||||||
|
|||||||
20
src/interface/neptune.ts
Normal file
20
src/interface/neptune.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export interface WalletState {
|
||||||
|
seedPhrase: string[] | null
|
||||||
|
receiverId: string | null
|
||||||
|
viewKey: string | null
|
||||||
|
address: string | null
|
||||||
|
network: 'mainnet' | 'testnet'
|
||||||
|
balance: string | null
|
||||||
|
utxos: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateSeedResult {
|
||||||
|
seed_phrase: string[]
|
||||||
|
receiver_identifier: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewKeyResult {
|
||||||
|
receiver_identifier: string
|
||||||
|
view_key: string
|
||||||
|
address: string
|
||||||
|
}
|
||||||
@ -1,45 +1,26 @@
|
|||||||
import * as Page from '@/views'
|
import * as Page from '@/views'
|
||||||
import { getToken } from '@/utils'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
|
||||||
const ifAuthenticated = (to: any, from: any, next: any) => {
|
export const ifAuthenticated = (to: any, from: any, next: any) => {
|
||||||
if (getToken()) {
|
const neptuneStore = useNeptuneStore()
|
||||||
|
if (neptuneStore.getReceiverId) {
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
next('/login')
|
next('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const ifNotAuthenticated = (to: any, from: any, next: any) => {
|
|
||||||
if (!getToken()) {
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const routes: any = [
|
export const routes: any = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'index',
|
name: 'home',
|
||||||
component: Page.Home,
|
component: Page.Home,
|
||||||
|
beforeEnter: ifAuthenticated,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Page.Auth,
|
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(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './seedStore'
|
export * from './seedStore'
|
||||||
export * from './authStore'
|
export * from './authStore'
|
||||||
|
export * from './neptuneStore'
|
||||||
|
|||||||
113
src/stores/neptuneStore.ts
Normal file
113
src/stores/neptuneStore.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import type { WalletState } from '@/interface'
|
||||||
|
|
||||||
|
export const useNeptuneStore = defineStore('neptune', () => {
|
||||||
|
// ===== STATE =====
|
||||||
|
const wallet = ref<WalletState>({
|
||||||
|
seedPhrase: null,
|
||||||
|
receiverId: null,
|
||||||
|
viewKey: null,
|
||||||
|
address: null,
|
||||||
|
network: 'testnet',
|
||||||
|
balance: null,
|
||||||
|
utxos: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
// ===== SETTERS =====
|
||||||
|
|
||||||
|
const setSeedPhrase = (seedPhrase: string[] | null) => {
|
||||||
|
wallet.value.seedPhrase = seedPhrase
|
||||||
|
}
|
||||||
|
|
||||||
|
const setReceiverId = (receiverId: string | null) => {
|
||||||
|
wallet.value.receiverId = receiverId
|
||||||
|
}
|
||||||
|
|
||||||
|
const setViewKey = (viewKey: string | null) => {
|
||||||
|
wallet.value.viewKey = viewKey
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAddress = (address: string | null) => {
|
||||||
|
wallet.value.address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
const setNetwork = (network: 'mainnet' | 'testnet') => {
|
||||||
|
wallet.value.network = network
|
||||||
|
}
|
||||||
|
|
||||||
|
const setBalance = (balance: string | null) => {
|
||||||
|
wallet.value.balance = balance
|
||||||
|
}
|
||||||
|
|
||||||
|
const setUtxos = (utxos: any[]) => {
|
||||||
|
wallet.value.utxos = utxos
|
||||||
|
}
|
||||||
|
|
||||||
|
const setLoading = (loading: boolean) => {
|
||||||
|
isLoading.value = loading
|
||||||
|
}
|
||||||
|
|
||||||
|
const setError = (err: string | null) => {
|
||||||
|
error.value = err
|
||||||
|
}
|
||||||
|
|
||||||
|
const setWallet = (walletData: Partial<WalletState>) => {
|
||||||
|
wallet.value = { ...wallet.value, ...walletData }
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearWallet = () => {
|
||||||
|
wallet.value = {
|
||||||
|
seedPhrase: null,
|
||||||
|
receiverId: null,
|
||||||
|
viewKey: null,
|
||||||
|
address: null,
|
||||||
|
network: 'testnet',
|
||||||
|
balance: null,
|
||||||
|
utxos: [],
|
||||||
|
}
|
||||||
|
error.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== GETTERS =====
|
||||||
|
const getWallet = computed(() => wallet.value)
|
||||||
|
const getSeedPhrase = computed(() => wallet.value.seedPhrase)
|
||||||
|
const getReceiverId = computed(() => wallet.value.receiverId)
|
||||||
|
const getViewKey = computed(() => wallet.value.viewKey)
|
||||||
|
const getAddress = computed(() => wallet.value.address)
|
||||||
|
const getNetwork = computed(() => wallet.value.network)
|
||||||
|
const getBalance = computed(() => wallet.value.balance)
|
||||||
|
const getUtxos = computed(() => wallet.value.utxos)
|
||||||
|
const hasWallet = computed(() => wallet.value.address !== null)
|
||||||
|
const getLoading = computed(() => isLoading.value)
|
||||||
|
const getError = computed(() => error.value)
|
||||||
|
|
||||||
|
return {
|
||||||
|
getWallet,
|
||||||
|
getSeedPhrase,
|
||||||
|
getReceiverId,
|
||||||
|
getViewKey,
|
||||||
|
getAddress,
|
||||||
|
getNetwork,
|
||||||
|
getBalance,
|
||||||
|
getUtxos,
|
||||||
|
hasWallet,
|
||||||
|
getLoading,
|
||||||
|
getError,
|
||||||
|
|
||||||
|
setSeedPhrase,
|
||||||
|
setReceiverId,
|
||||||
|
setViewKey,
|
||||||
|
setAddress,
|
||||||
|
setNetwork,
|
||||||
|
setBalance,
|
||||||
|
setUtxos,
|
||||||
|
setLoading,
|
||||||
|
setError,
|
||||||
|
setWallet,
|
||||||
|
clearWallet,
|
||||||
|
}
|
||||||
|
})
|
||||||
39
src/types/neptune-wasm.d.ts
vendored
Normal file
39
src/types/neptune-wasm.d.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Type declarations for Neptune WASM module
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module '/wasm/neptune_wasm.js' {
|
||||||
|
export default function init(): Promise<void>
|
||||||
|
|
||||||
|
export function generate_seed(): string
|
||||||
|
export function get_viewkey(seedPhraseJson: string, network: string): string
|
||||||
|
export function address_from_seed(seedPhraseJson: string, network: string): string
|
||||||
|
export function validate_seed_phrase(seedPhraseJson: string): boolean
|
||||||
|
export function decode_viewkey(viewKeyHex: string): string
|
||||||
|
export function get_balance(rpcUrl: string): Promise<string>
|
||||||
|
export function get_block_height(rpcUrl: string): Promise<number>
|
||||||
|
export function get_network_info(rpcUrl: string): Promise<string>
|
||||||
|
export function get_wallet_address(rpcUrl: string): Promise<string>
|
||||||
|
export function get_utxos_from_viewkey(
|
||||||
|
rpcUrl: string,
|
||||||
|
viewKeyHex: string,
|
||||||
|
startBlock: number,
|
||||||
|
endBlock: number | null
|
||||||
|
): Promise<string>
|
||||||
|
export function build_and_sign_tx(requestJson: string): string
|
||||||
|
export function send_tx_jsonrpc(
|
||||||
|
rpcUrl: string,
|
||||||
|
toAddress: string,
|
||||||
|
amount: string,
|
||||||
|
fee: string
|
||||||
|
): Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global type definitions
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
neptuneWasm?: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { OnboardingComponent } from '@/components'
|
import { OnboardingComponent } from './components'
|
||||||
import { useAuthStore } from '@/stores'
|
import { useAuthStore } from '@/stores'
|
||||||
import { LoginTab, CreateTab, RecoveryTab, ConfirmTab } from './components'
|
import { LoginTab, CreateTab, RecoveryTab, ConfirmTab } from './components'
|
||||||
|
|
||||||
|
|||||||
@ -1,141 +1,62 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineEmits, onMounted } from 'vue'
|
import { ref, defineEmits, onMounted, computed } from 'vue'
|
||||||
import { ButtonCommon } from '@/components'
|
import { ButtonCommon } from '@/components'
|
||||||
import { useSeedStore } from '@/stores'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
next: []
|
next: []
|
||||||
back: []
|
back: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const seedStore = useSeedStore()
|
const neptuneStore = useNeptuneStore()
|
||||||
|
|
||||||
const seedWords = ref<string[]>([])
|
const seedWords = computed(() => neptuneStore.getSeedPhrase || [])
|
||||||
const currentQuestionIndex = ref(0)
|
const currentQuestionIndex = ref(0)
|
||||||
const selectedAnswer = ref('')
|
const selectedAnswer = ref('')
|
||||||
const isCorrect = ref(false)
|
const isCorrect = ref(false)
|
||||||
const showResult = ref(false)
|
const showResult = ref(false)
|
||||||
|
const correctCount = ref(0)
|
||||||
|
const totalQuestions = 3
|
||||||
|
const askedPositions = ref<Set<number>>(new Set())
|
||||||
|
|
||||||
const generateQuiz = (): {
|
const generateQuiz = (): {
|
||||||
position: number
|
position: number
|
||||||
correctWord: string
|
correctWord: string
|
||||||
options: string[]
|
options: string[]
|
||||||
} | null => {
|
} | null => {
|
||||||
if (seedWords.value.length === 0) return null
|
if (!seedWords.value || seedWords.value.length === 0) {
|
||||||
|
message.error('No seed phrase found')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let randomPosition: number
|
||||||
|
let attempts = 0
|
||||||
|
const maxAttempts = 50
|
||||||
|
|
||||||
|
do {
|
||||||
|
randomPosition = Math.floor(Math.random() * seedWords.value.length) + 1
|
||||||
|
attempts++
|
||||||
|
if (attempts > maxAttempts) {
|
||||||
|
message.error('Unable to generate new question')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} while (askedPositions.value.has(randomPosition))
|
||||||
|
|
||||||
const randomPosition = Math.floor(Math.random() * 12) + 1
|
|
||||||
currentQuestionIndex.value = randomPosition - 1
|
currentQuestionIndex.value = randomPosition - 1
|
||||||
|
|
||||||
const correctWord = seedWords.value[randomPosition - 1]
|
const correctWord = seedWords.value[randomPosition - 1]
|
||||||
const options = [correctWord]
|
const options = [correctWord]
|
||||||
|
|
||||||
const BIP39_WORDS = [
|
const otherWords = seedWords.value.filter((_, index) => index !== randomPosition - 1)
|
||||||
'abandon',
|
|
||||||
'ability',
|
while (options.length < 4 && otherWords.length > 0) {
|
||||||
'able',
|
const randomIndex = Math.floor(Math.random() * otherWords.length)
|
||||||
'about',
|
const randomWord = otherWords[randomIndex]
|
||||||
'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)) {
|
if (!options.includes(randomWord)) {
|
||||||
options.push(randomWord)
|
options.push(randomWord)
|
||||||
|
otherWords.splice(randomIndex, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +83,10 @@ const handleAnswerSelect = (answer: string) => {
|
|||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
if (isCorrect.value) {
|
if (isCorrect.value) {
|
||||||
|
correctCount.value++
|
||||||
|
askedPositions.value.add(quizData.value!.position)
|
||||||
|
|
||||||
|
if (correctCount.value >= totalQuestions) {
|
||||||
emit('next')
|
emit('next')
|
||||||
} else {
|
} else {
|
||||||
showResult.value = false
|
showResult.value = false
|
||||||
@ -171,37 +96,32 @@ const handleNext = () => {
|
|||||||
quizData.value = newQuiz
|
quizData.value = newQuiz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
showResult.value = false
|
||||||
|
selectedAnswer.value = ''
|
||||||
|
const newQuiz = generateQuiz()
|
||||||
|
if (newQuiz) {
|
||||||
|
quizData.value = newQuiz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
emit('back')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const words = seedStore.getSeedWords()
|
const seeds = neptuneStore.getSeedPhrase
|
||||||
if (words.length > 0) {
|
|
||||||
seedWords.value = words
|
if (!seeds || seeds.length === 0) {
|
||||||
|
message.warning('No seed phrase found. Please go back and generate a wallet first.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const newQuiz = generateQuiz()
|
const newQuiz = generateQuiz()
|
||||||
if (newQuiz) {
|
if (newQuiz) {
|
||||||
quizData.value = 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>
|
</script>
|
||||||
|
|
||||||
@ -224,6 +144,9 @@ onMounted(() => {
|
|||||||
Make sure you wrote the phrase down correctly by answering this quick
|
Make sure you wrote the phrase down correctly by answering this quick
|
||||||
checkup.
|
checkup.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="progress-text">
|
||||||
|
Question {{ correctCount + 1 }} / {{ totalQuestions }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quiz-section">
|
<div class="quiz-section">
|
||||||
@ -250,14 +173,42 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showResult" class="result-message">
|
<div v-if="showResult" class="result-message">
|
||||||
<p v-if="isCorrect" class="success-message">✓ Correct! You can proceed.</p>
|
<p
|
||||||
|
v-if="isCorrect && correctCount + 1 >= totalQuestions"
|
||||||
|
class="success-message"
|
||||||
|
>
|
||||||
|
✓ Correct! You answered all {{ totalQuestions }} questions correctly.
|
||||||
|
</p>
|
||||||
|
<p v-else-if="isCorrect" class="success-message">
|
||||||
|
✓ Correct! Next question...
|
||||||
|
</p>
|
||||||
<p v-else class="error-message">✗ Incorrect. Please try again.</p>
|
<p v-else class="error-message">✗ Incorrect. Please try again.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="confirm-actions">
|
<div class="confirm-actions">
|
||||||
<ButtonCommon
|
<ButtonCommon
|
||||||
v-if="showResult && isCorrect"
|
v-if="
|
||||||
|
!showResult ||
|
||||||
|
!isCorrect ||
|
||||||
|
(isCorrect && correctCount + 1 < totalQuestions)
|
||||||
|
"
|
||||||
|
type="default"
|
||||||
|
size="large"
|
||||||
|
@click="handleBack"
|
||||||
|
>
|
||||||
|
BACK
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon
|
||||||
|
v-if="showResult && isCorrect && correctCount + 1 < totalQuestions"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="handleNext"
|
||||||
|
>
|
||||||
|
NEXT QUESTION
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon
|
||||||
|
v-if="showResult && isCorrect && correctCount + 1 >= totalQuestions"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
@click="handleNext"
|
@click="handleNext"
|
||||||
@ -331,6 +282,13 @@ onMounted(() => {
|
|||||||
line-height: var(--leading-normal);
|
line-height: var(--leading-normal);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: var(--font-base);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quiz-section {
|
.quiz-section {
|
||||||
@ -411,10 +369,14 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
|
|
||||||
|
&:has(:only-child) {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@include screen(mobile) {
|
||||||
.confirm-container {
|
.confirm-container {
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
@ -423,6 +385,7 @@ onMounted(() => {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.confirm-content {
|
||||||
.quiz-section {
|
.quiz-section {
|
||||||
.answer-options {
|
.answer-options {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@ -433,4 +396,5 @@ onMounted(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ConfirmSeedComponent } from '@/components'
|
import { ConfirmSeedComponent } from '.'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
next: []
|
next: []
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CreateWalletComponent } from '@/components'
|
import { CreateWalletComponent } from '.'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
goToLogin: []
|
goToLogin: []
|
||||||
|
|||||||
176
src/views/Auth/components/CreateWalletComponent.vue
Normal file
176
src/views/Auth/components/CreateWalletComponent.vue
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineEmits, onMounted } from 'vue'
|
||||||
|
import { KeystoreDownloadComponent, RecoverySeedComponent, ConfirmSeedComponent } from '.'
|
||||||
|
import { ChooseBackupMethodStep, CreatePasswordStep, WalletCreatedStep } from './steps'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
navigateToOpenWallet: [event: Event]
|
||||||
|
navigateToRecoverySeed: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { initWasm, generateWallet } = useNeptuneWallet()
|
||||||
|
const neptuneStore = useNeptuneStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const step = ref(1)
|
||||||
|
const backupMethod = ref<'none' | 'seed' | 'keystore'>('none')
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await initWasm()
|
||||||
|
} catch (err) {
|
||||||
|
message.error('Failed to initialize wallet. Please refresh the page.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleIHaveWallet = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
emit('navigateToOpenWallet', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChooseMethod = async (method: 'seed' | 'keystore'): Promise<void> => {
|
||||||
|
backupMethod.value = method
|
||||||
|
|
||||||
|
if (method === 'seed') {
|
||||||
|
try {
|
||||||
|
await generateWallet()
|
||||||
|
message.success('Wallet generated successfully!')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Failed to generate wallet:', err)
|
||||||
|
message.error('Failed to generate wallet. Please try again.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step.value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextFromRecoverySeed = () => {
|
||||||
|
step.value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextFromConfirmSeed = () => {
|
||||||
|
step.value = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextFromPassword = () => {
|
||||||
|
step.value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadKeystoreFile() {
|
||||||
|
const data = {
|
||||||
|
account: 'neptune-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 = 'neptune-wallet-keystore.json'
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
step.value = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackToChoose = () => {
|
||||||
|
backupMethod.value = 'none'
|
||||||
|
neptuneStore.clearWallet()
|
||||||
|
step.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackToRecoverySeed = () => {
|
||||||
|
step.value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackToPassword = () => {
|
||||||
|
step.value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAccessWallet = () => {
|
||||||
|
router.push({ name: 'home' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAll() {
|
||||||
|
step.value = 1
|
||||||
|
backupMethod.value = 'none'
|
||||||
|
neptuneStore.clearWallet()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="auth-container">
|
||||||
|
<div class="auth-card">
|
||||||
|
<!-- Step 1: Choose Backup Method -->
|
||||||
|
<ChooseBackupMethodStep v-if="step === 1" @choose-method="handleChooseMethod" />
|
||||||
|
|
||||||
|
<!-- Step 2: -->
|
||||||
|
<template v-else-if="step === 2">
|
||||||
|
<!-- Seed flow: Show recovery seed -->
|
||||||
|
<RecoverySeedComponent
|
||||||
|
v-if="backupMethod === 'seed'"
|
||||||
|
@next="handleNextFromRecoverySeed"
|
||||||
|
@back="handleBackToChoose"
|
||||||
|
/>
|
||||||
|
<!-- Keystore flow: Create password -->
|
||||||
|
<CreatePasswordStep
|
||||||
|
v-else-if="backupMethod === 'keystore'"
|
||||||
|
@next="handleNextFromPassword"
|
||||||
|
@navigate-to-open-wallet="handleIHaveWallet"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Step 3: Based on chosen method -->
|
||||||
|
<template v-else-if="step === 3">
|
||||||
|
<ConfirmSeedComponent
|
||||||
|
v-if="backupMethod === 'seed'"
|
||||||
|
@next="handleNextFromConfirmSeed"
|
||||||
|
@back="handleBackToRecoverySeed"
|
||||||
|
/>
|
||||||
|
<KeystoreDownloadComponent
|
||||||
|
v-else-if="backupMethod === 'keystore'"
|
||||||
|
@download="downloadKeystoreFile"
|
||||||
|
@back="handleBackToPassword"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Step 4: Success -->
|
||||||
|
<WalletCreatedStep
|
||||||
|
v-else-if="step === 4"
|
||||||
|
@access-wallet="handleAccessWallet"
|
||||||
|
@create-another="resetAll"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Fallback slot -->
|
||||||
|
<template v-else>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.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%;
|
||||||
|
|
||||||
|
@include screen(mobile) {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -255,7 +255,7 @@ const handleContinue = () => {
|
|||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 600px) {
|
@include screen(mobile) {
|
||||||
.import-wallet {
|
.import-wallet {
|
||||||
padding: 16px 5px;
|
padding: 16px 5px;
|
||||||
}
|
}
|
||||||
@ -218,13 +218,19 @@ function handleBack() {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 900px) {
|
@include screen(tablet) {
|
||||||
.steps-bar .step {
|
.steps-bar .step {
|
||||||
min-width: 90px;
|
min-width: 90px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 650px) {
|
|
||||||
|
@include screen(mobile) {
|
||||||
|
.steps-bar .step {
|
||||||
|
min-width: 90px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.keystore-step {
|
.keystore-step {
|
||||||
padding: 15px 3px 13px 3px;
|
padding: 15px 3px 13px 3px;
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ImportWalletComponent, OpenWalletComponent } from '@/components'
|
import { ImportWalletComponent, OpenWalletComponent } from '.'
|
||||||
|
|
||||||
const emit = defineEmits<{ goToCreate: [] }>()
|
const emit = defineEmits<{ goToCreate: [] }>()
|
||||||
|
|
||||||
|
|||||||
@ -188,7 +188,7 @@ const navigateToNewWallet = () => {
|
|||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@include screen(mobile) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,12 +312,11 @@ const navigateToNewWallet = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Responsive Design
|
// Responsive Design
|
||||||
@media (max-width: 640px) {
|
@include screen(mobile) {
|
||||||
.auth-container {
|
.auth-container {
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-card-header {
|
|
||||||
.logo-container {
|
.logo-container {
|
||||||
.logo-circle {
|
.logo-circle {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@ -331,11 +330,6 @@ const navigateToNewWallet = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-title {
|
|
||||||
font-size: var(--font-xl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-icon {
|
.wallet-icon {
|
||||||
.icon-circle {
|
.icon-circle {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
@ -1,32 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineEmits, onMounted } from 'vue'
|
import { defineEmits, computed } from 'vue'
|
||||||
import { ButtonCommon } from '@/components'
|
import { ButtonCommon } from '@/components'
|
||||||
import { generateSeedPhrase } from '@/utils'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
import { useSeedStore } from '@/stores'
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
next: []
|
next: []
|
||||||
back: []
|
back: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const seedStore = useSeedStore()
|
const neptuneStore = useNeptuneStore()
|
||||||
|
|
||||||
const seedWords = ref<string[]>([])
|
const seedWords = computed(() => neptuneStore.getSeedPhrase || [])
|
||||||
const extraWord = ref('')
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const words = generateSeedPhrase()
|
|
||||||
seedWords.value = words
|
|
||||||
seedStore.setSeedWords(words)
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
|
if (!seedWords.value || seedWords.value.length === 0) {
|
||||||
|
message.error('❌ Cannot proceed without seed phrase')
|
||||||
|
return
|
||||||
|
}
|
||||||
emit('next')
|
emit('next')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
emit('back')
|
emit('back')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCopySeed = async () => {
|
||||||
|
if (!seedWords.value || seedWords.value.length === 0) {
|
||||||
|
message.error('No seed phrase to copy')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const seedPhrase = seedWords.value.join(' ')
|
||||||
|
await navigator.clipboard.writeText(seedPhrase)
|
||||||
|
message.success('Seed phrase copied to clipboard!')
|
||||||
|
} catch (err) {
|
||||||
|
message.error('Failed to copy seed phrase')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -40,36 +52,49 @@ const handleBack = () => {
|
|||||||
<div class="instruction-text">
|
<div class="instruction-text">
|
||||||
<p>
|
<p>
|
||||||
Your wallet is accessible by a seed phrase. The seed phrase is an ordered
|
Your wallet is accessible by a seed phrase. The seed phrase is an ordered
|
||||||
12-word secret phrase.
|
18-word secret phrase.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Make sure no one is looking, as anyone with your seed phrase can access your
|
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.
|
wallet and funds. Write it down and keep it safe.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="seed-words-container">
|
<div v-if="seedWords.length > 0" class="seed-words-container">
|
||||||
<div class="seed-words-grid">
|
<div class="seed-words-grid">
|
||||||
<div v-for="(word, index) in seedWords" :key="index" class="seed-word-item">
|
<div v-for="(word, index) in seedWords" :key="index" class="seed-word-item">
|
||||||
<span class="word-number">{{ index + 1 }}</span>
|
<span class="word-number">{{ index + 1 }}</span>
|
||||||
<span class="word-text">{{ word }}</span>
|
<span class="word-text">{{ word }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="copy-seed-btn" @click="handleCopySeed" type="button">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
Copy Seed Phrase
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="extra-word-box">
|
<div v-else class="no-seed-warning">
|
||||||
<label class="extra-label">Add Extra Word</label>
|
<p>⚠️ No seed phrase found. Please go back and generate a wallet first.</p>
|
||||||
<input
|
|
||||||
class="extra-input"
|
|
||||||
type="text"
|
|
||||||
v-model="extraWord"
|
|
||||||
placeholder="Extra word"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cool-fact">
|
<div class="cool-fact">
|
||||||
<p>
|
<p>
|
||||||
Cool fact: there are more 12-word phrase combinations than nanoseconds since
|
Cool fact: there are more 18-word phrase combinations than atoms in the
|
||||||
the big bang!
|
observable universe!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -77,7 +102,12 @@ const handleBack = () => {
|
|||||||
<ButtonCommon type="default" size="large" @click="handleBack">
|
<ButtonCommon type="default" size="large" @click="handleBack">
|
||||||
BACK
|
BACK
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
<ButtonCommon type="primary" size="large" @click="handleNext">
|
<ButtonCommon
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="handleNext"
|
||||||
|
:disabled="!seedWords || seedWords.length === 0"
|
||||||
|
>
|
||||||
NEXT
|
NEXT
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
@ -144,6 +174,7 @@ const handleBack = () => {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
|
||||||
.seed-word-item {
|
.seed-word-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -168,6 +199,37 @@ const handleBack = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-seed-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: var(--text-light);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 127, 207, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cool-fact {
|
.cool-fact {
|
||||||
@ -187,33 +249,25 @@ const handleBack = () => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.extra-word-box {
|
.no-seed-warning {
|
||||||
border: 1px solid var(--border-light);
|
padding: var(--spacing-xl);
|
||||||
|
background: var(--error-light);
|
||||||
|
border: 2px solid var(--error-color);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
padding: 16px;
|
text-align: center;
|
||||||
margin: 14px 0 28px 0;
|
margin-bottom: var(--spacing-2xl);
|
||||||
.extra-label {
|
|
||||||
color: var(--text-primary);
|
p {
|
||||||
font-weight: 600;
|
font-size: var(--font-md);
|
||||||
display: block;
|
color: var(--error-color);
|
||||||
margin-bottom: 8px;
|
font-weight: var(--font-bold);
|
||||||
font-size: 1.1em;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.extra-input {
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 13px 10px;
|
|
||||||
font-size: 1em;
|
|
||||||
color: var(--text-primary);
|
|
||||||
background: var(--bg-hover);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Responsive Design
|
@include screen(mobile) {
|
||||||
@media (max-width: 640px) {
|
|
||||||
.recovery-container {
|
.recovery-container {
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
@ -222,6 +276,7 @@ const handleBack = () => {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recovery-content {
|
||||||
.seed-words-container {
|
.seed-words-container {
|
||||||
.seed-words-grid {
|
.seed-words-grid {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
@ -237,4 +292,5 @@ const handleBack = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RecoverySeedComponent } from '@/components'
|
import { RecoverySeedComponent } from '.'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
next: []
|
next: []
|
||||||
|
|||||||
@ -1,4 +1,17 @@
|
|||||||
|
// Tabs
|
||||||
export { default as LoginTab } from './LoginTab.vue'
|
export { default as LoginTab } from './LoginTab.vue'
|
||||||
export { default as CreateTab } from './CreateTab.vue'
|
export { default as CreateTab } from './CreateTab.vue'
|
||||||
export { default as RecoveryTab } from './RecoveryTab.vue'
|
export { default as RecoveryTab } from './RecoveryTab.vue'
|
||||||
export { default as ConfirmTab } from './ConfirmTab.vue'
|
export { default as ConfirmTab } from './ConfirmTab.vue'
|
||||||
|
|
||||||
|
// Auth Components
|
||||||
|
export { default as OnboardingComponent } from './OnboardingComponent.vue'
|
||||||
|
export { default as OpenWalletComponent } from './OpenWalletComponent.vue'
|
||||||
|
export { default as CreateWalletComponent } from './CreateWalletComponent.vue'
|
||||||
|
export { default as RecoverySeedComponent } from './RecoverySeedComponent.vue'
|
||||||
|
export { default as ConfirmSeedComponent } from './ConfirmSeedComponent.vue'
|
||||||
|
export { default as ImportWalletComponent } from './ImportWalletComponent.vue'
|
||||||
|
export { default as KeystoreDownloadComponent } from './KeystoreDownloadComponent.vue'
|
||||||
|
|
||||||
|
// Steps
|
||||||
|
export * from './steps'
|
||||||
|
|||||||
46
src/views/Auth/components/steps/ChooseBackupMethodStep.vue
Normal file
46
src/views/Auth/components/steps/ChooseBackupMethodStep.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ButtonCommon } from '@/components'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
chooseMethod: [method: 'seed' | 'keystore']
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const handleChooseMethod = (method: 'seed' | 'keystore') => {
|
||||||
|
emit('chooseMethod', method)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="choose-backup-method">
|
||||||
|
<h2>Choose your method</h2>
|
||||||
|
<div class="choose-btns">
|
||||||
|
<ButtonCommon type="primary" size="large" @click="() => handleChooseMethod('seed')">
|
||||||
|
Mnemonic Phrase
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon type="default" size="large" @click="() => handleChooseMethod('keystore')">
|
||||||
|
Keystore File
|
||||||
|
</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.choose-backup-method {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 8px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-btns {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
384
src/views/Auth/components/steps/CreatePasswordStep.vue
Normal file
384
src/views/Auth/components/steps/CreatePasswordStep.vue
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { ButtonCommon, FormCommon } from '@/components'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
next: []
|
||||||
|
navigateToOpenWallet: [event: Event]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const password = ref('')
|
||||||
|
const confirmPassword = ref('')
|
||||||
|
const passwordError = ref('')
|
||||||
|
const confirmPasswordError = ref('')
|
||||||
|
|
||||||
|
const passwordStrength = computed(() => {
|
||||||
|
if (!password.value) return { level: 0, text: '', color: '' }
|
||||||
|
|
||||||
|
let strength = 0
|
||||||
|
const checks = {
|
||||||
|
length: password.value.length >= 8,
|
||||||
|
uppercase: /[A-Z]/.test(password.value),
|
||||||
|
lowercase: /[a-z]/.test(password.value),
|
||||||
|
number: /[0-9]/.test(password.value),
|
||||||
|
special: /[!@#$%^&*(),.?":{}|<>]/.test(password.value),
|
||||||
|
}
|
||||||
|
strength = Object.values(checks).filter(Boolean).length
|
||||||
|
if (strength <= 2) return { level: 1, text: 'Weak', color: 'var(--error-color)' }
|
||||||
|
if (strength <= 3) return { level: 2, text: 'Medium', color: 'var(--warning-color)' }
|
||||||
|
if (strength <= 4) return { level: 3, text: 'Good', color: 'var(--info-color)' }
|
||||||
|
return { level: 4, text: 'Strong', color: 'var(--success-color)' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const isPasswordMatch = computed(() => {
|
||||||
|
if (!confirmPassword.value) return true
|
||||||
|
return password.value === confirmPassword.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const canProceed = computed(() => {
|
||||||
|
return (
|
||||||
|
password.value.length >= 8 &&
|
||||||
|
confirmPassword.value.length >= 8 &&
|
||||||
|
isPasswordMatch.value &&
|
||||||
|
passwordStrength.value.level >= 2
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
if (!canProceed.value) {
|
||||||
|
if (password.value.length < 8) {
|
||||||
|
passwordError.value = 'Password must be at least 8 characters'
|
||||||
|
}
|
||||||
|
if (!isPasswordMatch.value) {
|
||||||
|
confirmPasswordError.value = 'Passwords do not match'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('next')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleIHaveWallet = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
emit('navigateToOpenWallet', e)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="create-password-step">
|
||||||
|
<div class="auth-card-header">
|
||||||
|
<div class="logo-container">
|
||||||
|
<div class="logo-circle">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
class="neptune-logo"
|
||||||
|
>
|
||||||
|
<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 class="auth-card-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<FormCommon
|
||||||
|
v-model="password"
|
||||||
|
type="password"
|
||||||
|
label="Create Password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
show-password-toggle
|
||||||
|
required
|
||||||
|
:error="passwordError"
|
||||||
|
@input="passwordError = ''"
|
||||||
|
/>
|
||||||
|
<div v-if="password" class="password-strength">
|
||||||
|
<div class="strength-bar">
|
||||||
|
<div
|
||||||
|
class="strength-fill"
|
||||||
|
:style="{
|
||||||
|
width: `${(passwordStrength.level / 4) * 100}%`,
|
||||||
|
backgroundColor: passwordStrength.color,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<span class="strength-text" :style="{ color: passwordStrength.color }">{{
|
||||||
|
passwordStrength.text
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<FormCommon
|
||||||
|
v-model="confirmPassword"
|
||||||
|
type="password"
|
||||||
|
label="Confirm Password"
|
||||||
|
placeholder="Re-enter your password"
|
||||||
|
show-password-toggle
|
||||||
|
required
|
||||||
|
:error="confirmPasswordError"
|
||||||
|
@input="confirmPasswordError = ''"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<p class="helper-text">
|
||||||
|
Password must be at least 8 characters with uppercase, lowercase, and numbers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="auth-button-group">
|
||||||
|
<ButtonCommon
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
class="auth-button"
|
||||||
|
block
|
||||||
|
:disabled="!canProceed"
|
||||||
|
@click="handleNext"
|
||||||
|
>
|
||||||
|
Create Wallet
|
||||||
|
</ButtonCommon>
|
||||||
|
<div class="secondary-actions">
|
||||||
|
<button class="link-button" @click="handleIHaveWallet">
|
||||||
|
Already have a wallet?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.create-password-step {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screen(mobile) {
|
||||||
|
.auth-card-header {
|
||||||
|
.logo-container {
|
||||||
|
.logo-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
.coin-name {
|
||||||
|
font-size: var(--font-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-title {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
125
src/views/Auth/components/steps/WalletCreatedStep.vue
Normal file
125
src/views/Auth/components/steps/WalletCreatedStep.vue
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ButtonCommon } from '@/components'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
accessWallet: []
|
||||||
|
createAnother: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const handleAccessWallet = () => {
|
||||||
|
emit('accessWallet')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateAnother = () => {
|
||||||
|
emit('createAnother')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<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="handleAccessWallet"
|
||||||
|
>
|
||||||
|
Access Wallet
|
||||||
|
</ButtonCommon>
|
||||||
|
<button class="done-link" type="button" @click="handleCreateAnother">
|
||||||
|
Create Another Wallet
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.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>
|
||||||
3
src/views/Auth/components/steps/index.ts
Normal file
3
src/views/Auth/components/steps/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as ChooseBackupMethodStep } from './ChooseBackupMethodStep.vue'
|
||||||
|
export { default as CreatePasswordStep } from './CreatePasswordStep.vue'
|
||||||
|
export { default as WalletCreatedStep } from './WalletCreatedStep.vue'
|
||||||
@ -46,7 +46,11 @@ const network = ref('kaspa-mainnet')
|
|||||||
padding: var(--spacing-lg);
|
padding: var(--spacing-lg);
|
||||||
font-family: var(--font-primary);
|
font-family: var(--font-primary);
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@include screen(tablet) {
|
||||||
|
padding: var(--spacing-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screen(desktop) {
|
||||||
padding: var(--spacing-2xl);
|
padding: var(--spacing-2xl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +66,7 @@ const network = ref('kaspa-mainnet')
|
|||||||
letter-spacing: var(--tracking-wide);
|
letter-spacing: var(--tracking-wide);
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@include screen(mobile) {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,111 +1,83 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { formatNumberToLocaleString } from '@/utils'
|
import { formatNumberToLocaleString } from '@/utils'
|
||||||
import type { NetworkStatus } from '@/interface'
|
import { SpinnerCommon } from '@/components'
|
||||||
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
const networkStatus = ref<NetworkStatus>({
|
const neptuneStore = useNeptuneStore()
|
||||||
network: 'kaspa-mainnet',
|
const { getBlockHeight, getNetworkInfo } = useNeptuneWallet()
|
||||||
daaScore: 0,
|
|
||||||
dagHeader: 0,
|
|
||||||
dagBlocks: 0,
|
|
||||||
difficulty: 0,
|
|
||||||
medianOffset: '00:00:00',
|
|
||||||
medianTimeUTC: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const blockHeight = ref(0)
|
||||||
|
const networkInfo = ref<any>(null)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const isConnected = ref(false)
|
const lastUpdate = ref<Date | null>(null)
|
||||||
|
|
||||||
let rpcClient: any = null
|
// let pollingInterval: number | null = null
|
||||||
let unsubscribe: (() => void) | null = null
|
|
||||||
|
|
||||||
// Initialize Kaspa RPC connection
|
const network = computed(() => neptuneStore.getNetwork)
|
||||||
const initializeKaspaRPC = async () => {
|
|
||||||
|
const loadNetworkData = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
await simulateRPCConnection()
|
const [heightResult, infoResult] = await Promise.all([getBlockHeight(), getNetworkInfo()])
|
||||||
|
|
||||||
isConnected.value = true
|
if (heightResult !== undefined) {
|
||||||
|
blockHeight.value =
|
||||||
|
typeof heightResult === 'number'
|
||||||
|
? heightResult
|
||||||
|
: heightResult.height || heightResult || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoResult) {
|
||||||
|
networkInfo.value = infoResult
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdate.value = new Date()
|
||||||
|
|
||||||
|
if (loading.value) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = 'Failed to connect to Kaspa network'
|
const errorMsg = err instanceof Error ? err.message : 'Failed to load network data'
|
||||||
|
error.value = errorMsg
|
||||||
loading.value = false
|
loading.value = false
|
||||||
isConnected.value = false
|
message.error(errorMsg)
|
||||||
|
|
||||||
useMockData()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const simulateRPCConnection = async (): Promise<void> => {
|
const retryConnection = async () => {
|
||||||
return new Promise((resolve) => {
|
loading.value = true
|
||||||
setTimeout(() => {
|
error.value = ''
|
||||||
// Initial data
|
await loadNetworkData()
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockSubscription = setInterval(() => {
|
// const startPolling = () => {
|
||||||
networkStatus.value.daaScore += 1
|
// pollingInterval = window.setInterval(() => {
|
||||||
networkStatus.value.dagHeader += 1
|
// if (!loading.value) {
|
||||||
networkStatus.value.dagBlocks += 1
|
// loadNetworkData()
|
||||||
updateMedianTime()
|
// }
|
||||||
}, 1000)
|
// }, 10000)
|
||||||
|
// }
|
||||||
|
|
||||||
unsubscribe = () => clearInterval(mockSubscription)
|
// const stopPolling = () => {
|
||||||
|
// if (pollingInterval) {
|
||||||
|
// clearInterval(pollingInterval)
|
||||||
|
// pollingInterval = null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
resolve()
|
onMounted(async () => {
|
||||||
}, 1000)
|
await loadNetworkData()
|
||||||
})
|
// startPolling()
|
||||||
}
|
|
||||||
|
|
||||||
const updateMedianTime = () => {
|
|
||||||
networkStatus.value.medianTimeUTC = new Date().toISOString().replace('T', ' ').substring(0, 19)
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const retryConnection = () => {
|
|
||||||
initializeKaspaRPC()
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
if (unsubscribe) {
|
|
||||||
unsubscribe()
|
|
||||||
unsubscribe = null
|
|
||||||
}
|
|
||||||
if (rpcClient) {
|
|
||||||
rpcClient.disconnect()
|
|
||||||
rpcClient = null
|
|
||||||
}
|
|
||||||
isConnected.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initializeKaspaRPC()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
// onUnmounted(() => {
|
||||||
cleanup()
|
// stopPolling()
|
||||||
})
|
// })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -114,8 +86,8 @@ onUnmounted(() => {
|
|||||||
<h2 class="section-title">NETWORK STATUS</h2>
|
<h2 class="section-title">NETWORK STATUS</h2>
|
||||||
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div v-if="loading && networkStatus.daaScore === 0" class="loading-state">
|
<div v-if="loading && !blockHeight" class="loading-state">
|
||||||
<div class="spinner"></div>
|
<SpinnerCommon size="medium" />
|
||||||
<p>Loading network data...</p>
|
<p>Loading network data...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -129,53 +101,32 @@ onUnmounted(() => {
|
|||||||
<div v-else class="status-grid">
|
<div v-else class="status-grid">
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<span class="status-label">Network</span>
|
<span class="status-label">Network</span>
|
||||||
<span class="status-value">{{ networkStatus.network }}</span>
|
<span class="status-value">Neptune {{ network }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<span class="status-label">DAA Score</span>
|
<span class="status-label">Block Height</span>
|
||||||
<span class="status-value">{{
|
<span class="status-value">{{ formatNumberToLocaleString(blockHeight) }}</span>
|
||||||
formatNumberToLocaleString(networkStatus.daaScore)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div v-if="networkInfo?.version" class="status-item">
|
||||||
<span class="status-label">DAG Header</span>
|
<span class="status-label">Version</span>
|
||||||
<span class="status-value">{{
|
<span class="status-value">{{ networkInfo.version }}</span>
|
||||||
formatNumberToLocaleString(networkStatus.dagHeader)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div v-if="networkInfo?.peer_count !== undefined" class="status-item">
|
||||||
<span class="status-label">DAG Blocks</span>
|
<span class="status-label">Peers</span>
|
||||||
<span class="status-value">{{
|
<span class="status-value">{{ networkInfo.peer_count }}</span>
|
||||||
formatNumberToLocaleString(networkStatus.dagBlocks)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div v-if="networkInfo?.chain_id" class="status-item">
|
||||||
<span class="status-label">Difficulty</span>
|
<span class="status-label">Chain ID</span>
|
||||||
<span class="status-value">{{
|
<span class="status-value">{{ networkInfo.chain_id }}</span>
|
||||||
formatNumberToLocaleString(networkStatus.difficulty)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div v-if="lastUpdate" class="status-item">
|
||||||
<span class="status-label">Median Offset</span>
|
<span class="status-label">Last Updated</span>
|
||||||
<span class="status-value">{{ networkStatus.medianOffset }}</span>
|
<span class="status-value">{{ lastUpdate.toLocaleTimeString() }}</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>
|
||||||
</div>
|
</div>
|
||||||
@ -236,29 +187,14 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
// Loading State
|
// Loading State
|
||||||
.loading-state {
|
.loading-state {
|
||||||
text-align: center;
|
@include center_flex;
|
||||||
padding: var(--spacing-4xl);
|
padding: var(--spacing-4xl);
|
||||||
color: var(--text-secondary);
|
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
|
||||||
.error-state {
|
.error-state {
|
||||||
text-align: center;
|
@include center_flex;
|
||||||
padding: var(--spacing-4xl);
|
padding: var(--spacing-4xl);
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
|
|
||||||
@ -282,34 +218,5 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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>
|
</style>
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { EditOutlined } from '@ant-design/icons-vue'
|
import { EditOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
|
||||||
|
const { getUtxos } = useNeptuneWallet()
|
||||||
|
|
||||||
const inUseUtxosCount = ref(0)
|
const inUseUtxosCount = ref(0)
|
||||||
const inUseUtxosAmount = ref(0)
|
const inUseUtxosAmount = ref(0)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@ -1,36 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import { Divider } from 'ant-design-vue'
|
import { Divider } from 'ant-design-vue'
|
||||||
import ButtonCommon from '@/components/common/ButtonCommon.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 = () => {
|
const handleBackupFile = () => {
|
||||||
console.log('Backup File')
|
// TODO: Implement backup file functionality
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBackupSeed = () => {
|
const handleBackupSeed = () => {
|
||||||
console.log('Backup Seed')
|
// TODO: Implement backup seed functionality
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="content-card wallet-info-card">
|
<div class="content-card wallet-info-card">
|
||||||
<div class="wallet-header">
|
<div class="wallet-header">
|
||||||
<h2 class="wallet-title">KASPA WALLET</h2>
|
<h2 class="wallet-title">NEPTUNE 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>
|
||||||
|
|
||||||
<div class="wallet-actions">
|
<div class="wallet-actions">
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export default defineConfig({
|
|||||||
port: 3008,
|
port: 3008,
|
||||||
},
|
},
|
||||||
plugins: [vue(), vueJsx(), VueDevTools()],
|
plugins: [vue(), vueJsx(), VueDevTools()],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['@neptune/wasm'],
|
||||||
|
},
|
||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
scss: {
|
scss: {
|
||||||
|
|||||||
209
yarn.lock
209
yarn.lock
@ -37,24 +37,24 @@
|
|||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
|
|
||||||
"@babel/compat-data@^7.27.2":
|
"@babel/compat-data@^7.27.2":
|
||||||
version "7.28.4"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04"
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f"
|
||||||
integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==
|
integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==
|
||||||
|
|
||||||
"@babel/core@^7.23.0", "@babel/core@^7.23.3":
|
"@babel/core@^7.23.0", "@babel/core@^7.23.3":
|
||||||
version "7.28.4"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e"
|
||||||
integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==
|
integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.27.1"
|
"@babel/code-frame" "^7.27.1"
|
||||||
"@babel/generator" "^7.28.3"
|
"@babel/generator" "^7.28.5"
|
||||||
"@babel/helper-compilation-targets" "^7.27.2"
|
"@babel/helper-compilation-targets" "^7.27.2"
|
||||||
"@babel/helper-module-transforms" "^7.28.3"
|
"@babel/helper-module-transforms" "^7.28.3"
|
||||||
"@babel/helpers" "^7.28.4"
|
"@babel/helpers" "^7.28.4"
|
||||||
"@babel/parser" "^7.28.4"
|
"@babel/parser" "^7.28.5"
|
||||||
"@babel/template" "^7.27.2"
|
"@babel/template" "^7.27.2"
|
||||||
"@babel/traverse" "^7.28.4"
|
"@babel/traverse" "^7.28.5"
|
||||||
"@babel/types" "^7.28.4"
|
"@babel/types" "^7.28.5"
|
||||||
"@jridgewell/remapping" "^2.3.5"
|
"@jridgewell/remapping" "^2.3.5"
|
||||||
convert-source-map "^2.0.0"
|
convert-source-map "^2.0.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
@ -62,13 +62,13 @@
|
|||||||
json5 "^2.2.3"
|
json5 "^2.2.3"
|
||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
|
|
||||||
"@babel/generator@^7.28.3":
|
"@babel/generator@^7.28.5":
|
||||||
version "7.28.3"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e"
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298"
|
||||||
integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==
|
integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.28.3"
|
"@babel/parser" "^7.28.5"
|
||||||
"@babel/types" "^7.28.2"
|
"@babel/types" "^7.28.5"
|
||||||
"@jridgewell/gen-mapping" "^0.3.12"
|
"@jridgewell/gen-mapping" "^0.3.12"
|
||||||
"@jridgewell/trace-mapping" "^0.3.28"
|
"@jridgewell/trace-mapping" "^0.3.28"
|
||||||
jsesc "^3.0.2"
|
jsesc "^3.0.2"
|
||||||
@ -91,17 +91,17 @@
|
|||||||
lru-cache "^5.1.1"
|
lru-cache "^5.1.1"
|
||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin@^7.27.1":
|
"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.5":
|
||||||
version "7.28.3"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz#3e747434ea007910c320c4d39a6b46f20f371d46"
|
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46"
|
||||||
integrity sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==
|
integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-annotate-as-pure" "^7.27.3"
|
"@babel/helper-annotate-as-pure" "^7.27.3"
|
||||||
"@babel/helper-member-expression-to-functions" "^7.27.1"
|
"@babel/helper-member-expression-to-functions" "^7.28.5"
|
||||||
"@babel/helper-optimise-call-expression" "^7.27.1"
|
"@babel/helper-optimise-call-expression" "^7.27.1"
|
||||||
"@babel/helper-replace-supers" "^7.27.1"
|
"@babel/helper-replace-supers" "^7.27.1"
|
||||||
"@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
|
"@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
|
||||||
"@babel/traverse" "^7.28.3"
|
"@babel/traverse" "^7.28.5"
|
||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
|
|
||||||
"@babel/helper-globals@^7.28.0":
|
"@babel/helper-globals@^7.28.0":
|
||||||
@ -109,13 +109,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674"
|
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674"
|
||||||
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
|
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions@^7.27.1":
|
"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5":
|
||||||
version "7.27.1"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44"
|
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150"
|
||||||
integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==
|
integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/traverse" "^7.27.1"
|
"@babel/traverse" "^7.28.5"
|
||||||
"@babel/types" "^7.27.1"
|
"@babel/types" "^7.28.5"
|
||||||
|
|
||||||
"@babel/helper-module-imports@^7.27.1":
|
"@babel/helper-module-imports@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
@ -168,10 +168,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
|
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
|
||||||
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
|
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.27.1":
|
"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5":
|
||||||
version "7.27.1"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4"
|
||||||
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
|
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
|
||||||
|
|
||||||
"@babel/helper-validator-option@^7.27.1":
|
"@babel/helper-validator-option@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
@ -186,12 +186,12 @@
|
|||||||
"@babel/template" "^7.27.2"
|
"@babel/template" "^7.27.2"
|
||||||
"@babel/types" "^7.28.4"
|
"@babel/types" "^7.28.4"
|
||||||
|
|
||||||
"@babel/parser@^7.27.2", "@babel/parser@^7.28.0", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4":
|
"@babel/parser@^7.27.2", "@babel/parser@^7.28.0", "@babel/parser@^7.28.4", "@babel/parser@^7.28.5":
|
||||||
version "7.28.4"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08"
|
||||||
integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==
|
integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.28.4"
|
"@babel/types" "^7.28.5"
|
||||||
|
|
||||||
"@babel/plugin-proposal-decorators@^7.23.0":
|
"@babel/plugin-proposal-decorators@^7.23.0":
|
||||||
version "7.28.0"
|
version "7.28.0"
|
||||||
@ -238,12 +238,12 @@
|
|||||||
"@babel/helper-plugin-utils" "^7.27.1"
|
"@babel/helper-plugin-utils" "^7.27.1"
|
||||||
|
|
||||||
"@babel/plugin-transform-typescript@^7.22.15", "@babel/plugin-transform-typescript@^7.23.3":
|
"@babel/plugin-transform-typescript@^7.22.15", "@babel/plugin-transform-typescript@^7.23.3":
|
||||||
version "7.28.0"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz#796cbd249ab56c18168b49e3e1d341b72af04a6b"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz#441c5f9a4a1315039516c6c612fc66d5f4594e72"
|
||||||
integrity sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==
|
integrity sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-annotate-as-pure" "^7.27.3"
|
"@babel/helper-annotate-as-pure" "^7.27.3"
|
||||||
"@babel/helper-create-class-features-plugin" "^7.27.1"
|
"@babel/helper-create-class-features-plugin" "^7.28.5"
|
||||||
"@babel/helper-plugin-utils" "^7.27.1"
|
"@babel/helper-plugin-utils" "^7.27.1"
|
||||||
"@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
|
"@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
|
||||||
"@babel/plugin-syntax-typescript" "^7.27.1"
|
"@babel/plugin-syntax-typescript" "^7.27.1"
|
||||||
@ -262,26 +262,26 @@
|
|||||||
"@babel/parser" "^7.27.2"
|
"@babel/parser" "^7.27.2"
|
||||||
"@babel/types" "^7.27.1"
|
"@babel/types" "^7.27.1"
|
||||||
|
|
||||||
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4":
|
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5":
|
||||||
version "7.28.4"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b"
|
||||||
integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==
|
integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.27.1"
|
"@babel/code-frame" "^7.27.1"
|
||||||
"@babel/generator" "^7.28.3"
|
"@babel/generator" "^7.28.5"
|
||||||
"@babel/helper-globals" "^7.28.0"
|
"@babel/helper-globals" "^7.28.0"
|
||||||
"@babel/parser" "^7.28.4"
|
"@babel/parser" "^7.28.5"
|
||||||
"@babel/template" "^7.27.2"
|
"@babel/template" "^7.27.2"
|
||||||
"@babel/types" "^7.28.4"
|
"@babel/types" "^7.28.5"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
|
|
||||||
"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4":
|
"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5":
|
||||||
version "7.28.4"
|
version "7.28.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b"
|
||||||
integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==
|
integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-string-parser" "^7.27.1"
|
"@babel/helper-string-parser" "^7.27.1"
|
||||||
"@babel/helper-validator-identifier" "^7.27.1"
|
"@babel/helper-validator-identifier" "^7.28.5"
|
||||||
|
|
||||||
"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.5.0":
|
"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.5.0":
|
||||||
version "3.6.1"
|
version "3.6.1"
|
||||||
@ -421,9 +421,9 @@
|
|||||||
eslint-visitor-keys "^3.4.3"
|
eslint-visitor-keys "^3.4.3"
|
||||||
|
|
||||||
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
|
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
|
||||||
version "4.12.1"
|
version "4.12.2"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
|
||||||
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
|
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
|
||||||
|
|
||||||
"@eslint/eslintrc@^2.1.4":
|
"@eslint/eslintrc@^2.1.4":
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
@ -498,6 +498,9 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.1.0"
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
|
"@neptune/wasm@file:./packages/neptune-wasm":
|
||||||
|
version "0.1.0"
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||||
@ -738,9 +741,9 @@
|
|||||||
integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==
|
integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==
|
||||||
|
|
||||||
"@rushstack/eslint-patch@^1.8.0":
|
"@rushstack/eslint-patch@^1.8.0":
|
||||||
version "1.14.0"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.14.0.tgz#61b05741089552a3d352b3503d6a242978a2cb08"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.14.1.tgz#5f7c5c335643cff62ad8e6a9432d708a9c51e98c"
|
||||||
integrity sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==
|
integrity sha512-jGTk8UD/RdjsNZW8qq10r0RBvxL8OWtoT+kImlzPDFilmozzM+9QmIJsmze9UiSBrFU45ZxhTYBypn9q9z/VfQ==
|
||||||
|
|
||||||
"@sec-ant/readable-stream@^0.4.1":
|
"@sec-ant/readable-stream@^0.4.1":
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
@ -771,9 +774,9 @@
|
|||||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||||
|
|
||||||
"@types/node@^20.12.5":
|
"@types/node@^20.12.5":
|
||||||
version "20.19.22"
|
version "20.19.23"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.22.tgz#f17e80ee1d1fdd10d50bef449abe23bfc0216780"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.23.tgz#7de99389c814071cca78656a3243f224fed7453d"
|
||||||
integrity sha512-hRnu+5qggKDSyWHlnmThnUqg62l29Aj/6vcYgUaSFL9oc7DVjeWEQN3PRgdSc6F8d9QRMWkf36CLMch1Do/+RQ==
|
integrity sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~6.21.0"
|
undici-types "~6.21.0"
|
||||||
|
|
||||||
@ -1199,10 +1202,10 @@ balanced-match@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
baseline-browser-mapping@^2.8.9:
|
baseline-browser-mapping@^2.8.19:
|
||||||
version "2.8.18"
|
version "2.8.20"
|
||||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz#b44b18cadddfa037ee8440dafaba4a329dfb327c"
|
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz#6766cf270f3668d20b6712b9c54cc911b87da714"
|
||||||
integrity sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==
|
integrity sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==
|
||||||
|
|
||||||
birpc@^2.3.0:
|
birpc@^2.3.0:
|
||||||
version "2.6.1"
|
version "2.6.1"
|
||||||
@ -1237,15 +1240,15 @@ braces@^3.0.3:
|
|||||||
fill-range "^7.1.1"
|
fill-range "^7.1.1"
|
||||||
|
|
||||||
browserslist@^4.24.0:
|
browserslist@^4.24.0:
|
||||||
version "4.26.3"
|
version "4.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.27.0.tgz#755654744feae978fbb123718b2f139bc0fa6697"
|
||||||
integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==
|
integrity sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==
|
||||||
dependencies:
|
dependencies:
|
||||||
baseline-browser-mapping "^2.8.9"
|
baseline-browser-mapping "^2.8.19"
|
||||||
caniuse-lite "^1.0.30001746"
|
caniuse-lite "^1.0.30001751"
|
||||||
electron-to-chromium "^1.5.227"
|
electron-to-chromium "^1.5.238"
|
||||||
node-releases "^2.0.21"
|
node-releases "^2.0.26"
|
||||||
update-browserslist-db "^1.1.3"
|
update-browserslist-db "^1.1.4"
|
||||||
|
|
||||||
bundle-name@^4.1.0:
|
bundle-name@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
@ -1267,7 +1270,7 @@ callsites@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001746:
|
caniuse-lite@^1.0.30001751:
|
||||||
version "1.0.30001751"
|
version "1.0.30001751"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz#dacd5d9f4baeea841641640139d2b2a4df4226ad"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz#dacd5d9f4baeea841641640139d2b2a4df4226ad"
|
||||||
integrity sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==
|
integrity sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==
|
||||||
@ -1321,12 +1324,12 @@ convert-source-map@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||||
|
|
||||||
copy-anything@^3.0.2:
|
copy-anything@^4:
|
||||||
version "3.0.5"
|
version "4.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0"
|
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.0.5.tgz#16cabafd1ea4bb327a540b750f2b4df522825aea"
|
||||||
integrity sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==
|
integrity sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==
|
||||||
dependencies:
|
dependencies:
|
||||||
is-what "^4.1.8"
|
is-what "^5.2.0"
|
||||||
|
|
||||||
core-js@^3.15.1:
|
core-js@^3.15.1:
|
||||||
version "3.46.0"
|
version "3.46.0"
|
||||||
@ -1435,10 +1438,10 @@ dunder-proto@^1.0.1:
|
|||||||
es-errors "^1.3.0"
|
es-errors "^1.3.0"
|
||||||
gopd "^1.2.0"
|
gopd "^1.2.0"
|
||||||
|
|
||||||
electron-to-chromium@^1.5.227:
|
electron-to-chromium@^1.5.238:
|
||||||
version "1.5.237"
|
version "1.5.240"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz#eacf61cef3f6345d0069ab427585c5a04d7084f0"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz#bfd946570a723aa3754370065d02e23e30824774"
|
||||||
integrity sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==
|
integrity sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==
|
||||||
|
|
||||||
entities@^4.5.0:
|
entities@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
@ -2003,10 +2006,10 @@ is-unicode-supported@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a"
|
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a"
|
||||||
integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==
|
integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==
|
||||||
|
|
||||||
is-what@^4.1.8:
|
is-what@^5.2.0:
|
||||||
version "4.1.16"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f"
|
resolved "https://registry.yarnpkg.com/is-what/-/is-what-5.5.0.tgz#a3031815757cfe1f03fed990bf6355a2d3f628c4"
|
||||||
integrity sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==
|
integrity sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==
|
||||||
|
|
||||||
is-wsl@^3.1.0:
|
is-wsl@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
@ -2128,9 +2131,9 @@ lru-cache@^5.1.1:
|
|||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
magic-string@^0.30.19, magic-string@^0.30.4:
|
magic-string@^0.30.19, magic-string@^0.30.4:
|
||||||
version "0.30.19"
|
version "0.30.21"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
|
||||||
integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==
|
integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/sourcemap-codec" "^1.5.5"
|
"@jridgewell/sourcemap-codec" "^1.5.5"
|
||||||
|
|
||||||
@ -2228,10 +2231,10 @@ node-addon-api@^7.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
|
||||||
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
||||||
|
|
||||||
node-releases@^2.0.21:
|
node-releases@^2.0.26:
|
||||||
version "2.0.25"
|
version "2.0.26"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.25.tgz#95479437bd409231e03981c1f6abee67f5e962df"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.26.tgz#fdfa272f2718a1309489d18aef4ef5ba7f5dfb52"
|
||||||
integrity sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==
|
integrity sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==
|
||||||
|
|
||||||
npm-normalize-package-bin@^3.0.0:
|
npm-normalize-package-bin@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
@ -2631,11 +2634,11 @@ stylis@^4.1.3:
|
|||||||
integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
|
integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
|
||||||
|
|
||||||
superjson@^2.2.2:
|
superjson@^2.2.2:
|
||||||
version "2.2.2"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.2.tgz#9d52bf0bf6b5751a3c3472f1292e714782ba3173"
|
resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.3.tgz#c42236fff6ecc449b7ffa7f023a9a028a5ec9c87"
|
||||||
integrity sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==
|
integrity sha512-ay3d+LW/S6yppKoTz3Bq4mG0xrS5bFwfWEBmQfbC7lt5wmtk+Obq0TxVuA9eYRirBTQb1K3eEpBRHMQEo0WyVw==
|
||||||
dependencies:
|
dependencies:
|
||||||
copy-anything "^3.0.2"
|
copy-anything "^4"
|
||||||
|
|
||||||
supports-color@^7.1.0:
|
supports-color@^7.1.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
@ -2710,10 +2713,10 @@ universalify@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||||
|
|
||||||
update-browserslist-db@^1.1.3:
|
update-browserslist-db@^1.1.4:
|
||||||
version "1.1.3"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
|
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a"
|
||||||
integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
|
integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
escalade "^3.2.0"
|
escalade "^3.2.0"
|
||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user