feat: update roles card, mocks, schema, stories

This commit is contained in:
MTG2000
2022-08-22 13:34:30 +03:00
parent 80b7053f88
commit bb0fbfa572
15 changed files with 841 additions and 145 deletions

View File

@@ -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>

View File

@@ -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>
)
}

View File

@@ -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: () => { }
}

View File

@@ -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>
)
}

View File

@@ -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: () => { }
}

View File

@@ -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 youre 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>
)
}

View File

@@ -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
}
}
}