新增:登录页面
This commit is contained in:
parent
bfe52f4db4
commit
3e0bf253be
@ -16,6 +16,7 @@ const currentRoute = computed(() => {
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn text to="/" :class="{ active: currentRoute === '/' }"> Home </v-btn>
|
||||
<v-btn text to="/trade" :class="{ active: currentRoute === '/trade' }"> Trade </v-btn>
|
||||
<v-btn text to="/login" :class="{ active: currentRoute === '/login' }"> Login </v-btn>
|
||||
</v-app-bar>
|
||||
<v-main>
|
||||
<router-view />
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Home from '../views/Home.vue'
|
||||
import Trade from '../views/Trade.vue'
|
||||
import Login from '../views/Login.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -14,6 +15,11 @@ const router = createRouter({
|
||||
path: '/trade',
|
||||
name: 'trade',
|
||||
component: Trade
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: Login
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
235
src/views/Login.vue
Normal file
235
src/views/Login.vue
Normal file
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<v-container class="login-container">
|
||||
<v-row justify="center" align="center" class="login-row">
|
||||
<v-col cols="12" md="6" lg="4">
|
||||
<v-card class="login-card">
|
||||
<v-card-title class="login-title">Sign In</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="handleLogin">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
label="Email"
|
||||
type="email"
|
||||
required
|
||||
:rules="[
|
||||
(value) => !!value || 'Email is required',
|
||||
(value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'Email is invalid',
|
||||
]"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
required
|
||||
:rules="[
|
||||
(value) => !!value || 'Password is required',
|
||||
(value) => value.length >= 6 || 'Password must be at least 6 characters',
|
||||
]"
|
||||
></v-text-field>
|
||||
|
||||
<v-btn class="login-btn" color="primary" type="submit" block> Sign In </v-btn>
|
||||
|
||||
<div class="divider">
|
||||
<span>or</span>
|
||||
</div>
|
||||
|
||||
<v-btn
|
||||
class="metamask-btn"
|
||||
color="success"
|
||||
block
|
||||
@click="connectWithMetaMask"
|
||||
:loading="isConnecting"
|
||||
:disabled="isConnecting"
|
||||
>
|
||||
<span class="metamask-icon">🟡</span>
|
||||
{{ isConnecting ? 'Connecting...' : 'Connect with MetaMask' }}
|
||||
</v-btn>
|
||||
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<div class="login-links">
|
||||
<router-link to="/register">Don't have an account? Sign Up</router-link>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const isConnecting = ref(false)
|
||||
const errorMessage = ref('')
|
||||
|
||||
const handleLogin = () => {
|
||||
// Here you would typically make an API call to authenticate the user
|
||||
console.log('Logging in with:', email.value, password.value)
|
||||
|
||||
// For demo purposes, we'll just navigate to the home page
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const connectWithMetaMask = async () => {
|
||||
isConnecting.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
// Check if MetaMask is installed
|
||||
if (!window.ethereum) {
|
||||
throw new Error('MetaMask is not installed. Please install it to continue.')
|
||||
}
|
||||
|
||||
// Request account access
|
||||
const accounts = await window.ethereum.request({
|
||||
method: 'eth_requestAccounts',
|
||||
})
|
||||
|
||||
const walletAddress = accounts[0]
|
||||
console.log('Connected to MetaMask with account:', walletAddress)
|
||||
|
||||
// Generate nonce for security
|
||||
const nonce = Math.floor(Math.random() * 1000000).toString()
|
||||
|
||||
// Create message to sign
|
||||
const message = `happy new year`
|
||||
|
||||
// Request signature
|
||||
const signature = await window.ethereum.request({
|
||||
method: 'personal_sign',
|
||||
params: [message + nonce, walletAddress],
|
||||
})
|
||||
|
||||
console.log('Signature:', signature)
|
||||
console.log('Login data:', {
|
||||
message,
|
||||
nonce,
|
||||
signature,
|
||||
walletAddress,
|
||||
})
|
||||
|
||||
// Call login API
|
||||
const loginResponse = await fetch('https://api.xtrader.vip/base/walletLogin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message,
|
||||
nonce,
|
||||
signature,
|
||||
walletAddress,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!loginResponse.ok) {
|
||||
throw new Error('Login failed. Please try again.')
|
||||
}
|
||||
|
||||
const loginData = await loginResponse.json()
|
||||
console.log('Login API response:', loginData)
|
||||
|
||||
// For demo purposes, we'll just navigate to the home page
|
||||
// In a real application, you would store the token or session data here
|
||||
router.push('/')
|
||||
} catch (error: any) {
|
||||
console.error('Error connecting to MetaMask:', error)
|
||||
errorMessage.value = error.message || 'Failed to connect with MetaMask'
|
||||
} finally {
|
||||
isConnecting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
padding: 40px 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.login-row {
|
||||
min-height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #0066cc;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
margin-top: 24px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.divider span {
|
||||
padding: 0 16px;
|
||||
color: #808080;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.metamask-btn {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metamask-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 16px;
|
||||
color: #ff0000;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-links {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-links a {
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.login-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
20
src/vite-env.d.ts
vendored
Normal file
20
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
// Add MetaMask types
|
||||
declare interface Window {
|
||||
ethereum?: {
|
||||
isMetaMask?: boolean
|
||||
request: (args: {
|
||||
method: string
|
||||
params?: any[]
|
||||
}) => Promise<any>
|
||||
on: (event: string, callback: (...args: any[]) => void) => void
|
||||
removeListener: (event: string, callback: (...args: any[]) => void) => void
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user