From 1c94f46d0f680118e2cd0ab2b5ff9d0502390ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Goran=20Markovi=C4=87?= <91391493+goran010@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:03:10 +0200 Subject: [PATCH 1/3] feat(users): add new dynamic route for editing user profiles --- src/app/users/[id]/page.tsx | 22 ++++++++++++++++++++++ src/app/users/page.tsx | 27 +++++++++++++++++++++++++-- src/components/organisms/Calendar.tsx | 4 ++-- src/server/auth.ts | 2 +- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/app/users/[id]/page.tsx diff --git a/src/app/users/[id]/page.tsx b/src/app/users/[id]/page.tsx new file mode 100644 index 0000000..b635734 --- /dev/null +++ b/src/app/users/[id]/page.tsx @@ -0,0 +1,22 @@ +"use client"; +import MainLayout from "~/components/layout/mainLayout"; +import { useParams } from "next/navigation"; + +interface Params { + id: string; +} + +export default function Page() { + const params = useParams() as unknown as Params; // Correct type casting + const { id } = params; + + return ( + Uređivanje volontera {id} + } + > +
{/* form */}
+
+ ); +} diff --git a/src/app/users/page.tsx b/src/app/users/page.tsx index c3f52c1..c5c5ae2 100644 --- a/src/app/users/page.tsx +++ b/src/app/users/page.tsx @@ -19,8 +19,24 @@ import { useState, useEffect } from "react"; import LoadingSpinner from "~/components/organisms/loadingSpinner/LoadingSpinner"; import SearchInput from "~/components/atoms/SearchInput"; import { useDebounce } from "@uidotdev/usehooks"; +import { useRouter } from "next/navigation"; + +// Define the types for user and API response +interface UserProfile { + firstName: string; + lastName: string; +} + +interface FindUserReturnDTO { + id: string; + email: string; + active: boolean | null; + profile: UserProfile | null; + createdAt: Date; +} const Users = () => { + const router = useRouter(); const [page, setPage] = useState(0); const [totalPageNumber, setTotalPageNumber] = useState(1); const [filter, setFilter] = useState | undefined>( @@ -46,6 +62,10 @@ const Users = () => { setPage(0); }; + const handleEditClick = (user: FindUserReturnDTO) => { + router.push(`/users/${user.id}`); + }; + return ( Pretraživanje i odabir volontera}>
@@ -101,7 +121,10 @@ const Users = () => { )} - + handleEditClick(user)} + > @@ -114,7 +137,7 @@ const Users = () => { setPage(pageNumber)} + onChangePage={(pageNumber) => setPage(pageNumber - 1)} onPreviousPage={() => { if (page === 0) return; setPage(page - 1); diff --git a/src/components/organisms/Calendar.tsx b/src/components/organisms/Calendar.tsx index e89ae6c..f60723a 100644 --- a/src/components/organisms/Calendar.tsx +++ b/src/components/organisms/Calendar.tsx @@ -56,8 +56,8 @@ function Calendar({ ...classNames, }} components={{ - IconLeft: ({ ...props }) => , - IconRight: ({ ...props }) => , + IconLeft: () => , + IconRight: () => , }} {...props} /> diff --git a/src/server/auth.ts b/src/server/auth.ts index 185c4a0..2d23e09 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -58,7 +58,7 @@ export const authOptions: NextAuthOptions = { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, - async authorize(credentials, req) { + async authorize(credentials) { if (!credentials?.email || !credentials?.password) return null; const [user] = await db From b926bfcbfc08415170600fe2f31921bfeca35e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Goran=20Markovi=C4=87?= <91391493+goran010@users.noreply.github.com> Date: Tue, 27 Aug 2024 01:09:52 +0200 Subject: [PATCH 2/3] feat(user edit page): add form for editing user data --- src/app/users/[id]/page.tsx | 152 ++++++++++++++++++++++++++++++++++-- src/app/users/page.tsx | 27 +++---- 2 files changed, 156 insertions(+), 23 deletions(-) diff --git a/src/app/users/[id]/page.tsx b/src/app/users/[id]/page.tsx index b635734..bbc317a 100644 --- a/src/app/users/[id]/page.tsx +++ b/src/app/users/[id]/page.tsx @@ -1,22 +1,158 @@ "use client"; +import { useState, useEffect } from "react"; import MainLayout from "~/components/layout/mainLayout"; import { useParams } from "next/navigation"; +import { api } from "~/trpc/react"; +import type { FindUserReturnDTO } from "../page"; -interface Params { - id: string; -} +export default function UserDetailPage() { + const { id } = useParams(); // Get the user ID from the URL + + // Fetch user data by ID + const { data, isLoading, error } = api.user.findById.useQuery({ + id: id as string, + }); + + // State to manage form inputs + const [formData, setFormData] = useState(null); + + // Initialize formData when data is loaded + useEffect(() => { + if (data && data.length > 0 && data[0]) { + setFormData(data[0]); + } + }, [data]); + + if (isLoading) { + return
Loading...
; + } + + if (error ?? !formData) { + return ( +
Error loading user data. Please check the URL and try again.
+ ); + } + + // Handle input changes + const handleChange = (e: React.ChangeEvent) => { + const { name, value, type, checked } = e.target; + setFormData((prev) => { + if (!prev) return null; -export default function Page() { - const params = useParams() as unknown as Params; // Correct type casting - const { id } = params; + const updatedProfile = { + ...prev.profile, + [name]: type === "checkbox" ? checked : value ?? "", + }; + + return { + ...prev, + profile: { + ...prev.profile, + firstName: updatedProfile.firstName ?? prev.profile?.firstName ?? "", + lastName: updatedProfile.lastName ?? prev.profile?.lastName ?? "", + }, + }; + }); + }; + + // Handle form submission + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + // Implementation for form submission goes here + }; return ( Uređivanje volontera {id} +

+ Uređivanje volontera {formData.profile?.firstName}{" "} + {formData.profile?.lastName} +

} > -
{/* form */}
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
); } diff --git a/src/app/users/page.tsx b/src/app/users/page.tsx index c5c5ae2..613d5f1 100644 --- a/src/app/users/page.tsx +++ b/src/app/users/page.tsx @@ -1,4 +1,5 @@ "use client"; +import { useEffect, useState } from "react"; import MainLayout from "~/components/layout/mainLayout"; import { api } from "~/trpc/react"; import { @@ -15,28 +16,25 @@ import { PaginationContent, PaginationPages, } from "~/components/organisms/Pagination"; -import { useState, useEffect } from "react"; import LoadingSpinner from "~/components/organisms/loadingSpinner/LoadingSpinner"; import SearchInput from "~/components/atoms/SearchInput"; import { useDebounce } from "@uidotdev/usehooks"; -import { useRouter } from "next/navigation"; +import Link from "next/link"; -// Define the types for user and API response interface UserProfile { firstName: string; lastName: string; } -interface FindUserReturnDTO { +export interface FindUserReturnDTO { id: string; email: string; active: boolean | null; profile: UserProfile | null; - createdAt: Date; + createdAt?: Date; } const Users = () => { - const router = useRouter(); const [page, setPage] = useState(0); const [totalPageNumber, setTotalPageNumber] = useState(1); const [filter, setFilter] = useState | undefined>( @@ -62,10 +60,6 @@ const Users = () => { setPage(0); }; - const handleEditClick = (user: FindUserReturnDTO) => { - router.push(`/users/${user.id}`); - }; - return ( Pretraživanje i odabir volontera}>
@@ -121,11 +115,14 @@ const Users = () => { )} - handleEditClick(user)} - > - + + + + ))} From 66dec044522f22e2a75c88fe573652b03badee5d Mon Sep 17 00:00:00 2001 From: Andrej Arbanas Date: Tue, 24 Sep 2024 16:27:50 +0200 Subject: [PATCH 3/3] fix: unresolved comments --- package.json | 1 + src/app/users/[id]/page.tsx | 184 ++++++------------ src/app/users/page.tsx | 15 +- src/components/organisms/Calendar.tsx | 4 +- .../organisms/form/formSwitch/FormSwitch.tsx | 33 ++++ src/server/api/routers/user.ts | 2 +- src/server/auth.ts | 2 +- yarn.lock | 18 ++ 8 files changed, 118 insertions(+), 141 deletions(-) create mode 100644 src/components/organisms/form/formSwitch/FormSwitch.tsx diff --git a/package.json b/package.json index bff1d32..3d4b18c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.1.0", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.39.0", "@trpc/client": "next", diff --git a/src/app/users/[id]/page.tsx b/src/app/users/[id]/page.tsx index bbc317a..67f7807 100644 --- a/src/app/users/[id]/page.tsx +++ b/src/app/users/[id]/page.tsx @@ -1,9 +1,21 @@ "use client"; -import { useState, useEffect } from "react"; import MainLayout from "~/components/layout/mainLayout"; import { useParams } from "next/navigation"; import { api } from "~/trpc/react"; -import type { FindUserReturnDTO } from "../page"; +import FormComponent from "~/components/organisms/form/formComponent/FormComponent"; +import { useForm } from "react-hook-form"; +import LoadingSpinner from "~/components/organisms/loadingSpinner/LoadingSpinner"; +import FormInput from "~/components/organisms/form/formInput/FormInput"; +import FormSwitch from "~/components/organisms/form/formSwitch/FormSwitch"; +import { Button } from "~/components/atoms/Button"; + +type FindUserReturnDTO = { + id: string; + email: string; + active: boolean; + firstname: string; + lastname: string; +}; export default function UserDetailPage() { const { id } = useParams(); // Get the user ID from the URL @@ -13,48 +25,14 @@ export default function UserDetailPage() { id: id as string, }); - // State to manage form inputs - const [formData, setFormData] = useState(null); - - // Initialize formData when data is loaded - useEffect(() => { - if (data && data.length > 0 && data[0]) { - setFormData(data[0]); - } - }, [data]); - - if (isLoading) { - return
Loading...
; - } - - if (error ?? !formData) { - return ( -
Error loading user data. Please check the URL and try again.
- ); - } - - // Handle input changes - const handleChange = (e: React.ChangeEvent) => { - const { name, value, type, checked } = e.target; - setFormData((prev) => { - if (!prev) return null; - - const updatedProfile = { - ...prev.profile, - [name]: type === "checkbox" ? checked : value ?? "", - }; - - return { - ...prev, - profile: { - ...prev.profile, - firstName: updatedProfile.firstName ?? prev.profile?.firstName ?? "", - lastName: updatedProfile.lastName ?? prev.profile?.lastName ?? "", - }, - }; - }); - }; - + const form = useForm({ + defaultValues: { + email: data?.email ?? "", + active: data?.active ?? false, + firstname: data?.profile?.firstName ?? "", + lastname: data?.profile?.lastName ?? "", + }, + }); // Handle form submission const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -65,93 +43,53 @@ export default function UserDetailPage() { - Uređivanje volontera {formData.profile?.firstName}{" "} - {formData.profile?.lastName} + Uređivanje volontera {data?.profile?.firstName}{" "} + {data?.profile?.lastName} } >
-
-
- - -
+ {isLoading && } + {error &&
Greška
} + + + -
- - -
+ -
- - -
+ -
- - -
+ { + console.log("Switched"); + }} + /> -
- -
- + +
); diff --git a/src/app/users/page.tsx b/src/app/users/page.tsx index 613d5f1..277f3b2 100644 --- a/src/app/users/page.tsx +++ b/src/app/users/page.tsx @@ -21,19 +21,6 @@ import SearchInput from "~/components/atoms/SearchInput"; import { useDebounce } from "@uidotdev/usehooks"; import Link from "next/link"; -interface UserProfile { - firstName: string; - lastName: string; -} - -export interface FindUserReturnDTO { - id: string; - email: string; - active: boolean | null; - profile: UserProfile | null; - createdAt?: Date; -} - const Users = () => { const [page, setPage] = useState(0); const [totalPageNumber, setTotalPageNumber] = useState(1); @@ -134,7 +121,7 @@ const Users = () => { setPage(pageNumber - 1)} + onChangePage={(pageNumber) => setPage(pageNumber)} onPreviousPage={() => { if (page === 0) return; setPage(page - 1); diff --git a/src/components/organisms/Calendar.tsx b/src/components/organisms/Calendar.tsx index f60723a..f584720 100644 --- a/src/components/organisms/Calendar.tsx +++ b/src/components/organisms/Calendar.tsx @@ -56,8 +56,8 @@ function Calendar({ ...classNames, }} components={{ - IconLeft: () => , - IconRight: () => , + IconLeft: ({ ..._props }) => , + IconRight: ({ ..._props }) => , }} {...props} /> diff --git a/src/components/organisms/form/formSwitch/FormSwitch.tsx b/src/components/organisms/form/formSwitch/FormSwitch.tsx new file mode 100644 index 0000000..2d221b8 --- /dev/null +++ b/src/components/organisms/form/formSwitch/FormSwitch.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import * as Switch from "@radix-ui/react-switch"; + +type FormSwitchProps = { + id: string; + label: string; + active: boolean; + setActive: () => void; +}; + +// eslint-disable-next-line react/display-name +const FormSwitch: React.FC = ({ + id, + label, + setActive, + active, +}) => { + return ( +
+ + + + +
+ ); +}; + +export default FormSwitch; diff --git a/src/server/api/routers/user.ts b/src/server/api/routers/user.ts index 7a0a6d5..25f010e 100644 --- a/src/server/api/routers/user.ts +++ b/src/server/api/routers/user.ts @@ -9,7 +9,7 @@ export const userRouter = createTRPCRouter({ .query(async ({ input }) => { const result = await userService.getById(input.id); - return result; + return result[0]; }), find: protectedProcedure .input( diff --git a/src/server/auth.ts b/src/server/auth.ts index 2d23e09..3ea1e1d 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -58,7 +58,7 @@ export const authOptions: NextAuthOptions = { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, - async authorize(credentials) { + async authorize(credentials, _req) { if (!credentials?.email || !credentials?.password) return null; const [user] = await db diff --git a/yarn.lock b/yarn.lock index 586faf5..bf34bd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1426,6 +1426,19 @@ dependencies: "@radix-ui/react-compose-refs" "1.1.0" +"@radix-ui/react-switch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.0.tgz#fcf8e778500f1d60d4b2bec2fc3fad77a7c118e3" + integrity sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" + "@radix-ui/react-use-callback-ref@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" @@ -1450,6 +1463,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== +"@radix-ui/react-use-previous@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c" + integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og== + "@radix-ui/react-use-rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"