diff --git a/README.md b/README.md
index d1789cb7..43573ada 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Mixan is a simple analytics tool for logging events on web and react-native. My
### GUI
+* [ ] Fix tables on settings
* [ ] Rename event label
* [ ] Real time data (mostly screen_views stats)
* [ ] Active users (5min, 10min, 30min)
diff --git a/apps/web/src/components/Content.tsx b/apps/web/src/components/Content.tsx
index 9818b936..6ce3164c 100644
--- a/apps/web/src/components/Content.tsx
+++ b/apps/web/src/components/Content.tsx
@@ -20,7 +20,7 @@ export function ContentHeader({ title, text, children }: ContentHeaderProps) {
type ContentSectionProps = {
title: string;
- text: string;
+ text?: string | React.ReactNode;
children: React.ReactNode;
asCol?: boolean;
};
@@ -41,7 +41,7 @@ export function ContentSection({
{title && (
{title}
-
{text}
+ {text &&
{text}
}
)}
{children}
diff --git a/apps/web/src/components/forms/InputError.tsx b/apps/web/src/components/forms/InputError.tsx
new file mode 100644
index 00000000..fcf0744f
--- /dev/null
+++ b/apps/web/src/components/forms/InputError.tsx
@@ -0,0 +1,9 @@
+type InputErrorProps = { message?: string };
+
+export function InputError({ message }: InputErrorProps) {
+ if (!message) {
+ return null;
+ }
+
+ return {message}
;
+}
diff --git a/apps/web/src/components/user/ChangePassword.tsx b/apps/web/src/components/user/ChangePassword.tsx
new file mode 100644
index 00000000..a0dc55bb
--- /dev/null
+++ b/apps/web/src/components/user/ChangePassword.tsx
@@ -0,0 +1,85 @@
+import { api, handleError } from "@/utils/api";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { ContentHeader, ContentSection } from "@/components/Content";
+import { useForm } from "react-hook-form";
+import { toast } from "@/components/ui/use-toast";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { InputError } from "../forms/InputError";
+
+const validator = z
+ .object({
+ oldPassword: z.string().min(1),
+ password: z.string().min(8),
+ confirmPassword: z.string().min(8),
+ })
+ .superRefine(({ confirmPassword, password }, ctx) => {
+ if (confirmPassword !== password) {
+ ctx.addIssue({
+ path: ["confirmPassword"],
+ code: "custom",
+ message: "The passwords did not match",
+ });
+ }
+ });
+
+type IForm = z.infer;
+
+export function ChangePassword() {
+ const mutation = api.user.changePassword.useMutation({
+ onSuccess() {
+ toast({
+ title: "Success",
+ description: "You have updated your password",
+ });
+ },
+ onError: handleError,
+ });
+
+ const { register, handleSubmit, formState } = useForm({
+ resolver: zodResolver(validator),
+ defaultValues: {
+ oldPassword: "",
+ password: "",
+ confirmPassword: "",
+ },
+ });
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/pages/[organization]/settings/profile.tsx b/apps/web/src/pages/[organization]/settings/profile.tsx
index 4a5d0025..d5d674fb 100644
--- a/apps/web/src/pages/[organization]/settings/profile.tsx
+++ b/apps/web/src/pages/[organization]/settings/profile.tsx
@@ -7,24 +7,36 @@ import { useEffect } from "react";
import { SettingsLayout } from "@/components/layouts/SettingsLayout";
import { toast } from "@/components/ui/use-toast";
import { createServerSideProps } from "@/server/getServerSideProps";
+import { ChangePassword } from "@/components/user/ChangePassword";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { InputError } from "@/components/forms/InputError";
-export const getServerSideProps = createServerSideProps()
+export const getServerSideProps = createServerSideProps();
+
+const validator = z.object({
+ name: z.string().min(2),
+ email: z.string().email(),
+})
+
+type IForm = z.infer;
export default function Profile() {
const query = api.user.current.useQuery();
const mutation = api.user.update.useMutation({
onSuccess() {
toast({
- title: 'Profile updated',
- description: 'Your profile has been updated.',
- })
- query.refetch()
+ title: "Profile updated",
+ description: "Your profile has been updated.",
+ });
+ query.refetch();
},
onError: handleError,
});
const data = query.data;
const { register, handleSubmit, reset, formState } = useForm({
+ resolver: zodResolver(validator),
defaultValues: {
name: "",
email: "",
@@ -39,35 +51,29 @@ export default function Profile() {
return (
-
);
}
diff --git a/apps/web/src/server/api/routers/user.ts b/apps/web/src/server/api/routers/user.ts
index af731224..46d2a9a4 100644
--- a/apps/web/src/server/api/routers/user.ts
+++ b/apps/web/src/server/api/routers/user.ts
@@ -5,7 +5,7 @@ import {
protectedProcedure,
} from "@/server/api/trpc";
import { db } from "@/server/db";
-import { hashPassword } from "@/server/services/hash.service";
+import { hashPassword, verifyPassword } from "@/server/services/hash.service";
export const userRouter = createTRPCRouter({
current: protectedProcedure.query(({ ctx }) => {
@@ -47,14 +47,10 @@ export const userRouter = createTRPCRouter({
}
})
- if(user.password !== input.oldPassword) {
+ if(!(await verifyPassword(input.oldPassword, user.password))) {
throw new Error('Old password is incorrect')
}
-
- if(user.password === input.password) {
- throw new Error('New password cannot be the same as old password')
- }
-
+
return db.user.update({
where: {
id: ctx.session.user.id
diff --git a/apps/web/src/server/auth.ts b/apps/web/src/server/auth.ts
index 3d79b981..f8e60666 100644
--- a/apps/web/src/server/auth.ts
+++ b/apps/web/src/server/auth.ts
@@ -8,7 +8,7 @@ import {
import { db } from "@/server/db";
import Credentials from "next-auth/providers/credentials";
import { createError } from "./exceptions";
-import { verifyPassword } from "@/server/services/hash.service";
+import { hashPassword, verifyPassword } from "@/server/services/hash.service";
/**
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
@@ -55,18 +55,26 @@ export const authOptions: NextAuthOptions = {
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
+ if(!credentials?.password || !credentials?.email) {
+ return null
+ }
+
const user = await db.user.findFirst({
where: { email: credentials?.email },
});
- if (user) {
- return {
- ...user,
- image: 'https://avatars.githubusercontent.com/u/18133?v=4'
- };
- } else {
- return null;
+ if(!user) {
+ return null
}
+
+ if(!await verifyPassword(credentials.password, user.password)) {
+ return null
+ }
+
+ return {
+ ...user,
+ image: 'https://api.dicebear.com/7.x/adventurer/svg?seed=Abby'
+ };
},
}),
/**