diff --git a/app/api/gql/gql.ts b/app/api/gql/gql.ts index 8e5819d..f6f8ed5 100644 --- a/app/api/gql/gql.ts +++ b/app/api/gql/gql.ts @@ -17,6 +17,8 @@ const documents = { types.FetchExampleEventsDocument, "query myEvents($input: PaginatedInputEventsSearchInput!, $userTicketSearchInput: EventsTicketsSearchInput) {\n searchEvents(input: $input) {\n data {\n id\n name\n description\n startDateTime\n community {\n id\n name\n }\n status\n usersTickets(input: $userTicketSearchInput) {\n id\n approvalStatus\n paymentStatus\n redemptionStatus\n ticketTemplate {\n description\n id\n }\n }\n }\n pagination {\n currentPage\n pageSize\n totalPages\n totalRecords\n }\n }\n}": types.MyEventsDocument, + "query myProfile {\n me {\n id\n bio\n lastName\n username\n imageUrl\n email\n name\n communities {\n id\n name\n }\n }\n}": + types.MyProfileDocument, "mutation CheckPurchaseOrderStatus($input: CheckForPurchaseOrderInput!) {\n checkPurchaseOrderStatus(input: $input) {\n status\n tickets {\n approvalStatus\n paymentStatus\n redemptionStatus\n }\n }\n}": types.CheckPurchaseOrderStatusDocument, "mutation createPurchaseOrder($input: TicketClaimInput!) {\n claimUserTicket(input: $input) {\n __typename\n ... on PurchaseOrder {\n __typename\n id\n currency {\n id\n }\n finalPrice\n paymentLink\n status\n tickets {\n id\n approvalStatus\n redemptionStatus\n paymentStatus\n }\n }\n ... on RedeemUserTicketError {\n __typename\n error\n errorMessage\n }\n }\n}": @@ -53,6 +55,12 @@ export function graphql( export function graphql( source: "query myEvents($input: PaginatedInputEventsSearchInput!, $userTicketSearchInput: EventsTicketsSearchInput) {\n searchEvents(input: $input) {\n data {\n id\n name\n description\n startDateTime\n community {\n id\n name\n }\n status\n usersTickets(input: $userTicketSearchInput) {\n id\n approvalStatus\n paymentStatus\n redemptionStatus\n ticketTemplate {\n description\n id\n }\n }\n }\n pagination {\n currentPage\n pageSize\n totalPages\n totalRecords\n }\n }\n}", ): (typeof documents)["query myEvents($input: PaginatedInputEventsSearchInput!, $userTicketSearchInput: EventsTicketsSearchInput) {\n searchEvents(input: $input) {\n data {\n id\n name\n description\n startDateTime\n community {\n id\n name\n }\n status\n usersTickets(input: $userTicketSearchInput) {\n id\n approvalStatus\n paymentStatus\n redemptionStatus\n ticketTemplate {\n description\n id\n }\n }\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: "query myProfile {\n me {\n id\n bio\n lastName\n username\n imageUrl\n email\n name\n communities {\n id\n name\n }\n }\n}", +): (typeof documents)["query myProfile {\n me {\n id\n bio\n lastName\n username\n imageUrl\n email\n name\n communities {\n id\n name\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/app/api/gql/graphql.ts b/app/api/gql/graphql.ts index 7afa503..046cfc9 100644 --- a/app/api/gql/graphql.ts +++ b/app/api/gql/graphql.ts @@ -906,6 +906,21 @@ export type MyEventsQuery = { }; }; +export type MyProfileQueryVariables = Exact<{ [key: string]: never }>; + +export type MyProfileQuery = { + me: { + id: string; + bio?: string | null; + lastName?: string | null; + username: string; + imageUrl?: string | null; + email?: string | null; + name?: string | null; + communities: Array<{ id: string; name?: string | null }>; + }; +}; + export type CheckPurchaseOrderStatusMutationVariables = Exact<{ input: CheckForPurchaseOrderInput; }>; @@ -1328,6 +1343,48 @@ export const MyEventsDocument = { }, ], } as unknown as DocumentNode; +export const MyProfileDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "query", + name: { kind: "Name", value: "myProfile" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "me" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + { kind: "Field", name: { kind: "Name", value: "bio" } }, + { kind: "Field", name: { kind: "Name", value: "lastName" } }, + { kind: "Field", name: { kind: "Name", value: "username" } }, + { kind: "Field", name: { kind: "Name", value: "imageUrl" } }, + { kind: "Field", name: { kind: "Name", value: "email" } }, + { kind: "Field", name: { kind: "Name", value: "name" } }, + { + kind: "Field", + name: { kind: "Name", value: "communities" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + { kind: "Field", name: { kind: "Name", value: "name" } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode; export const CheckPurchaseOrderStatusDocument = { kind: "Document", definitions: [ diff --git a/app/components/Login/Login.tsx b/app/components/Login/Login.tsx index e6d4537..68097ba 100644 --- a/app/components/Login/Login.tsx +++ b/app/components/Login/Login.tsx @@ -15,7 +15,7 @@ export const Login = () => { }, []); return ( -
+

Regístrate.

diff --git a/app/components/Navbar/MainNav.tsx b/app/components/Navbar/MainNav.tsx index 87cdb02..ef9a966 100644 --- a/app/components/Navbar/MainNav.tsx +++ b/app/components/Navbar/MainNav.tsx @@ -1,26 +1,14 @@ -import { useIsAuthReady, useIsLoggedIn } from "~/utils/supabase/AuthProvider"; -import { urls } from "~/utils/urls"; - import { NavbarItem } from "./NavbarItem"; import type { NavBarProps } from "./types"; -import { buttonVariants } from "../ui/button"; export function MainNav({ items }: NavBarProps) { - const user = useIsLoggedIn(); - const isReady = useIsAuthReady(); - return ( -

); diff --git a/app/components/Navbar/types.d.ts b/app/components/Navbar/types.d.ts index 372e695..0ebc00d 100644 --- a/app/components/Navbar/types.d.ts +++ b/app/components/Navbar/types.d.ts @@ -3,6 +3,7 @@ export type NavbarMenuItem = { show: boolean; link?: string; icon?: React.ReactNode; + variant?: "secondary" | "link" | "default"; onClick?: (e: React.MouseEvent) => void; children?: Array; }; diff --git a/app/components/Profile/MyProfile.tsx b/app/components/Profile/MyProfile.tsx new file mode 100644 index 0000000..8cb7619 --- /dev/null +++ b/app/components/Profile/MyProfile.tsx @@ -0,0 +1,26 @@ +import { useMyProfileSuspenseQuery } from "~/components/Profile/graphql/myProfile.generated"; + +export const MyProfile = () => { + const { data } = useMyProfileSuspenseQuery(); + + return ( +
+
+

Información personal

+
+ Nombre: + + {data.me.name} {data.me.lastName && data.me.lastName} + +
+
+
+

Información de contacto

+
+ Email: + {data.me.email} +
+
+
+ ); +}; diff --git a/app/components/Profile/MyProfileLoadingSkeleton.tsx b/app/components/Profile/MyProfileLoadingSkeleton.tsx new file mode 100644 index 0000000..cc01758 --- /dev/null +++ b/app/components/Profile/MyProfileLoadingSkeleton.tsx @@ -0,0 +1,33 @@ +import { Button } from "~/components/ui/button"; +import { Card } from "~/components/ui/card"; +import { Skeleton } from "~/components/ui/skeleton"; + +const LoadingCard = () => { + return ( + +
+ + + + + + +
+ +
+ +
+
+ ); +}; +export const MyProfileLoadingSkeleton = () => { + return ( +
+ + + +
+ ); +}; diff --git a/app/components/Profile/graphql/myProfile.generated.tsx b/app/components/Profile/graphql/myProfile.generated.tsx new file mode 100644 index 0000000..6f95fc5 --- /dev/null +++ b/app/components/Profile/graphql/myProfile.generated.tsx @@ -0,0 +1,111 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck +/* eslint-disable */ +/* prettier-ignore */ +/* This file is automatically generated. Please do not modify it manually. */ +import * as Types from '../../../api/gql/graphql'; + +import { gql } from "graphql-tag"; +import * as Apollo from "@apollo/client"; +const defaultOptions = {} as const; +export type MyProfileQueryVariables = Types.Exact<{ [key: string]: never }>; + +export type MyProfileQuery = { + __typename?: "Query"; + me: { + __typename?: "User"; + id: string; + bio?: string | null; + lastName?: string | null; + username: string; + imageUrl?: string | null; + email?: string | null; + name?: string | null; + communities: Array<{ + __typename?: "Community"; + id: string; + name?: string | null; + }>; + }; +}; + +export const MyProfileDocument = gql` + query myProfile { + me { + id + bio + lastName + username + imageUrl + email + name + communities { + id + name + } + } + } +`; + +/** + * __useMyProfileQuery__ + * + * To run a query within a React component, call `useMyProfileQuery` and pass it any options that fit your needs. + * When your component renders, `useMyProfileQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useMyProfileQuery({ + * variables: { + * }, + * }); + */ +export function useMyProfileQuery( + baseOptions?: Apollo.QueryHookOptions< + MyProfileQuery, + MyProfileQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery( + MyProfileDocument, + options, + ); +} +export function useMyProfileLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + MyProfileQuery, + MyProfileQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery( + MyProfileDocument, + options, + ); +} +export function useMyProfileSuspenseQuery( + baseOptions?: Apollo.SuspenseQueryHookOptions< + MyProfileQuery, + MyProfileQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useSuspenseQuery( + MyProfileDocument, + options, + ); +} +export type MyProfileQueryHookResult = ReturnType; +export type MyProfileLazyQueryHookResult = ReturnType< + typeof useMyProfileLazyQuery +>; +export type MyProfileSuspenseQueryHookResult = ReturnType< + typeof useMyProfileSuspenseQuery +>; +export type MyProfileQueryResult = Apollo.QueryResult< + MyProfileQuery, + MyProfileQueryVariables +>; diff --git a/app/components/Profile/graphql/myProfile.gql b/app/components/Profile/graphql/myProfile.gql new file mode 100644 index 0000000..b4ce4b6 --- /dev/null +++ b/app/components/Profile/graphql/myProfile.gql @@ -0,0 +1,15 @@ +query myProfile { + me { + id + bio + lastName + username + imageUrl + email + name + communities { + id + name + } + } +} diff --git a/app/routes/_authenticated/_layout.tsx b/app/routes/_authenticated/_layout.tsx index e257c22..e9c12bc 100644 --- a/app/routes/_authenticated/_layout.tsx +++ b/app/routes/_authenticated/_layout.tsx @@ -13,7 +13,8 @@ export default function AuthenticatedLayout() { if (!isLoggedIn) { return ( -
+
+
); diff --git a/app/routes/_authenticated/profile/index.tsx b/app/routes/_authenticated/profile/index.tsx new file mode 100644 index 0000000..f7cf528 --- /dev/null +++ b/app/routes/_authenticated/profile/index.tsx @@ -0,0 +1,19 @@ +import { cx } from "class-variance-authority"; +import { Suspense } from "react"; + +import { MyProfile } from "~/components/Profile/MyProfile"; +import { MyProfileLoadingSkeleton } from "~/components/Profile/MyProfileLoadingSkeleton"; +import { sharedLayoutStyle } from "~/components/sharedLayouts"; + +export default function Layout() { + return ( +
+
+

Tu Perfil

+
+ }> + + +
+ ); +} diff --git a/app/routes/login/index.tsx b/app/routes/login/index.tsx index 153ee0c..647411e 100644 --- a/app/routes/login/index.tsx +++ b/app/routes/login/index.tsx @@ -28,11 +28,9 @@ export default function LoginRoute() { } return ( -
-
- -
-
+
+
+
); } diff --git a/app/utils/supabase/AuthProvider/index.tsx b/app/utils/supabase/AuthProvider/index.tsx index fbebadf..db15ad9 100644 --- a/app/utils/supabase/AuthProvider/index.tsx +++ b/app/utils/supabase/AuthProvider/index.tsx @@ -32,6 +32,7 @@ export const AuthContext = createContext({ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const [supabaseSession, setSession] = useState(null); + const [isReady, setIsReady] = useState(false); const [user, setUser] = useState(null); const tokenRef = useRef(null); @@ -62,6 +63,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { setter(session); await supabaseClient.auth.startAutoRefresh(); + await supabaseClient.auth.refreshSession(); }; const { @@ -72,7 +74,9 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { }); // eslint-disable-next-line no-console - initialize().catch(console.error); + initialize() + .then(() => setIsReady(true)) + .catch(console.error); return () => subscription.unsubscribe(); }, [setter]); @@ -94,12 +98,13 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { ({ user, isLogged: Boolean(user?.id), - isReady: Boolean(supabaseSession), + isReady: Boolean(isReady), tokenRef, setTokenRef, refreshSession, }) satisfies AuthContextType, - [user, supabaseSession, tokenRef, setTokenRef, refreshSession], + // eslint-disable-next-line react-hooks/exhaustive-deps + [user, isReady, setTokenRef, refreshSession, supabaseSession], ); return {children}; diff --git a/app/utils/urls.ts b/app/utils/urls.ts index e6e1ee5..8b977ea 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -1,5 +1,8 @@ export const urls = { home: "/", + profile: { + root: "/profile", + }, events: { root: "/eventos", tickets: (id: string) => `/eventos/${id}/tickets`,