init_base
This commit is contained in:
commit
570a5f5317
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@ -0,0 +1,19 @@
|
||||
# EditorConfig helps maintain consistent coding styles
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_APP_API=
|
||||
VITE_NODE_NETWORK=
|
||||
8
.eslintignore
Normal file
8
.eslintignore
Normal file
@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
dist
|
||||
.vscode
|
||||
.git
|
||||
*.config.js
|
||||
*.config.ts
|
||||
components.json
|
||||
|
||||
37
.eslintrc.cjs
Normal file
37
.eslintrc.cjs
Normal file
@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier',
|
||||
],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'vue'],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-v-html': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
},
|
||||
}
|
||||
|
||||
52
.gitignore
vendored
Normal file
52
.gitignore
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Dependencies
|
||||
node_modules
|
||||
|
||||
# Build outputs
|
||||
dist
|
||||
dist-ssr
|
||||
out
|
||||
build
|
||||
*.local
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# OS files
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
10
.prettierignore
Normal file
10
.prettierignore
Normal file
@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
dist
|
||||
.vscode
|
||||
.git
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
*.min.js
|
||||
*.min.css
|
||||
public
|
||||
|
||||
12
.prettierrc.json
Normal file
12
.prettierrc.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto",
|
||||
"vueIndentScriptAndStyle": false
|
||||
}
|
||||
|
||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue"
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"vetur.validation.template": false
|
||||
}
|
||||
|
||||
59
README.md
Normal file
59
README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Neptune Wallet
|
||||
|
||||
A modern, secure wallet application for the Neptune blockchain network.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🎨 Modern UI with Shadcn-vue components and Tailwind CSS v4
|
||||
- 🌓 Dark/Light theme support
|
||||
- 🔒 Secure wallet management
|
||||
- 💼 Transaction handling
|
||||
- 📊 UTXO tracking
|
||||
- 🌐 Network monitoring
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Setup environment
|
||||
cp .env.example .env
|
||||
|
||||
# Start development server
|
||||
pnpm dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 📋 Available Scripts
|
||||
|
||||
- `pnpm dev` - Start development server
|
||||
- `pnpm build` - Build for production
|
||||
- `pnpm preview` - Preview production build
|
||||
- `pnpm type-check` - Run TypeScript type checking
|
||||
- `pnpm lint` - Lint and fix code with ESLint
|
||||
- `pnpm format` - Format code with Prettier
|
||||
|
||||
## 🛠️ Tech Stack
|
||||
|
||||
- **Framework**: Vue 3.5 + TypeScript
|
||||
- **Build Tool**: Vite 7
|
||||
- **State Management**: Pinia 2.3
|
||||
- **Routing**: Vue Router 4.5
|
||||
- **HTTP Client**: Axios 1.7
|
||||
- **i18n**: Vue I18n 10.0
|
||||
- **Styling**: Tailwind CSS v4 (CSS-first configuration)
|
||||
- **UI Components**: Shadcn-vue (New York style)
|
||||
- **Icons**: Lucide Vue Next
|
||||
- **Utilities**: VueUse, class-variance-authority, clsx
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
For detailed installation and setup instructions, see [INSTALLATION.md](./INSTALLATION.md)
|
||||
|
||||
## 📝 License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
21
components.json
Normal file
21
components.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "new-york",
|
||||
"typescript": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/style.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"composables": "@/composables"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
17
index.html
Normal file
17
index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Neptune Wallet - Secure cryptocurrency wallet for Neptune network" />
|
||||
<title>Neptune Wallet</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
46
package.json
Normal file
46
package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "neptune-wallet",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .eslintignore",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,vue,css,scss,json}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.7.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-vue-next": "^0.554.0",
|
||||
"pinia": "^2.3.1",
|
||||
"reka-ui": "^2.6.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue": "^3.5.24",
|
||||
"vue-i18n": "^10.0.8",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.15.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/eslint-config-prettier": "^10.0.0",
|
||||
"@vue/eslint-config-typescript": "^14.0.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"prettier": "^3.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vue-tsc": "^3.1.4"
|
||||
}
|
||||
}
|
||||
3036
pnpm-lock.yaml
generated
Normal file
3036
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
30
src/App.vue
Normal file
30
src/App.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ThemeToggle } from '@/components/ui/theme-toggle'
|
||||
import neptuneLogo from '@/assets/imgs/neptune_logo.jpg'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-background">
|
||||
<!-- Header with Logo and Theme Toggle -->
|
||||
<header class="border-b border-border">
|
||||
<div class="container mx-auto flex items-center justify-between px-4 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
:src="neptuneLogo"
|
||||
alt="Neptune Wallet Logo"
|
||||
class="h-12 w-12 rounded-lg object-cover"
|
||||
/>
|
||||
<span class="text-xl font-bold text-foreground">Neptune</span>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
80
src/api/neptuneApi.ts
Normal file
80
src/api/neptuneApi.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import request from './request'
|
||||
|
||||
const DEFAULT_MIN_BLOCK_HEIGHT = 0
|
||||
|
||||
/**
|
||||
* JSON-RPC 2.0 call helper
|
||||
*/
|
||||
export const callJsonRpc = (method: string, params: any = []) => {
|
||||
const requestData = {
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params,
|
||||
id: 1,
|
||||
}
|
||||
return request({
|
||||
method: 'POST',
|
||||
data: requestData,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UTXOs from view key
|
||||
*/
|
||||
export const getUtxosFromViewKey = async (
|
||||
viewKey: string,
|
||||
startBlock: number = DEFAULT_MIN_BLOCK_HEIGHT,
|
||||
endBlock: number | null = null,
|
||||
maxSearchDepth: number = 1000
|
||||
): Promise<any> => {
|
||||
const params = {
|
||||
viewKey,
|
||||
startBlock,
|
||||
endBlock,
|
||||
maxSearchDepth,
|
||||
}
|
||||
return await callJsonRpc('wallet_getUtxoInfo', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get balance from view key
|
||||
*/
|
||||
export const getBalance = async (
|
||||
viewKey: string,
|
||||
startBlock: number = DEFAULT_MIN_BLOCK_HEIGHT,
|
||||
endBlock: number | null = null,
|
||||
maxSearchDepth: number = 1000
|
||||
): Promise<any> => {
|
||||
const params = {
|
||||
viewKey,
|
||||
startBlock,
|
||||
endBlock,
|
||||
maxSearchDepth,
|
||||
}
|
||||
return await callJsonRpc('wallet_getBalanceFromViewKey', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current block height
|
||||
*/
|
||||
export const getBlockHeight = async (): Promise<any> => {
|
||||
return await callJsonRpc('chain_height')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network info
|
||||
*/
|
||||
export const getNetworkInfo = async (): Promise<any> => {
|
||||
return await callJsonRpc('node_network')
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast signed transaction
|
||||
*/
|
||||
export const broadcastSignedTransaction = async (transactionHex: string): Promise<any> => {
|
||||
const params = {
|
||||
transactionHex,
|
||||
}
|
||||
return await callJsonRpc('mempool_submitTransaction', params)
|
||||
}
|
||||
|
||||
50
src/api/request.ts
Normal file
50
src/api/request.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import axios, { type AxiosInstance, type AxiosResponse, type AxiosError } from 'axios'
|
||||
|
||||
const STATUS_CODE_SUCCESS = 200
|
||||
|
||||
export const API_URL = import.meta.env.VITE_APP_API || ''
|
||||
|
||||
// Create axios instance
|
||||
const instance: AxiosInstance = axios.create({
|
||||
baseURL: API_URL,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
withCredentials: false,
|
||||
})
|
||||
|
||||
// Request interceptor
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
// Add request interceptors here (e.g., auth tokens)
|
||||
return config
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// Response interceptor
|
||||
instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
if (response?.status !== STATUS_CODE_SUCCESS) {
|
||||
return Promise.reject(response?.data)
|
||||
}
|
||||
return response.data
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
if (error?.response?.data) {
|
||||
return Promise.reject(error?.response?.data)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// Set locale for API requests
|
||||
export const setLocaleApi = (locale: string) => {
|
||||
instance.defaults.headers.common['lang'] = locale
|
||||
}
|
||||
|
||||
export default instance
|
||||
|
||||
BIN
src/assets/imgs/neptune_logo.jpg
Normal file
BIN
src/assets/imgs/neptune_logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
41
src/components/HelloWorld.vue
Normal file
41
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
28
src/components/ui/button/Button.vue
Normal file
28
src/components/ui/button/Button.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import type { ButtonVariants } from "."
|
||||
import { Primitive } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "."
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants["variant"]
|
||||
size?: ButtonVariants["size"]
|
||||
class?: HTMLAttributes["class"]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: "button",
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
38
src/components/ui/button/index.ts
Normal file
38
src/components/ui/button/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
export { default as Button } from "./Button.vue"
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
"default": "h-9 px-4 py-2",
|
||||
"xs": "h-7 rounded px-2",
|
||||
"sm": "h-8 rounded-md px-3 text-xs",
|
||||
"lg": "h-10 rounded-md px-8",
|
||||
"icon": "h-9 w-9",
|
||||
"icon-sm": "size-8",
|
||||
"icon-lg": "size-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
35
src/components/ui/switch/Switch.vue
Normal file
35
src/components/ui/switch/Switch.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { SwitchRootEmits, SwitchRootProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
SwitchRoot,
|
||||
SwitchThumb,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const emits = defineEmits<SwitchRootEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SwitchRoot
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<SwitchThumb
|
||||
:class="cn('pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0')"
|
||||
>
|
||||
<slot name="thumb" />
|
||||
</SwitchThumb>
|
||||
</SwitchRoot>
|
||||
</template>
|
||||
1
src/components/ui/switch/index.ts
Normal file
1
src/components/ui/switch/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Switch } from "./Switch.vue"
|
||||
20
src/components/ui/theme-toggle/ThemeToggle.vue
Normal file
20
src/components/ui/theme-toggle/ThemeToggle.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { Moon, Sun } from 'lucide-vue-next'
|
||||
import { watchEffect } from 'vue';
|
||||
|
||||
const { isDark } = useTheme()
|
||||
|
||||
watchEffect(() => console.log('isDark :>> ', isDark.value))
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<Sun class="h-4 w-4 text-muted-foreground transition-colors" :class="{ 'text-foreground': !isDark }" />
|
||||
<Switch v-model="isDark" />
|
||||
<Moon class="h-4 w-4 text-muted-foreground transition-colors" :class="{ 'text-foreground': isDark }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
2
src/components/ui/theme-toggle/index.ts
Normal file
2
src/components/ui/theme-toggle/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as ThemeToggle } from './ThemeToggle.vue'
|
||||
|
||||
53
src/composables/useTheme.ts
Normal file
53
src/composables/useTheme.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
type Theme = 'light' | 'dark'
|
||||
|
||||
const theme = ref<Theme>('light')
|
||||
const isDark = ref(false)
|
||||
|
||||
// Initialize theme immediately
|
||||
function initTheme() {
|
||||
if (window != null) return
|
||||
|
||||
// Load saved theme or default to light
|
||||
const savedTheme = localStorage.getItem('theme') as Theme | null
|
||||
if (savedTheme && ['light', 'dark'].includes(savedTheme)) {
|
||||
theme.value = savedTheme
|
||||
}
|
||||
|
||||
applyTheme(theme.value)
|
||||
}
|
||||
|
||||
function applyTheme(newTheme: Theme) {
|
||||
const root = document.documentElement
|
||||
|
||||
root.classList.toggle('dark', newTheme === 'dark')
|
||||
isDark.value = newTheme === 'dark'
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('theme', newTheme)
|
||||
}
|
||||
|
||||
// Initialize on module load
|
||||
if (window != null) initTheme()
|
||||
|
||||
export function useTheme() {
|
||||
const setTheme = (newTheme: Theme) => {
|
||||
theme.value = newTheme
|
||||
applyTheme(newTheme)
|
||||
}
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(isDark.value ? 'light' : 'dark')
|
||||
}
|
||||
|
||||
watch(isDark, () => setTheme(isDark.value ? 'dark' : 'light'))
|
||||
|
||||
return {
|
||||
theme,
|
||||
isDark,
|
||||
setTheme,
|
||||
toggleTheme,
|
||||
}
|
||||
}
|
||||
|
||||
19
src/i18n/index.ts
Normal file
19
src/i18n/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import en from './locales/en'
|
||||
import jp from './locales/jp'
|
||||
|
||||
export type MessageSchema = typeof en
|
||||
|
||||
const i18n = createI18n<[MessageSchema], 'en' | 'jp'>({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en,
|
||||
jp,
|
||||
},
|
||||
globalInjection: true,
|
||||
})
|
||||
|
||||
export default i18n
|
||||
|
||||
41
src/i18n/locales/en.ts
Normal file
41
src/i18n/locales/en.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export default {
|
||||
common: {
|
||||
app_name: 'Neptune Wallet',
|
||||
welcome: 'Welcome to Neptune Wallet',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
save: 'Save',
|
||||
back: 'Back',
|
||||
next: 'Next',
|
||||
submit: 'Submit',
|
||||
close: 'Close',
|
||||
},
|
||||
wallet: {
|
||||
title: 'Wallet',
|
||||
create: 'Create New Wallet',
|
||||
recover: 'Recover Wallet',
|
||||
balance: 'Balance',
|
||||
address: 'Address',
|
||||
send: 'Send',
|
||||
receive: 'Receive',
|
||||
},
|
||||
auth: {
|
||||
login: 'Login',
|
||||
password: 'Password',
|
||||
unlock: 'Unlock Wallet',
|
||||
create_password: 'Create Password',
|
||||
confirm_password: 'Confirm Password',
|
||||
},
|
||||
validation: {
|
||||
required: 'This field is required',
|
||||
invalid_password: 'Invalid password',
|
||||
password_mismatch: 'Passwords do not match',
|
||||
invalid_address: 'Invalid address',
|
||||
},
|
||||
errors: {
|
||||
generic: 'An error occurred',
|
||||
network: 'Network error',
|
||||
failed_to_load: 'Failed to load data',
|
||||
},
|
||||
}
|
||||
|
||||
41
src/i18n/locales/jp.ts
Normal file
41
src/i18n/locales/jp.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export default {
|
||||
common: {
|
||||
app_name: 'Neptune ウォレット',
|
||||
welcome: 'Neptune ウォレットへようこそ',
|
||||
cancel: 'キャンセル',
|
||||
confirm: '確認',
|
||||
save: '保存',
|
||||
back: '戻る',
|
||||
next: '次へ',
|
||||
submit: '送信',
|
||||
close: '閉じる',
|
||||
},
|
||||
wallet: {
|
||||
title: 'ウォレット',
|
||||
create: '新しいウォレットを作成',
|
||||
recover: 'ウォレットを復元',
|
||||
balance: '残高',
|
||||
address: 'アドレス',
|
||||
send: '送信',
|
||||
receive: '受信',
|
||||
},
|
||||
auth: {
|
||||
login: 'ログイン',
|
||||
password: 'パスワード',
|
||||
unlock: 'ウォレットをアンロック',
|
||||
create_password: 'パスワードを作成',
|
||||
confirm_password: 'パスワードを確認',
|
||||
},
|
||||
validation: {
|
||||
required: 'この項目は必須です',
|
||||
invalid_password: '無効なパスワード',
|
||||
password_mismatch: 'パスワードが一致しません',
|
||||
invalid_address: '無効なアドレス',
|
||||
},
|
||||
errors: {
|
||||
generic: 'エラーが発生しました',
|
||||
network: 'ネットワークエラー',
|
||||
failed_to_load: 'データの読み込みに失敗しました',
|
||||
},
|
||||
}
|
||||
|
||||
8
src/lib/utils.ts
Normal file
8
src/lib/utils.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { ClassValue } from 'clsx'
|
||||
import { clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
20
src/main.ts
Normal file
20
src/main.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
import App from './App.vue'
|
||||
import { useTheme } from './composables/useTheme'
|
||||
import './style.css'
|
||||
|
||||
// Initialize theme before mounting app
|
||||
useTheme()
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// Install plugins
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
46
src/router/index.ts
Normal file
46
src/router/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/views/HomeView.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/auth',
|
||||
name: 'auth',
|
||||
component: () => import('@/views/AuthView.vue'),
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: () => import('@/views/NotFoundView.vue'),
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
})
|
||||
|
||||
// Navigation guards
|
||||
router.beforeEach((to, _from, next) => {
|
||||
// Add authentication logic here
|
||||
// const hasWallet = useNeptuneStore().hasWallet
|
||||
|
||||
if (to.meta.requiresAuth) {
|
||||
// Check if user has wallet
|
||||
// if (!hasWallet) {
|
||||
// next({ name: 'auth' })
|
||||
// return
|
||||
// }
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
52
src/stores/authStore.ts
Normal file
52
src/stores/authStore.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export type AuthState = 'onboarding' | 'login' | 'create' | 'recovery' | 'confirm' | 'complete'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// State
|
||||
const currentState = ref<AuthState>('onboarding')
|
||||
const previousState = ref<AuthState | null>(null)
|
||||
|
||||
// Getters
|
||||
const getCurrentState = computed(() => currentState.value)
|
||||
const getPreviousState = computed(() => previousState.value)
|
||||
|
||||
// Actions
|
||||
const setState = (state: AuthState) => {
|
||||
previousState.value = currentState.value
|
||||
currentState.value = state
|
||||
}
|
||||
|
||||
const goToCreate = () => {
|
||||
setState('create')
|
||||
}
|
||||
|
||||
const goToLogin = () => {
|
||||
setState('login')
|
||||
}
|
||||
|
||||
const goToRecover = () => {
|
||||
setState('recovery')
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
if (previousState.value) {
|
||||
setState(previousState.value)
|
||||
} else {
|
||||
setState('onboarding')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentState,
|
||||
getCurrentState,
|
||||
getPreviousState,
|
||||
setState,
|
||||
goToCreate,
|
||||
goToLogin,
|
||||
goToRecover,
|
||||
goBack,
|
||||
}
|
||||
})
|
||||
|
||||
3
src/stores/index.ts
Normal file
3
src/stores/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { useAuthStore } from './authStore'
|
||||
export { useNeptuneStore } from './neptuneStore'
|
||||
|
||||
140
src/stores/neptuneStore.ts
Normal file
140
src/stores/neptuneStore.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export interface WalletState {
|
||||
receiverId: string | null
|
||||
viewKey: string | null
|
||||
spendingKey: string | null
|
||||
address: string | null
|
||||
network: 'mainnet' | 'testnet'
|
||||
balance: string | null
|
||||
pendingBalance: string | null
|
||||
utxos: any[]
|
||||
minBlockHeight: number | null
|
||||
}
|
||||
|
||||
export const useNeptuneStore = defineStore('neptune', () => {
|
||||
const defaultNetwork = (import.meta.env.VITE_NODE_NETWORK || 'mainnet') as 'mainnet' | 'testnet'
|
||||
|
||||
// State
|
||||
const wallet = ref<WalletState>({
|
||||
receiverId: null,
|
||||
viewKey: null,
|
||||
spendingKey: null,
|
||||
address: null,
|
||||
network: defaultNetwork,
|
||||
balance: null,
|
||||
pendingBalance: null,
|
||||
utxos: [],
|
||||
minBlockHeight: null,
|
||||
})
|
||||
|
||||
const keystoreFileName = ref<string | null>(null)
|
||||
|
||||
// Getters
|
||||
const getWallet = computed(() => wallet.value)
|
||||
const getReceiverId = computed(() => wallet.value.receiverId)
|
||||
const getViewKey = computed(() => wallet.value.viewKey)
|
||||
const getSpendingKey = computed(() => wallet.value.spendingKey)
|
||||
const getAddress = computed(() => wallet.value.address)
|
||||
const getNetwork = computed(() => wallet.value.network)
|
||||
const getBalance = computed(() => wallet.value.balance)
|
||||
const getPendingBalance = computed(() => wallet.value.pendingBalance)
|
||||
const getUtxos = computed(() => wallet.value.utxos)
|
||||
const getMinBlockHeight = computed(() => wallet.value.minBlockHeight ?? null)
|
||||
const hasWallet = computed(() => wallet.value.address !== null)
|
||||
const getKeystoreFileName = computed(() => keystoreFileName.value ?? null)
|
||||
|
||||
// Actions
|
||||
const setReceiverId = (receiverId: string | null) => {
|
||||
wallet.value.receiverId = receiverId
|
||||
}
|
||||
|
||||
const setViewKey = (viewKey: string | null) => {
|
||||
wallet.value.viewKey = viewKey
|
||||
}
|
||||
|
||||
const setSpendingKey = (spendingKey: string | null) => {
|
||||
wallet.value.spendingKey = spendingKey
|
||||
}
|
||||
|
||||
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 setPendingBalance = (pendingBalance: string | null) => {
|
||||
wallet.value.pendingBalance = pendingBalance
|
||||
}
|
||||
|
||||
const setUtxos = (utxos: any[]) => {
|
||||
wallet.value.utxos = utxos
|
||||
}
|
||||
|
||||
const setMinBlockHeight = (minBlockHeight: number | null) => {
|
||||
wallet.value.minBlockHeight = minBlockHeight
|
||||
}
|
||||
|
||||
const setWallet = (walletData: Partial<WalletState>) => {
|
||||
wallet.value = { ...wallet.value, ...walletData }
|
||||
}
|
||||
|
||||
const setKeystoreFileName = (fileName: string | null) => {
|
||||
keystoreFileName.value = fileName
|
||||
}
|
||||
|
||||
const clearWallet = () => {
|
||||
wallet.value = {
|
||||
receiverId: null,
|
||||
viewKey: null,
|
||||
spendingKey: null,
|
||||
address: null,
|
||||
network: defaultNetwork,
|
||||
balance: null,
|
||||
pendingBalance: null,
|
||||
utxos: [],
|
||||
minBlockHeight: null,
|
||||
}
|
||||
keystoreFileName.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
wallet,
|
||||
keystoreFileName,
|
||||
// Getters
|
||||
getWallet,
|
||||
getReceiverId,
|
||||
getViewKey,
|
||||
getSpendingKey,
|
||||
getAddress,
|
||||
getNetwork,
|
||||
getBalance,
|
||||
getPendingBalance,
|
||||
getUtxos,
|
||||
getMinBlockHeight,
|
||||
hasWallet,
|
||||
getKeystoreFileName,
|
||||
// Actions
|
||||
setReceiverId,
|
||||
setViewKey,
|
||||
setSpendingKey,
|
||||
setAddress,
|
||||
setNetwork,
|
||||
setBalance,
|
||||
setPendingBalance,
|
||||
setUtxos,
|
||||
setMinBlockHeight,
|
||||
setWallet,
|
||||
setKeystoreFileName,
|
||||
clearWallet,
|
||||
}
|
||||
})
|
||||
|
||||
121
src/style.css
Normal file
121
src/style.css
Normal file
@ -0,0 +1,121 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
6
src/utils/constants.ts
Normal file
6
src/utils/constants.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const PAGE_FIRST = 1
|
||||
export const PER_PAGE = 20
|
||||
export const POLLING_INTERVAL = 1000 * 60 // 1 minute
|
||||
export const DEFAULT_MIN_BLOCK_HEIGHT = 0
|
||||
export const STATUS_CODE_SUCCESS = 200
|
||||
|
||||
13
src/views/AuthView.vue
Normal file
13
src/views/AuthView.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-4xl font-bold text-foreground">{{ t('auth.login') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">Auth View - Coming soon</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
13
src/views/HomeView.vue
Normal file
13
src/views/HomeView.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-4xl font-bold text-foreground">{{ t('wallet.title') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">Home View - Coming soon</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
21
src/views/NotFoundView.vue
Normal file
21
src/views/NotFoundView.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const goHome = () => {
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-6xl font-bold text-foreground">404</h1>
|
||||
<p class="mt-4 text-xl text-muted-foreground">Page not found</p>
|
||||
<Button class="mt-8" @click="goHome">Go Home</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
4
tailwind.config.ts
Normal file
4
tailwind.config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// Tailwind CSS v4 uses CSS-first configuration
|
||||
// This file is kept for shadcn-vue compatibility but configuration is done in CSS
|
||||
export default {}
|
||||
|
||||
24
tsconfig.app.json
Normal file
24
tsconfig.app.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
]
|
||||
}
|
||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
23
tsconfig.node.json
Normal file
23
tsconfig.node.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
14
vite.config.ts
Normal file
14
vite.config.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import path from 'node:path'
|
||||
import { defineConfig } from 'vite'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user