Skip to content

Commit

Permalink
Lego/feat/user info (#57)
Browse files Browse the repository at this point in the history
# Summary
- Se agrega ventana de modificación de datos básicos de usuario 

## Reference

![profile](https://github.com/user-attachments/assets/c9ea1c07-b7ad-49eb-8987-df0c343295bd)

![edit_profile](https://github.com/user-attachments/assets/a8c25e7d-436d-4ef8-8136-299b6d60e23b)
  • Loading branch information
joseglego authored Oct 7, 2024
1 parent 5fa51c2 commit 2fa1644
Show file tree
Hide file tree
Showing 16 changed files with 671 additions and 6 deletions.
4 changes: 2 additions & 2 deletions app/api/ApolloWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const errorPromotionLink = new ApolloLink((operation, forward) => {
if (
data.errors &&
data.errors.length > 0 &&
data.errors.some((error) => error.extensions.code === "UNAUTHENTICATED")
data.errors.some((error) => error?.extensions?.code === "UNAUTHENTICATED")
) {
throw new Error("GraphQL Authentication Error. Retriable");
}
Expand Down Expand Up @@ -77,7 +77,7 @@ const useErrorLink = () => {
return onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
if (err.extensions.type === "UNAUTHENTICATED") {
if (err?.extensions?.type === "UNAUTHENTICATED") {
refreshSession()
.then(() => {
forward(operation);
Expand Down
8 changes: 8 additions & 0 deletions app/api/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const documents = {
types.MyPurchaseOrdersDocument,
"query SearchUsers($input: PaginatedInputUserSearchValues!) {\n userSearch(input: $input) {\n data {\n id\n username\n name\n lastName\n imageUrl\n email\n }\n pagination {\n currentPage\n pageSize\n totalPages\n totalRecords\n }\n }\n}":
types.SearchUsersDocument,
"mutation updateUser($input: userEditInput!) {\n updateUser(input: $input) {\n id\n name\n lastName\n username\n bio\n email\n }\n}":
types.UpdateUserDocument,
"query myProfile {\n me {\n id\n bio\n lastName\n username\n imageUrl\n isSuperAdmin\n email\n name\n impersonatedUser {\n id\n name\n }\n communities {\n id\n name\n }\n }\n}":
types.MyProfileDocument,
"mutation CheckPurchaseOrderStatus($input: CheckForPurchaseOrderInput!) {\n checkPurchaseOrderStatus(input: $input) {\n id\n paymentLink\n status\n finalPrice\n paymentPlatform\n createdAt\n publicId\n currency {\n id\n currency\n }\n tickets {\n id\n approvalStatus\n paymentStatus\n redemptionStatus\n publicId\n ticketTemplate {\n id\n name\n description\n event {\n id\n name\n address\n description\n startDateTime\n endDateTime\n status\n publicShareURL\n logoImage {\n url\n }\n community {\n name\n }\n }\n prices {\n id\n amount\n currency {\n currency\n id\n }\n }\n }\n }\n }\n}":
Expand Down Expand Up @@ -79,6 +81,12 @@ export function graphql(
export function graphql(
source: "query SearchUsers($input: PaginatedInputUserSearchValues!) {\n userSearch(input: $input) {\n data {\n id\n username\n name\n lastName\n imageUrl\n email\n }\n pagination {\n currentPage\n pageSize\n totalPages\n totalRecords\n }\n }\n}",
): (typeof documents)["query SearchUsers($input: PaginatedInputUserSearchValues!) {\n userSearch(input: $input) {\n data {\n id\n username\n name\n lastName\n imageUrl\n email\n }\n pagination {\n currentPage\n pageSize\n totalPages\n totalRecords\n }\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: "mutation updateUser($input: userEditInput!) {\n updateUser(input: $input) {\n id\n name\n lastName\n username\n bio\n email\n }\n}",
): (typeof documents)["mutation updateUser($input: userEditInput!) {\n updateUser(input: $input) {\n id\n name\n lastName\n username\n bio\n email\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
71 changes: 71 additions & 0 deletions app/api/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,21 @@ export type SearchUsersQuery = {
};
};

export type UpdateUserMutationVariables = Exact<{
input: UserEditInput;
}>;

export type UpdateUserMutation = {
updateUser: {
id: string;
name?: string | null;
lastName?: string | null;
username: string;
bio?: string | null;
email?: string | null;
};
};

export type MyProfileQueryVariables = Exact<{ [key: string]: never }>;

export type MyProfileQuery = {
Expand Down Expand Up @@ -2451,6 +2466,62 @@ export const SearchUsersDocument = {
},
],
} as unknown as DocumentNode<SearchUsersQuery, SearchUsersQueryVariables>;
export const UpdateUserDocument = {
kind: "Document",
definitions: [
{
kind: "OperationDefinition",
operation: "mutation",
name: { kind: "Name", value: "updateUser" },
variableDefinitions: [
{
kind: "VariableDefinition",
variable: {
kind: "Variable",
name: { kind: "Name", value: "input" },
},
type: {
kind: "NonNullType",
type: {
kind: "NamedType",
name: { kind: "Name", value: "userEditInput" },
},
},
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{
kind: "Field",
name: { kind: "Name", value: "updateUser" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "input" },
value: {
kind: "Variable",
name: { kind: "Name", value: "input" },
},
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{ kind: "Field", name: { kind: "Name", value: "name" } },
{ kind: "Field", name: { kind: "Name", value: "lastName" } },
{ kind: "Field", name: { kind: "Name", value: "username" } },
{ kind: "Field", name: { kind: "Name", value: "bio" } },
{ kind: "Field", name: { kind: "Name", value: "email" } },
],
},
},
],
},
},
],
} as unknown as DocumentNode<UpdateUserMutation, UpdateUserMutationVariables>;
export const MyProfileDocument = {
kind: "Document",
definitions: [
Expand Down
169 changes: 169 additions & 0 deletions app/components/Profile/Info/MyProfileInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Link, useNavigate } from "@remix-run/react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";

import { useMyProfileSuspenseQuery } from "~/components/Profile/graphql/myProfile.generated";
import { Button, buttonVariants } from "~/components/ui/button";
import { Card, CardContent, CardTitle } from "~/components/ui/card";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { urls } from "~/utils/urls";
import { cn } from "~/utils/utils";

import { useUpdateUserMutation } from "./graphql/updateUser.generated";

const UpdateUserSchema = z.object({
name: z
.string({ required_error: "El nombre es requerido." })
.min(1, "El nombre es requerido."),
lastName: z
.string({ required_error: "El apellido es requerido." })
.min(1, "El apellido es requerido."),
username: z
.string({ required_error: "El nombre de usaurio es requerido." })
.min(1, "El nombre de usaurio es requerido."),
});

const USERNAME_ALREADY_EXISTS =
'duplicate key value violates unique constraint "users_username_unique"';

export const MyProfileInfo = () => {
const navigate = useNavigate();
const { data } = useMyProfileSuspenseQuery();
const [updateUser, { loading: isLoading }] = useUpdateUserMutation();

const form = useForm<z.infer<typeof UpdateUserSchema>>({
resolver: zodResolver(UpdateUserSchema),
defaultValues: {
name: "",
lastName: "",
username: "",
},
});

useEffect(() => {
form.setValue("name", data.me?.name ?? "");
form.setValue("lastName", data.me?.lastName ?? "");
form.setValue("username", data.me?.username ?? "");
}, [form, data]);

const onSubmit = async ({
name,
lastName,
username,
}: z.infer<typeof UpdateUserSchema>) => {
await updateUser({
variables: {
input: {
id: data.me.id,
name,
lastName,
username,
},
},
onCompleted() {
toast.success("Datos Actualizados.", {
description: "Los datos se han actualizado correctamente.",
});
navigate(urls.profile.root);
},
onError(error) {
const message =
error.message == USERNAME_ALREADY_EXISTS
? "Nombre de usuario en uso. Debes elegir otro nombre de usuario"
: "Error Desconocido";

toast.error("Hubo un error.", {
description: `${message}. Verifica que hayas ingresado correctamente la información e intenta nuevamente, si el error persiste comunicate con el equipo.`,
});
},
});
};

return (
<div className="mx-auto w-full max-w-[800px] md:p-4">
<div>
<Card className="mx-auto w-full max-w-md rounded-xl shadow-md lg:w-96">
<CardTitle className="p-6 pb-4 text-xl font-semibold">
Datos Básicos
</CardTitle>
<CardContent>
<Form {...form}>
<form
onSubmit={(event) => void form.handleSubmit(onSubmit)(event)}
className={"grid w-full items-start gap-6 pt-0"}
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre</FormLabel>
<FormControl>
<Input placeholder="Nombre" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Apellido</FormLabel>
<FormControl>
<Input placeholder="Apellido" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre de Usuario</FormLabel>
<FormControl>
<Input placeholder="Nombre de Usuario" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-col justify-end gap-4 md:flex-row">
{isLoading ? (
<Button variant="secondary" disabled>
Cancelar
</Button>
) : (
<Link
to={urls.profile.root}
className={cn(buttonVariants({ variant: "secondary" }))}
>
Cancelar
</Link>
)}
<Button disabled={isLoading}>Guardar</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</div>
);
};
47 changes: 47 additions & 0 deletions app/components/Profile/Info/MyProfileInfoLoadingSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Button } from "~/components/ui/button";
import { Card, CardTitle, CardContent } from "~/components/ui/card";
import { Label } from "~/components/ui/label";
import { Skeleton } from "~/components/ui/skeleton";

const LoadingCard = () => {
return (
<div className="mx-auto w-full max-w-[800px] md:p-4">
<div>
<Card className="mx-auto w-full max-w-md rounded-xl shadow-md lg:w-96">
<CardTitle className="p-6 pb-4 text-xl font-semibold">
Datos Básicos
</CardTitle>
<CardContent>
<div className="grid w-full items-start gap-6 pt-0">
<div className="grid space-y-2">
<Label className="leading-5">Nombre</Label>
<Skeleton className="mt-1 h-10 w-full" />
</div>

<div className="grid space-y-2">
<Label className="leading-5">Apellido</Label>
<Skeleton className="mt-1 h-10 w-full" />
</div>

<div className="grid space-y-2">
<Label className="leading-5">Nombre de Usuario</Label>
<Skeleton className="mt-1 h-10 w-full" />
</div>
<div className="flex flex-col justify-end gap-4 md:flex-row">
<Button variant="secondary">Cancelar</Button>
<Button className="">Guardar</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
};
export const MyProfileInfoLoadingSkeleton = () => {
return (
<div className="flex flex-col gap-6">
<LoadingCard />
</div>
);
};
Loading

0 comments on commit 2fa1644

Please sign in to comment.