From 12708e6149c35c862c47e2820bd7b5a148a5b192 Mon Sep 17 00:00:00 2001 From: grabbit Date: Sat, 9 Aug 2025 10:04:14 +0800 Subject: [PATCH] display --- meteor-frontend/meteor_homepage_design.html | 434 ++++++++++++++++++ meteor-frontend/src/app/globals.css | 149 ++++-- meteor-frontend/src/app/layout.tsx | 6 +- meteor-frontend/src/app/page.tsx | 142 +++++- .../scripts/create-premium-user.js | 136 ++++++ test-api.js | 30 ++ 6 files changed, 833 insertions(+), 64 deletions(-) create mode 100644 meteor-frontend/meteor_homepage_design.html create mode 100644 meteor-web-backend/scripts/create-premium-user.js create mode 100644 test-api.js diff --git a/meteor-frontend/meteor_homepage_design.html b/meteor-frontend/meteor_homepage_design.html new file mode 100644 index 0000000..158b276 --- /dev/null +++ b/meteor-frontend/meteor_homepage_design.html @@ -0,0 +1,434 @@ + + + + + + 分布式流星监测网络 + + + +
+ +
+
+

分布式流星监测网络

+

+ 连接全球观测者,记录天空中每一道流星的轨迹,为科学研究提供珍贵数据 +

+
+ +
+
+
+ +
+

实时监测网络

+
+ +
+

科学价值

+

+ 通过分布式监测网络,我们能够精确追踪流星轨迹,分析其起源、速度和成分。 + 每一次观测都为天体物理学研究贡献宝贵数据。 +

+

+ 加入我们的网络,成为公民科学家,让您的观测为人类对宇宙的理解做出贡献。 +

+
+
+ +
+
+
🛰️
+

设备网络

+

分布在全球的高精度监测设备,24小时不间断捕捉流星事件,构建完整的观测网络。

+
+ +
+
📸
+

事件画廊

+

精彩的流星照片和视频集合,每一帧都记录着宇宙的壮丽瞬间,见证天空的奇迹。

+
+ +
+
📊
+

研究数据

+

开放的科学数据平台,为天文学家和研究人员提供准确、详细的流星观测数据。

+
+
+ +
+

+ 开始您的星空探索之旅 +

+

+ 注册账户,访问实时数据,加入全球观测者社区 +

+ +
+
+ + + + \ No newline at end of file diff --git a/meteor-frontend/src/app/globals.css b/meteor-frontend/src/app/globals.css index 7c4f1b1..9de8f2d 100644 --- a/meteor-frontend/src/app/globals.css +++ b/meteor-frontend/src/app/globals.css @@ -1,26 +1,26 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; - --card: #ffffff; - --card-foreground: #171717; - --popover: #ffffff; - --popover-foreground: #171717; - --primary: #171717; - --primary-foreground: #fafafa; - --secondary: #f5f5f5; - --secondary-foreground: #171717; - --muted: #f5f5f5; - --muted-foreground: #737373; - --accent: #f5f5f5; - --accent-foreground: #171717; + --background: #0a0a23; + --foreground: #ffffff; + --card: rgba(255, 255, 255, 0.05); + --card-foreground: #ffffff; + --popover: rgba(255, 255, 255, 0.05); + --popover-foreground: #ffffff; + --primary: #ff6b35; + --primary-foreground: #ffffff; + --secondary: rgba(255, 255, 255, 0.1); + --secondary-foreground: #ffffff; + --muted: rgba(255, 255, 255, 0.05); + --muted-foreground: #cccccc; + --accent: #ff6b35; + --accent-foreground: #ffffff; --destructive: #ef4444; - --destructive-foreground: #fafafa; - --border: #e5e5e5; - --input: #e5e5e5; - --ring: #171717; - --radius: 0.5rem; + --destructive-foreground: #ffffff; + --border: rgba(255, 255, 255, 0.1); + --input: rgba(255, 255, 255, 0.1); + --ring: #ff6b35; + --radius: 1rem; } @theme inline { @@ -50,30 +50,99 @@ @media (prefers-color-scheme: dark) { :root { - --background: #0a0a0a; - --foreground: #ededed; - --card: #0a0a0a; - --card-foreground: #ededed; - --popover: #0a0a0a; - --popover-foreground: #ededed; - --primary: #ededed; - --primary-foreground: #0a0a0a; - --secondary: #262626; - --secondary-foreground: #ededed; - --muted: #262626; - --muted-foreground: #a3a3a3; - --accent: #262626; - --accent-foreground: #ededed; - --destructive: #dc2626; - --destructive-foreground: #ededed; - --border: #262626; - --input: #262626; - --ring: #d4d4d8; + --background: #0a0a23; + --foreground: #ffffff; + --card: rgba(255, 255, 255, 0.05); + --card-foreground: #ffffff; + --popover: rgba(255, 255, 255, 0.05); + --popover-foreground: #ffffff; + --primary: #ff6b35; + --primary-foreground: #ffffff; + --secondary: rgba(255, 255, 255, 0.1); + --secondary-foreground: #ffffff; + --muted: rgba(255, 255, 255, 0.05); + --muted-foreground: #cccccc; + --accent: #ff6b35; + --accent-foreground: #ffffff; + --destructive: #ef4444; + --destructive-foreground: #ffffff; + --border: rgba(255, 255, 255, 0.1); + --input: rgba(255, 255, 255, 0.1); + --ring: #ff6b35; } } body { - background: var(--background); + background: linear-gradient(135deg, #0a0a23 0%, #1a1a3a 50%, #2a2a4a 100%); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + overflow-x: hidden; +} + +.starfield { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + pointer-events: none; +} + +.star { + position: absolute; + background: white; + border-radius: 50%; + animation: twinkle 2s infinite alternate; +} + +@keyframes twinkle { + 0% { opacity: 0.3; transform: scale(1); } + 100% { opacity: 1; transform: scale(1.2); } +} + +.meteor { + position: absolute; + width: 2px; + height: 100px; + background: linear-gradient(to bottom, #ff6b35, transparent); + animation: meteor-fall 3s linear infinite; +} + +@keyframes meteor-fall { + 0% { + transform: translateY(-100px) translateX(0); + opacity: 1; + } + 100% { + transform: translateY(100vh) translateX(-200px); + opacity: 0; + } +} + +.hero-title-gradient { + background: linear-gradient(45deg, #ffffff, #ff6b35); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: glow 2s ease-in-out infinite alternate; +} + +@keyframes glow { + 0% { text-shadow: 0 0 20px rgba(255, 107, 53, 0.5); } + 100% { text-shadow: 0 0 40px rgba(255, 107, 53, 0.8); } +} + +.glass-card { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 20px; + transition: all 0.3s ease; +} + +.glass-card:hover { + transform: translateY(-5px); + box-shadow: 0 20px 40px rgba(255, 107, 53, 0.2); + border-color: #ff6b35; } diff --git a/meteor-frontend/src/app/layout.tsx b/meteor-frontend/src/app/layout.tsx index b4ef1c4..226f80c 100644 --- a/meteor-frontend/src/app/layout.tsx +++ b/meteor-frontend/src/app/layout.tsx @@ -15,8 +15,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "分布式流星监测网络 - Distributed Meteor Monitoring Network", + description: "连接全球观测者,记录天空中每一道流星的轨迹,为科学研究提供珍贵数据", }; export default function RootLayout({ @@ -25,7 +25,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/meteor-frontend/src/app/page.tsx b/meteor-frontend/src/app/page.tsx index e0a830b..d435a65 100644 --- a/meteor-frontend/src/app/page.tsx +++ b/meteor-frontend/src/app/page.tsx @@ -3,18 +3,58 @@ import Link from "next/link" import { Button } from "@/components/ui/button" import { useAuth } from "@/contexts/auth-context" +import { useEffect } from "react" export default function Home() { const { isAuthenticated, user } = useAuth() + useEffect(() => { + createStarfield() + }, []) + + const createStarfield = () => { + const starfield = document.getElementById('starfield') + if (!starfield) return + + const numStars = 100 + + for (let i = 0; i < numStars; i++) { + const star = document.createElement('div') + star.className = 'star' + star.style.left = Math.random() * 100 + '%' + star.style.top = Math.random() * 100 + '%' + star.style.width = Math.random() * 3 + 1 + 'px' + star.style.height = star.style.width + star.style.animationDelay = Math.random() * 2 + 's' + starfield.appendChild(star) + } + + // Add falling meteors + const createMeteor = () => { + const meteor = document.createElement('div') + meteor.className = 'meteor' + meteor.style.left = Math.random() * 100 + '%' + meteor.style.animationDuration = (Math.random() * 2 + 2) + 's' + starfield.appendChild(meteor) + + setTimeout(() => { + meteor.remove() + }, 5000) + } + + setInterval(createMeteor, 3000) + } + return ( -
+
+
+ {/* Navigation */}
{isAuthenticated ? ( <> - + Welcome, {user?.email}
- {/* Main Content */} -
-
-

- 分布式流星监测网络 -

-

- Distributed Meteor Monitoring Network -

- - {!isAuthenticated && ( -
- + +
+ )} + + + {/* Feature Cards Section */} +
+
+
+
+
+ 🛰️ +
+

设备网络

+

+ 分布在全球的高精度监测设备,24小时不间断捕捉流星事件,构建完整的观测网络。 +

+
+ +
+
+ 📸 +
+

事件画廊

+

+ 精彩的流星照片和视频集合,每一帧都记录着宇宙的壮丽瞬间,见证天空的奇迹。 +

+
+ +
+
+ 📊 +
+

研究数据

+

+ 开放的科学数据平台,为天文学家和研究人员提供准确、详细的流星观测数据。 +

+
+
+
+
+ + {/* CTA Section */} + {!isAuthenticated && ( +
+
+

+ 开始您的星空探索之旅 +

+

+ 注册账户,访问实时数据,加入全球观测者社区 +

+
+ -
- )} -
-
+
+ + )}
) } diff --git a/meteor-web-backend/scripts/create-premium-user.js b/meteor-web-backend/scripts/create-premium-user.js new file mode 100644 index 0000000..00072de --- /dev/null +++ b/meteor-web-backend/scripts/create-premium-user.js @@ -0,0 +1,136 @@ +#!/usr/bin/env node + +/** + * 创建付费测试用户的脚本 + * + * 使用方法: + * node scripts/create-premium-user.js [email] [password] [displayName] + * + * 例子: + * node scripts/create-premium-user.js premium@test.com TestPass123 "Premium User" + */ + +const { Client } = require('pg'); +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); + +async function createPremiumUser(email, password, displayName) { + const client = new Client({ + connectionString: process.env.DATABASE_URL + }); + + try { + await client.connect(); + console.log('✅ Connected to database'); + + // 1. 首先注册普通用户 + console.log(`\n📝 Registering user: ${email}`); + + const registerResponse = await fetch('http://localhost:3001/api/v1/auth/register-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + password, + displayName + }) + }); + + if (!registerResponse.ok) { + const error = await registerResponse.json(); + throw new Error(`Registration failed: ${error.message}`); + } + + const registerData = await registerResponse.json(); + const userId = registerData.userId; + + console.log(`✅ User registered successfully with ID: ${userId}`); + + // 2. 更新用户为付费用户 + const fakeCustomerId = 'cus_test_premium_' + Date.now(); + const fakeSubscriptionId = 'sub_test_premium_' + Date.now(); + + const updateQuery = ` + UPDATE user_profiles + SET + payment_provider_customer_id = $1, + payment_provider_subscription_id = $2, + updated_at = NOW() + WHERE id = $3 + `; + + const result = await client.query(updateQuery, [fakeCustomerId, fakeSubscriptionId, userId]); + + if (result.rowCount > 0) { + console.log('✅ User upgraded to premium successfully!'); + + // 3. 测试登录和profile获取 + console.log('\n🔐 Testing login...'); + const loginResponse = await fetch('http://localhost:3001/api/v1/auth/login-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + password + }) + }); + + if (loginResponse.ok) { + const loginData = await loginResponse.json(); + console.log('✅ Login successful'); + + // 测试profile + const profileResponse = await fetch('http://localhost:3001/api/v1/auth/profile', { + method: 'GET', + headers: { + 'Authorization': `Bearer ${loginData.accessToken}`, + 'Content-Type': 'application/json', + } + }); + + if (profileResponse.ok) { + const profileData = await profileResponse.json(); + console.log('✅ Profile retrieved successfully'); + + console.log('\n🎉 Premium user created successfully!'); + console.log('='.repeat(50)); + console.log(`👤 User Details:`); + console.log(` Email: ${email}`); + console.log(` Password: ${password}`); + console.log(` Display Name: ${displayName}`); + console.log(` User ID: ${userId}`); + console.log(` Customer ID: ${fakeCustomerId}`); + console.log(` Subscription ID: ${fakeSubscriptionId}`); + console.log(` Subscription Status: ${profileData.subscriptionStatus}`); + console.log(` Has Active Subscription: ${profileData.hasActiveSubscription}`); + console.log('='.repeat(50)); + } + } + } else { + console.log('❌ Failed to upgrade user to premium'); + } + + } catch (error) { + console.error('❌ Error:', error.message); + process.exit(1); + } finally { + await client.end(); + } +} + +// 命令行参数处理 +const args = process.argv.slice(2); +const email = args[0] || 'premium' + Date.now() + '@test.com'; +const password = args[1] || 'TestPassword123'; +const displayName = args[2] || 'Premium User'; + +console.log('🚀 Creating Premium User...'); +console.log(`Email: ${email}`); +console.log(`Password: ${password}`); +console.log(`Display Name: ${displayName}`); + +createPremiumUser(email, password, displayName); \ No newline at end of file diff --git a/test-api.js b/test-api.js new file mode 100644 index 0000000..85b3359 --- /dev/null +++ b/test-api.js @@ -0,0 +1,30 @@ +const fetch = require('node-fetch'); + +async function testAPI() { + try { + console.log('Testing register-email API...'); + + const response = await fetch('http://localhost:3001/api/v1/auth/register-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: 'testuser@example.com', + password: 'TestPass123', + displayName: 'Test User' + }) + }); + + console.log('Response status:', response.status); + console.log('Response headers:', Object.fromEntries(response.headers)); + + const data = await response.text(); + console.log('Response body:', data); + + } catch (error) { + console.error('Error:', error.message); + } +} + +testAPI(); \ No newline at end of file