Merge pull request #35 from sijinhui/users

Users
This commit is contained in:
sijinhui 2024-03-26 10:00:57 +08:00 committed by GitHub
commit 32ab9af2a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 23 deletions

View File

@ -4,8 +4,11 @@ import { User } from "@prisma/client";
import UsersTable from "../../components/users-table"; import UsersTable from "../../components/users-table";
async function getData() { async function getData() {
const users = await prisma.user.findMany(); return await prisma.user.findMany({
return users; orderBy: {
createdAt: "desc",
},
});
} }
export default async function UsersPage() { export default async function UsersPage() {
const users: User[] = await getData(); const users: User[] = await getData();

View File

@ -36,21 +36,16 @@ const items: MenuItem[] = [
getItem("使用分析", "/admin/ana"), getItem("使用分析", "/admin/ana"),
]), ]),
getItem("Navigation Two", "sub2", <AppstoreOutlined />, [ getItem("管理", "manage", <AppstoreOutlined />, [
getItem("Option 5", "5"), getItem("用户管理", "/admin/users"),
getItem("Option 6", "6"),
getItem("Submenu", "sub3", null, [
getItem("Option 7", "7"),
getItem("Option 8", "8"),
]),
]), ]),
getItem("Navigation Three", "sub4", <SettingOutlined />, [ // getItem("Navigation Three", "sub4", <SettingOutlined />, [
getItem("Option 9", "9"), // getItem("Option 9", "9"),
getItem("Option 10", "10"), // getItem("Option 10", "10"),
getItem("Option 11", "11"), // getItem("Option 11", "11"),
getItem("Option 12", "12"), // getItem("Option 12", "12"),
]), // ]),
]; ];
const SideBar: React.FC = () => { const SideBar: React.FC = () => {
@ -68,12 +63,12 @@ const SideBar: React.FC = () => {
setCurrent(e.key); setCurrent(e.key);
router.push(e.key); router.push(e.key);
}; };
// useEffect(() => { useEffect(() => {
// // 如果按钮和路径不相等,那其实应该跳转到按钮的网址 // 如果按钮和路径不相等,那其实应该跳转到按钮的网址
// if (current != pathname) { if (current != pathname) {
// router.push(current); router.push(current);
// } }
// }, [current, pathname, router]); }, [current, pathname, router]);
return ( return (
<> <>

View File

@ -118,11 +118,13 @@ function UsersTable({ users }: UserInterface) {
}, },
onFilterDropdownOpenChange: (visible) => { onFilterDropdownOpenChange: (visible) => {
if (visible) { if (visible) {
// @ts-ignore
setTimeout(() => searchInput.current?.select(), 100); setTimeout(() => searchInput.current?.select(), 100);
} }
}, },
render: (text) => render: (text) =>
searchedColumn === dataIndex ? ( searchedColumn === dataIndex ? (
// @ts-ignore
<Highlighter <Highlighter
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }} highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
searchWords={[searchText]} searchWords={[searchText]}

View File

@ -1,10 +1,11 @@
import { getServerSession, type NextAuthOptions } from "next-auth"; import {getServerSession, type NextAuthOptions, Theme} from "next-auth";
import GitHubProvider from "next-auth/providers/github"; import GitHubProvider from "next-auth/providers/github";
import EmailProvider from "next-auth/providers/email"; import EmailProvider from "next-auth/providers/email";
import CredentialsProvider from "next-auth/providers/credentials"; import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { isEmail, isName } from "@/lib/auth_list"; import { isEmail, isName } from "@/lib/auth_list";
import { createTransport } from "nodemailer";
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES; const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
@ -31,13 +32,36 @@ export const authOptions: NextAuthOptions = {
EmailProvider({ EmailProvider({
server: { server: {
host: process.env.EMAIL_SERVER_HOST, host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT, port: parseInt(process.env.EMAIL_SERVER_PORT ?? "0"),
auth: { auth: {
user: process.env.EMAIL_SERVER_USER, user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD, pass: process.env.EMAIL_SERVER_PASSWORD,
}, },
}, },
from: process.env.EMAIL_FROM, from: process.env.EMAIL_FROM,
async sendVerificationRequest({
identifier: email,
url,
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 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({ CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...") // The name to display on the sign in form (e.g. "Sign in with...")
@ -232,3 +256,62 @@ function cleanUpString(input: string): string {
return ''; return '';
} }
} }
/**
* Email HTML body
* Insert invisible space into domains from being turned into a hyperlink by email
* clients like Outlook and Apple mail, as this is confusing because it seems
* like they are supposed to click on it to sign in.
*
* @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
const escapedHost = host.replace(/\./g, "&#8203;.")
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>
`
}

View File

@ -60,6 +60,7 @@
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/cookie": "^0.6.0", "@types/cookie": "^0.6.0",
"@types/node": "^20.11.10", "@types/node": "^20.11.10",
"@types/nodemailer": "^6.4.14",
"@types/react": "^18.2.48", "@types/react": "^18.2.48",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@types/react-highlight-words": "^0.16.7", "@types/react-highlight-words": "^0.16.7",