mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-01 23:56:39 +08:00
微调样式
This commit is contained in:
parent
a697822445
commit
b7a216c35b
@ -1,23 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { signIn } from "next-auth/react";
|
||||
import React, { useState, useEffect, useRef, use } from "react";
|
||||
import React, { useState, useEffect, useRef, use, useMemo } from "react";
|
||||
import { isName } from "@/lib/auth_list";
|
||||
import {
|
||||
Form,
|
||||
Tabs,
|
||||
Input,
|
||||
Button,
|
||||
InputRef,
|
||||
notification as notificationModule,
|
||||
NotificationArgsProps,
|
||||
} from "antd";
|
||||
import { UserOutlined, MailOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
UserOutlined,
|
||||
MailOutlined,
|
||||
LoadingOutlined,
|
||||
AudioOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import type { FormProps, TabsProps } from "antd";
|
||||
import { SignInOptions } from "next-auth/react";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
export default function UserLoginCore() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [capLoading, setCapLoading] = useState(false);
|
||||
const [timeLeft, setTimeLeft] = useState(0);
|
||||
const [loginForm] = Form.useForm();
|
||||
const [loginMethod, setLoginMethod] = useState<"common" | "cap">("common");
|
||||
const [notification, notificationContextHolder] =
|
||||
@ -37,11 +45,53 @@ export default function UserLoginCore() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let timer = undefined;
|
||||
if (timeLeft > 0) {
|
||||
timer = setTimeout(() => {
|
||||
setTimeLeft(timeLeft - 1);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [timeLeft]);
|
||||
|
||||
const capIcon = useMemo(() => {
|
||||
if (capLoading) {
|
||||
return (
|
||||
<LoadingOutlined
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: "rgb(234, 149, 24)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}, [capLoading]);
|
||||
const sendCap = () => {
|
||||
loginForm.validateFields().then((values) => {
|
||||
setCapLoading(true);
|
||||
signIn("email", {
|
||||
redirect: false,
|
||||
email: values.email,
|
||||
}).then((result) => {
|
||||
console.log("33333333333", result);
|
||||
setCapLoading(false);
|
||||
setTimeLeft(60);
|
||||
});
|
||||
});
|
||||
|
||||
// const email = loginForm.getFieldValue("email");
|
||||
// console.log('----------', email)
|
||||
};
|
||||
|
||||
// const [error, setError] = useState(false);
|
||||
type FieldType = {
|
||||
username?: string;
|
||||
password?: string;
|
||||
email?: string;
|
||||
cap?: string;
|
||||
};
|
||||
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
|
||||
setLoading(true);
|
||||
@ -52,7 +102,20 @@ export default function UserLoginCore() {
|
||||
|
||||
if (loginMethod === "cap") {
|
||||
loginProvider = "email";
|
||||
signInOptions = { ...signInOptions, email: values.email };
|
||||
signInOptions = {
|
||||
...signInOptions,
|
||||
email: values.email,
|
||||
cap: values.cap,
|
||||
};
|
||||
fetch(
|
||||
`/api/auth/callback/email?token=${values.cap}&email=${values.email}`,
|
||||
).then((result) => {
|
||||
console.log("------------", result);
|
||||
if (result.redirected) {
|
||||
window.location.href = result.url;
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
loginProvider = "credentials";
|
||||
signInOptions = {
|
||||
@ -257,26 +320,63 @@ export default function UserLoginCore() {
|
||||
type: "email",
|
||||
message: "The input is not valid E-mail!",
|
||||
},
|
||||
// {
|
||||
// validator: async (_, value) => {
|
||||
// const username_value = loginForm.getFieldValue("username");
|
||||
// if (value && username_value) {
|
||||
// return Promise.reject(new Error("Field must be unique!"));
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={
|
||||
<MailOutlined style={{ color: "rgba(0,0,0,.25)" }} />
|
||||
}
|
||||
addonAfter={
|
||||
<button
|
||||
onClick={sendCap}
|
||||
disabled={capLoading || timeLeft > 0}
|
||||
className="align-bottom"
|
||||
>
|
||||
{capLoading ? (
|
||||
<span style={{ width: "70px" }}>
|
||||
<LoadingOutlined
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: "rgb(234, 149, 24)",
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
) : timeLeft > 0 ? (
|
||||
<span style={{ color: "gray" }}>
|
||||
{timeLeft}秒后重试
|
||||
</span>
|
||||
) : (
|
||||
"发送验证码"
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
size="middle"
|
||||
placeholder="邮箱验证,测试阶段"
|
||||
className={
|
||||
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="cap"
|
||||
rules={[
|
||||
{
|
||||
len: 4,
|
||||
message: "Make sure it's at 4 characters",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
size="middle"
|
||||
placeholder="验证码"
|
||||
className={
|
||||
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -126,3 +126,7 @@ input {
|
||||
#set-password-form {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
#login-form .ant-input-group-addon {
|
||||
background-color: white;
|
||||
}
|
||||
|
@ -550,8 +550,7 @@
|
||||
.chat-input-panel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
padding: 10px 20px 25px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
border-top: var(--border-in-light);
|
||||
@ -667,6 +666,10 @@
|
||||
position: absolute;
|
||||
right: 100px;
|
||||
bottom: 32px;
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
right: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
@ -677,4 +680,20 @@
|
||||
.chat-input-send {
|
||||
bottom: 30px;
|
||||
}
|
||||
.bottom-tip {
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-tip {
|
||||
text-align: center;
|
||||
margin-top: -25px;
|
||||
font-size: .75rem;
|
||||
line-height: 1.5rem;
|
||||
//margin-bottom: .5rem;
|
||||
//padding-top: .5rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #7d7d7d !important;
|
||||
}
|
||||
|
@ -1786,6 +1786,19 @@ function _Chat() {
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className={styles["bottom-tip"]}
|
||||
// style={{
|
||||
// color: "var(--text-secondary)",
|
||||
// fontSize: ".75rem",
|
||||
// lineHeight: "1rem",
|
||||
// textAlign: "center",
|
||||
// paddingBottom: ".5rem",
|
||||
// paddingTop: ".5rem"
|
||||
// }}
|
||||
>
|
||||
<span>AI 也可能会犯错。请核查重要信息。</span>
|
||||
</div>
|
||||
|
||||
{showExport && (
|
||||
<ExportMessageModal onClose={() => setShowExport(false)} />
|
||||
|
@ -677,7 +677,7 @@ export function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["settings"]}>
|
||||
<div className={styles["settings"]} id="user-settings">
|
||||
<List>
|
||||
<ListItem title={Locale.Settings.Avatar}>
|
||||
<Popover
|
||||
|
@ -112,7 +112,7 @@ const cn = {
|
||||
if (submitKey === String(SubmitKey.Enter)) {
|
||||
inputHints += ",Shift + Enter 换行";
|
||||
}
|
||||
return inputHints + ",/ 触发补全,: 触发命令";
|
||||
return inputHints + ",/ 内置提示词";
|
||||
},
|
||||
Send: "发送",
|
||||
Config: {
|
||||
|
@ -224,20 +224,20 @@ input[type="range"]::-ms-thumb:hover {
|
||||
@include thumbHover();
|
||||
}
|
||||
|
||||
//input[type="number"],
|
||||
//input[type="text"],
|
||||
//input[type="password"] {
|
||||
// appearance: none;
|
||||
// border-radius: 10px;
|
||||
// border: var(--border-in-light);
|
||||
// min-height: 36px;
|
||||
// box-sizing: border-box;
|
||||
// background: var(--white);
|
||||
// color: var(--black);
|
||||
// padding: 0 10px;
|
||||
// max-width: 50%;
|
||||
// font-family: inherit;
|
||||
//}
|
||||
#user-settings {
|
||||
input[type="number"], input[type="text"], input[type="password"] {
|
||||
appearance: none;
|
||||
border-radius: 10px;
|
||||
border: var(--border-in-light);
|
||||
min-height: 36px;
|
||||
box-sizing: border-box;
|
||||
background: var(--white);
|
||||
color: var(--black);
|
||||
padding: 0 10px;
|
||||
max-width: 50%;
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
div.math {
|
||||
overflow-x: auto;
|
||||
|
100
lib/auth.ts
100
lib/auth.ts
@ -8,8 +8,10 @@ import { User } from "@prisma/client";
|
||||
import { isEmail, isName } from "@/lib/auth_list";
|
||||
import {createTransport} from "nodemailer";
|
||||
import { comparePassword, hashPassword } from "@/lib/utils";
|
||||
import { randomInt, randomBytes } from "crypto";
|
||||
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
||||
|
||||
let verificationTokens = new Map();
|
||||
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
@ -44,28 +46,38 @@ export const authOptions: NextAuthOptions = {
|
||||
},
|
||||
},
|
||||
from: process.env.EMAIL_FROM,
|
||||
maxAge: 5 * 60,
|
||||
async generateVerificationToken() {
|
||||
return randomBytes(4).toString("hex").substring(0, 4);
|
||||
},
|
||||
async sendVerificationRequest({
|
||||
identifier: email,
|
||||
url,
|
||||
token,
|
||||
provider: { server, from, name },
|
||||
theme,
|
||||
}) {
|
||||
/* your function */
|
||||
console.log('send mail,', email, url, server, from, )
|
||||
const { host } = new URL(url)
|
||||
const transport = createTransport(server)
|
||||
const result = await transport.sendMail({
|
||||
to: email,
|
||||
from: from,
|
||||
subject: `Sign in to ${host}`,
|
||||
html: email_html({ url, host, theme }),
|
||||
})
|
||||
|
||||
// const token = randomInt(1000, 10000);
|
||||
|
||||
verificationTokens.set(email, token);
|
||||
/* your function */
|
||||
const { host } = new URL(url)
|
||||
// console.log('send mail,-----', email, host, token )
|
||||
|
||||
const transport = createTransport(server)
|
||||
const result = await transport.sendMail({
|
||||
to: email,
|
||||
from: from,
|
||||
subject: `Your sign-in code for ${host}`,
|
||||
text: email_text({url, token, host}),
|
||||
html: email_html({ url, token, host, theme }),
|
||||
})
|
||||
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
||||
console.log('[result],', result)
|
||||
if (failed.length) {
|
||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
||||
}
|
||||
|
||||
},
|
||||
}),
|
||||
CredentialsProvider({
|
||||
@ -290,6 +302,7 @@ function cleanPassword(input: string): string {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Email HTML body
|
||||
* Insert invisible space into domains from being turned into a hyperlink by email
|
||||
@ -298,50 +311,31 @@ function cleanPassword(input: string): string {
|
||||
*
|
||||
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
|
||||
*/
|
||||
function email_html(params: { url: string, host: string, theme: Theme }) {
|
||||
const { url, host, theme } = params
|
||||
function email_text(params: { url: string, token: number|string, host: string}) {
|
||||
const { url, token, host } = params;
|
||||
return `Sign in to ${host}\n\nYour sign-in code is: ${token}\n\nOr click on this link to sign in:\n${url}\n\n`;
|
||||
}
|
||||
|
||||
|
||||
function email_html(params: { url: string, token: number|string, host: string, theme: Theme }) {
|
||||
const { url, token, host, theme } = params
|
||||
|
||||
const escapedHost = host.replace(/\./g, "​.")
|
||||
const escapedUrl = url.replace(/\./g, "​.")
|
||||
|
||||
const brandColor = theme.brandColor || "#346df1"
|
||||
const color = {
|
||||
background: "#f9f9f9",
|
||||
text: "#444",
|
||||
mainBackground: "#fff",
|
||||
buttonBackground: brandColor,
|
||||
buttonBorder: brandColor,
|
||||
buttonText: theme.buttonText || "#fff",
|
||||
}
|
||||
// const brandColor = theme.brandColor || "#346df1"
|
||||
// const color = {
|
||||
// background: "#f9f9f9",
|
||||
// text: "#444",
|
||||
// mainBackground: "#fff",
|
||||
// buttonBackground: brandColor,
|
||||
// buttonBorder: brandColor,
|
||||
// buttonText: theme.buttonText || "#fff",
|
||||
// }
|
||||
|
||||
return `
|
||||
<body style="background: ${color.background};">
|
||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
Sign in to <strong>${escapedHost}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
||||
target="_blank"
|
||||
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
|
||||
in</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
If you did not request this email you can safely ignore it.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
`
|
||||
return `<p>Sign in to <strong>${escapedHost}</strong></p>
|
||||
<p>Your sign-in code is: <strong>${token}</strong></p>
|
||||
<p>Or click on this link to sign in:</p>
|
||||
<p><a href="${url}">${escapedUrl}</a></p>
|
||||
<p>If you did not request this email, you can safely ignore it.</p>`;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user