- Add TokenCard and CexAnalysisCard components for displaying token data - Implement real-time Redis event streaming for token updates - Add environment-based configuration system for dev/prod Redis servers - Create comprehensive hunting ground dashboard with card management - Add individual and bulk card removal functionality - Implement browser integration for token details viewing - Add timestamp utilities and proper type handling for Redis events - Create production-ready configuration with 154.38.185.112 Redis server - Add comprehensive documentation in README.md and CONTRIBUTORS.md - Restructure project architecture with proper Electron-Vue integration BREAKING CHANGE: Redis configuration now uses environment-based settings
179 lines
4.8 KiB
Vue
179 lines
4.8 KiB
Vue
<template>
|
|
<div class="login-content">
|
|
<div class="w-96 space-y-4">
|
|
<!-- Login Card -->
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<div class="text-center mb-6">
|
|
<h1 class="text-3xl font-bold">Welcome to Ziya</h1>
|
|
<p class="text-base-content/70">Sign in to your trading platform</p>
|
|
</div>
|
|
|
|
<form
|
|
class="space-y-4"
|
|
@submit.prevent="handleLogin"
|
|
>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Email Address</span>
|
|
</label>
|
|
<input
|
|
v-model="email"
|
|
type="email"
|
|
required
|
|
class="input input-bordered w-full"
|
|
placeholder="Enter your email"
|
|
>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Password</span>
|
|
</label>
|
|
<input
|
|
v-model="password"
|
|
type="password"
|
|
required
|
|
class="input input-bordered w-full"
|
|
placeholder="Enter your password"
|
|
>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start">
|
|
<input type="checkbox" class="checkbox checkbox-sm mr-2">
|
|
<span class="label-text">Remember me</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control mt-6">
|
|
<button
|
|
type="submit"
|
|
class="btn btn-primary w-full"
|
|
:class="{ loading: isLoading }"
|
|
:disabled="isLoading"
|
|
>
|
|
{{ isLoading ? 'Signing in...' : 'Sign In' }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="divider">OR</div>
|
|
|
|
<div class="text-center">
|
|
<p class="text-sm text-base-content/70">
|
|
Don't have an account?
|
|
<a href="#" class="link link-primary">Sign up</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Back Button -->
|
|
<div class="text-center">
|
|
<button
|
|
class="btn btn-ghost btn-sm text-base-content/70 hover:text-base-content"
|
|
title="Go back to home"
|
|
@click="goBack"
|
|
>
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
Back to Home
|
|
</button>
|
|
</div>
|
|
|
|
<!-- App Version -->
|
|
<div class="text-center">
|
|
<p class="text-xs opacity-50">
|
|
Version {{ appVersion }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, ref } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
|
|
definePageMeta({
|
|
layout: 'auth',
|
|
});
|
|
|
|
const router = useRouter();
|
|
|
|
// Local reactive state instead of accessing store immediately
|
|
const email = ref('');
|
|
const password = ref('');
|
|
const isLoading = ref(false);
|
|
const appVersion = ref('1.0.0');
|
|
const isAuthenticated = ref(false);
|
|
|
|
// Redirect if already authenticated - but only after mount
|
|
onMounted(async () => {
|
|
if (import.meta.client) {
|
|
try {
|
|
const { useAppStore } = await import('../stores/app');
|
|
const appStore = useAppStore();
|
|
|
|
// Initialize app if not already done
|
|
if (!appStore.isInitialized) {
|
|
await appStore.initialize();
|
|
}
|
|
|
|
appVersion.value = appStore.appVersion;
|
|
isAuthenticated.value = appStore.isAuthenticated;
|
|
|
|
// Redirect if already authenticated
|
|
if (appStore.isAuthenticated) {
|
|
router.push('/dashboard');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to initialize app store:', error);
|
|
}
|
|
}
|
|
});
|
|
|
|
const handleLogin = async () => {
|
|
if (!email.value || !password.value) {
|
|
// Simple client-side validation without store
|
|
console.warn('Please fill in all fields');
|
|
return;
|
|
}
|
|
|
|
isLoading.value = true;
|
|
|
|
try {
|
|
// Dynamic import to avoid SSR issues
|
|
const { useAppStore } = await import('../stores/app');
|
|
const appStore = useAppStore();
|
|
|
|
const success = await appStore.login(email.value, password.value);
|
|
|
|
if (success) {
|
|
router.push('/dashboard');
|
|
}
|
|
} catch (error) {
|
|
console.error('Login failed:', error);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const goBack = () => {
|
|
router.push('/');
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-content {
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, hsl(var(--b3)) 0%, hsl(var(--b2)) 100%);
|
|
/* Ensure this area cannot be used for dragging */
|
|
-webkit-app-region: no-drag;
|
|
}
|
|
</style>
|