Skip to content

Commit

Permalink
Merge pull request #260 from hotosm/feat/user-profile
Browse files Browse the repository at this point in the history
Feat: Complete profile, update user details, change password and other minor fixes
  • Loading branch information
nrjadkry authored Oct 4, 2024
2 parents 17012de + 9203dd6 commit a8b2aba
Show file tree
Hide file tree
Showing 25 changed files with 728 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function App() {
'/',
'/login',
'/forgot-password',
'/user-profile',
'/complete-profile',
];

return (
Expand Down
9 changes: 7 additions & 2 deletions src/frontend/src/api/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@ export const useGetUserDetailsQuery = (
queryOptions?: Partial<UseQueryOptions>,
) => {
return useQuery({
queryKey: ['user=profile'],
queryKey: ['user-profile'],
queryFn: getUserProfileInfo,
select: (res: any) => res.data,
select: (res: any) => {
const userDetails = res.data;
const userDetailsString = JSON.stringify(userDetails);
localStorage.setItem('userprofile', userDetailsString as string);
return userDetails;
},
...queryOptions,
});
};
4 changes: 4 additions & 0 deletions src/frontend/src/assets/css/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,7 @@ input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 1000px transparent inset;
transition: background-color 5000s ease-in-out 0s;
}

.main-content{
@apply naxatw-h-[calc(100vh-3.5rem)]
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default function BasicDetails({ formProps }: { formProps: any }) {
placeholder="Enter City"
className="naxatw-mt-1"
{...register('city', {
setValueAs: (value: string) => value?.trim(),
required: 'City is Required',
})}
/>
Expand All @@ -80,6 +81,10 @@ export default function BasicDetails({ formProps }: { formProps: any }) {
type="number"
{...register('phone_number', {
required: 'Phone Number is Required',
minLength: {
value: 5,
message: 'Invalid Phone Number',
},
})}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { FormControl, Input, Label } from '@Components/common/FormUI';
import { Flex, FlexColumn } from '@Components/common/Layouts';

export default function PasswordSection({ formProps }: { formProps: any }) {
const { register, formState } = formProps;
const { register, formState, watch } = formProps;

const password = watch('password');
return (
<section className="naxatw-px-14">
<Flex>
Expand Down Expand Up @@ -33,7 +35,9 @@ export default function PasswordSection({ formProps }: { formProps: any }) {
className="naxatw-mt-1"
placeholder="Enter confirm Password"
{...register('confirm_password', {
required: 'Confirm Password is Required',
validate: (value: string) =>
value === password || 'The passwords do not match',
// required: 'Confirm Password is Required',
})}
/>
<ErrorMessage message={formState.errors?.confirm_password?.message} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const KeyParameters = ({ formProps }: { formProps: UseFormPropsType }) => {
max={300}
min={0}
{...register('altitude_from_ground', {
required: 'Altitude From Round is Required',
required: 'Altitude From ground is Required',
valueAsNumber: true,
max: {
value: 300,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const DescriptionBox = () => {
: null,
},
{
name: 'TAsk locked date',
name: 'Task locked date',
value: taskData?.updated_at
? taskData?.updated_at?.slice(0, 10) || '-'
: null,
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/components/GoogleAuth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function GoogleAuth() {
if (userDetails?.has_user_profile) {
navigate('/projects');
} else {
navigate('/user-profile');
navigate('/complete-profile');
}
};
await completeLogin();
Expand All @@ -56,7 +56,7 @@ function GoogleAuth() {
setIsReadyToRedirect(true);
};
loginRedirect();
}, [location.search]);
}, [location.search, navigate]);

return (
<Flex className="naxatw-h-screen-nav naxatw-w-full naxatw-animate-pulse naxatw-items-center naxatw-justify-center">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { toast } from 'react-toastify';
import { Controller, useForm } from 'react-hook-form';
import { FormControl, Input, Label, Select } from '@Components/common/FormUI';
import { Flex, FlexColumn } from '@Components/common/Layouts';
import { countriesWithPhoneCodes } from '@Constants/countryCode';
import { getLocalStorageValue } from '@Utils/getLocalStorageValue';
import ErrorMessage from '@Components/common/ErrorMessage';
import { Button } from '@Components/RadixComponents/Button';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { patchUserProfile } from '@Services/common';

const BasicDetails = () => {
const userProfile = getLocalStorageValue('userprofile');
const initialState = {
name: userProfile?.name,
country: userProfile?.country || null,
city: userProfile?.city || null,
phone_number: userProfile?.phone_number || null,
};
const queryClient = useQueryClient();

const { register, handleSubmit, formState, control } = useForm({
defaultValues: initialState,
});

const { mutate: updateBasicInfo, isLoading } = useMutation<
any,
any,
any,
unknown
>({
mutationFn: payloadDataObject => patchUserProfile(payloadDataObject),
onSuccess: () => {
queryClient.invalidateQueries(['user-profile']);
toast.success('Details Updated Successfully');
},
onError: err => {
// eslint-disable-next-line no-console
console.log(err);
toast.error(err?.response?.data?.detail || 'Something went wrong');
},
});

const onSubmit = (formData: Record<string, any>) => {
updateBasicInfo({ userId: userProfile?.id, data: formData });
};

return (
<section className="naxatw-w-full naxatw-px-14">
<Flex>
<p className="naxatw-text-lg naxatw-font-bold">Basic Details</p>
</Flex>
<FlexColumn gap={5} className="naxatw-mt-5">
<Flex className="naxatw-h-14 naxatw-w-14 naxatw-items-center naxatw-justify-center naxatw-overflow-hidden naxatw-rounded-full naxatw-bg-grey-600">
<img src={userProfile.profile_img} alt="profilepic" />
</Flex>
<FormControl>
<Label>Name</Label>
<Input
placeholder="Enter Name"
className="naxatw-mt-1"
{...register('name', {
required: 'Name is Required',
})}
readOnly
/>
<ErrorMessage message={formState?.errors?.name?.message as string} />
</FormControl>
<FormControl>
<Label required>Country</Label>
<Controller
control={control}
name="country"
defaultValue=""
rules={{
required: 'Country is Required',
}}
render={({ field: { value, onChange } }) => (
<Select
placeholder="Choose a Country"
options={countriesWithPhoneCodes}
labelKey="label"
valueKey="label"
selectedOption={value}
onChange={onChange}
/>
)}
/>
<ErrorMessage
message={formState?.errors?.country?.message as string}
/>
</FormControl>
<FormControl>
<Label required>City</Label>
<Input
placeholder="Enter City"
className="naxatw-mt-1"
{...register('city', {
setValueAs: (value: string) => value?.trim(),
required: 'City is Required',
})}
/>
<ErrorMessage message={formState.errors?.city?.message as string} />
</FormControl>
<FormControl>
<Label required>Phone number</Label>
<div className="naxatw-flex naxatw-space-x-1">
{/* <Input
placeholder="+977"
className="naxatw-mt-1 naxatw-w-14"
{...register('country_code', {
required: 'Phone Number is Required',
})}
/> */}
<Input
placeholder="Enter Phone number"
className="naxatw-mt-1 naxatw-w-full"
type="number"
{...register('phone_number', {
required: 'Phone Number is Required',
minLength: {
value: 5,
message: 'Invalid Phone Number',
},
})}
/>
</div>
<ErrorMessage
message={formState.errors?.phone_number?.message as string}
/>
</FormControl>
</FlexColumn>
<div className="naxatw-flex naxatw-justify-center naxatw-py-4">
<Button
className="naxatw-bg-red"
onClick={e => {
e.preventDefault();
handleSubmit(onSubmit)();
}}
withLoader
isLoading={isLoading}
>
Save
</Button>
</div>
</section>
);
};

export default BasicDetails;
16 changes: 16 additions & 0 deletions src/frontend/src/components/UpdateUserDetails/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import BreadCrumb from '@Components/common/Breadcrumb';

const Header = () => {
return (
<div className="naxatw-py-1">
<BreadCrumb
data={[
{ name: 'Dashboard', navLink: '/' },
{ name: 'Edit Profile', navLink: '' },
]}
/>
</div>
);
};

export default Header;
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import ErrorMessage from '@Components/common/ErrorMessage';
import { FormControl, Input, Label } from '@Components/common/FormUI';
import { Flex, FlexColumn } from '@Components/common/Layouts';
import { Button } from '@Components/RadixComponents/Button';
import { patchUserProfile } from '@Services/common';
import { getLocalStorageValue } from '@Utils/getLocalStorageValue';

const OrganizationDetails = () => {
const userProfile = getLocalStorageValue('userprofile');

const initialState = {
organization_name: userProfile?.organization_name || null,
organization_address: userProfile?.organization_address || null,
job_title: userProfile?.job_title || null,
};
const queryClient = useQueryClient();

const { register, handleSubmit, formState } = useForm({
defaultValues: initialState,
});

const { mutate: updateOrganizationDetails, isLoading } = useMutation<
any,
any,
any,
unknown
>({
mutationFn: payloadDataObject => patchUserProfile(payloadDataObject),
onSuccess: () => {
queryClient.invalidateQueries(['user-profile']);

toast.success('Details Updated successfully');
},
onError: err => {
// eslint-disable-next-line no-console
console.log(err);
toast.error(err?.response?.data?.detail || 'Something went wrong');
},
});

const onSubmit = (formData: Record<string, any>) => {
updateOrganizationDetails({ userId: userProfile?.id, data: formData });
};

return (
<section className="naxatw-w-full naxatw-px-14">
<Flex>
<p className="naxatw-mb-2 naxatw-text-lg naxatw-font-bold">
Organization Details
</p>
</Flex>
<FlexColumn gap={5}>
<FormControl>
<Label>Organization Name</Label>
<Input
placeholder="Enter Organization Name"
className="naxatw-mt-1"
{...register('organization_name', {
// required: 'Organization name is Required',
})}
/>
<ErrorMessage
message={formState.errors?.organization_name?.message as string}
/>
</FormControl>
<FormControl>
<Label>Organization Address</Label>
<Input
placeholder="Enter Organization Address"
className="naxatw-mt-1"
{...register('organization_address', {
// required: 'Organization Address is Required',
})}
/>
<ErrorMessage
message={formState.errors?.organization_address?.message as string}
/>
</FormControl>
<FormControl>
<Label>Job Title</Label>
<Input
placeholder="Enter Job Title"
className="naxatw-mt-1"
{...register('job_title', {
// required: 'Job Title is Required',
})}
/>
<ErrorMessage
message={formState.errors?.job_title?.message as string}
/>
</FormControl>
</FlexColumn>
<div className="naxatw-flex naxatw-justify-center naxatw-py-4">
<Button
className="naxatw-bg-red"
onClick={e => {
e.preventDefault();
handleSubmit(onSubmit)();
}}
withLoader
isLoading={isLoading}
>
Save
</Button>
</div>
</section>
);
};

export default OrganizationDetails;
Loading

0 comments on commit a8b2aba

Please sign in to comment.