Merge branch 'main' into 766_admin_enhancement

This commit is contained in:
notmd
2023-01-20 23:08:59 +07:00
83 changed files with 2770 additions and 449 deletions
+3 -4
View File
@@ -1,8 +1,7 @@
import { Box, Button, Center, Link, Text, useColorModeValue } from "@chakra-ui/react";
import { Box, Button, Center, Link, Text } from "@chakra-ui/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { FiAlertTriangle } from "react-icons/fi";
import { PageEmptyState } from "src/components/EmptyState";
import { EmptyState } from "src/components/EmptyState";
import { getTransparentHeaderLayout } from "src/components/Layout";
function Error() {
@@ -13,7 +12,7 @@ function Error() {
<meta name="404" content="Sorry, this page doesn't exist." />
</Head>
<Center flexDirection="column" gap="4" fontSize="lg" className="subpixel-antialiased">
<PageEmptyState />
<EmptyState text="Sorry, the page you are looking for does not exist." icon={FiAlertTriangle} />
<Box display="flex" flexDirection="column" alignItems="center" gap="2" mt="6">
<Text fontSize="sm">If you were trying to contribute data but ended up here, please file a bug.</Text>
<Button
+25 -11
View File
@@ -1,32 +1,46 @@
import { Button, Link, Stack } from "@chakra-ui/react";
import { Box, Button, Center, Link, Text } from "@chakra-ui/react";
import Head from "next/head";
import NextLink from "next/link";
import { FiAlertTriangle } from "react-icons/fi";
import { EmptyState } from "src/components/EmptyState";
import { getTransparentHeaderLayout } from "src/components/Layout";
export default function Error() {
function ServerError() {
return (
<>
<Head>
<title>500 - Open Assistant</title>
<meta name="404" content="Sorry, this page doesn't exist." />
</Head>
<main className="flex h-3/4 items-center justify-center overflow-hidden subpixel-antialiased text-xl">
<Stack>
<p>Sorry, We encountered a server error. We&apos;re not sure what went wrong</p>
<p>Please file a but below and describe what you were trying to accomplish</p>
<Button leftIcon={<FiAlertTriangle className="text-blue-500" aria-hidden="true" />} variant="solid">
<Center flexDirection="column" gap="4" fontSize="lg" className="subpixel-antialiased">
<EmptyState
text="Sorry, we encountered a server error. We're not sure what went wrong."
icon={FiAlertTriangle}
/>
<Box display="flex" flexDirection="column" alignItems="center" gap="2" mt="6">
<Text fontSize="sm">If you were trying to contribute data but ended up here, please file a bug.</Text>
<Button
width="fit-content"
leftIcon={<FiAlertTriangle className="text-blue-500" aria-hidden="true" />}
variant="solid"
size="xs"
>
<Link
as={NextLink}
key="Report a Bug"
href="https://github.com/LAION-AI/Open-Assistant/issues/new/choose"
aria-label="Report a Bug"
className="flex items-center"
_hover={{ textDecoration: "none" }}
isExternal
>
Report a Bug
</Link>
</Button>
</Stack>
</main>
</Box>
</Center>
</>
);
}
ServerError.getLayout = getTransparentHeaderLayout;
export default ServerError;
+3 -1
View File
@@ -3,11 +3,13 @@ import "focus-visible";
import type { AppProps } from "next/app";
import { SessionProvider } from "next-auth/react";
import { appWithTranslation } from "next-i18next";
import { FlagsProvider } from "react-feature-flags";
import { getDefaultLayout, NextPageWithLayout } from "src/components/Layout";
import flags from "src/flags";
import { SWRConfig, SWRConfiguration } from "swr";
import nextI18NextConfig from "../../next-i18next.config.js";
import { Chakra, getServerSideProps } from "../styles/Chakra";
type AppPropsWithLayout = AppProps & {
@@ -34,4 +36,4 @@ function MyApp({ Component, pageProps: { session, cookies, ...pageProps } }: App
);
}
export { getServerSideProps };
export default MyApp;
export default appWithTranslation(MyApp, nextI18NextConfig);
+46 -31
View File
@@ -2,27 +2,11 @@ import { Button, Input, InputGroup } from "@chakra-ui/react";
import Head from "next/head";
import Router from "next/router";
import { useSession } from "next-auth/react";
import React, { useState } from "react";
import React from "react";
import { Control, useForm, useWatch } from "react-hook-form";
export default function Account() {
const { data: session } = useSession();
const [username, setUsername] = useState("");
const updateUser = async (e: React.SyntheticEvent) => {
e.preventDefault();
try {
const body = { username };
await fetch("/api/username", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
session.user.name = username;
await Router.push("/account");
} catch (error) {
console.error(error);
}
};
if (!session) {
return;
@@ -39,21 +23,52 @@ export default function Account() {
<div className="oa-basic-theme">
<main className="h-3/4 z-0 flex flex-col items-center justify-center">
<p>{session.user.name || "No username"}</p>
<form onSubmit={updateUser}>
<InputGroup>
<Input
onChange={(e) => setUsername(e.target.value)}
placeholder="Edit Username"
type="text"
value={username}
></Input>
<Button disabled={!username} type="submit" value="Change">
Submit
</Button>
</InputGroup>
</form>
<EditForm></EditForm>
</main>
</div>
</>
);
}
const EditForm = () => {
const { data: session } = useSession();
const updateUser = async ({ username }: { username: string }) => {
try {
const body = { username };
await fetch("/api/username", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
session.user.name = username;
await Router.push("/account");
} catch (error) {
console.error(error);
}
};
const { register, handleSubmit, control } = useForm<{ username: string }>({
defaultValues: {
username: session?.user.name,
},
});
return (
<form onSubmit={handleSubmit(updateUser)}>
<InputGroup>
<Input placeholder="Edit Username" type="text" {...register("username")}></Input>
<SubmitButton control={control}></SubmitButton>
</InputGroup>
</form>
);
};
const SubmitButton = ({ control }: { control: Control<{ username: string }> }) => {
const username = useWatch({ control, name: "username" });
return (
<Button disabled={!username} type="submit" value="Change">
Submit
</Button>
);
};
+17 -3
View File
@@ -7,6 +7,7 @@ import CredentialsProvider from "next-auth/providers/credentials";
import DiscordProvider from "next-auth/providers/discord";
import EmailProvider from "next-auth/providers/email";
import prisma from "src/lib/prismadb";
import { generateUsername } from "unique-username-generator";
const providers: Provider[] = [];
@@ -90,6 +91,7 @@ export const authOptions: AuthOptions = {
async session({ session, token }) {
session.user.role = token.role;
session.user.isNew = token.isNew;
session.user.name = token.name;
return session;
},
/**
@@ -97,10 +99,11 @@ export const authOptions: AuthOptions = {
* This let's use forward the role to the session object.
*/
async jwt({ token }) {
const { isNew, role } = await prisma.user.findUnique({
const { isNew, name, role } = await prisma.user.findUnique({
where: { id: token.sub },
select: { role: true, isNew: true },
select: { name: true, role: true, isNew: true },
});
token.name = name;
token.role = role;
token.isNew = isNew;
return token;
@@ -110,7 +113,18 @@ export const authOptions: AuthOptions = {
/**
* Update the user's role after they have successfully signed in
*/
async signIn({ user, account }) {
async signIn({ user, account, isNewUser }) {
if (isNewUser && account.provider === "email") {
await prisma.user.update({
data: {
name: generateUsername(),
},
where: {
id: user.id,
},
});
}
// Get the admin list for the user's auth type.
const adminForAccountType = adminUserMap.get(account.provider);
+20
View File
@@ -0,0 +1,20 @@
import { withoutRole } from "src/lib/auth";
import prisma from "src/lib/prismadb";
/**
* Updates the user's `name` field in the `User` table.
*/
const handler = withoutRole("banned", async (req, res, token) => {
const { username } = req.body;
const { name } = await prisma.user.update({
where: {
id: token.sub,
},
data: {
name: username,
},
});
res.json({ name });
});
export default handler;
-20
View File
@@ -1,20 +0,0 @@
import { getSession } from "next-auth/react";
import prisma from "src/lib/prismadb";
// POST /api/post
// Required fields in body: title
// Optional fields in body: content
export default async function handle(req, res) {
const { username } = req.body;
const session = await getSession({ req });
const result = await prisma.user.update({
where: {
email: session.user.email,
},
data: {
name: username,
},
});
res.json({ name: result.name });
}
+28 -16
View File
@@ -6,11 +6,12 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { ClientSafeProvider, getProviders, signIn } from "next-auth/react";
import React, { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { FaBug, FaDiscord, FaEnvelope, FaGithub } from "react-icons/fa";
import { AuthLayout } from "src/components/AuthLayout";
import { Footer } from "src/components/Footer";
import { Header } from "src/components/Header";
import { RoleSelect } from "src/components/RoleSelect";
import { Role, RoleSelect } from "src/components/RoleSelect";
export type SignInErrorTypes =
| "Signin"
@@ -60,15 +61,14 @@ function Signin({ providers }: SigninProps) {
}
}, [router]);
const signinWithEmail = (ev: React.FormEvent) => {
ev.preventDefault();
signIn(email.id, { callbackUrl: "/dashboard", email: emailEl.current.value });
const signinWithEmail = (data: { email: string }) => {
signIn(email.id, { callbackUrl: "/dashboard", email: data.email });
};
const { colorMode } = useColorMode();
const bgColorClass = colorMode === "light" ? "bg-gray-50" : "bg-chakra-gray-900";
const buttonBgColor = colorMode === "light" ? "#2563eb" : "#2563eb";
const { register, handleSubmit } = useForm<{ email: string }>();
return (
<div className={bgColorClass}>
<Head>
@@ -79,7 +79,7 @@ function Signin({ providers }: SigninProps) {
<Stack spacing="2">
{credentials && <DebugSigninForm credentials={credentials} bgColorClass={bgColorClass} />}
{email && (
<form onSubmit={signinWithEmail}>
<form onSubmit={handleSubmit(signinWithEmail)}>
<Stack>
<Input
type="email"
@@ -87,7 +87,7 @@ function Signin({ providers }: SigninProps) {
variant="outline"
size="lg"
placeholder="Email Address"
ref={emailEl}
{...register("email")}
/>
<SigninButton data-cy="signin-email-button" leftIcon={<FaEnvelope />}>
Continue with Email
@@ -174,23 +174,35 @@ const SigninButton = (props: ButtonProps) => {
);
};
interface DebugSigninFormData {
username: string;
role: Role;
}
const DebugSigninForm = ({ credentials, bgColorClass }: { credentials: ClientSafeProvider; bgColorClass: string }) => {
const debugUsernameEl = useRef(null);
const roleRef = useRef(null);
function signinWithDebugCredentials(ev: React.FormEvent) {
ev.preventDefault();
const { register, handleSubmit } = useForm<DebugSigninFormData>({
defaultValues: {
role: "general",
username: "dev",
},
});
function signinWithDebugCredentials(data: DebugSigninFormData) {
signIn(credentials.id, {
callbackUrl: "/dashboard",
username: debugUsernameEl.current.value,
role: roleRef.current.value,
...data,
});
}
return (
<form onSubmit={signinWithDebugCredentials} className="border-2 border-orange-600 rounded-md p-4 relative">
<form
onSubmit={handleSubmit(signinWithDebugCredentials)}
className="border-2 border-orange-600 rounded-md p-4 relative"
>
<span className={`text-orange-600 absolute -top-3 left-5 ${bgColorClass} px-1`}>For Debugging Only</span>
<Stack>
<Input variant="outline" size="lg" placeholder="Username" ref={debugUsernameEl} />
<RoleSelect defaultValue={"general"} ref={roleRef}></RoleSelect>
<Input variant="outline" size="lg" placeholder="Username" {...register("username")} />
<RoleSelect {...register("role")}></RoleSelect>
<SigninButton leftIcon={<FaBug />}>Continue with Debug User</SigninButton>
</Stack>
</form>
+14 -7
View File
@@ -1,6 +1,9 @@
import { Box } from "@chakra-ui/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useSession } from "next-auth/react";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useEffect } from "react";
import { CallToAction } from "src/components/CallToAction";
import { Faq } from "src/components/Faq";
@@ -10,6 +13,7 @@ import { getTransparentHeaderLayout } from "src/components/Layout";
const Home = () => {
const router = useRouter();
const { status } = useSession();
const { t } = useTranslation("index");
useEffect(() => {
if (status === "authenticated") {
router.push("/dashboard");
@@ -19,21 +23,24 @@ const Home = () => {
return (
<>
<Head>
<title>Open Assistant</title>
<meta
name="description"
content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world."
/>
<title>{t("title")}</title>
<meta name="description" content={t("description")} />
</Head>
<main className="oa-basic-theme">
<Box as="main" className="oa-basic-theme">
<Hero />
<CallToAction />
<Faq />
</main>
</Box>
</>
);
};
Home.getLayout = getTransparentHeaderLayout;
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ["index", "common"])),
},
});
export default Home;