mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-20 06:44:21 +01:00
feat: update roles card, mocks, schema, stories
This commit is contained in:
@@ -2,20 +2,24 @@ import { Navigate, NavLink, Route, Routes } from "react-router-dom";
|
||||
import LoadingPage from "src/Components/LoadingPage/LoadingPage";
|
||||
import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage";
|
||||
import Slider from "src/Components/Slider/Slider";
|
||||
import { useProfileQuery } from "src/graphql";
|
||||
import { useAppSelector, useMediaQuery } from "src/utils/hooks";
|
||||
import UpdateMyProfileTab from "./UpdateMyProfileTab/UpdateMyProfileTab";
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { MEDIA_QUERIES } from "src/utils/theme";
|
||||
import PreferencesTab from "./PreferencesTab/PreferencesTab";
|
||||
import RolesSkillsTab from "./RolesSkillsTab/RolesSkillsTab";
|
||||
import Card from "src/Components/Card/Card";
|
||||
|
||||
|
||||
const links = [
|
||||
{
|
||||
text: "👾 My Profile",
|
||||
text: "🤠 Basic information",
|
||||
path: 'my-profile',
|
||||
},
|
||||
{
|
||||
text: "🎛️ Roles & Skills",
|
||||
path: 'roles-skills',
|
||||
},
|
||||
{
|
||||
text: "⚙️ Settings & Preferences",
|
||||
path: 'preferences',
|
||||
@@ -85,6 +89,7 @@ export default function EditProfilePage() {
|
||||
<Routes>
|
||||
<Route index element={<Navigate to='my-profile' />} />
|
||||
<Route path='my-profile' element={<UpdateMyProfileTab />} />
|
||||
<Route path='roles-skills' element={<RolesSkillsTab />} />
|
||||
<Route path='preferences' element={<PreferencesTab />
|
||||
} />
|
||||
</Routes>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
|
||||
import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage";
|
||||
import * as yup from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import SaveChangesCard from '../SaveChangesCard/SaveChangesCard';
|
||||
import { toast } from 'react-toastify';
|
||||
import { NotificationsService } from 'src/services';
|
||||
import { NetworkStatus } from '@apollo/client';
|
||||
import { usePrompt } from 'src/utils/hooks';
|
||||
import { UpdateUserRolesSkillsMutationVariables, useMyProfileRolesSkillsQuery, useUpdateUserRolesSkillsMutation } from 'src/graphql'
|
||||
import LoadingPage from "src/Components/LoadingPage/LoadingPage";
|
||||
import UpdateRolesCard from "./UpdateRolesCard/UpdateRolesCard";
|
||||
|
||||
|
||||
interface Props {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type IRolesSkillsForm = NonNullable<UpdateUserRolesSkillsMutationVariables['data']>;
|
||||
|
||||
const schema: yup.SchemaOf<IRolesSkillsForm> = yup.object({
|
||||
roles: yup.array().of(
|
||||
yup.object().shape({
|
||||
id: yup.number().required(),
|
||||
role: yup.string().required(),
|
||||
}).required()
|
||||
).required(),
|
||||
skills: yup.array().of(
|
||||
yup.object().shape({
|
||||
id: yup.number().required(),
|
||||
}).required()
|
||||
).required(),
|
||||
}).required();
|
||||
|
||||
export default function PreferencesTab() {
|
||||
|
||||
const { formState: { isDirty, }, handleSubmit, reset, control } = useForm<IRolesSkillsForm>({
|
||||
defaultValues: {
|
||||
roles: [],
|
||||
skills: [],
|
||||
},
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const query = useMyProfileRolesSkillsQuery({
|
||||
onCompleted: data => {
|
||||
if (data.me) reset(data.me)
|
||||
},
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
const [mutate, mutationStatus] = useUpdateUserRolesSkillsMutation();
|
||||
|
||||
|
||||
usePrompt('You may have some unsaved changes. You still want to leave?', isDirty)
|
||||
|
||||
|
||||
if (query.networkStatus === NetworkStatus.loading)
|
||||
return <LoadingPage />
|
||||
|
||||
if (!query.data || !query.data?.me)
|
||||
return <NotFoundPage />
|
||||
|
||||
if (!query.data?.getAllMakersRoles || !query.data?.getAllMakersSkills)
|
||||
return null;
|
||||
|
||||
const onSubmit: SubmitHandler<IRolesSkillsForm> = data => {
|
||||
const toastId = toast.loading("Saving changes...", NotificationsService.defaultOptions)
|
||||
mutate({
|
||||
variables: {
|
||||
data
|
||||
},
|
||||
onCompleted: ({ updateProfileRoles }) => {
|
||||
if (updateProfileRoles) {
|
||||
reset(updateProfileRoles);
|
||||
toast.update(toastId, { render: "Saved changes successfully", type: "success", ...NotificationsService.defaultOptions, isLoading: false });
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast.update(toastId, { render: "A network error happened", type: "error", ...NotificationsService.defaultOptions, isLoading: false });
|
||||
mutationStatus.reset()
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-24">
|
||||
<div className="col-span-2 flex flex-col gap-24">
|
||||
<Controller
|
||||
control={control}
|
||||
name="roles"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<UpdateRolesCard
|
||||
allRoles={query.data?.getAllMakersRoles!}
|
||||
value={value}
|
||||
onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="self-start sticky-side-element">
|
||||
<SaveChangesCard
|
||||
isLoading={mutationStatus.loading}
|
||||
isDirty={isDirty}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
onCancel={() => reset()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { MOCK_DATA } from 'src/mocks/data';
|
||||
import UpdateRolesCard from './UpdateRolesCard';
|
||||
|
||||
export default {
|
||||
title: 'Profiles/Edit Profile Page/Update Roles Card',
|
||||
component: UpdateRolesCard,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
|
||||
} as ComponentMeta<typeof UpdateRolesCard>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof UpdateRolesCard> = (args) => <UpdateRolesCard {...args} ></UpdateRolesCard>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
value: MOCK_DATA['user'].roles,
|
||||
onChange: () => { }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import { Control, useFieldArray } from 'react-hook-form'
|
||||
import Card from 'src/Components/Card/Card'
|
||||
import { GenericMakerRole, MakerRole, RoleLevelEnum } from 'src/graphql'
|
||||
import { IRolesSkillsForm } from '../RolesSkillsTab'
|
||||
|
||||
type Value = Pick<MakerRole, 'id' | 'level'>
|
||||
|
||||
interface Props {
|
||||
allRoles: Pick<GenericMakerRole, 'id' | 'title' | 'icon'>[];
|
||||
value: Value[],
|
||||
onChange: (newValue: Value[]) => void
|
||||
}
|
||||
|
||||
export default function UpdateRolesCard(props: Props) {
|
||||
|
||||
const add = (idx: number) => {
|
||||
props.onChange([...props.value.slice(-2), { ...props.allRoles[idx], level: RoleLevelEnum.Beginner }])
|
||||
}
|
||||
|
||||
const remove = (idx: number) => {
|
||||
props.onChange(props.value.filter(v => v.id !== props.allRoles[idx].id))
|
||||
}
|
||||
|
||||
const setLevel = (roleId: number, level: RoleLevelEnum) => {
|
||||
props.onChange(props.value.map(v => {
|
||||
if (v.id !== roleId) return v;
|
||||
return {
|
||||
...v,
|
||||
level
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<p className="text-body2 font-bold">🎛️ Roles</p>
|
||||
<p className="text-body4 text-gray-600 mt-8"> Select your top 3 roles, and let other makers know what your level is.</p>
|
||||
<ul className=' flex flex-wrap gap-8 mt-24'>
|
||||
{props.allRoles.map((role, idx) => {
|
||||
const isActive = props.value.some(v => v.id === role.id);
|
||||
|
||||
return <button
|
||||
key={role.id}
|
||||
className={`
|
||||
px-12 py-8 border rounded-10 text-body5 font-medium
|
||||
active:scale-95 transition-transform
|
||||
${!isActive ? "bg-gray-100 hover:bg-gray-200 border-gray-200" : "bg-primary-100 text-primary-600 border-primary-200"}
|
||||
`}
|
||||
onClick={() => isActive ? remove(idx) : add(idx)}
|
||||
>{role.icon} {role.title}
|
||||
</button>
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{props.value.length > 0 && <div className="pt-24 mt-24 border-t border-gray-200">
|
||||
<ul className="grid grid-cols-1 lg:grid-cols-[auto_1fr] items-center gap-16">
|
||||
{props.value.map(role => {
|
||||
const { title, icon } = props.allRoles.find(r => r.id === role.id)!;
|
||||
|
||||
return <>
|
||||
<p className="shrink-0 text-body4 whitespace-nowrap">{icon} {title}</p>
|
||||
<div className="flex flex-wrap gap-8 grow text-body5 mb-8 last-of-type:mb-0">
|
||||
{[RoleLevelEnum.Beginner, RoleLevelEnum.Hobbyist, RoleLevelEnum.Intermediate, RoleLevelEnum.Advanced, RoleLevelEnum.Pro].map(r =>
|
||||
<button className={`
|
||||
px-12 py-4 bg-gray-100 border rounded-8 flex-1
|
||||
active:scale-95 transition-transform
|
||||
${r !== role.level ? "bg-gray-100 hover:bg-gray-200 border-gray-200" : "bg-primary-100 text-primary-600 border-primary-200"}
|
||||
`}
|
||||
onClick={() => setLevel(role.id, r)}
|
||||
>{r}</button>
|
||||
)}</div>
|
||||
</>
|
||||
})}
|
||||
</ul>
|
||||
</div>}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { MOCK_DATA } from 'src/mocks/data';
|
||||
import UpdateSkillsCard from './UpdateSkillsCard';
|
||||
|
||||
export default {
|
||||
title: 'Profiles/Edit Profile Page/Update Skills Card',
|
||||
component: UpdateSkillsCard,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
|
||||
} as ComponentMeta<typeof UpdateSkillsCard>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof UpdateSkillsCard> = (args) => <UpdateSkillsCard {...args} ></UpdateSkillsCard>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
value: MOCK_DATA['user'].skills,
|
||||
onChange: () => { }
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import { Control, useFieldArray } from 'react-hook-form'
|
||||
import Card from 'src/Components/Card/Card'
|
||||
import { GenericMakerRole, MakerRole, MakerSkill, RoleLevelEnum } from 'src/graphql'
|
||||
import { IRolesSkillsForm } from '../RolesSkillsTab'
|
||||
|
||||
type Value = Pick<MakerSkill, 'id'>
|
||||
|
||||
interface Props {
|
||||
allSkills: Pick<MakerSkill, 'id' | 'title'>[];
|
||||
value: Value[],
|
||||
onChange: (newValue: Value[]) => void
|
||||
}
|
||||
|
||||
export default function UpdateSkillsCard(props: Props) {
|
||||
|
||||
const add = (idx: number) => {
|
||||
props.onChange([...props.value.slice(-2), { ...props.allSkills[idx] }])
|
||||
}
|
||||
|
||||
const remove = (idx: number) => {
|
||||
props.onChange(props.value.filter(v => v.id !== props.allSkills[idx].id))
|
||||
}
|
||||
|
||||
const setLevel = (roleId: number, level: RoleLevelEnum) => {
|
||||
props.onChange(props.value.map(v => {
|
||||
if (v.id !== roleId) return v;
|
||||
return {
|
||||
...v,
|
||||
level
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<p className="text-body2 font-bold">🌈 Skills</p>
|
||||
<p className="text-body4 text-gray-600 mt-8">Add some of your skills and let other makers know what you’re good at.</p>
|
||||
{/* <ul className=' flex flex-wrap gap-8 mt-24'>
|
||||
{props.allSkills.map((role, idx) => {
|
||||
const isActive = props.value.some(v => v.id === role.id);
|
||||
|
||||
return <button
|
||||
key={role.id}
|
||||
className={`
|
||||
px-12 py-8 border rounded-10 text-body5 font-medium
|
||||
active:scale-95 transition-transform
|
||||
${!isActive ? "bg-gray-100 hover:bg-gray-200 border-gray-200" : "bg-primary-100 text-primary-600 border-primary-200"}
|
||||
`}
|
||||
onClick={() => isActive ? remove(idx) : add(idx)}
|
||||
>{role.icon} {role.title}
|
||||
</button>
|
||||
})}
|
||||
</ul> */}
|
||||
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
query MyProfileRolesSkills {
|
||||
me {
|
||||
id
|
||||
skills {
|
||||
id
|
||||
title
|
||||
}
|
||||
roles {
|
||||
id
|
||||
title
|
||||
icon
|
||||
level
|
||||
}
|
||||
}
|
||||
getAllMakersRoles {
|
||||
id
|
||||
title
|
||||
icon
|
||||
}
|
||||
getAllMakersSkills {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateUserRolesSkills($data: ProfileRolesInput) {
|
||||
updateProfileRoles(data: $data) {
|
||||
id
|
||||
skills {
|
||||
id
|
||||
title
|
||||
}
|
||||
roles {
|
||||
id
|
||||
title
|
||||
icon
|
||||
level
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user