Skip to content

Commit e3ebe50

Browse files
committed
feat: email login
1 parent c9433f4 commit e3ebe50

File tree

13 files changed

+484
-137
lines changed

13 files changed

+484
-137
lines changed

frontend/src/components/SiliconFlowLoginCard.vue

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
<script setup lang="ts">
2+
import { useScriptTag } from '@vueuse/core'
23
import { useI18n } from 'vue-i18n'
34
import Captcha from '@/components/Captcha.vue'
45
import { Button } from '@/components/ui/button'
56
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
7+
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
68
import { Input } from '@/components/ui/input'
7-
import { useSiliconFlowStore } from '@/stores'
9+
import { useSiliconFlowStore, useThemeStore } from '@/stores'
810
9-
const { t } = useI18n()
11+
const { t, locale } = useI18n()
1012
const siliconFlowStore = useSiliconFlowStore()
13+
const themeStore = useThemeStore()
14+
15+
useScriptTag('http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js')
16+
17+
function wxLogin() {
18+
setTimeout(() => {
19+
// @ts-expect-error WeChat SDK
20+
// eslint-disable-next-line no-new
21+
new window.WxLogin({
22+
self_redirect: !1,
23+
id: 'SF_wx_login_qr_code_f',
24+
appid: 'wx637ec58e4e15a258',
25+
scope: 'snsapi_login',
26+
style: themeStore.isDark ? 'white' : 'black',
27+
lang: locale.value === 'zh-CN' ? 'cn' : 'en',
28+
// stylelite: 1,
29+
// fast_login: 1,
30+
redirect_uri: encodeURIComponent('https://account.siliconflow.cn/api/open/weixin'),
31+
})
32+
}, 500)
33+
}
1134
</script>
1235

1336
<template>
@@ -17,11 +40,12 @@ const siliconFlowStore = useSiliconFlowStore()
1740
{{ t('siliconFlow.loginTitle') }}
1841
</CardTitle>
1942
<CardDescription class="text-sm">
20-
{{ t('siliconFlow.loginDescription') }}
43+
{{ siliconFlowStore.isEmailLogin ? t('siliconFlow.emailLoginDescription') : t('siliconFlow.loginDescription') }}
2144
</CardDescription>
2245
</CardHeader>
2346
<CardContent class="space-y-4">
24-
<div class="flex rounded-md border border-input bg-background">
47+
<!-- Phone Number Input -->
48+
<div v-if="!siliconFlowStore.isEmailLogin" class="flex rounded-md border border-input bg-background">
2549
<div class="flex items-center px-3 border-r border-input bg-muted/50 rounded-l-md">
2650
<span class="text-sm font-medium text-muted-foreground">+86</span>
2751
</div>
@@ -30,21 +54,34 @@ const siliconFlowStore = useSiliconFlowStore()
3054
class="border-0 rounded-l-none focus-visible:ring-0 focus-visible:ring-offset-0 h-10"
3155
/>
3256
</div>
57+
58+
<!-- Email Input -->
59+
<div v-if="siliconFlowStore.isEmailLogin" class="flex rounded-md border border-input bg-background">
60+
<Input
61+
id="email" v-model="siliconFlowStore.email" :placeholder="t('siliconFlow.emailAddressPlaceholder')" type="email"
62+
class="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-10"
63+
/>
64+
</div>
65+
66+
<!-- SMS/Email Code Input -->
3367
<div class="flex rounded-md border border-input bg-background">
3468
<Input
35-
id="sms" v-model="siliconFlowStore.smsCode" :placeholder="t('siliconFlow.smsCode')" type="text" maxlength="6"
69+
id="sms" v-model="siliconFlowStore.smsCode"
70+
:placeholder="siliconFlowStore.isEmailLogin ? t('siliconFlow.emailCode') : t('siliconFlow.smsCode')"
71+
type="text" maxlength="6"
3672
class="w-fit flex-grow border-0 rounded-r-none focus-visible:ring-0 focus-visible:ring-offset-0 h-10"
3773
/>
3874
<div class="border-l border-input">
3975
<Captcha
40-
:enabled="siliconFlowStore.phoneNumber.length > 0" :config="siliconFlowStore.captchaConfig"
76+
:enabled="siliconFlowStore.isEmailLogin ? siliconFlowStore.email.length > 0 : siliconFlowStore.phoneNumber.length > 0"
77+
:config="siliconFlowStore.captchaConfig"
4178
class="h-10 px-4 bg-muted/50 rounded-r-md border-0 text-xs text-primary hover:bg-muted/70 transition-colors disabled:opacity-50"
4279
@next="siliconFlowStore.sendSMS"
4380
/>
4481
</div>
4582
</div>
4683
</CardContent>
47-
<CardFooter class="flex flex-col space-y-3 pt-3">
84+
<CardFooter class="flex flex-col space-y-3 pt-3 items-start">
4885
<div class="flex items-center space-x-2 text-xs text-muted-foreground">
4986
<input id="agree" v-model="siliconFlowStore.agreed" type="checkbox" class="h-3 w-3 rounded border border-input">
5087
<label for="agree" class="flex items-center gap-1 cursor-pointer">
@@ -60,14 +97,33 @@ const siliconFlowStore = useSiliconFlowStore()
6097
>{{ t('siliconFlow.privacyPolicy') }}</a>
6198
</label>
6299
</div>
63-
<Button class="w-full h-10" :disabled="!siliconFlowStore.canLogin" @click="siliconFlowStore.login">
100+
<Button class="w-full h-10" :disabled="!siliconFlowStore.canLogin" @click="siliconFlowStore.login()">
64101
<span v-if="siliconFlowStore.isLoading">{{ t('siliconFlow.loggingIn') }}</span>
65102
<span v-else>{{ t('siliconFlow.registerLogin') }}</span>
66103
</Button>
67-
<div class="flex items-center space-x-2 text-xs text-muted-foreground">
104+
<div class="w-full grid grid-cols-2 gap-3">
105+
<Dialog>
106+
<DialogTrigger>
107+
<Button variant="outline" class="w-full h-10" @click="wxLogin">
108+
{{ t('siliconFlow.wechatLogin') }}
109+
</Button>
110+
</DialogTrigger>
111+
<DialogContent class="max-w-sm">
112+
<DialogHeader>
113+
<DialogTitle>{{ t('siliconFlow.wechatLogin') }}</DialogTitle>
114+
<DialogClose />
115+
</DialogHeader>
116+
<div id="SF_wx_login_qr_code_f" class="w-full flex justify-center" />
117+
</DialogContent>
118+
</Dialog>
119+
<Button variant="outline" class="w-full h-10" @click="siliconFlowStore.isEmailLogin = !siliconFlowStore.isEmailLogin">
120+
{{ siliconFlowStore.isEmailLogin ? t('siliconFlow.phoneLogin') : t('siliconFlow.emailLogin') }}
121+
</Button>
122+
</div>
123+
<!-- <div class="flex items-center space-x-2 text-xs text-muted-foreground">
68124
<input id="keep" v-model="siliconFlowStore.keepLogin" type="checkbox" class="h-3 w-3 rounded border border-input" checked>
69125
<label for="keep" class="cursor-pointer">{{ t('siliconFlow.keepLoggedIn30Days') }}</label>
70-
</div>
126+
</div> -->
71127
</CardFooter>
72128
</Card>
73129
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import type { TabsRootEmits, TabsRootProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { TabsRoot, useForwardPropsEmits } from "reka-ui"
6+
import { cn } from "@/lib/utils"
7+
8+
const props = defineProps<TabsRootProps & { class?: HTMLAttributes["class"] }>()
9+
const emits = defineEmits<TabsRootEmits>()
10+
11+
const delegatedProps = reactiveOmit(props, "class")
12+
const forwarded = useForwardPropsEmits(delegatedProps, emits)
13+
</script>
14+
15+
<template>
16+
<TabsRoot
17+
data-slot="tabs"
18+
v-bind="forwarded"
19+
:class="cn('flex flex-col gap-2', props.class)"
20+
>
21+
<slot />
22+
</TabsRoot>
23+
</template>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import type { TabsContentProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { TabsContent } from "reka-ui"
6+
import { cn } from "@/lib/utils"
7+
8+
const props = defineProps<TabsContentProps & { class?: HTMLAttributes["class"] }>()
9+
10+
const delegatedProps = reactiveOmit(props, "class")
11+
</script>
12+
13+
<template>
14+
<TabsContent
15+
data-slot="tabs-content"
16+
:class="cn('flex-1 outline-none', props.class)"
17+
v-bind="delegatedProps"
18+
>
19+
<slot />
20+
</TabsContent>
21+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import type { TabsListProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { TabsList } from "reka-ui"
6+
import { cn } from "@/lib/utils"
7+
8+
const props = defineProps<TabsListProps & { class?: HTMLAttributes["class"] }>()
9+
10+
const delegatedProps = reactiveOmit(props, "class")
11+
</script>
12+
13+
<template>
14+
<TabsList
15+
data-slot="tabs-list"
16+
v-bind="delegatedProps"
17+
:class="cn(
18+
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
19+
props.class,
20+
)"
21+
>
22+
<slot />
23+
</TabsList>
24+
</template>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup lang="ts">
2+
import type { TabsTriggerProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import { reactiveOmit } from "@vueuse/core"
5+
import { TabsTrigger, useForwardProps } from "reka-ui"
6+
import { cn } from "@/lib/utils"
7+
8+
const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes["class"] }>()
9+
10+
const delegatedProps = reactiveOmit(props, "class")
11+
12+
const forwardedProps = useForwardProps(delegatedProps)
13+
</script>
14+
15+
<template>
16+
<TabsTrigger
17+
data-slot="tabs-trigger"
18+
v-bind="forwardedProps"
19+
:class="cn(
20+
`data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`,
21+
props.class,
22+
)"
23+
>
24+
<slot />
25+
</TabsTrigger>
26+
</template>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { default as Tabs } from "./Tabs.vue"
2+
export { default as TabsContent } from "./TabsContent.vue"
3+
export { default as TabsList } from "./TabsList.vue"
4+
export { default as TabsTrigger } from "./TabsTrigger.vue"

frontend/src/locales/en-US.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,10 @@
189189
"logout": "Logout",
190190
"loginTitle": "Login to SiliconFlow",
191191
"loginDescription": "Login using phone number and SMS verification code",
192+
"emailLoginDescription": "Login using email address and verification code",
192193
"phoneNumber": "Your phone number",
193194
"smsCode": "SMS verification code",
195+
"emailCode": "Email verification code",
194196
"agreeToTerms": "I agree to SiliconFlow's",
195197
"userAgreement": "User Agreement",
196198
"and": "and",
@@ -266,7 +268,12 @@
266268
"auth-success": "Verified",
267269
"not-authenticated": "Not verified",
268270
"realNameRequired": "Real-name verification required before recharge",
269-
"accountRechargeDescription": "Recharge to your SiliconFlow account"
271+
"accountRechargeDescription": "Recharge to your SiliconFlow account",
272+
"wechatLogin": "WeChat",
273+
"emailLogin": "Email Login",
274+
"phoneLogin": "Phone Login",
275+
"emailAddress": "Email Address",
276+
"emailAddressPlaceholder": "Your email address"
270277
},
271278
"presets": {
272279
"preset": "Preset",

frontend/src/locales/zh-CN.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,10 @@
189189
"logout": "退出登录",
190190
"loginTitle": "登录 硅基流动",
191191
"loginDescription": "使用手机号码和短信验证码登录",
192+
"emailLoginDescription": "使用邮箱地址和验证码登录",
192193
"phoneNumber": "您的手机号",
193194
"smsCode": "短信验证码",
195+
"emailCode": "邮箱验证码",
194196
"agreeToTerms": "我同意硅基流动的",
195197
"userAgreement": "用户协议",
196198
"and": "",
@@ -266,7 +268,12 @@
266268
"auth-success": "已认证",
267269
"not-authenticated": "未认证",
268270
"realNameRequired": "实名认证后,才能充值",
269-
"accountRechargeDescription": "向硅基流动账户充值"
271+
"accountRechargeDescription": "向硅基流动账户充值",
272+
"wechatLogin": "微信登录",
273+
"emailLogin": "邮箱登录",
274+
"phoneLogin": "短信登录",
275+
"emailAddress": "邮箱地址",
276+
"emailAddressPlaceholder": "您的邮箱地址"
270277
},
271278
"presets": {
272279
"preset": "预设",

0 commit comments

Comments
 (0)