Image editing infra added

This commit is contained in:
Fergal Moran
2023-06-17 03:44:48 +01:00
parent 7cb9ad382b
commit 5d01837cf7
26 changed files with 747 additions and 596 deletions

View File

@@ -11,7 +11,11 @@ const config = {
esmExternals: false,
},
images: {
domains: ["cloudflare-ipfs.com", "avatars.githubusercontent.com"],
domains: [
"cloudflare-ipfs.com",
"avatars.githubusercontent.com",
"uploadthing.com",
],
},
};
export default config;

768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,21 +13,22 @@
"start": "next start"
},
"dependencies": {
"@headlessui/react": "^1.7.14",
"@next-auth/prisma-adapter": "^1.0.6",
"@headlessui/react": "^1.7.15",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^4.15.0",
"@radix-ui/react-toast": "^1.1.4",
"@t3-oss/env-nextjs": "^0.4.0",
"@tanstack/react-query": "^4.29.12",
"@t3-oss/env-nextjs": "^0.4.1",
"@tanstack/react-query": "^4.29.14",
"@trpc/client": "^10.28.1",
"@trpc/next": "^10.28.1",
"@trpc/next": "^10.30.0",
"@trpc/react-query": "^10.28.1",
"@trpc/server": "^10.28.1",
"@uploadthing/react": "^4.1.3",
"argon2": "^0.30.3",
"classnames": "^2.3.2",
"http-status-codes": "^2.2.0",
"install": "^0.13.0",
"next": "13.4.5",
"next": "13.4.6",
"next-auth": "^4.22.1",
"quirrel": "^1.13.4",
"react": "18.2.0",
@@ -35,54 +36,55 @@
"react-hot-toast": "^2.4.1",
"react-icons": "^4.9.0",
"superjson": "1.12.3",
"uploadthing": "^4.1.3",
"yup": "^1.2.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@hookform/resolvers": "^3.1.0",
"@ianvs/prettier-plugin-sort-imports": "^4.0.1",
"@hookform/resolvers": "^3.1.1",
"@ianvs/prettier-plugin-sort-imports": "^4.0.2",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@types/eslint": "^8.40.0",
"@types/node": "^20.2.5",
"@types/eslint": "^8.40.2",
"@types/node": "^20.3.1",
"@types/prettier": "^2.7.3",
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",
"@types/react": "^18.2.12",
"@types/react-dom": "^18.2.5",
"@types/superagent": "^4.1.18",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"autoprefixer": "^10.4.14",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"concurrently": "^8.0.1",
"dayjs": "^1.11.7",
"eslint": "^8.41.0",
"eslint-config-next": "^13.4.4",
"hls.js": "^1.4.4",
"lucide-react": "^0.229.0",
"concurrently": "^8.2.0",
"dayjs": "^1.11.8",
"eslint": "^8.42.0",
"eslint-config-next": "^13.4.6",
"hls.js": "^1.4.6",
"lucide-react": "^0.244.0",
"next-themes": "^0.2.1",
"postcss": "^8.4.24",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"prisma": "^4.15.0",
"pusher": "^5.1.3",
"pusher-js": "^8.0.2",
"pusher-js": "^8.1.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.44.2",
"recharts": "^2.6.2",
"react-hook-form": "^7.44.3",
"recharts": "^2.7.1",
"retry": "^0.13.1",
"superagent": "^8.0.9",
"tailwind-merge": "^1.12.0",
"tailwind-merge": "^1.13.2",
"tailwindcss": "^3.3.2",
"tailwindcss-animate": "^1.0.5",
"typescript": "^5.0.4",
"tailwindcss-animate": "^1.0.6",
"typescript": "^5.1.3",
"uuid": "^9.0.0"
},
"ct3aMetadata": {

View File

@@ -95,9 +95,9 @@ model User {
password String?
name String?
bio String?
email String? @unique
email String @unique
emailVerified DateTime?
image String?
profileImage String?
headerImage String?
urls String[]
streamKey String? @unique

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

View File

@@ -22,14 +22,14 @@ const DashboardPage = ({ prop1 }: DashboardPageProps) => {
<>
<div className="md:hidden">
<Image
src="/examples/dashboard-light.png"
src="/img/dashboard-light.png"
width={1280}
height={866}
alt="Dashboard"
className="block dark:hidden"
/>
<Image
src="/examples/dashboard-dark.png"
src="/img/dashboard-dark.png"
width={1280}
height={866}
alt="Dashboard"

View File

@@ -10,7 +10,10 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/use-toast";
import { notice } from "@/lib/components/notifications/toast";
import { type UserModel } from "@/lib/models";
import { api } from "@/lib/utils/api";
import { cn } from "@/lib/utils/styles";
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
@@ -29,11 +32,8 @@ const formSchema = z.object({
.max(30, {
message: "Username must not be longer than 30 characters.",
}),
email: z
.string()
.email()
.optional(),
bio: z.string().max(160).min(0).optional(),
email: z.string().email(),
bio: z.string().max(160).min(0).nullable(),
urls: z
.array(
z.object({
@@ -47,8 +47,15 @@ type FormValues = z.infer<typeof formSchema>;
const ProfileEditForm: React.FC<ProfileEditFormProps> = ({
profile,
}: ProfileEditFormProps) => {
const updateUser = api.user.updateUser.useMutation({
onSuccess: (result) => {
console.log("profile-edit-form", "onSuccess", result);
notice("Success", "Profile updated successfully");
},
});
const defaultValues: Partial<FormValues> = {
username: profile.username,
email: profile.email || "",
bio: profile.bio || "",
urls: [],
};
@@ -63,8 +70,15 @@ const ProfileEditForm: React.FC<ProfileEditFormProps> = ({
control: form.control,
});
const onSubmit = (data: FormValues) => {
const onSubmit = async (data: FormValues) => {
console.log("profile-edit-form", "onSubmit", data);
await updateUser.mutateAsync({
...profile,
username: data.username,
email: data.email,
bio: data.bio,
urls: [],
});
};
return (

View File

@@ -1,6 +1,5 @@
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
@@ -8,14 +7,17 @@ import {
FormMessage,
} from "@/components/form";
import { Button } from "@/components/ui/button";
import ImageUpload from "@/lib/components/widgets/ImageUpload";
import ImageUpload from "@/components/widgets/image-upload";
import { notice } from "@/lib/components/notifications/toast";
import { type UserModel } from "@/lib/models";
import { api } from "@/lib/utils/api";
import { useUploadThing } from "@/lib/utils/upload";
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
const MAX_FILE_SIZE = 500000;
const MAX_FILE_SIZE = 5242880;
const ACCEPTED_IMAGE_TYPES = [
"image/jpeg",
"image/jpg",
@@ -29,43 +31,71 @@ type ProfileImageEditFormProps = {
const formSchema = z.object({
profileImage: z
.any()
.refine(
(file: File) => file?.size <= MAX_FILE_SIZE,
`Max image size is 5MB.`
)
.refine((file: File) => {
const ret = file?.size <= MAX_FILE_SIZE;
console.log("profile-images-form", "profileImage_refine", ret);
return ret;
}, `Max image size is 5MB.`)
.refine(
(file: File) => ACCEPTED_IMAGE_TYPES.includes(file?.type),
"Only .jpg, .jpeg, .png and .webp formats are supported."
),
headerImage: z
.any()
.refine(
(file: File) => file?.size <= MAX_FILE_SIZE,
`Max image size is 5MB.`
)
.refine(
(file: File) => ACCEPTED_IMAGE_TYPES.includes(file?.type),
"Only .jpg, .jpeg, .png and .webp formats are supported."
),
.optional(),
// headerImage: z
// .any()
// .refine((file: File) => {
// const ret = file === null ?? file?.size <= MAX_FILE_SIZE;
// return ret;
// }, `Max image size is 5MB.`)
// .refine(
// (file: File) => ACCEPTED_IMAGE_TYPES.includes(file?.type),
// "Only .jpg, .jpeg, .png and .webp formats are supported."
// )
// .nullable()
// .optional(),
});
type FormValues = z.infer<typeof formSchema>;
const ProfileImageEditForm: React.FC<ProfileImageEditFormProps> = ({
profile,
}) => {
const updateUser = api.user.updateUser.useMutation({
onSuccess: (result) => {
console.log("profile-edit-form", "onSuccess", result);
notice("Success", "Profile updated successfully");
},
});
const { startUpload: uploadProfileImage } = useUploadThing({
endpoint: "profileImageUpload",
onUploadError: () => {
alert("error occurred while uploading");
},
});
const defaultValues: Partial<FormValues> = {
profileImage: profile.image,
headerImage: profile.headerImage,
profileImage: profile.profileImage,
// headerImage: profile.headerImage,
};
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues,
mode: "onChange",
});
const onSubmit = (data: FormValues) => {
const onSubmit = async (data: FormValues) => {
console.log("profile-images-form", "onSubmit", data);
if (data.profileImage && data.profileImage instanceof File) {
const results = await uploadProfileImage([data.profileImage]);
if (results && results[0]) {
const { fileUrl, fileKey } = results[0];
await updateUser.mutateAsync({
...profile,
profileImage: fileUrl,
});
}
}
};
return (
<Form {...form}>
<p>{JSON.stringify(form.formState.errors, null, 2)}</p>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
@@ -73,9 +103,24 @@ const ProfileImageEditForm: React.FC<ProfileImageEditFormProps> = ({
render={({ field }) => (
<FormItem>
<FormLabel>Profile Image</FormLabel>
<FormControl>
<div className="w-1/2">
<ImageUpload
<div className="w-1/2">
<Controller
control={form.control}
name={"profileImage"}
rules={{ required: "Gonna need a profile image" }}
render={({ field: { value, onChange, ...field } }) => {
return (
<ImageUpload
{...field}
imageUrl={value as string}
onImageChanged={(image) => {
onChange(image);
}}
/>
);
}}
/>
{/* <ImageUpload
onImageChanged={(image) => {
console.log(
"profile-images-form",
@@ -83,9 +128,8 @@ const ProfileImageEditForm: React.FC<ProfileImageEditFormProps> = ({
image
);
}}
/>
</div>
</FormControl>
/> */}
</div>
<FormDescription>
{
"This is your avatar, it's how you will be recognised on the site"
@@ -95,18 +139,14 @@ const ProfileImageEditForm: React.FC<ProfileImageEditFormProps> = ({
</FormItem>
)}
/>
<FormField
{/* <FormField
control={form.control}
name="headerImage"
render={({ field }) => (
<FormItem>
<FormLabel>Header Image</FormLabel>
<FormControl>
<ImageUpload
onImageChanged={(image) => {
console.log("profile-images-form", "onImageChanged", image);
}}
/>
</FormControl>
<FormDescription>
This is the image that will show at the top of your profile
@@ -115,7 +155,7 @@ const ProfileImageEditForm: React.FC<ProfileImageEditFormProps> = ({
<FormMessage />
</FormItem>
)}
/>
/> */}
<Button type="submit">Update profile images</Button>
</form>
</Form>

View File

@@ -0,0 +1,5 @@
import { NextResponse } from "next/server";
export function GET() {
return NextResponse.json({ ping: "pong" });
}

View File

View File

@@ -0,0 +1,24 @@
import { createUploadthing, type FileRouter } from "uploadthing/next";
const f = createUploadthing();
const auth = async (req: Request) => ({ id: "fakeId" }); // Fake auth function
export const profileImageRouter = {
profileImageUpload: f({ image: { maxFileSize: "4MB" } })
.middleware(async (req) => {
const user = await auth(req);
if (!user) throw new Error("Unauthorized");
return { userId: user.id };
})
.onUploadComplete(async ({ metadata, file }) => {
// This code RUNS ON YOUR SERVER after upload
console.log("Upload complete for userId:", metadata.userId);
console.log("file url", file.url);
}),
} satisfies FileRouter;
export type ProfileImageRouter = typeof profileImageRouter;

View File

@@ -0,0 +1,7 @@
import { createNextRouteHandler } from "uploadthing/next";
import { profileImageRouter } from "./core";
// Export routes for Next App Router
export const { GET, POST } = createNextRouteHandler({
router: profileImageRouter,
});

View File

@@ -1,11 +1,13 @@
import Navbar from "@/lib/components/layout/Navbar";
import "@/styles/globals.css";
import { type Metadata } from "next";
import Providers from "./providers";
import { fontSans } from "@/config/fonts";
import Image from "next/image";
import { cn } from "@/lib/utils/styles";
import { siteConfig } from "@/config/site";
import Navbar from "@/lib/components/layout/Navbar";
import { cn } from "@/lib/utils/styles";
import { type Metadata } from "next";
import Image from "next/image";
import Providers from "./providers";
//the order of these matters
import "@uploadthing/react/styles.css";
import "@/styles/globals.css";
const RootLayout = ({ children }: { children: React.ReactNode }) => {
return (
@@ -19,14 +21,14 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => {
<Providers>
<div className="md:hidden">
<Image
src="/examples/dashboard-light.png"
src="/img/dashboard-light.png"
width={1280}
height={866}
alt="Dashboard"
className="block dark:hidden"
/>
<Image
src="/examples/dashboard-dark.png"
src="/img/dashboard-dark.png"
width={1280}
height={866}
alt="Dashboard"

View File

@@ -0,0 +1,94 @@
import React from "react";
import Dropzone, { type DropzoneRef } from "react-dropzone";
type ImageUploadProps = {
imageUrl: string | undefined;
onImageChanged: (image: File) => void;
};
const ImageUpload: React.FC<ImageUploadProps> = ({ onImageChanged }) => {
const dropzoneRef = React.createRef<DropzoneRef>();
return (
<Dropzone
accept={{
"image/png": [".png"],
"image/jpg": [".jpg", ".jpeg"],
}}
maxFiles={1}
ref={dropzoneRef}
onDrop={(acceptedFiles) => {
if (acceptedFiles.length !== 0 && acceptedFiles[0]) {
onImageChanged(acceptedFiles[0]);
}
}}
>
{({ getRootProps, getInputProps, acceptedFiles }) => {
return (
<div className="w-64 h-64">
<div
{...getRootProps({ className: "dropzone" })}
onClick={(e) => e.stopPropagation()}
>
{acceptedFiles?.length ? (
<div
id="preview"
className="flex w-full items-center justify-center"
>
{acceptedFiles[0] && (
// eslint-disable-next-line @next/next/no-img-element
<img
className="object-cover"
src={URL.createObjectURL(acceptedFiles[0])}
alt="image preview"
/>
)}
</div>
) : (
<div id="drop">
<label
htmlFor="dropzone-file"
className="dark:hover:bg-bray-800 flex h-64 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-slate-700 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<svg
aria-hidden="true"
className="mb-3 h-10 w-10 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
></path>
</svg>
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Click to upload</span>{" "}
or drag and drop
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
SVG, PNG, JPG or GIF (MAX. 800x400px)
</p>
</div>
<input
{...getInputProps()}
id="dropzone-file"
type="file"
className="hidden"
/>
</label>
</div>
)}
</div>
</div>
);
}}
</Dropzone>
);
};
export default ImageUpload;

View File

@@ -26,20 +26,20 @@ const UserNav: React.FC<UserNavProps> = ({ session }) => {
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage
src={session.user.image || "/img/default-avatar.png"}
src={session.user.profileImage || "/img/default-avatar.png"}
alt="User Avatar"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuContent className="w-56 bg-muted" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{session.user.name}
</p>
<p className="text-xs leading-none text-muted-foreground">
<p className="text-xs leading-none ">
{session.user.email}
</p>
</div>

View File

@@ -57,7 +57,7 @@ const Chat = ({ show }: ChatProps) => {
{messages.map((item) => (
<ChatItem
key={item.id}
img={item.fromUser.image}
img={item.fromUser.profileImage}
date={item.timestamp}
from={item.fromUser.name}
message={item.message}

View File

@@ -36,8 +36,8 @@ const DashboardSidebar = ({ session }: DashboardSidebarProps) => {
return (
<div className="h-full w-60 space-y-2 p-3 ">
<div className="flex items-center space-x-4 p-2">
{session.user.image && (
<UserImage src={session.user.image} status={"offline"} size={"md"} />
{session.user.profileImage && (
<UserImage src={session.user.profileImage} status={"offline"} size={"md"} />
)}
<div>
<h2 className="text-sm font-semibold">

View File

@@ -58,7 +58,7 @@ const ShowPlayerPage = ({ title, show }: ShowPlayerPageProps) => {
<div className="flex items-center justify-between bg-white px-4 py-4 dark:bg-slate-800">
<div className="flex min-w-0 items-center">
<UserImage
src={show.user.image || ""}
src={show.user.profileImage || ""}
status={"offline"}
size={"md"}
/>

View File

@@ -1,9 +1,10 @@
type UserModel = {
username: string;
name: string;
bio: string | undefined;
image: string | undefined;
headerImage: string | undefined;
sites: string[] | undefined;
email: string;
bio: string | null;
profileImage: string | null;
headerImage: string | null;
urls: string[];
};
export default UserModel;

View File

@@ -9,8 +9,11 @@ const mapAuthUserToUserModel = (
? {
username: user.username,
name: user.name,
email: user.email,
bio: user.bio,
image: user.image,
profileImage: user.profileImage,
headerImage: user.headerImage,
urls: [],
}
: undefined;
@@ -21,8 +24,11 @@ const mapDbAuthUserToUserModel = (
? {
username: user.username ?? "unknownuser",
name: user.name ?? "Unknown User",
email: user.email,
bio: user.bio,
image: user.image,
profileImage: user.profileImage,
headerImage: user.headerImage,
urls: [],
}
: undefined;

5
src/lib/utils/upload.ts Normal file
View File

@@ -0,0 +1,5 @@
import { type ProfileImageRouter } from "@/app/api/uploadthing/core";
import { generateReactHelpers } from "@uploadthing/react/hooks";
export const { useUploadThing, uploadFiles } =
generateReactHelpers<ProfileImageRouter>();

View File

@@ -1,7 +1,4 @@
import {
mapAuthUserToUserModel,
mapDbAuthUserToUserModel,
} from "@/lib/utils/mappers/userMapper";
import { mapDbAuthUserToUserModel } from "@/lib/utils/mappers/userMapper";
import {
createTRPCRouter,
protectedProcedure,
@@ -47,4 +44,43 @@ export const userRouter = createTRPCRouter({
return user;
}),
updateUser: protectedProcedure
.input(
z.object({
username: z.string(),
email: z.string().email(),
bio: z.string().nullable(),
urls: z.array(z.string()),
profileImage: z.string().nullable(),
headerImage: z.string().nullable(),
})
)
.mutation(
async ({
input: { username, email, bio, urls, profileImage, headerImage },
ctx,
}) => {
const user = await ctx.prisma.user.findUnique({
where: { id: ctx.session.id },
});
if (!user) {
throw new trpc.TRPCError({
code: "FORBIDDEN",
message: "User is not authenticated.",
});
}
await ctx.prisma.user.update({
where: { id: ctx.session.id },
data: {
...user,
username,
email,
bio,
urls,
profileImage,
headerImage,
},
});
}
),
});

View File

@@ -1,11 +1,17 @@
import { type GetServerSidePropsContext } from "next";
import { getServerSession, type NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { env } from "@/env.mjs";
import { mapDbAuthUserToUserModel } from "@/lib/utils/mappers/userMapper";
import { prisma } from "@/server/db";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { verify } from "argon2";
import { type GetServerSidePropsContext } from "next";
import {
getServerSession,
type NextAuthOptions,
type Session,
type User,
} from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
export const authOptions: NextAuthOptions = {
session: {
@@ -13,21 +19,26 @@ export const authOptions: NextAuthOptions = {
},
callbacks: {
session: ({ session, token }) => {
if (token) {
session.id = token.id as string;
session.id = token.id as string;
}
return session;
return {
id: token.id,
user: token.user,
} as Session;
},
jwt: ({ token, user }) => {
if (user) {
token.id = user.id;
token.email = user.email;
token.name = user.name;
token.bio = user.bio;
jwt: async ({ token }) => {
if (!token.email) {
return token;
}
const user = await prisma.user.findFirst({
where: {
email: token.email,
},
});
if (user) {
return {
id: token.sub,
user: mapDbAuthUserToUserModel(user),
};
}
return token;
},
},
@@ -48,35 +59,40 @@ export const authOptions: NextAuthOptions = {
password: { label: "Password", type: "password" },
},
authorize: async (credentials) => {
if (!credentials?.email || !credentials?.password) {
try {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findFirst({
where: { email: credentials.email },
});
if (!user || !user.password) {
return null;
}
const isValidPassword = await verify(
user.password,
credentials.password
);
if (!isValidPassword) {
return null;
}
return {
id: user.id,
name: user.name,
bio: user.bio,
email: user.email,
username: user.username,
profileImage: user.profileImage,
headerImage: user.headerImage,
} as User;
} catch (e) {
return null;
}
const user = await prisma.user.findFirst({
where: { email: credentials.email },
});
if (!user || !user.password) {
return null;
}
const isValidPassword = await verify(
user.password,
credentials.password
);
if (!isValidPassword) {
return null;
}
return {
id: user.id,
name: user.name,
bio: user.bio,
email: user.email,
username: user.username,
image: user.image,
};
},
}),
],

View File

@@ -37,23 +37,22 @@
}
.dark {
--background: #0E2954;
--background: #0e2954;
--foreground: 222.2 47.4% 11.2%;
--card: #1F6E8C;
--card: #1f6e8c;
--card-foreground: 222.2 47.4% 11.2%;
--muted: #2E8A99;
--muted-foreground: 222.2 47.4% 11.2%;
--accent: #84A7A1;
--muted: #2e8a99;
--muted-foreground: #374151;
--accent: #84a7a1;
--accent-foreground: 210 40% 98%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--border: #374151;
--input: #374151;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
@@ -64,7 +63,7 @@
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--ring: #374151;
--radius: 0.5rem;
}

View File

@@ -14,9 +14,9 @@ const config: Config = {
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
border: "var(--border)",
input: "var(--input)",
ring: "var(--ring)",
background: "var(--background)",
foreground: "var(--foreground)",
primary: {
@@ -33,7 +33,7 @@ const config: Config = {
},
muted: {
DEFAULT: "var(--muted)",
foreground: "hsl(var(--muted-foreground))",
foreground: "var(--muted-foreground)",
},
accent: {
DEFAULT: "var(--accent)",

10
types/next-auth.d.ts vendored
View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import NextAuth from "next-auth";
declare module "next-auth" {
@@ -8,10 +9,11 @@ declare module "next-auth" {
interface User {
id: string;
name: string;
email: string;
image: string;
bio: string;
username: string;
name: string;
bio: string;
email: string;
profileImage: string;
headerImage: string;
}
}