mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-02 08:06:38 +08:00
微调样式
This commit is contained in:
parent
a697822445
commit
b7a216c35b
@ -1,23 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { signIn } from "next-auth/react";
|
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 { isName } from "@/lib/auth_list";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
Tabs,
|
Tabs,
|
||||||
Input,
|
Input,
|
||||||
|
Button,
|
||||||
InputRef,
|
InputRef,
|
||||||
notification as notificationModule,
|
notification as notificationModule,
|
||||||
NotificationArgsProps,
|
NotificationArgsProps,
|
||||||
} from "antd";
|
} 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 type { FormProps, TabsProps } from "antd";
|
||||||
import { SignInOptions } from "next-auth/react";
|
import { SignInOptions } from "next-auth/react";
|
||||||
import { getSession } from "next-auth/react";
|
import { getSession } from "next-auth/react";
|
||||||
|
|
||||||
export default function UserLoginCore() {
|
export default function UserLoginCore() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [capLoading, setCapLoading] = useState(false);
|
||||||
|
const [timeLeft, setTimeLeft] = useState(0);
|
||||||
const [loginForm] = Form.useForm();
|
const [loginForm] = Form.useForm();
|
||||||
const [loginMethod, setLoginMethod] = useState<"common" | "cap">("common");
|
const [loginMethod, setLoginMethod] = useState<"common" | "cap">("common");
|
||||||
const [notification, notificationContextHolder] =
|
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);
|
// const [error, setError] = useState(false);
|
||||||
type FieldType = {
|
type FieldType = {
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
cap?: string;
|
||||||
};
|
};
|
||||||
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
|
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -52,7 +102,20 @@ export default function UserLoginCore() {
|
|||||||
|
|
||||||
if (loginMethod === "cap") {
|
if (loginMethod === "cap") {
|
||||||
loginProvider = "email";
|
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 {
|
} else {
|
||||||
loginProvider = "credentials";
|
loginProvider = "credentials";
|
||||||
signInOptions = {
|
signInOptions = {
|
||||||
@ -257,26 +320,63 @@ export default function UserLoginCore() {
|
|||||||
type: "email",
|
type: "email",
|
||||||
message: "The input is not valid E-mail!",
|
message: "The input is not valid E-mail!",
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// validator: async (_, value) => {
|
required: true,
|
||||||
// const username_value = loginForm.getFieldValue("username");
|
},
|
||||||
// if (value && username_value) {
|
|
||||||
// return Promise.reject(new Error("Field must be unique!"));
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={
|
prefix={
|
||||||
<MailOutlined style={{ color: "rgba(0,0,0,.25)" }} />
|
<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="邮箱验证,测试阶段"
|
placeholder="邮箱验证,测试阶段"
|
||||||
className={
|
className={
|
||||||
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</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>
|
</div>
|
||||||
|
@ -126,3 +126,7 @@ input {
|
|||||||
#set-password-form {
|
#set-password-form {
|
||||||
text-align: right !important;
|
text-align: right !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login-form .ant-input-group-addon {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
@ -550,8 +550,7 @@
|
|||||||
.chat-input-panel {
|
.chat-input-panel {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 10px 20px 25px;
|
||||||
padding-top: 10px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-top: var(--border-in-light);
|
border-top: var(--border-in-light);
|
||||||
@ -667,6 +666,10 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 100px;
|
right: 100px;
|
||||||
bottom: 32px;
|
bottom: 32px;
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
right: 110px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
@ -677,4 +680,20 @@
|
|||||||
.chat-input-send {
|
.chat-input-send {
|
||||||
bottom: 30px;
|
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>
|
</label>
|
||||||
</div>
|
</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 && (
|
{showExport && (
|
||||||
<ExportMessageModal onClose={() => setShowExport(false)} />
|
<ExportMessageModal onClose={() => setShowExport(false)} />
|
||||||
|
@ -677,7 +677,7 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["settings"]}>
|
<div className={styles["settings"]} id="user-settings">
|
||||||
<List>
|
<List>
|
||||||
<ListItem title={Locale.Settings.Avatar}>
|
<ListItem title={Locale.Settings.Avatar}>
|
||||||
<Popover
|
<Popover
|
||||||
|
@ -112,7 +112,7 @@ const cn = {
|
|||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter 换行";
|
inputHints += ",Shift + Enter 换行";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ 触发补全,: 触发命令";
|
return inputHints + ",/ 内置提示词";
|
||||||
},
|
},
|
||||||
Send: "发送",
|
Send: "发送",
|
||||||
Config: {
|
Config: {
|
||||||
|
@ -224,20 +224,20 @@ input[type="range"]::-ms-thumb:hover {
|
|||||||
@include thumbHover();
|
@include thumbHover();
|
||||||
}
|
}
|
||||||
|
|
||||||
//input[type="number"],
|
#user-settings {
|
||||||
//input[type="text"],
|
input[type="number"], input[type="text"], input[type="password"] {
|
||||||
//input[type="password"] {
|
appearance: none;
|
||||||
// appearance: none;
|
border-radius: 10px;
|
||||||
// border-radius: 10px;
|
border: var(--border-in-light);
|
||||||
// border: var(--border-in-light);
|
min-height: 36px;
|
||||||
// min-height: 36px;
|
box-sizing: border-box;
|
||||||
// box-sizing: border-box;
|
background: var(--white);
|
||||||
// background: var(--white);
|
color: var(--black);
|
||||||
// color: var(--black);
|
padding: 0 10px;
|
||||||
// padding: 0 10px;
|
max-width: 50%;
|
||||||
// max-width: 50%;
|
font-family: inherit;
|
||||||
// font-family: inherit;
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
div.math {
|
div.math {
|
||||||
overflow-x: auto;
|
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 { isEmail, isName } from "@/lib/auth_list";
|
||||||
import {createTransport} from "nodemailer";
|
import {createTransport} from "nodemailer";
|
||||||
import { comparePassword, hashPassword } from "@/lib/utils";
|
import { comparePassword, hashPassword } from "@/lib/utils";
|
||||||
|
import { randomInt, randomBytes } from "crypto";
|
||||||
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
||||||
|
|
||||||
|
let verificationTokens = new Map();
|
||||||
|
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
@ -44,28 +46,38 @@ export const authOptions: NextAuthOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
from: process.env.EMAIL_FROM,
|
from: process.env.EMAIL_FROM,
|
||||||
|
maxAge: 5 * 60,
|
||||||
|
async generateVerificationToken() {
|
||||||
|
return randomBytes(4).toString("hex").substring(0, 4);
|
||||||
|
},
|
||||||
async sendVerificationRequest({
|
async sendVerificationRequest({
|
||||||
identifier: email,
|
identifier: email,
|
||||||
url,
|
url,
|
||||||
|
token,
|
||||||
provider: { server, from, name },
|
provider: { server, from, name },
|
||||||
theme,
|
theme,
|
||||||
}) {
|
}) {
|
||||||
/* your function */
|
|
||||||
console.log('send mail,', email, url, server, from, )
|
// const token = randomInt(1000, 10000);
|
||||||
const { host } = new URL(url)
|
|
||||||
const transport = createTransport(server)
|
verificationTokens.set(email, token);
|
||||||
const result = await transport.sendMail({
|
/* your function */
|
||||||
to: email,
|
const { host } = new URL(url)
|
||||||
from: from,
|
// console.log('send mail,-----', email, host, token )
|
||||||
subject: `Sign in to ${host}`,
|
|
||||||
html: email_html({ url, host, theme }),
|
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)
|
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
||||||
console.log('[result],', result)
|
console.log('[result],', result)
|
||||||
if (failed.length) {
|
if (failed.length) {
|
||||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
@ -290,6 +302,7 @@ function cleanPassword(input: string): string {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email HTML body
|
* Email HTML body
|
||||||
* Insert invisible space into domains from being turned into a hyperlink by email
|
* 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!
|
* @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 }) {
|
function email_text(params: { url: string, token: number|string, host: string}) {
|
||||||
const { url, host, theme } = params
|
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 escapedHost = host.replace(/\./g, "​.")
|
||||||
|
const escapedUrl = url.replace(/\./g, "​.")
|
||||||
|
|
||||||
const brandColor = theme.brandColor || "#346df1"
|
// const brandColor = theme.brandColor || "#346df1"
|
||||||
const color = {
|
// const color = {
|
||||||
background: "#f9f9f9",
|
// background: "#f9f9f9",
|
||||||
text: "#444",
|
// text: "#444",
|
||||||
mainBackground: "#fff",
|
// mainBackground: "#fff",
|
||||||
buttonBackground: brandColor,
|
// buttonBackground: brandColor,
|
||||||
buttonBorder: brandColor,
|
// buttonBorder: brandColor,
|
||||||
buttonText: theme.buttonText || "#fff",
|
// buttonText: theme.buttonText || "#fff",
|
||||||
}
|
// }
|
||||||
|
|
||||||
return `
|
return `<p>Sign in to <strong>${escapedHost}</strong></p>
|
||||||
<body style="background: ${color.background};">
|
<p>Your sign-in code is: <strong>${token}</strong></p>
|
||||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
<p>Or click on this link to sign in:</p>
|
||||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
<p><a href="${url}">${escapedUrl}</a></p>
|
||||||
<tr>
|
<p>If you did not request this email, you can safely ignore it.</p>`;
|
||||||
<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>
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user