From f0d9797cac207a23fcb390e53ed5b61eb949db24 Mon Sep 17 00:00:00 2001 From: songtianlun Date: Sun, 3 Aug 2025 09:52:59 +0800 Subject: [PATCH] better mobile layout --- src/app/api/users/sync/route.ts | 6 +- src/app/studio/new/page.tsx | 44 +++++++------ src/app/studio/page.tsx | 113 +++++++++++++++++--------------- src/hooks/useAuth.ts | 57 ++++++++++++---- 4 files changed, 129 insertions(+), 91 deletions(-) diff --git a/src/app/api/users/sync/route.ts b/src/app/api/users/sync/route.ts index a2ff81a..65dc687 100644 --- a/src/app/api/users/sync/route.ts +++ b/src/app/api/users/sync/route.ts @@ -1,10 +1,10 @@ -import { NextRequest, NextResponse } from 'next/server' +import { NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' import { createServerSupabaseClient } from '@/lib/supabase-server' import { addSystemGiftCredit } from '@/lib/credits' // POST /api/users/sync - 同步Supabase用户到Prisma数据库 -export async function POST(_request: NextRequest) { +export async function POST() { try { const supabase = await createServerSupabaseClient() const { data: { user: supabaseUser }, error: authError } = await supabase.auth.getUser() @@ -68,7 +68,7 @@ export async function POST(_request: NextRequest) { } // GET /api/users/sync - 获取当前用户信息 -export async function GET(_request: NextRequest) { +export async function GET() { try { const supabase = await createServerSupabaseClient() const { data: { user: supabaseUser }, error: authError } = await supabase.auth.getUser() diff --git a/src/app/studio/new/page.tsx b/src/app/studio/new/page.tsx index 67b2f0b..a842d42 100644 --- a/src/app/studio/new/page.tsx +++ b/src/app/studio/new/page.tsx @@ -121,29 +121,31 @@ export default function NewPromptPage() {
-
-
+
+
-

{t('newPrompt')}

+

{t('newPrompt')}

Create a new AI prompt

-
+
@@ -152,7 +154,7 @@ export default function NewPromptPage() { size="sm" onClick={handleSave} disabled={isSaving || !promptName.trim()} - className="flex items-center space-x-2" + className="flex items-center space-x-2 flex-1 sm:flex-none h-10" > {isSaving ? ( @@ -166,11 +168,11 @@ export default function NewPromptPage() {
-
-
+
+
{/* Basic Information */} -
-

Basic Information

+
+

Basic Information

@@ -199,25 +201,27 @@ export default function NewPromptPage() {
{/* Tags */} -
-

{t('tags')}

+
+

{t('tags')}

-
+
setNewTag(e.target.value)} onKeyDown={handleKeyDown} placeholder={t('addTag')} - className="flex-1" + className="flex-1 h-10" />
@@ -245,7 +249,7 @@ export default function NewPromptPage() { {availableTags.length > 0 && (

Quick add from existing tags:

-
+
{availableTags .filter(tag => !tags.includes(tag)) .slice(0, 8) @@ -266,8 +270,8 @@ export default function NewPromptPage() {
{/* Prompt Content */} -
-

{t('promptContent')}

+
+

{t('promptContent')}

@@ -276,7 +280,7 @@ export default function NewPromptPage() { value={promptContent} onChange={(e) => setPromptContent(e.target.value)} placeholder="Enter your prompt here..." - className="mt-1 min-h-[300px] font-mono text-sm" + className="mt-1 min-h-[200px] sm:min-h-[300px] font-mono text-sm" />

Write your prompt instructions here. You can use variables like {'{variable}'} for dynamic content. diff --git a/src/app/studio/page.tsx b/src/app/studio/page.tsx index 2f67ebe..e7af4cd 100644 --- a/src/app/studio/page.tsx +++ b/src/app/studio/page.tsx @@ -292,21 +292,22 @@ export default function StudioPage() {

{/* Header */}
-
-
-

{t('title')}

+
+
+

{t('title')}

{t('myPrompts')}

-
+
- @@ -314,29 +315,27 @@ export default function StudioPage() {
{/* Search and Filters */} -
-
+
+
{/* Search */} -
-
- - setSearchQuery(e.target.value)} - className="pl-9 bg-background border-border/50 focus:border-primary transition-colors" - /> -
+
+ + setSearchQuery(e.target.value)} + className="pl-9 bg-background border-border/50 focus:border-primary transition-colors h-10" + />
{/* Filters Row */} -
+
{/* Tag Filter */} -
+
{ @@ -355,27 +354,25 @@ export default function StudioPage() { setSortField(field as SortField) setSortOrder(order as SortOrder) }} - className="appearance-none bg-background border border-border/50 rounded-lg px-3 py-2 pr-8 min-w-[150px] focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors text-sm" + className="appearance-none bg-background border border-border/50 rounded-lg px-3 py-2 pr-8 w-full focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors text-sm h-10" > - - - - - - - - + + + + + +
{/* View Mode Toggle */} -
+
{/* Quick Stats */} -
+
{prompts.length > 0 ? ( - Showing {prompts.length} of {pagination.total} prompts + Showing {prompts.length} of {pagination.total} prompts ) : ( No prompts found @@ -409,7 +406,7 @@ export default function StudioPage() { variant="ghost" size="sm" onClick={() => setSearchQuery('')} - className="text-xs text-muted-foreground hover:text-foreground" + className="text-xs text-muted-foreground hover:text-foreground self-start sm:self-auto" > Clear search @@ -441,7 +438,7 @@ export default function StudioPage() { <> {/* Prompts Grid/List */} {viewMode === 'grid' ? ( -
+
{currentPrompts.map((prompt) => (
handleShowDetails(prompt)} > {/* Card Header */} -
+
-

+

{prompt.name}

-
+

{prompt.description || 'No description available'}

@@ -490,7 +487,7 @@ export default function StudioPage() {
{/* Card Footer */} -
+
{/* Metadata */}
@@ -524,7 +521,7 @@ export default function StudioPage() {
{/* Action Buttons */} -
+
- {Array.from({ length: Math.min(5, pagination.totalPages) }, (_, i) => { + {/* Show fewer page numbers on mobile */} + {Array.from({ length: Math.min(3, pagination.totalPages) }, (_, i) => { const page = i + 1 return ( @@ -731,8 +734,10 @@ export default function StudioPage() { size="sm" disabled={pagination.page === pagination.totalPages} onClick={() => setCurrentPage(prev => Math.min(pagination.totalPages, prev + 1))} + className="h-8 px-2 sm:px-3" > - Next + Next + Next
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 74017ff..f8f2c63 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -2,30 +2,51 @@ import { createClient } from '@/lib/supabase' import { User } from '@supabase/supabase-js' -import { useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' export function useAuth() { const [user, setUser] = useState(null) const [loading, setLoading] = useState(true) const supabase = createClient() + const syncTimeoutRef = useRef(null) + const lastSyncTimeRef = useRef(0) + const lastUserIdRef = useRef(null) useEffect(() => { - // 同步用户到Prisma数据库 - const syncUser = async () => { - try { - await fetch('/api/users/sync', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - console.error('Failed to sync user:', error) + // 同步用户到Prisma数据库 - 带防抖和缓存 + const syncUser = async (userId: string) => { + const now = Date.now() + const timeSinceLastSync = now - lastSyncTimeRef.current + + // 如果是同一个用户且距离上次同步不到30秒,则跳过 + if (lastUserIdRef.current === userId && timeSinceLastSync < 30000) { + return } + + // 清除之前的定时器 + if (syncTimeoutRef.current) { + clearTimeout(syncTimeoutRef.current) + } + + // 防抖处理:延迟500ms执行,避免频繁调用 + syncTimeoutRef.current = setTimeout(async () => { + try { + await fetch('/api/users/sync', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }) + lastSyncTimeRef.current = Date.now() + lastUserIdRef.current = userId + } catch (error) { + console.error('Failed to sync user:', error) + } + }, 500) } const getUser = async () => { const { data: { user: userData } } = await supabase.auth.getUser() if (userData) { - await syncUser() + await syncUser(userData.id) } setUser(userData) setLoading(false) @@ -35,14 +56,22 @@ export function useAuth() { const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, session) => { const userData = session?.user ?? null - if (userData && (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED')) { - await syncUser() + + // 只在首次登录时同步,不在token刷新时同步 + if (userData && event === 'SIGNED_IN') { + await syncUser(userData.id) } + setUser(userData) setLoading(false) }) - return () => subscription.unsubscribe() + return () => { + subscription.unsubscribe() + if (syncTimeoutRef.current) { + clearTimeout(syncTimeoutRef.current) + } + } }, [supabase.auth]) const signOut = async () => {