diff --git a/apps/admin/src/generated/graphql.ts b/apps/admin/src/generated/graphql.ts index 4e26ecd..e3e5fa8 100644 --- a/apps/admin/src/generated/graphql.ts +++ b/apps/admin/src/generated/graphql.ts @@ -124,6 +124,55 @@ export type FieldError = { message: Scalars["String"]; }; +export type Issue = { + __typename?: "Issue"; + category: IssueCategory; + categoryId: Scalars["Int"]; + comments?: Maybe>; + completed_at?: Maybe; + content: Scalars["String"]; + created_at: Scalars["String"]; + html?: Maybe; + id: Scalars["Int"]; + status: Scalars["String"]; + subject: Scalars["String"]; + updated_at: Scalars["String"]; + user: User; + userId: Scalars["Float"]; +}; + +export type IssueCategory = { + __typename?: "IssueCategory"; + created_at: Scalars["String"]; + desc: Scalars["String"]; + id: Scalars["Int"]; + identifier: Scalars["String"]; + issues?: Maybe>; + name: Scalars["String"]; + updated_at: Scalars["String"]; +}; + +export type IssueComment = { + __typename?: "IssueComment"; + completed_at?: Maybe; + content: Scalars["String"]; + created_at: Scalars["String"]; + html?: Maybe; + id: Scalars["Int"]; + issue: Issue; + issueId: Scalars["Int"]; + updated_at: Scalars["String"]; + user: User; + userId: Scalars["Float"]; +}; + +export type IssueInput = { + categoryId: Scalars["Float"]; + content: Scalars["String"]; + html: Scalars["String"]; + subject: Scalars["String"]; +}; + export type Mutation = { __typename?: "Mutation"; addAddress: Address; @@ -134,6 +183,8 @@ export type Mutation = { addToFavourite: Favourite; changePassword: UserResponse; clearCart: Scalars["Boolean"]; + createComment: IssueComment; + createIssue: Issue; createOrder: OrderDetail; createPayment: CreatePaymentResponse; deleteAddress: Scalars["Boolean"]; @@ -148,13 +199,16 @@ export type Mutation = { register: UserResponse; removeFromFavourite: Scalars["Boolean"]; resendVerificationEmail: Scalars["Boolean"]; + resolveByCustomer: Scalars["Boolean"]; updateAddress: Address; updateCart: Cart; updateCategory: ProductCategory; updateDiscount?: Maybe; + updateIssue: Issue; updateLanguagePreference: Scalars["Boolean"]; updateMarketingPreference: Scalars["Boolean"]; updatePassword: UserResponse; + updateProfile: User; updateReview?: Maybe; updateStatus: OrderDetail; verifyEmail: Scalars["Boolean"]; @@ -196,6 +250,15 @@ export type MutationChangePasswordArgs = { token: Scalars["String"]; }; +export type MutationCreateCommentArgs = { + content: Scalars["String"]; + issueId: Scalars["Int"]; +}; + +export type MutationCreateIssueArgs = { + input: IssueInput; +}; + export type MutationCreateOrderArgs = { options: CreateOrderInput; }; @@ -252,6 +315,10 @@ export type MutationResendVerificationEmailArgs = { email: Scalars["String"]; }; +export type MutationResolveByCustomerArgs = { + issueId: Scalars["Int"]; +}; + export type MutationUpdateAddressArgs = { id: Scalars["Int"]; input: AddressInput; @@ -271,6 +338,11 @@ export type MutationUpdateDiscountArgs = { options: UpdateDiscountInput; }; +export type MutationUpdateIssueArgs = { + id: Scalars["Int"]; + input: IssueInput; +}; + export type MutationUpdateLanguagePreferenceArgs = { currency: Scalars["String"]; language: Scalars["String"]; @@ -287,6 +359,12 @@ export type MutationUpdatePasswordArgs = { newPassword: Scalars["String"]; }; +export type MutationUpdateProfileArgs = { + first_name: Scalars["String"]; + imageUrl: Scalars["String"]; + last_name: Scalars["String"]; +}; + export type MutationUpdateReviewArgs = { desc: Scalars["String"]; isAnonymous: Scalars["Boolean"]; @@ -484,6 +562,9 @@ export type Query = { favouritesWithProduct: Array; fetchCartItems?: Maybe>; hello: Scalars["String"]; + issueCategories: Array; + issues: Array; + issuesWithComments: Issue; me?: Maybe; meWithAccount?: Maybe; orderById?: Maybe; @@ -506,6 +587,10 @@ export type QueryAllReviewsArgs = { productId: Scalars["Int"]; }; +export type QueryIssuesWithCommentsArgs = { + issueId: Scalars["Int"]; +}; + export type QueryOrderByIdArgs = { orderId: Scalars["String"]; }; @@ -594,6 +679,7 @@ export type User = { marketing_product_news: Scalars["Boolean"]; phone_number?: Maybe; phone_number_verified: Scalars["Boolean"]; + role: UserRole; roleId: Scalars["Float"]; updated_at: Scalars["String"]; }; @@ -610,6 +696,7 @@ export type UserRole = { id: Scalars["Int"]; name: Scalars["String"]; updated_at: Scalars["String"]; + users: Array; }; export type Variant = { diff --git a/apps/api/src/migration/1694454773982-Init.ts b/apps/api/src/migration/1694454773982-Init.ts new file mode 100644 index 0000000..9695c88 --- /dev/null +++ b/apps/api/src/migration/1694454773982-Init.ts @@ -0,0 +1,38 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Init1694454773982 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + INSERT INTO public.user_role (id, name) VALUES (1, 'CLIENT'); + INSERT INTO public.user_role (id, name) VALUES (2, 'DELIVERY'); + INSERT INTO public.user_role (id, name) VALUES (3, 'STAFF'); + INSERT INTO public.user_role (id, name) VALUES (4, 'MANAGER'); + INSERT INTO public.user_role (id, name) VALUES (5, 'ADMIN'); + + INSERT INTO public.shipping_method (name, price, dispatch_in) VALUES ( 'Standard Delivery', 8000, 96 ); + INSERT INTO public.shipping_method (name, price, dispatch_in) VALUES ( 'Express Delivery', 20000, 24); + + INSERT INTO public.issue_category (name, identifier, "desc") + VALUES + ('Order and Checkout Issues', 'order-and-checkout-issues', 'Problems related to the ordering and checkout process.'), + ('Product and Inventory Problems', 'product-and-inventory-problems', 'Issues concerning product availability, listings, and inventory management.'), + ('Security and Privacy Concerns', 'security-and-privacy-concerns', 'Concerns about data security, privacy, and unauthorized access.'), + ('Delivery and Shipping Issues', 'delivery-and-shipping-issues', 'Problems with product delivery, shipping delays, and lost shipments.'), + ('Customer Account and Login Problems', 'customer-account-and-login-problems', 'Issues related to customer accounts, logins, and profile management.'), + ('Returns, Refunds, and Exchanges', 'returns-refunds-and-exchanges', 'Matters concerning product returns, refunds, and exchange requests.'), + ('Customer Support and Communication', 'customer-support-and-communication', 'Problems with customer support, response times, and communication.'), + ('Discounts, Coupons, and Promotions', 'discounts-coupons-and-promotions', 'Issues related to discounts, coupon codes, and promotional offers.'), + ('Product Reviews and Ratings', 'product-reviews-and-ratings', 'Concerns about product reviews, ratings, and feedback management.'), + ('Technical Account Management', 'technical-account-management', 'Issues related to account suspension, recovery, or subscription management.'), + ('Website Accessibility and Usability', 'website-accessibility-and-usability', 'Accessibility and usability issues for all users, including those with disabilities.'), + ('Performance and Loading Speed', 'performance-and-loading-speed', 'Problems with website performance, loading times, and optimization.'), + ('Analytics and Reporting', 'analytics-and-reporting', 'Issues with data analytics, reporting, and data privacy.'), + ('Feedback and Suggestions', 'feedback-and-suggestions', 'General feedback and suggestions for improving the ecommerce platform.'), + ('Legal and Compliance Matters', 'legal-and-compliance-matters', 'Concerns regarding legal compliance, terms of service, and data protection.'), + ('Third-Party Integrations and Plugins', 'third-party-integrations-and-plugins', 'Issues with third-party integrations and plugin functionality.'), + ('Account Billing and Payments', 'account-billing-and-payments', 'Billing disputes, invoices, and payment-related problems.'); + `); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/apps/api/src/resolvers/user.ts b/apps/api/src/resolvers/user.ts index facf3fb..4c8fbe2 100644 --- a/apps/api/src/resolvers/user.ts +++ b/apps/api/src/resolvers/user.ts @@ -78,6 +78,32 @@ export class UserResolver { }); } + @Mutation(() => User) + @UseMiddleware(isVerified) + async updateProfile( + @Arg("imageUrl") imageUrl: string, + @Arg("first_name") first_name: string, + @Arg("last_name") last_name: string, + @Ctx() { req }: MyContext + ): Promise { + await User.update( + { + id: req.session.userId, + }, + { + imageUrl, + first_name, + last_name, + } + ); + + return User.findOneOrFail({ + where: { + id: req.session.userId, + }, + }); + } + @Mutation(() => UserResponse) async register( @Arg("options") options: RegisterInput, diff --git a/apps/storefront/src/components/pages/account/setting/ProfileForm.tsx b/apps/storefront/src/components/pages/account/setting/ProfileForm.tsx index 2c47599..8830bbb 100644 --- a/apps/storefront/src/components/pages/account/setting/ProfileForm.tsx +++ b/apps/storefront/src/components/pages/account/setting/ProfileForm.tsx @@ -12,16 +12,19 @@ import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Yup from "yup"; import { HiCloudUpload } from "react-icons/hi"; +import { useUpdateProfileMutation } from "@/generated/graphql"; import InputField from "@/components/ui/InputField"; type ProfileFormValues = { first_name: string; last_name: string; + imageUrl: string; }; const ProfileFormSchema = Yup.object({ first_name: Yup.string().required("Required"), last_name: Yup.string().required("Required"), + imageUrl: Yup.string().required("Required"), }); interface ProfileFormProps { @@ -37,6 +40,7 @@ const ProfileForm = ({ imageUrl, onSubmissionSuccess: closeModal, }: ProfileFormProps) => { + const toast = useToast(); const { register, handleSubmit, @@ -45,16 +49,41 @@ const ProfileForm = ({ defaultValues: { first_name, last_name, + imageUrl, }, resolver: yupResolver(ProfileFormSchema), }); - const toast = useToast(); + + const [updateProfileMutation] = useUpdateProfileMutation({ + refetchQueries: ["Me"], + onCompleted: () => { + toast({ + title: "Successfully Updated", + description: `Profile Updated`, + status: "success", + duration: 5000, + isClosable: true, + }); + }, + onError: (error) => { + toast({ + title: "Profile Update Failed", + description: error.message, + status: "error", + duration: 5000, + isClosable: true, + }); + }, + }); return (
{ - if (closeModal) { + const res = await updateProfileMutation({ + variables: values, + }); + + if (closeModal && res.data?.updateProfile.id) { closeModal(); } })} diff --git a/apps/storefront/src/generated/graphql.ts b/apps/storefront/src/generated/graphql.ts index 96b528f..a1afa4c 100644 --- a/apps/storefront/src/generated/graphql.ts +++ b/apps/storefront/src/generated/graphql.ts @@ -208,6 +208,7 @@ export type Mutation = { updateLanguagePreference: Scalars["Boolean"]; updateMarketingPreference: Scalars["Boolean"]; updatePassword: UserResponse; + updateProfile: User; updateReview?: Maybe; updateStatus: OrderDetail; verifyEmail: Scalars["Boolean"]; @@ -358,6 +359,12 @@ export type MutationUpdatePasswordArgs = { newPassword: Scalars["String"]; }; +export type MutationUpdateProfileArgs = { + first_name: Scalars["String"]; + imageUrl: Scalars["String"]; + last_name: Scalars["String"]; +}; + export type MutationUpdateReviewArgs = { desc: Scalars["String"]; isAnonymous: Scalars["Boolean"]; @@ -1894,6 +1901,34 @@ export type UpdatePasswordMutation = { }; }; +export type UpdateProfileMutationVariables = Exact<{ + first_name: Scalars["String"]; + last_name: Scalars["String"]; + imageUrl: Scalars["String"]; +}>; + +export type UpdateProfileMutation = { + __typename?: "Mutation"; + updateProfile: { + __typename?: "User"; + id: number; + first_name: string; + last_name: string; + email: string; + email_verified: boolean; + phone_number?: string | null; + phone_number_verified: boolean; + imageUrl?: string | null; + roleId: number; + language: string; + currency: string; + marketing_product_news: boolean; + marketing_company_news: boolean; + created_at: string; + updated_at: string; + }; +}; + export type VerifyEmailMutationVariables = Exact<{ token: Scalars["String"]; }>; @@ -4730,6 +4765,67 @@ export type UpdatePasswordMutationOptions = Apollo.BaseMutationOptions< UpdatePasswordMutation, UpdatePasswordMutationVariables >; +export const UpdateProfileDocument = gql` + mutation UpdateProfile( + $first_name: String! + $last_name: String! + $imageUrl: String! + ) { + updateProfile( + first_name: $first_name + last_name: $last_name + imageUrl: $imageUrl + ) { + ...UserFragment + } + } + ${UserFragmentFragmentDoc} +`; +export type UpdateProfileMutationFn = Apollo.MutationFunction< + UpdateProfileMutation, + UpdateProfileMutationVariables +>; + +/** + * __useUpdateProfileMutation__ + * + * To run a mutation, you first call `useUpdateProfileMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateProfileMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateProfileMutation, { data, loading, error }] = useUpdateProfileMutation({ + * variables: { + * first_name: // value for 'first_name' + * last_name: // value for 'last_name' + * imageUrl: // value for 'imageUrl' + * }, + * }); + */ +export function useUpdateProfileMutation( + baseOptions?: Apollo.MutationHookOptions< + UpdateProfileMutation, + UpdateProfileMutationVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useMutation< + UpdateProfileMutation, + UpdateProfileMutationVariables + >(UpdateProfileDocument, options); +} +export type UpdateProfileMutationHookResult = ReturnType< + typeof useUpdateProfileMutation +>; +export type UpdateProfileMutationResult = + Apollo.MutationResult; +export type UpdateProfileMutationOptions = Apollo.BaseMutationOptions< + UpdateProfileMutation, + UpdateProfileMutationVariables +>; export const VerifyEmailDocument = gql` mutation VerifyEmail($token: String!) { verifyEmail(token: $token) diff --git a/apps/storefront/src/graphql/mutation/user/updateProfile.graphql b/apps/storefront/src/graphql/mutation/user/updateProfile.graphql new file mode 100644 index 0000000..0f56747 --- /dev/null +++ b/apps/storefront/src/graphql/mutation/user/updateProfile.graphql @@ -0,0 +1,13 @@ +mutation UpdateProfile( + $first_name: String! + $last_name: String! + $imageUrl: String! +) { + updateProfile( + first_name: $first_name + last_name: $last_name + imageUrl: $imageUrl + ) { + ...UserFragment + } +}