2024-04-29 06:42:33 +00:00
|
|
|
import React, { useMemo, useState } from "react";
|
2023-09-01 11:12:30 +00:00
|
|
|
import { observer } from "mobx-react-lite";
|
2023-10-20 12:21:01 +00:00
|
|
|
import { Controller, useForm } from "react-hook-form";
|
2024-04-29 06:42:33 +00:00
|
|
|
// types
|
|
|
|
import { IUser } from "@plane/types";
|
|
|
|
// ui
|
|
|
|
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
|
|
|
// components
|
|
|
|
import { UserImageUploadModal } from "@/components/accounts";
|
2023-09-01 11:12:30 +00:00
|
|
|
// hooks
|
2024-04-29 06:42:33 +00:00
|
|
|
import { useMobxStore } from "@/lib/mobx/store-provider";
|
2023-09-01 11:12:30 +00:00
|
|
|
// services
|
2024-04-29 06:42:33 +00:00
|
|
|
import fileService from "@/services/file.service";
|
|
|
|
|
|
|
|
type TProfileSetupFormValues = {
|
|
|
|
first_name: string;
|
|
|
|
last_name: string;
|
|
|
|
avatar?: string | null;
|
|
|
|
};
|
2023-09-01 11:12:30 +00:00
|
|
|
|
2024-04-29 06:42:33 +00:00
|
|
|
const defaultValues: Partial<TProfileSetupFormValues> = {
|
2023-09-01 11:12:30 +00:00
|
|
|
first_name: "",
|
|
|
|
last_name: "",
|
2024-04-29 06:42:33 +00:00
|
|
|
avatar: "",
|
2023-09-01 11:12:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
type Props = {
|
2024-04-29 06:42:33 +00:00
|
|
|
user?: IUser;
|
|
|
|
finishOnboarding: () => Promise<void>;
|
2023-09-01 11:12:30 +00:00
|
|
|
};
|
|
|
|
|
2024-04-29 06:42:33 +00:00
|
|
|
export const OnBoardingForm: React.FC<Props> = observer((props) => {
|
|
|
|
const { user, finishOnboarding } = props;
|
|
|
|
// states
|
|
|
|
const [isRemoving, setIsRemoving] = useState(false);
|
|
|
|
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
|
|
|
// store hooks
|
2023-09-01 11:12:30 +00:00
|
|
|
const {
|
2024-04-29 06:42:33 +00:00
|
|
|
user: { updateCurrentUser },
|
|
|
|
} = useMobxStore();
|
|
|
|
// form info
|
|
|
|
const {
|
|
|
|
getValues,
|
2023-09-01 11:12:30 +00:00
|
|
|
handleSubmit,
|
|
|
|
control,
|
2024-04-29 06:42:33 +00:00
|
|
|
watch,
|
|
|
|
setValue,
|
2023-09-01 11:12:30 +00:00
|
|
|
formState: { errors, isSubmitting, isValid },
|
2024-04-29 06:42:33 +00:00
|
|
|
} = useForm<TProfileSetupFormValues>({
|
|
|
|
defaultValues: {
|
|
|
|
...defaultValues,
|
|
|
|
first_name: user?.first_name,
|
|
|
|
last_name: user?.last_name,
|
|
|
|
avatar: user?.avatar,
|
|
|
|
},
|
|
|
|
mode: "onChange",
|
2023-09-01 11:12:30 +00:00
|
|
|
});
|
|
|
|
|
2024-04-29 06:42:33 +00:00
|
|
|
const onSubmit = async (formData: TProfileSetupFormValues) => {
|
|
|
|
if (!user) return;
|
2023-09-01 11:12:30 +00:00
|
|
|
|
2024-04-29 06:42:33 +00:00
|
|
|
const userDetailsPayload: Partial<IUser> = {
|
|
|
|
first_name: formData.first_name,
|
|
|
|
last_name: formData.last_name,
|
|
|
|
avatar: formData.avatar,
|
|
|
|
};
|
2023-09-01 11:12:30 +00:00
|
|
|
|
2024-04-29 06:42:33 +00:00
|
|
|
try {
|
|
|
|
await updateCurrentUser(userDetailsPayload).then(() => {
|
|
|
|
setToast({
|
|
|
|
type: TOAST_TYPE.SUCCESS,
|
|
|
|
title: "Success",
|
|
|
|
message: "Profile setup completed!",
|
|
|
|
});
|
|
|
|
finishOnboarding();
|
2023-10-20 12:21:01 +00:00
|
|
|
});
|
2024-04-29 06:42:33 +00:00
|
|
|
} catch {
|
|
|
|
setToast({
|
|
|
|
type: TOAST_TYPE.ERROR,
|
|
|
|
title: "Error",
|
|
|
|
message: "Profile setup failed. Please try again!",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDelete = (url: string | null | undefined) => {
|
|
|
|
if (!url) return;
|
|
|
|
|
|
|
|
setIsRemoving(true);
|
|
|
|
fileService.deleteUserFile(url).finally(() => {
|
|
|
|
setValue("avatar", "");
|
|
|
|
setIsRemoving(false);
|
2023-10-20 12:21:01 +00:00
|
|
|
});
|
2023-09-01 11:12:30 +00:00
|
|
|
};
|
|
|
|
|
2024-04-29 06:42:33 +00:00
|
|
|
const isButtonDisabled = useMemo(() => (isValid ? false : true), [isValid]);
|
2023-09-01 11:12:30 +00:00
|
|
|
|
|
|
|
return (
|
2024-04-29 06:42:33 +00:00
|
|
|
<form onSubmit={handleSubmit(onSubmit)} className="w-full mx-auto mt-2 space-y-4 sm:w-96">
|
|
|
|
<Controller
|
|
|
|
control={control}
|
|
|
|
name="avatar"
|
|
|
|
render={({ field: { onChange, value } }) => (
|
|
|
|
<UserImageUploadModal
|
|
|
|
isOpen={isImageUploadModalOpen}
|
|
|
|
onClose={() => setIsImageUploadModalOpen(false)}
|
|
|
|
isRemoving={isRemoving}
|
|
|
|
handleDelete={() => handleDelete(getValues("avatar"))}
|
|
|
|
onSuccess={(url) => {
|
|
|
|
onChange(url);
|
|
|
|
setIsImageUploadModalOpen(false);
|
|
|
|
}}
|
|
|
|
value={value && value.trim() !== "" ? value : null}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
<div className="space-y-1 flex items-center justify-center">
|
|
|
|
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
|
|
|
{!watch("avatar") || watch("avatar") === "" ? (
|
|
|
|
<div className="flex flex-col items-center justify-between">
|
|
|
|
<div className="relative h-14 w-14 overflow-hidden">
|
|
|
|
<div className="absolute left-0 top-0 flex items-center justify-center h-full w-full rounded-full text-white text-3xl font-medium bg-[#9747FF] uppercase">
|
|
|
|
{watch("first_name")[0] ?? "R"}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="pt-1 text-sm font-medium text-custom-primary-300 hover:text-custom-primary-400">
|
|
|
|
Choose image
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="relative mr-3 h-16 w-16 overflow-hidden">
|
|
|
|
<img
|
|
|
|
src={watch("avatar") || undefined}
|
|
|
|
className="absolute left-0 top-0 h-full w-full rounded-full object-cover"
|
|
|
|
onClick={() => setIsImageUploadModalOpen(true)}
|
|
|
|
alt={user?.display_name}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</button>
|
2023-09-01 11:12:30 +00:00
|
|
|
</div>
|
2024-04-29 06:42:33 +00:00
|
|
|
<div className="flex gap-4">
|
|
|
|
<div className="space-y-1">
|
|
|
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="first_name">
|
|
|
|
First name
|
|
|
|
</label>
|
|
|
|
<Controller
|
|
|
|
control={control}
|
|
|
|
name="first_name"
|
|
|
|
rules={{
|
2023-09-01 11:12:30 +00:00
|
|
|
required: "First name is required",
|
2024-04-29 06:42:33 +00:00
|
|
|
maxLength: {
|
|
|
|
value: 24,
|
|
|
|
message: "First name must be within 24 characters.",
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
render={({ field: { value, onChange, ref } }) => (
|
|
|
|
<Input
|
|
|
|
id="first_name"
|
|
|
|
name="first_name"
|
|
|
|
type="text"
|
|
|
|
value={value}
|
|
|
|
autoFocus
|
|
|
|
onChange={onChange}
|
|
|
|
ref={ref}
|
|
|
|
hasError={Boolean(errors.first_name)}
|
|
|
|
placeholder="RWilbur"
|
|
|
|
className="w-full border-onboarding-border-100 focus:border-custom-primary-100"
|
|
|
|
/>
|
|
|
|
)}
|
2023-09-01 11:12:30 +00:00
|
|
|
/>
|
2024-04-29 06:42:33 +00:00
|
|
|
{errors.first_name && <span className="text-sm text-red-500">{errors.first_name.message}</span>}
|
2023-09-01 11:12:30 +00:00
|
|
|
</div>
|
2024-04-29 06:42:33 +00:00
|
|
|
<div className="space-y-1">
|
|
|
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="last_name">
|
|
|
|
Last name
|
|
|
|
</label>
|
|
|
|
<Controller
|
|
|
|
control={control}
|
|
|
|
name="last_name"
|
|
|
|
rules={{
|
2023-09-01 11:12:30 +00:00
|
|
|
required: "Last name is required",
|
2024-04-29 06:42:33 +00:00
|
|
|
maxLength: {
|
|
|
|
value: 24,
|
|
|
|
message: "Last name must be within 24 characters.",
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
render={({ field: { value, onChange, ref } }) => (
|
|
|
|
<Input
|
|
|
|
id="last_name"
|
|
|
|
name="last_name"
|
|
|
|
type="text"
|
|
|
|
value={value}
|
|
|
|
onChange={onChange}
|
|
|
|
ref={ref}
|
|
|
|
hasError={Boolean(errors.last_name)}
|
|
|
|
placeholder="Wright"
|
|
|
|
className="w-full border-onboarding-border-100 focus:border-custom-primary-100"
|
|
|
|
/>
|
|
|
|
)}
|
2023-09-01 11:12:30 +00:00
|
|
|
/>
|
2024-04-29 06:42:33 +00:00
|
|
|
{errors.last_name && <span className="text-sm text-red-500">{errors.last_name.message}</span>}
|
2023-09-01 11:12:30 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2024-04-29 06:42:33 +00:00
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
type="submit"
|
|
|
|
size="lg"
|
|
|
|
className="w-full"
|
|
|
|
disabled={isButtonDisabled}
|
|
|
|
loading={isSubmitting}
|
|
|
|
>
|
2023-09-01 11:12:30 +00:00
|
|
|
{isSubmitting ? "Updating..." : "Continue"}
|
2023-12-05 10:37:25 +00:00
|
|
|
</Button>
|
2023-09-01 11:12:30 +00:00
|
|
|
</form>
|
|
|
|
);
|
|
|
|
});
|