add some multi language
This commit is contained in:
parent
8d24d3c36e
commit
03f38a76db
@ -97,7 +97,7 @@ Required environment variables:
|
||||
- [ ] Free
|
||||
- [ ] 20 Prompt Limit
|
||||
- [ ] 3 Version Each Prompt
|
||||
- [ ] 5 USD Credit Every Month
|
||||
- [ ] 5 USD Credit Once Time
|
||||
- [ ] 19.9 USD Paid Monthly
|
||||
- [ ] 500 Prompt Limit
|
||||
- [ ] 10 Version Each Prompt
|
||||
|
@ -34,13 +34,23 @@
|
||||
"hasAccount": "Already have an account?",
|
||||
"signInWithGoogle": "Sign in with Google",
|
||||
"signUpWithGoogle": "Sign up with Google",
|
||||
"signInTitle": "Welcome Back",
|
||||
"signInSubtitle": "Sign in to your Prmbr account",
|
||||
"continueWithGoogle": "Continue with Google",
|
||||
"signInTitle": "Sign In",
|
||||
"signInSubtitle": "Welcome back to Prmbr",
|
||||
"signUpTitle": "Create Account",
|
||||
"signUpSubtitle": "Join Prmbr - AI Prompt Studio"
|
||||
"signUpSubtitle": "Start building better prompts today",
|
||||
"emailPlaceholder": "you@example.com",
|
||||
"passwordPlaceholder": "••••••••",
|
||||
"passwordsNotMatch": "Passwords do not match",
|
||||
"checkEmailVerification": "Check your email for verification link",
|
||||
"errorOccurred": "An error occurred",
|
||||
"or": "OR",
|
||||
"noAccountSignUp": "Don't have an account? Sign up",
|
||||
"hasAccountSignIn": "Already have an account? Sign in"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profile Settings",
|
||||
"subtitle": "Manage your account settings and preferences",
|
||||
"personalInfo": "Personal Information",
|
||||
"accountSettings": "Account Settings",
|
||||
"preferences": "Preferences",
|
||||
@ -52,7 +62,33 @@
|
||||
"changePassword": "Change Password",
|
||||
"currentPassword": "Current Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmNewPassword": "Confirm New Password"
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"profilePicture": "Profile Picture",
|
||||
"accessDenied": "Access Denied",
|
||||
"pleaseSignIn": "Please sign in to access your profile",
|
||||
"failedToLoadProfile": "Failed to load profile",
|
||||
"checkEmailToConfirm": "Check your email to confirm the change",
|
||||
"updatedSuccessfully": "updated successfully",
|
||||
"failedToUpdate": "Failed to update",
|
||||
"passwordsNotMatch": "Passwords do not match",
|
||||
"passwordMinLength": "Password must be at least 6 characters",
|
||||
"passwordUpdatedSuccessfully": "Password updated successfully",
|
||||
"failedToUpdatePassword": "Failed to update password",
|
||||
"uploading": "Uploading...",
|
||||
"clickCameraToUpload": "Click the camera icon to upload a new picture",
|
||||
"avatarUpdatedSuccessfully": "Avatar updated successfully",
|
||||
"failedToUploadAvatar": "Failed to upload avatar",
|
||||
"noUsernameSet": "No username set",
|
||||
"enterUsername": "Enter username",
|
||||
"enterEmail": "Enter email",
|
||||
"tellUsAboutYourself": "Tell us about yourself",
|
||||
"charactersLimit": "characters",
|
||||
"noBioAdded": "No bio added yet",
|
||||
"enterNewPassword": "Enter new password",
|
||||
"confirmNewPassword": "Confirm new password",
|
||||
"updatePassword": "Update Password",
|
||||
"english": "English",
|
||||
"chinese": "中文"
|
||||
},
|
||||
"studio": {
|
||||
"title": "AI Prompt Studio",
|
||||
@ -68,7 +104,11 @@
|
||||
"createVersion": "Create Version",
|
||||
"runTest": "Run Test",
|
||||
"noPrompts": "No prompts yet",
|
||||
"createFirstPrompt": "Create your first prompt to get started"
|
||||
"createFirstPrompt": "Create your first prompt to get started",
|
||||
"loadingStudio": "Loading Studio...",
|
||||
"searchPrompts": "Search prompts...",
|
||||
"filter": "Filter",
|
||||
"clickRunTestToSee": "Click \"Run Test\" to see your prompt results"
|
||||
},
|
||||
"home": {
|
||||
"hero": {
|
||||
@ -112,7 +152,11 @@
|
||||
"10 Versions per Prompt",
|
||||
"$20 AI Credit Monthly"
|
||||
]
|
||||
}
|
||||
},
|
||||
"getStartedFree": "Get Started Free",
|
||||
"popular": "Popular",
|
||||
"perMonth": "per month",
|
||||
"startProTrial": "Start Pro Trial"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "Something went wrong. Please try again.",
|
||||
|
@ -34,13 +34,23 @@
|
||||
"hasAccount": "已有账户?",
|
||||
"signInWithGoogle": "使用 Google 登录",
|
||||
"signUpWithGoogle": "使用 Google 注册",
|
||||
"signInTitle": "欢迎回来",
|
||||
"signInSubtitle": "登录您的 Prmbr 账户",
|
||||
"continueWithGoogle": "使用 Google 继续",
|
||||
"signInTitle": "登录",
|
||||
"signInSubtitle": "欢迎回到 Prmbr",
|
||||
"signUpTitle": "创建账户",
|
||||
"signUpSubtitle": "加入 Prmbr - AI 提示词工作室"
|
||||
"signUpSubtitle": "立即开始构建更好的提示词",
|
||||
"emailPlaceholder": "your@example.com",
|
||||
"passwordPlaceholder": "••••••••",
|
||||
"passwordsNotMatch": "密码不匹配",
|
||||
"checkEmailVerification": "请检查您的邮箱以获取验证链接",
|
||||
"errorOccurred": "出现了错误",
|
||||
"or": "或",
|
||||
"noAccountSignUp": "还没有账户?立即注册",
|
||||
"hasAccountSignIn": "已有账户?立即登录"
|
||||
},
|
||||
"profile": {
|
||||
"title": "个人设置",
|
||||
"subtitle": "管理您的账户设置和偏好",
|
||||
"personalInfo": "个人信息",
|
||||
"accountSettings": "账户设置",
|
||||
"preferences": "偏好设置",
|
||||
@ -52,7 +62,33 @@
|
||||
"changePassword": "修改密码",
|
||||
"currentPassword": "当前密码",
|
||||
"newPassword": "新密码",
|
||||
"confirmNewPassword": "确认新密码"
|
||||
"confirmNewPassword": "确认新密码",
|
||||
"profilePicture": "头像",
|
||||
"accessDenied": "访问被拒绝",
|
||||
"pleaseSignIn": "请登录以访问您的个人资料",
|
||||
"failedToLoadProfile": "加载个人资料失败",
|
||||
"checkEmailToConfirm": "请检查您的邮箱以确认更改",
|
||||
"updatedSuccessfully": "更新成功",
|
||||
"failedToUpdate": "更新失败",
|
||||
"passwordsNotMatch": "密码不匹配",
|
||||
"passwordMinLength": "密码至少需要6个字符",
|
||||
"passwordUpdatedSuccessfully": "密码更新成功",
|
||||
"failedToUpdatePassword": "密码更新失败",
|
||||
"uploading": "上传中...",
|
||||
"clickCameraToUpload": "点击相机图标上传新头像",
|
||||
"avatarUpdatedSuccessfully": "头像更新成功",
|
||||
"failedToUploadAvatar": "头像上传失败",
|
||||
"noUsernameSet": "未设置用户名",
|
||||
"enterUsername": "输入用户名",
|
||||
"enterEmail": "输入邮箱",
|
||||
"tellUsAboutYourself": "介绍一下自己",
|
||||
"charactersLimit": "个字符",
|
||||
"noBioAdded": "暂未添加个人简介",
|
||||
"enterNewPassword": "输入新密码",
|
||||
"confirmNewPassword": "确认新密码",
|
||||
"updatePassword": "更新密码",
|
||||
"english": "English",
|
||||
"chinese": "中文"
|
||||
},
|
||||
"studio": {
|
||||
"title": "AI 提示词工作室",
|
||||
@ -68,7 +104,11 @@
|
||||
"createVersion": "创建版本",
|
||||
"runTest": "运行测试",
|
||||
"noPrompts": "暂无提示词",
|
||||
"createFirstPrompt": "创建您的第一个提示词开始使用"
|
||||
"createFirstPrompt": "创建您的第一个提示词开始使用",
|
||||
"loadingStudio": "加载工作室中...",
|
||||
"searchPrompts": "搜索提示词...",
|
||||
"filter": "筛选",
|
||||
"clickRunTestToSee": "点击\"运行测试\"查看您的提示词结果"
|
||||
},
|
||||
"home": {
|
||||
"hero": {
|
||||
@ -112,7 +152,11 @@
|
||||
"每个提示词 10 个版本",
|
||||
"每月 20 美元 AI 积分"
|
||||
]
|
||||
}
|
||||
},
|
||||
"getStartedFree": "免费开始",
|
||||
"popular": "热门",
|
||||
"perMonth": "每月",
|
||||
"startProTrial": "开始专业试用"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "出现错误,请重试。",
|
||||
|
@ -125,18 +125,18 @@ export default function Home() {
|
||||
</li>
|
||||
</ul>
|
||||
<Button className="w-full" onClick={() => window.location.href = '/signup'}>
|
||||
Get Started Free
|
||||
{tPricing('getStartedFree')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="bg-primary p-8 rounded-lg shadow-sm text-primary-foreground relative">
|
||||
<div className="absolute top-4 right-4 bg-primary-foreground text-primary px-3 py-1 rounded-full text-xs font-semibold">
|
||||
Popular
|
||||
{tPricing('popular')}
|
||||
</div>
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold mb-2">{tPricing('pro.title')}</h3>
|
||||
<div className="text-4xl font-bold mb-2">{tPricing('pro.price')}</div>
|
||||
<p className="text-primary-foreground/80">per month</p>
|
||||
<p className="text-primary-foreground/80">{tPricing('perMonth')}</p>
|
||||
</div>
|
||||
<ul className="space-y-3 mb-8">
|
||||
<li className="flex items-center">
|
||||
@ -157,7 +157,7 @@ export default function Home() {
|
||||
</li>
|
||||
</ul>
|
||||
<Button variant="outline" className="w-full bg-primary-foreground text-primary hover:bg-primary-foreground/90" onClick={() => window.location.href = '/signup'}>
|
||||
Start Pro Trial
|
||||
{tPricing('startProTrial')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { createClient } from '@/lib/supabase'
|
||||
import { Header } from '@/components/layout/Header'
|
||||
@ -24,6 +25,9 @@ interface UserProfile {
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { user, loading } = useAuth()
|
||||
const t = useTranslations('profile')
|
||||
const tCommon = useTranslations('common')
|
||||
const tAuth = useTranslations('auth')
|
||||
const [profile, setProfile] = useState<UserProfile | null>(null)
|
||||
const [isEditing, setIsEditing] = useState({
|
||||
username: false,
|
||||
@ -90,7 +94,7 @@ export default function ProfilePage() {
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error loading profile:', error)
|
||||
setSaveStatus({type: 'error', message: 'Failed to load profile'})
|
||||
setSaveStatus({type: 'error', message: t('failedToLoadProfile')})
|
||||
} finally {
|
||||
setProfileLoading(false)
|
||||
}
|
||||
@ -109,14 +113,14 @@ export default function ProfilePage() {
|
||||
if (field === 'email') {
|
||||
const { error } = await supabase.auth.updateUser({ email: value })
|
||||
if (error) throw error
|
||||
setSaveStatus({type: 'success', message: 'Check your email to confirm the change'})
|
||||
setSaveStatus({type: 'success', message: t('checkEmailToConfirm')})
|
||||
} else {
|
||||
updates[field] = value
|
||||
const { error } = await supabase.auth.updateUser({
|
||||
data: updates
|
||||
})
|
||||
if (error) throw error
|
||||
setSaveStatus({type: 'success', message: `${field} updated successfully`})
|
||||
setSaveStatus({type: 'success', message: `${field} ${t('updatedSuccessfully')}`})
|
||||
}
|
||||
|
||||
// Reload profile
|
||||
@ -126,7 +130,7 @@ export default function ProfilePage() {
|
||||
setIsEditing(prev => ({ ...prev, [field]: false }))
|
||||
|
||||
} catch (error: unknown) {
|
||||
setSaveStatus({type: 'error', message: (error instanceof Error ? error.message : 'Unknown error') || `Failed to update ${field}`})
|
||||
setSaveStatus({type: 'error', message: (error instanceof Error ? error.message : 'Unknown error') || `${t('failedToUpdate')} ${field}`})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setFieldLoading(prev => ({ ...prev, [field]: false }))
|
||||
@ -135,12 +139,12 @@ export default function ProfilePage() {
|
||||
|
||||
const updatePassword = async () => {
|
||||
if (!formData.newPassword || formData.newPassword !== formData.confirmPassword) {
|
||||
setSaveStatus({type: 'error', message: 'Passwords do not match'})
|
||||
setSaveStatus({type: 'error', message: t('passwordsNotMatch')})
|
||||
return
|
||||
}
|
||||
|
||||
if (formData.newPassword.length < 6) {
|
||||
setSaveStatus({type: 'error', message: 'Password must be at least 6 characters'})
|
||||
setSaveStatus({type: 'error', message: t('passwordMinLength')})
|
||||
return
|
||||
}
|
||||
|
||||
@ -155,7 +159,7 @@ export default function ProfilePage() {
|
||||
|
||||
if (error) throw error
|
||||
|
||||
setSaveStatus({type: 'success', message: 'Password updated successfully'})
|
||||
setSaveStatus({type: 'success', message: t('passwordUpdatedSuccessfully')})
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
currentPassword: '',
|
||||
@ -165,7 +169,7 @@ export default function ProfilePage() {
|
||||
setIsEditing(prev => ({ ...prev, password: false }))
|
||||
|
||||
} catch (error: unknown) {
|
||||
setSaveStatus({type: 'error', message: (error instanceof Error ? error.message : 'Unknown error') || 'Failed to update password'})
|
||||
setSaveStatus({type: 'error', message: (error instanceof Error ? error.message : 'Unknown error') || t('failedToUpdatePassword')})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setFieldLoading(prev => ({ ...prev, password: false }))
|
||||
@ -198,14 +202,14 @@ export default function ProfilePage() {
|
||||
if (error) throw error
|
||||
|
||||
await loadProfile()
|
||||
setSaveStatus({type: 'success', message: 'Avatar updated successfully'})
|
||||
setSaveStatus({type: 'success', message: t('avatarUpdatedSuccessfully')})
|
||||
setIsLoading(false)
|
||||
setAvatarUploading(false)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
|
||||
} catch (error: unknown) {
|
||||
setSaveStatus({type: 'error', message: (error instanceof Error ? error.message : 'Unknown error') || 'Failed to upload avatar'})
|
||||
setSaveStatus({type: 'error', message: (error instanceof Error ? error.message : 'Unknown error') || t('failedToUploadAvatar')})
|
||||
setIsLoading(false)
|
||||
setAvatarUploading(false)
|
||||
}
|
||||
@ -223,10 +227,10 @@ export default function ProfilePage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-foreground mb-4">Access Denied</h1>
|
||||
<p className="text-muted-foreground mb-4">Please sign in to access your profile</p>
|
||||
<h1 className="text-2xl font-bold text-foreground mb-4">{t('accessDenied')}</h1>
|
||||
<p className="text-muted-foreground mb-4">{t('pleaseSignIn')}</p>
|
||||
<Button onClick={() => window.location.href = '/signin'}>
|
||||
Sign In
|
||||
{tAuth('signIn')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -239,8 +243,8 @@ export default function ProfilePage() {
|
||||
|
||||
<div className="max-w-4xl mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">Profile Settings</h1>
|
||||
<p className="text-muted-foreground">Manage your account settings and preferences</p>
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">{t('title')}</h1>
|
||||
<p className="text-muted-foreground">{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
{saveStatus.type && (
|
||||
@ -261,7 +265,7 @@ export default function ProfilePage() {
|
||||
) : (
|
||||
<LoadingOverlay isLoading={avatarUploading}>
|
||||
<div className="bg-card p-6 rounded-lg border border-border">
|
||||
<h2 className="text-xl font-semibold text-foreground mb-4">Profile Picture</h2>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-4">{t('profilePicture')}</h2>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative">
|
||||
<Avatar
|
||||
@ -286,7 +290,7 @@ export default function ProfilePage() {
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-2 text-center">
|
||||
{avatarUploading ? 'Uploading...' : 'Click the camera icon to upload a new picture'}
|
||||
{avatarUploading ? t('uploading') : t('clickCameraToUpload')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -303,7 +307,7 @@ export default function ProfilePage() {
|
||||
<LoadingOverlay isLoading={fieldLoading.username}>
|
||||
<div className="bg-card p-6 rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">Username</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('username')}</h3>
|
||||
{!isEditing.username && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -311,7 +315,7 @@ export default function ProfilePage() {
|
||||
onClick={() => setIsEditing(prev => ({ ...prev, username: true }))}
|
||||
disabled={isLoading || avatarUploading}
|
||||
>
|
||||
Edit
|
||||
{tCommon('edit')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -321,7 +325,7 @@ export default function ProfilePage() {
|
||||
<Input
|
||||
value={formData.username}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value }))}
|
||||
placeholder="Enter username"
|
||||
placeholder={t('enterUsername')}
|
||||
disabled={fieldLoading.username}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
@ -335,7 +339,7 @@ export default function ProfilePage() {
|
||||
) : (
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Save
|
||||
{tCommon('save')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -346,12 +350,12 @@ export default function ProfilePage() {
|
||||
}}
|
||||
disabled={fieldLoading.username}
|
||||
>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-foreground">{profile?.username || 'No username set'}</p>
|
||||
<p className="text-foreground">{profile?.username || t('noUsernameSet')}</p>
|
||||
)}
|
||||
</div>
|
||||
</LoadingOverlay>
|
||||
@ -364,7 +368,7 @@ export default function ProfilePage() {
|
||||
<LoadingOverlay isLoading={fieldLoading.email}>
|
||||
<div className="bg-card p-6 rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">Email</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('email')}</h3>
|
||||
{!isEditing.email && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -372,7 +376,7 @@ export default function ProfilePage() {
|
||||
onClick={() => setIsEditing(prev => ({ ...prev, email: true }))}
|
||||
disabled={isLoading || avatarUploading}
|
||||
>
|
||||
Edit
|
||||
{tCommon('edit')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -383,7 +387,7 @@ export default function ProfilePage() {
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
||||
placeholder="Enter email"
|
||||
placeholder={t('enterEmail')}
|
||||
disabled={fieldLoading.email}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
@ -397,7 +401,7 @@ export default function ProfilePage() {
|
||||
) : (
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Save
|
||||
{tCommon('save')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -408,7 +412,7 @@ export default function ProfilePage() {
|
||||
}}
|
||||
disabled={fieldLoading.email}
|
||||
>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -426,7 +430,7 @@ export default function ProfilePage() {
|
||||
<LoadingOverlay isLoading={fieldLoading.bio}>
|
||||
<div className="bg-card p-6 rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">Bio</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('bio')}</h3>
|
||||
{!isEditing.bio && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -434,7 +438,7 @@ export default function ProfilePage() {
|
||||
onClick={() => setIsEditing(prev => ({ ...prev, bio: true }))}
|
||||
disabled={isLoading || avatarUploading}
|
||||
>
|
||||
Edit
|
||||
{tCommon('edit')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -444,13 +448,13 @@ export default function ProfilePage() {
|
||||
<Textarea
|
||||
value={formData.bio}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, bio: e.target.value }))}
|
||||
placeholder="Tell us about yourself"
|
||||
placeholder={t('tellUsAboutYourself')}
|
||||
className="min-h-[100px]"
|
||||
maxLength={500}
|
||||
disabled={fieldLoading.bio}
|
||||
/>
|
||||
<div className="text-sm text-muted-foreground text-right">
|
||||
{formData.bio.length}/500 characters
|
||||
{formData.bio.length}/500 {t('charactersLimit')}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
@ -463,7 +467,7 @@ export default function ProfilePage() {
|
||||
) : (
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Save
|
||||
{tCommon('save')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -474,12 +478,12 @@ export default function ProfilePage() {
|
||||
}}
|
||||
disabled={fieldLoading.bio}
|
||||
>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-foreground">{profile?.bio || 'No bio added yet'}</p>
|
||||
<p className="text-foreground">{profile?.bio || t('noBioAdded')}</p>
|
||||
)}
|
||||
</div>
|
||||
</LoadingOverlay>
|
||||
@ -492,7 +496,7 @@ export default function ProfilePage() {
|
||||
<LoadingOverlay isLoading={fieldLoading.language}>
|
||||
<div className="bg-card p-6 rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">Language</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('language')}</h3>
|
||||
{fieldLoading.language ? (
|
||||
<LoadingSpinner size="sm" />
|
||||
) : (
|
||||
@ -510,8 +514,8 @@ export default function ProfilePage() {
|
||||
className="w-full px-3 py-2 border border-border rounded-md bg-input text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
disabled={isLoading || fieldLoading.language || avatarUploading}
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="zh">中文</option>
|
||||
<option value="en">{t('english')}</option>
|
||||
<option value="zh">{t('chinese')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</LoadingOverlay>
|
||||
@ -524,7 +528,7 @@ export default function ProfilePage() {
|
||||
<LoadingOverlay isLoading={fieldLoading.password}>
|
||||
<div className="bg-card p-6 rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">Password</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">{tAuth('password')}</h3>
|
||||
{!isEditing.password && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -532,7 +536,7 @@ export default function ProfilePage() {
|
||||
onClick={() => setIsEditing(prev => ({ ...prev, password: true }))}
|
||||
disabled={isLoading || avatarUploading}
|
||||
>
|
||||
Change Password
|
||||
{t('changePassword')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -540,14 +544,14 @@ export default function ProfilePage() {
|
||||
{isEditing.password ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="newPassword">New Password</Label>
|
||||
<Label htmlFor="newPassword">{t('newPassword')}</Label>
|
||||
<div className="relative mt-1">
|
||||
<Input
|
||||
id="newPassword"
|
||||
type={showPasswords.new ? 'text' : 'password'}
|
||||
value={formData.newPassword}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, newPassword: e.target.value }))}
|
||||
placeholder="Enter new password"
|
||||
placeholder={t('enterNewPassword')}
|
||||
className="pr-10"
|
||||
disabled={fieldLoading.password}
|
||||
/>
|
||||
@ -563,14 +567,14 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="confirmPassword">Confirm New Password</Label>
|
||||
<Label htmlFor="confirmPassword">{t('confirmNewPassword')}</Label>
|
||||
<div className="relative mt-1">
|
||||
<Input
|
||||
id="confirmPassword"
|
||||
type={showPasswords.confirm ? 'text' : 'password'}
|
||||
value={formData.confirmPassword}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))}
|
||||
placeholder="Confirm new password"
|
||||
placeholder={t('confirmNewPassword')}
|
||||
className="pr-10"
|
||||
disabled={fieldLoading.password}
|
||||
/>
|
||||
@ -596,7 +600,7 @@ export default function ProfilePage() {
|
||||
) : (
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Update Password
|
||||
{t('updatePassword')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -612,7 +616,7 @@ export default function ProfilePage() {
|
||||
}}
|
||||
disabled={fieldLoading.password}
|
||||
>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@ -24,6 +25,8 @@ import {
|
||||
|
||||
export default function StudioPage() {
|
||||
const { user, loading } = useAuth()
|
||||
const t = useTranslations('studio')
|
||||
const tCommon = useTranslations('common')
|
||||
const [promptContent, setPromptContent] = useState('')
|
||||
const [promptTitle, setPromptTitle] = useState('Untitled Prompt')
|
||||
const [testResult, setTestResult] = useState('')
|
||||
@ -42,7 +45,7 @@ export default function StudioPage() {
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<LoadingSpinner size="lg" />
|
||||
<p className="mt-4 text-muted-foreground">Loading Studio...</p>
|
||||
<p className="mt-4 text-muted-foreground">{t('loadingStudio')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -123,7 +126,7 @@ export default function StudioPage() {
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search prompts..."
|
||||
placeholder={t('searchPrompts')}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
@ -132,7 +135,7 @@ export default function StudioPage() {
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Button size="sm" variant="outline">
|
||||
<Filter className="h-4 w-4 mr-1" />
|
||||
Filter
|
||||
{t('filter')}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline">
|
||||
<Folder className="h-4 w-4 mr-1" />
|
||||
@ -206,7 +209,7 @@ export default function StudioPage() {
|
||||
) : (
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Save
|
||||
{tCommon('save')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -219,7 +222,7 @@ export default function StudioPage() {
|
||||
) : (
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Run Test
|
||||
{t('runTest')}
|
||||
</Button>
|
||||
|
||||
<Button size="sm" variant="outline">
|
||||
@ -307,7 +310,7 @@ Please write a professional email that..."
|
||||
<div>
|
||||
<Zap className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">
|
||||
Click "Run Test" to see your prompt results
|
||||
{t('clickRunTestToSee')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { createClient } from '@/lib/supabase'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@ -29,7 +30,9 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
|
||||
const t = useTranslations('auth')
|
||||
const tCommon = useTranslations('common')
|
||||
const supabase = createClient()
|
||||
|
||||
const handleGoogleSignIn = async () => {
|
||||
@ -45,7 +48,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
})
|
||||
if (error) throw error
|
||||
} catch (error: unknown) {
|
||||
setError(error instanceof Error ? error.message : 'An error occurred')
|
||||
setError(error instanceof Error ? error.message : t('errorOccurred'))
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@ -56,7 +59,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
setError('')
|
||||
|
||||
if (mode === 'signup' && password !== confirmPassword) {
|
||||
setError('Passwords do not match')
|
||||
setError(t('passwordsNotMatch'))
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
@ -77,10 +80,10 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
})
|
||||
if (error) throw error
|
||||
// Show success message for sign up
|
||||
setError('Check your email for verification link')
|
||||
setError(t('checkEmailVerification'))
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
setError(error instanceof Error ? error.message : 'An error occurred')
|
||||
setError(error instanceof Error ? error.message : t('errorOccurred'))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@ -91,19 +94,19 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
<div className="bg-background p-8 rounded-lg shadow-sm border border-border dark:bg-background">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-foreground mb-2">
|
||||
{mode === 'signin' ? 'Sign In' : 'Create Account'}
|
||||
{mode === 'signin' ? t('signInTitle') : t('signUpTitle')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{mode === 'signin'
|
||||
? 'Welcome back to Prmbr'
|
||||
: 'Start building better prompts today'
|
||||
? t('signInSubtitle')
|
||||
: t('signUpSubtitle')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Label htmlFor="email">{t('email')}</Label>
|
||||
<div className="relative mt-1">
|
||||
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
|
||||
<Input
|
||||
@ -111,7 +114,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
placeholder={t('emailPlaceholder')}
|
||||
className="pl-10"
|
||||
required
|
||||
/>
|
||||
@ -119,7 +122,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Label htmlFor="password">{t('password')}</Label>
|
||||
<div className="relative mt-1">
|
||||
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
|
||||
<Input
|
||||
@ -127,7 +130,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
className="pl-10 pr-10"
|
||||
required
|
||||
/>
|
||||
@ -143,7 +146,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
|
||||
{mode === 'signup' && (
|
||||
<div>
|
||||
<Label htmlFor="confirmPassword">Confirm Password</Label>
|
||||
<Label htmlFor="confirmPassword">{t('confirmPassword')}</Label>
|
||||
<div className="relative mt-1">
|
||||
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
|
||||
<Input
|
||||
@ -151,7 +154,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
className="pl-10"
|
||||
required
|
||||
/>
|
||||
@ -170,7 +173,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
className="w-full"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading...' : mode === 'signin' ? 'Sign In' : 'Create Account'}
|
||||
{loading ? tCommon('loading') : mode === 'signin' ? t('signIn') : t('signUp')}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@ -179,7 +182,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
<div className="w-full border-t border-border"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="bg-background px-2 text-muted-foreground">OR</span>
|
||||
<span className="bg-background px-2 text-muted-foreground">{t('or')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -191,7 +194,7 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
disabled={loading}
|
||||
>
|
||||
<GoogleIcon />
|
||||
<span className="ml-2">Continue with Google</span>
|
||||
<span className="ml-2">{t('continueWithGoogle')}</span>
|
||||
</Button>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
@ -200,8 +203,8 @@ export function AuthForm({ mode, onToggleMode }: AuthFormProps) {
|
||||
className="text-sm text-primary hover:text-primary/80 underline-offset-4 hover:underline"
|
||||
>
|
||||
{mode === 'signin'
|
||||
? "Don't have an account? Sign up"
|
||||
: 'Already have an account? Sign in'
|
||||
? t('noAccountSignUp')
|
||||
: t('hasAccountSignIn')
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user