Compare commits
No commits in common. "729aba68ceb055b5ebb84108f40cd4a19c4617a5" and "b9b744e97fb23d0ca52e8bc0ddaced69373a08d4" have entirely different histories.
729aba68ce
...
b9b744e97f
@ -4,9 +4,6 @@ WORKDIR /ttc
|
|||||||
|
|
||||||
ADD . .
|
ADD . .
|
||||||
|
|
||||||
ENV NODE_ENV production
|
|
||||||
ENV AUTH_TRUST_HOST true
|
|
||||||
|
|
||||||
RUN npm i
|
RUN npm i
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import { signIn, signOut } from "@/auth";
|
|
||||||
|
|
||||||
export const signInWithDiscord = async () => {
|
|
||||||
await signIn("discord");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithCreds = async (formData: FormData) => {
|
|
||||||
await signIn("credentials", formData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signOutOfApp = () => signOut();
|
|
@ -1,3 +0,0 @@
|
|||||||
import { handlers } from "@/auth";
|
|
||||||
|
|
||||||
export const { GET, POST } = handlers;
|
|
@ -1,9 +1,21 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useToast } from "@/components/toast";
|
||||||
import { TTCMD } from "@/components/ttcmd";
|
import { TTCMD } from "@/components/ttcmd";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { FC, use } from "react";
|
import { FC, use } from "react";
|
||||||
|
|
||||||
export const HomeClient: FC<{ body: Promise<string> }> = ({ body }) => {
|
export const HomeClient: FC<{ body: Promise<string> }> = ({ body }) => {
|
||||||
const text = use(body);
|
const text = use(body);
|
||||||
|
|
||||||
|
const { createToast } = useToast();
|
||||||
|
useEffect(() => {
|
||||||
|
createToast({
|
||||||
|
fading: false,
|
||||||
|
msg: "TEST TOAST",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}, [createToast]);
|
||||||
|
|
||||||
return <TTCMD body={text} parserId="home" title="home" />;
|
return <TTCMD body={text} parserId="home" title="home" />;
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
input,
|
input,
|
||||||
select {
|
select {
|
||||||
@apply py-2 px-4 rounded-lg dark:bg-mixed-200 bg-mixed-600 placeholder:text-dark-500;
|
@apply py-2 px-4 rounded-full dark:bg-mixed-200 bg-mixed-600 placeholder:text-dark-500;
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
@apply dark:bg-mixed-200 bg-primary-600 rounded-md p-1;
|
@apply dark:bg-mixed-200 bg-primary-600 rounded-md p-1;
|
||||||
@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@apply rounded-lg;
|
@apply rounded-full;
|
||||||
}
|
}
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@apply dark:bg-primary-500 bg-primary-100 py-4 px-6 dark:text-mixed-100 text-white font-bold text-lg btn;
|
@apply dark:bg-primary-500 bg-primary-100 py-4 px-6 dark:text-mixed-100 text-white font-bold text-lg btn;
|
||||||
@ -97,17 +97,6 @@
|
|||||||
.fade-toast {
|
.fade-toast {
|
||||||
animation: fadeOut 300ms forwards;
|
animation: fadeOut 300ms forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separated-list > li:not(:last-child) {
|
|
||||||
@apply border-b border-mixed-600 w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-menu {
|
|
||||||
animation: fadeIn 100ms forwards;
|
|
||||||
}
|
|
||||||
.fade-menu[data-closing="true"] {
|
|
||||||
animation: fadeOut 100ms forwards;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes identifier {
|
@keyframes identifier {
|
||||||
@ -128,11 +117,3 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -13,8 +13,6 @@ import { DevToolboxContextProvider } from "@/components/devtools/context";
|
|||||||
import { RecoilRootClient } from "@/components/recoilRoot";
|
import { RecoilRootClient } from "@/components/recoilRoot";
|
||||||
import { JotaiProvider } from "@/components/jotaiProvider";
|
import { JotaiProvider } from "@/components/jotaiProvider";
|
||||||
import { Toaster } from "@/components/toast";
|
import { Toaster } from "@/components/toast";
|
||||||
import { SessionProvider } from "next-auth/react";
|
|
||||||
import { User } from "@/components/user/index";
|
|
||||||
|
|
||||||
const roboto = Roboto({ subsets: ["latin"], weight: "400" });
|
const roboto = Roboto({ subsets: ["latin"], weight: "400" });
|
||||||
|
|
||||||
@ -58,44 +56,37 @@ export default function RootLayout({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<SessionProvider>
|
<body className={roboto.className + " flex min-h-[100vh]"}>
|
||||||
<body className={roboto.className + " flex min-h-[100vh]"}>
|
<nav className="h-[100vh] sticky top-0 left-0 bottom-0 p-8 rounded-r-3xl dark:bg-mixed-300 bg-primary-400 w-max shadow-2xl">
|
||||||
<nav className="h-[100vh] sticky top-0 left-0 bottom-0 p-8 rounded-r-3xl dark:bg-mixed-300 bg-primary-400 w-max shadow-2xl">
|
<h1 className="text-lg font-bold pb-6 border-b dark:border-dark-500 border-primary-600">
|
||||||
<div className="flex flex-col h-full">
|
<Link href="/">Tabletop Commander</Link>
|
||||||
<h1 className="text-lg font-bold pb-6 border-b dark:border-dark-500 border-primary-600">
|
</h1>
|
||||||
<Link href="/">Tabletop Commander</Link>
|
<ul className="my-6 flex flex-col gap-6">
|
||||||
</h1>
|
{navItems.map((n) => (
|
||||||
<ul className="my-6 flex flex-col gap-6">
|
<li key={"nav-item" + n.text}>
|
||||||
{navItems.map((n) => (
|
<Link
|
||||||
<li key={"nav-item" + n.text}>
|
href={n.to}
|
||||||
<Link
|
className="flex items-center gap-2 group hover:text-purple-300 transition-colors"
|
||||||
href={n.to}
|
>
|
||||||
className="flex items-center gap-2 group hover:text-purple-300 transition-colors"
|
<n.icon className="w-6 h-6 group-hover:fill-purple-300 transition-colors" />
|
||||||
>
|
{n.text}
|
||||||
<n.icon className="w-6 h-6 group-hover:fill-purple-300 transition-colors" />
|
</Link>
|
||||||
{n.text}
|
</li>
|
||||||
</Link>
|
))}
|
||||||
</li>
|
</ul>
|
||||||
))}
|
</nav>
|
||||||
</ul>
|
<RecoilRootClient>
|
||||||
<div className="mt-auto">
|
<JotaiProvider>
|
||||||
<User />
|
<DevToolboxContextProvider
|
||||||
</div>
|
isDev={process.env.NODE_ENV !== "production"}
|
||||||
</div>
|
>
|
||||||
</nav>
|
<main className="p-8 w-full overflow-visible">{children}</main>
|
||||||
<RecoilRootClient>
|
<Toaster />
|
||||||
<JotaiProvider>
|
</DevToolboxContextProvider>
|
||||||
<DevToolboxContextProvider
|
</JotaiProvider>
|
||||||
isDev={process.env.NODE_ENV !== "production"}
|
</RecoilRootClient>
|
||||||
>
|
<div id="root-portal"></div>
|
||||||
<main className="p-8 w-full overflow-visible">{children}</main>
|
</body>
|
||||||
<Toaster />
|
|
||||||
</DevToolboxContextProvider>
|
|
||||||
</JotaiProvider>
|
|
||||||
</RecoilRootClient>
|
|
||||||
<div id="root-portal"></div>
|
|
||||||
</body>
|
|
||||||
</SessionProvider>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { auth } from "@/auth";
|
|
||||||
import SignIn from "@/components/signIn";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
async function SignInUp() {
|
|
||||||
const session = await auth();
|
|
||||||
|
|
||||||
if (session?.user) redirect("/");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid place-items-center h-full">
|
|
||||||
<div>
|
|
||||||
<SignIn />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SignInUp;
|
|
@ -1,5 +1,6 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect class="prefix__anvil-base" x="4.5" y="9.5" width="10" height="2" rx=".5" stroke="#000" />
|
<rect class="anvil-base" x="4.5" y="9.5" width="10" height="2" rx="0.5" stroke="inherit" fill="none" />
|
||||||
<path class="prefix__anvil-body" d="M6 1H2c1 1 3.5 1.5 5 2s1.543 1.292 0 3L6 7v1h7V7s-1.5-1-2-2 0-2.5 4-3V.5H6V1z"
|
<path class="anvil-body"
|
||||||
stroke="#000" />
|
d="M6 1H2C3 2 5.5 2.5 7 3C8.5 3.5 8.54315 4.2918 7 6L6 7V8H13V7C13 7 11.5 6 11 5C10.5 4 11 2.5 15 2V0.5H6V1Z"
|
||||||
|
stroke="inherit" fill="none" />
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 385 B |
@ -1,5 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-discord"
|
|
||||||
viewBox="0 0 16 16">
|
|
||||||
<path
|
|
||||||
d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,70 +0,0 @@
|
|||||||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import NextAuth from "next-auth";
|
|
||||||
import Credentials from "next-auth/providers/credentials";
|
|
||||||
import Discord from "next-auth/providers/discord";
|
|
||||||
|
|
||||||
import bcrypt from "bcryptjs";
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
||||||
providers: [
|
|
||||||
Discord({
|
|
||||||
clientId: process.env.DISCORD_CLIENT_ID,
|
|
||||||
clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
Credentials({
|
|
||||||
credentials: {
|
|
||||||
email: {},
|
|
||||||
password: {},
|
|
||||||
},
|
|
||||||
authorize: async (credentials) => {
|
|
||||||
let user = null;
|
|
||||||
|
|
||||||
const pwHash = await saltAndHashPassword(
|
|
||||||
credentials.password as string
|
|
||||||
);
|
|
||||||
user = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
email: credentials.email as string,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
image: true,
|
|
||||||
email: true,
|
|
||||||
emailVerified: true,
|
|
||||||
username: true,
|
|
||||||
passwordHash: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
email: credentials.email as string,
|
|
||||||
passwordHash: pwHash,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
image: true,
|
|
||||||
email: true,
|
|
||||||
emailVerified: true,
|
|
||||||
username: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.passwordHash = null;
|
|
||||||
|
|
||||||
return user;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
adapter: PrismaAdapter(prisma),
|
|
||||||
});
|
|
||||||
async function saltAndHashPassword(password: string) {
|
|
||||||
const hash = await bcrypt.hash(password, 10);
|
|
||||||
return hash;
|
|
||||||
}
|
|
@ -1,184 +1,23 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import Help from "../../assets/icons/Help Icon.svg";
|
||||||
const Help = () => (
|
import Trash from "../../assets/icons/Trash Icon.svg";
|
||||||
<svg
|
import Trash_hover from "../../assets/icons/Trash Icon Open.svg";
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
import Anvil from "../../assets/icons/Anvil Icon.svg";
|
||||||
stroke="#fff"
|
|
||||||
fill="#fff"
|
|
||||||
height="28"
|
|
||||||
width="28"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
stroke-width="3"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
r="12.5"
|
|
||||||
cy="14"
|
|
||||||
cx="14"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill="inherit"
|
|
||||||
d="M12.004 17.816v-.867c0-.531.074-1 .223-1.406.148-.414.386-.805.714-1.172.328-.375.762-.758 1.301-1.148.485-.344.871-.653 1.16-.926.297-.274.512-.543.645-.809.14-.273.21-.582.21-.925 0-.508-.187-.895-.562-1.16-.375-.266-.898-.4-1.57-.4s-1.34.106-2.004.317c-.656.211-1.324.489-2.004.832L8.84 7.586c.781-.438 1.629-.79 2.543-1.055.914-.273 1.914-.41 3-.41 1.672 0 2.965.402 3.879 1.207.922.797 1.382 1.813 1.382 3.047 0 .656-.105 1.227-.316 1.71a4.165 4.165 0 01-.937 1.337c-.414.406-.934.836-1.559 1.289-.469.344-.828.633-1.078.867-.25.235-.422.469-.516.703a2.356 2.356 0 00-.129.832v.703zm-.375 4.008c0-.734.2-1.25.598-1.547.406-.297.894-.445 1.464-.445.555 0 1.032.148 1.43.445.406.297.61.813.61 1.547 0 .703-.204 1.211-.61 1.524-.398.312-.875.468-1.43.468-.57 0-1.058-.156-1.464-.468-.399-.313-.598-.82-.598-1.524z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Trash = () => (
|
|
||||||
<svg
|
|
||||||
className="trash-can"
|
|
||||||
width="35"
|
|
||||||
height="30"
|
|
||||||
viewBox="0 0 22 30"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M2 7H20V27C20 28.1046 19.1046 29 18 29H4C2.89543 29 2 28.1046 2 27V7Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M6 11L6 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M11 11V24"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M16 11V24"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
className="trash-lid"
|
|
||||||
d="M9 0.5H13C13.2761 0.5 13.5 0.723858 13.5 1V2.5H8.5V1C8.5 0.723858 8.72386 0.5 9 0.5Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
className="trash-lid"
|
|
||||||
d="M2 2.5H20C20.8284 2.5 21.5 3.17157 21.5 4V6.5H0.5V4C0.5 3.17157 1.17157 2.5 2 2.5Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Trash_hover = () => (
|
|
||||||
<svg
|
|
||||||
width="35"
|
|
||||||
height="35"
|
|
||||||
viewBox="0 0 23 35"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3 12H21V32C21 33.1046 20.1046 34 19 34H5C3.89543 34 3 33.1046 3 32V12Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M7 16L7 29"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 16V29"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M17 16V29"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M8.59244 1.36064L12.5317 0.666048C12.8036 0.618097 13.063 0.799681 13.1109 1.07163L13.3714 2.54884L8.44734 3.41708L8.18687 1.93987C8.13891 1.66792 8.3205 1.40859 8.59244 1.36064Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M2.05644 4.54394L19.783 1.41827C20.5988 1.27442 21.3768 1.81917 21.5207 2.63501L21.9548 5.09703L1.27382 8.74365L0.8397 6.28163C0.695845 5.46578 1.2406 4.6878 2.05644 4.54394Z"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Anvil = () => (
|
|
||||||
<svg
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 12"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
className="prefix__anvil-base"
|
|
||||||
x="4.5"
|
|
||||||
y="9.5"
|
|
||||||
width="10"
|
|
||||||
height="2"
|
|
||||||
rx=".5"
|
|
||||||
stroke="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
className="prefix__anvil-body"
|
|
||||||
d="M6 1H2c1 1 3.5 1.5 5 2s1.543 1.292 0 3L6 7v1h7V7s-1.5-1-2-2 0-2.5 4-3V.5H6V1z"
|
|
||||||
stroke="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Discord = () => (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
fill="currentColor"
|
|
||||||
className="bi bi-discord"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
>
|
|
||||||
<path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const library = {
|
const library = {
|
||||||
Help,
|
Help,
|
||||||
Trash,
|
Trash,
|
||||||
Trash_hover,
|
Trash_hover,
|
||||||
Anvil,
|
Anvil,
|
||||||
Discord,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
className?: string;
|
className: string;
|
||||||
icon: keyof typeof library;
|
icon: keyof typeof library;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon: FC<IProps> = ({ className, icon }) => {
|
export const Icon: FC<IProps> = ({ className, icon }) => {
|
||||||
const ICON = library[icon];
|
const ICON = library[icon];
|
||||||
|
|
||||||
return (
|
return <ICON className={className} />;
|
||||||
<span className={className}>
|
|
||||||
<ICON />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import { signInWithCreds, signInWithDiscord } from "@/actions/auth";
|
|
||||||
import { Icon } from "./Icon";
|
|
||||||
|
|
||||||
export default function SignIn() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<form action={signInWithCreds} className="flex flex-col gap-2">
|
|
||||||
<input
|
|
||||||
className="w-full"
|
|
||||||
placeholder="email"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className="w-full"
|
|
||||||
placeholder="password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<div className="dark:border-dark-500 border-primary-600 flex-grow border-b"></div>
|
|
||||||
<div className="dark:text-dark-500 text-primary-600 ">or</div>
|
|
||||||
<div className="dark:border-dark-500 border-primary-600 flex-grow border-b"></div>
|
|
||||||
</div>
|
|
||||||
<form action={signInWithDiscord}>
|
|
||||||
<button className="w-full p-2 bg-[#816ab1] rounded-lg" type="submit">
|
|
||||||
<Icon icon="Discord" className="mr-4 inline-block" />
|
|
||||||
Sign in with Discord
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
|
||||||
import { auth } from "@/auth";
|
|
||||||
import { UserCircleIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { FC } from "react";
|
|
||||||
import { UserMenu } from "./menu";
|
|
||||||
|
|
||||||
export const User: FC = async () => {
|
|
||||||
const session = await auth();
|
|
||||||
return (
|
|
||||||
<UserMenu signedIn={!!session?.user}>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
{session?.user?.image ? (
|
|
||||||
<img
|
|
||||||
src={session.user.image}
|
|
||||||
alt="user avatar"
|
|
||||||
className="rounded-full w-12"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span className="w-12 h-12 inline-block">
|
|
||||||
<UserCircleIcon className="w-full h-full" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{session?.user?.name ? (
|
|
||||||
<span>Hey there, {session.user.name}!</span>
|
|
||||||
) : (
|
|
||||||
<a className="block flex-grow h-full" href="/sign-in">
|
|
||||||
Sign In
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</UserMenu>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,50 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { signOutOfApp } from "@/actions/auth";
|
|
||||||
import { FC, PropsWithChildren, useCallback, useState } from "react";
|
|
||||||
|
|
||||||
export const UserMenu: FC<PropsWithChildren<{ signedIn: boolean }>> = ({
|
|
||||||
children,
|
|
||||||
signedIn,
|
|
||||||
}) => {
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [closing, setClosing] = useState(true);
|
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
|
||||||
setClosing((c) => !c);
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
setVisible((v) => !v);
|
|
||||||
},
|
|
||||||
visible ? 100 : 0
|
|
||||||
);
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onClick={signedIn ? toggle : undefined}
|
|
||||||
className="relative bg-mixed-200 p-2 rounded-lg cursor-pointer w-[220px]"
|
|
||||||
>
|
|
||||||
{visible && (
|
|
||||||
<div
|
|
||||||
data-closing={closing}
|
|
||||||
className="absolute bottom-full left-0 right-0 fade-menu"
|
|
||||||
>
|
|
||||||
<ul className="separated-list w-full">
|
|
||||||
<li>
|
|
||||||
<a className="block p-2" href="/profile">
|
|
||||||
Profile
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button className="p-2" onClick={() => signOutOfApp()}>
|
|
||||||
Sign Out
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export { auth as middleware } from "@/auth";
|
|
864
package-lock.json
generated
864
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -6,23 +6,17 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint"
|
||||||
"postinstall": "bun ./postinstall/index.ts"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/prisma-adapter": "^2.4.2",
|
|
||||||
"@heroicons/react": "^2.1.1",
|
"@heroicons/react": "^2.1.1",
|
||||||
"@prisma/client": "^5.18.0",
|
"@prisma/client": "^5.18.0",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
|
||||||
"bcryptjs": "^2.4.3",
|
|
||||||
"isomorphic-dompurify": "^2.4.0",
|
"isomorphic-dompurify": "^2.4.0",
|
||||||
"jotai": "^2.9.3",
|
"jotai": "^2.9.3",
|
||||||
"next": "^14.2.5",
|
"next": "^14.2.5",
|
||||||
"next-auth": "^5.0.0-beta.20",
|
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"recoil": "^0.7.7",
|
"recoil": "^0.7.7"
|
||||||
"url-loader": "^4.1.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
const { SecretClient } = require("../lib/secret/init");
|
|
||||||
const { writeFile } = require("fs/promises");
|
|
||||||
|
|
||||||
const requiredKeys = [
|
|
||||||
"discord_client_id",
|
|
||||||
"discord_client_secret",
|
|
||||||
"ttc:database_url",
|
|
||||||
];
|
|
||||||
|
|
||||||
const secretClient = SecretClient();
|
|
||||||
|
|
||||||
async function buildEnv() {
|
|
||||||
secretClient.fetchToken();
|
|
||||||
let secrets = "";
|
|
||||||
for (const key of requiredKeys) {
|
|
||||||
const value = await secretClient.fetchSecret(key);
|
|
||||||
secrets += `${key.replace("ttc:", "").toUpperCase()}=${value}\n`;
|
|
||||||
}
|
|
||||||
await writeFile(".env", secrets, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
buildEnv();
|
|
@ -1 +0,0 @@
|
|||||||
require("./buildEnv.ts");
|
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- A unique constraint covering the columns `[username]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
|
||||||
- Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE `User` ADD COLUMN `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
||||||
ADD COLUMN `emailVerified` DATETIME(3) NULL,
|
|
||||||
ADD COLUMN `image` VARCHAR(191) NULL,
|
|
||||||
ADD COLUMN `name` VARCHAR(191) NULL,
|
|
||||||
ADD COLUMN `updatedAt` DATETIME(3) NOT NULL,
|
|
||||||
MODIFY `username` VARCHAR(191) NULL,
|
|
||||||
MODIFY `email` VARCHAR(191) NULL;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE `Account` (
|
|
||||||
`id` VARCHAR(191) NOT NULL,
|
|
||||||
`userId` VARCHAR(191) NOT NULL,
|
|
||||||
`type` VARCHAR(191) NOT NULL,
|
|
||||||
`provider` VARCHAR(191) NOT NULL,
|
|
||||||
`providerAccountId` VARCHAR(191) NOT NULL,
|
|
||||||
`refresh_token` TEXT NULL,
|
|
||||||
`access_token` TEXT NULL,
|
|
||||||
`expires_at` INTEGER NULL,
|
|
||||||
`token_type` VARCHAR(191) NULL,
|
|
||||||
`scope` VARCHAR(191) NULL,
|
|
||||||
`id_token` TEXT NULL,
|
|
||||||
`session_state` VARCHAR(191) NULL,
|
|
||||||
`refresh_token_expires_in` INTEGER NULL,
|
|
||||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
||||||
`updatedAt` DATETIME(3) NOT NULL,
|
|
||||||
|
|
||||||
UNIQUE INDEX `Account_userId_key`(`userId`),
|
|
||||||
INDEX `Account_userId_idx`(`userId`),
|
|
||||||
UNIQUE INDEX `Account_provider_providerAccountId_key`(`provider`, `providerAccountId`),
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE `Session` (
|
|
||||||
`id` VARCHAR(191) NOT NULL,
|
|
||||||
`sessionToken` VARCHAR(191) NOT NULL,
|
|
||||||
`userId` VARCHAR(191) NOT NULL,
|
|
||||||
`expires` DATETIME(3) NOT NULL,
|
|
||||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
||||||
`updatedAt` DATETIME(3) NOT NULL,
|
|
||||||
|
|
||||||
UNIQUE INDEX `Session_sessionToken_key`(`sessionToken`),
|
|
||||||
INDEX `Session_userId_idx`(`userId`),
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE `VerificationToken` (
|
|
||||||
`identifier` VARCHAR(191) NOT NULL,
|
|
||||||
`token` VARCHAR(191) NOT NULL,
|
|
||||||
`expires` DATETIME(3) NOT NULL,
|
|
||||||
|
|
||||||
UNIQUE INDEX `VerificationToken_identifier_token_key`(`identifier`, `token`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX `User_username_key` ON `User`(`username`);
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE `Account` ADD CONSTRAINT `Account_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE `Session` ADD CONSTRAINT `Session_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE `User` ADD COLUMN `passwordHash` VARCHAR(191) NULL;
|
|
@ -85,59 +85,6 @@ model User {
|
|||||||
gameSystems GameSystem[]
|
gameSystems GameSystem[]
|
||||||
publications Publication[]
|
publications Publication[]
|
||||||
|
|
||||||
name String?
|
username String
|
||||||
username String? @unique
|
email String @unique
|
||||||
email String? @unique
|
|
||||||
emailVerified DateTime?
|
|
||||||
passwordHash String?
|
|
||||||
image String?
|
|
||||||
accounts Account[]
|
|
||||||
sessions Session[]
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
model Account {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
userId String @unique
|
|
||||||
type String
|
|
||||||
provider String
|
|
||||||
providerAccountId String
|
|
||||||
refresh_token String? @db.Text
|
|
||||||
access_token String? @db.Text
|
|
||||||
expires_at Int?
|
|
||||||
token_type String?
|
|
||||||
scope String?
|
|
||||||
id_token String? @db.Text
|
|
||||||
session_state String?
|
|
||||||
refresh_token_expires_in Int?
|
|
||||||
user User? @relation(fields: [userId], references: [id])
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@unique([provider, providerAccountId])
|
|
||||||
@@index([userId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model Session {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
sessionToken String @unique
|
|
||||||
userId String
|
|
||||||
expires DateTime
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@index([userId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model VerificationToken {
|
|
||||||
identifier String
|
|
||||||
token String
|
|
||||||
expires DateTime
|
|
||||||
|
|
||||||
@@unique([identifier, token])
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user