From bb0fbfa572ed04e3110a4f7c965126abd7676dce Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 22 Aug 2022 13:34:30 +0300 Subject: [PATCH] feat: update roles card, mocks, schema, stories --- api/functions/graphql/nexus-typegen.ts | 119 ++++++++---- api/functions/graphql/schema.graphql | 59 ++++-- api/functions/graphql/types/users.js | 111 ++++++++++- .../pages/EditProfilePage/EditProfilePage.tsx | 9 +- .../RolesSkillsTab/RolesSkillsTab.tsx | 111 +++++++++++ .../UpdateRolesCard.stories.tsx | 21 ++ .../UpdateRolesCard/UpdateRolesCard.tsx | 80 ++++++++ .../UpdateSkillsCard.stories.tsx | 21 ++ .../UpdateSkillsCard/UpdateSkillsCard.tsx | 59 ++++++ .../RolesSkillsTab/rolesSkills.graphql | 40 ++++ src/graphql/index.tsx | 179 ++++++++++++++++-- src/mocks/data.ts | 6 +- src/mocks/data/users.ts | 148 ++++++++++----- src/mocks/handlers.ts | 16 +- src/mocks/resolvers.ts | 7 + 15 files changed, 841 insertions(+), 145 deletions(-) create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.stories.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.stories.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/rolesSkills.graphql diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index 4c8413f..68d20f0 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -28,6 +28,13 @@ declare global { } export interface NexusGenInputs { + MakerRoleInput: { // input type + id: number; // Int! + level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum! + } + MakerSkillInput: { // input type + id: number; // Int! + } ProfileDetailsInput: { // input type avatar?: string | null; // String bio?: string | null; // String @@ -41,6 +48,10 @@ export interface NexusGenInputs { twitter?: string | null; // String website?: string | null; // String } + ProfileRolesInput: { // input type + roles: NexusGenInputs['MakerRoleInput'][]; // [MakerRoleInput!]! + skills: NexusGenInputs['MakerSkillInput'][]; // [MakerSkillInput!]! + } StoryInputType: { // input type body: string; // String! cover_image?: string | null; // String @@ -125,6 +136,11 @@ export interface NexusGenObjects { prizes: string; // String! touranments: string; // String! } + GenericMakerRole: { // root type + icon: string; // String! + id: number; // Int! + title: string; // String! + } Hackathon: { // root type cover_image: string; // String! description: string; // String! @@ -141,6 +157,16 @@ export interface NexusGenObjects { metadata?: string | null; // String minSendable?: number | null; // Int } + MakerRole: { // root type + icon: string; // String! + id: number; // Int! + level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum! + title: string; // String! + } + MakerSkill: { // root type + id: number; // Int! + title: string; // String! + } Mutation: {}; MyProfile: { // root type avatar: string; // String! @@ -235,16 +261,6 @@ export interface NexusGenObjects { twitter?: string | null; // String website?: string | null; // String } - UserRole: { // root type - icon: string; // String! - id: number; // Int! - level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum! - title: string; // String! - } - UserSkill: { // root type - id: number; // Int! - title: string; // String! - } Vote: { // root type amount_in_sat: number; // Int! id: number; // Int! @@ -336,6 +352,11 @@ export interface NexusGenFieldTypes { prizes: string; // String! touranments: string; // String! } + GenericMakerRole: { // field return type + icon: string; // String! + id: number; // Int! + title: string; // String! + } Hackathon: { // field return type cover_image: string; // String! description: string; // String! @@ -353,6 +374,16 @@ export interface NexusGenFieldTypes { metadata: string | null; // String minSendable: number | null; // Int } + MakerRole: { // field return type + icon: string; // String! + id: number; // Int! + level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum! + title: string; // String! + } + MakerSkill: { // field return type + id: number; // Int! + title: string; // String! + } Mutation: { // field return type confirmDonation: NexusGenRootTypes['Donation']; // Donation! confirmVote: NexusGenRootTypes['Vote']; // Vote! @@ -360,6 +391,7 @@ export interface NexusGenFieldTypes { deleteStory: NexusGenRootTypes['Story'] | null; // Story donate: NexusGenRootTypes['Donation']; // Donation! updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile + updateProfileRoles: NexusGenRootTypes['MyProfile'] | null; // MyProfile updateUserPreferences: NexusGenRootTypes['MyProfile']; // MyProfile! vote: NexusGenRootTypes['Vote']; // Vote! } @@ -378,9 +410,9 @@ export interface NexusGenFieldTypes { nostr_prv_key: string | null; // String nostr_pub_key: string | null; // String role: string | null; // String - roles: NexusGenRootTypes['UserRole'][]; // [UserRole!]! + roles: NexusGenRootTypes['MakerRole'][]; // [MakerRole!]! similar_makers: NexusGenRootTypes['User'][]; // [User!]! - skills: NexusGenRootTypes['UserSkill'][]; // [UserSkill!]! + skills: NexusGenRootTypes['MakerSkill'][]; // [MakerSkill!]! stories: NexusGenRootTypes['Story'][]; // [Story!]! tournaments: NexusGenRootTypes['Tournament'][]; // [Tournament!]! twitter: string | null; // String @@ -414,6 +446,8 @@ export interface NexusGenFieldTypes { allCategories: NexusGenRootTypes['Category'][]; // [Category!]! allProjects: NexusGenRootTypes['Project'][]; // [Project!]! getAllHackathons: NexusGenRootTypes['Hackathon'][]; // [Hackathon!]! + getAllMakersRoles: NexusGenRootTypes['GenericMakerRole'][]; // [GenericMakerRole!]! + getAllMakersSkills: NexusGenRootTypes['MakerSkill'][]; // [MakerSkill!]! getCategory: NexusGenRootTypes['Category']; // Category! getDonationsStats: NexusGenRootTypes['DonationsStats']; // DonationsStats! getFeed: NexusGenRootTypes['Post'][]; // [Post!]! @@ -492,24 +526,14 @@ export interface NexusGenFieldTypes { location: string | null; // String name: string; // String! role: string | null; // String - roles: NexusGenRootTypes['UserRole'][]; // [UserRole!]! + roles: NexusGenRootTypes['MakerRole'][]; // [MakerRole!]! similar_makers: NexusGenRootTypes['User'][]; // [User!]! - skills: NexusGenRootTypes['UserSkill'][]; // [UserSkill!]! + skills: NexusGenRootTypes['MakerSkill'][]; // [MakerSkill!]! stories: NexusGenRootTypes['Story'][]; // [Story!]! tournaments: NexusGenRootTypes['Tournament'][]; // [Tournament!]! twitter: string | null; // String website: string | null; // String } - UserRole: { // field return type - icon: string; // String! - id: number; // Int! - level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum! - title: string; // String! - } - UserSkill: { // field return type - id: number; // Int! - title: string; // String! - } Vote: { // field return type amount_in_sat: number; // Int! id: number; // Int! @@ -536,9 +560,9 @@ export interface NexusGenFieldTypes { location: string | null; // String name: string; // String! role: string | null; // String - roles: NexusGenRootTypes['UserRole'][]; // [UserRole!]! + roles: NexusGenRootTypes['MakerRole'][]; // [MakerRole!]! similar_makers: NexusGenRootTypes['User'][]; // [User!]! - skills: NexusGenRootTypes['UserSkill'][]; // [UserSkill!]! + skills: NexusGenRootTypes['MakerSkill'][]; // [MakerSkill!]! stories: NexusGenRootTypes['Story'][]; // [Story!]! tournaments: NexusGenRootTypes['Tournament'][]; // [Tournament!]! twitter: string | null; // String @@ -619,6 +643,11 @@ export interface NexusGenFieldTypeNames { prizes: 'String' touranments: 'String' } + GenericMakerRole: { // field return type name + icon: 'String' + id: 'Int' + title: 'String' + } Hackathon: { // field return type name cover_image: 'String' description: 'String' @@ -636,6 +665,16 @@ export interface NexusGenFieldTypeNames { metadata: 'String' minSendable: 'Int' } + MakerRole: { // field return type name + icon: 'String' + id: 'Int' + level: 'RoleLevelEnum' + title: 'String' + } + MakerSkill: { // field return type name + id: 'Int' + title: 'String' + } Mutation: { // field return type name confirmDonation: 'Donation' confirmVote: 'Vote' @@ -643,6 +682,7 @@ export interface NexusGenFieldTypeNames { deleteStory: 'Story' donate: 'Donation' updateProfileDetails: 'MyProfile' + updateProfileRoles: 'MyProfile' updateUserPreferences: 'MyProfile' vote: 'Vote' } @@ -661,9 +701,9 @@ export interface NexusGenFieldTypeNames { nostr_prv_key: 'String' nostr_pub_key: 'String' role: 'String' - roles: 'UserRole' + roles: 'MakerRole' similar_makers: 'User' - skills: 'UserSkill' + skills: 'MakerSkill' stories: 'Story' tournaments: 'Tournament' twitter: 'String' @@ -697,6 +737,8 @@ export interface NexusGenFieldTypeNames { allCategories: 'Category' allProjects: 'Project' getAllHackathons: 'Hackathon' + getAllMakersRoles: 'GenericMakerRole' + getAllMakersSkills: 'MakerSkill' getCategory: 'Category' getDonationsStats: 'DonationsStats' getFeed: 'Post' @@ -775,24 +817,14 @@ export interface NexusGenFieldTypeNames { location: 'String' name: 'String' role: 'String' - roles: 'UserRole' + roles: 'MakerRole' similar_makers: 'User' - skills: 'UserSkill' + skills: 'MakerSkill' stories: 'Story' tournaments: 'Tournament' twitter: 'String' website: 'String' } - UserRole: { // field return type name - icon: 'String' - id: 'Int' - level: 'RoleLevelEnum' - title: 'String' - } - UserSkill: { // field return type name - id: 'Int' - title: 'String' - } Vote: { // field return type name amount_in_sat: 'Int' id: 'Int' @@ -819,9 +851,9 @@ export interface NexusGenFieldTypeNames { location: 'String' name: 'String' role: 'String' - roles: 'UserRole' + roles: 'MakerRole' similar_makers: 'User' - skills: 'UserSkill' + skills: 'MakerSkill' stories: 'Story' tournaments: 'Tournament' twitter: 'String' @@ -861,6 +893,9 @@ export interface NexusGenArgTypes { updateProfileDetails: { // args data?: NexusGenInputs['ProfileDetailsInput'] | null; // ProfileDetailsInput } + updateProfileRoles: { // args + data?: NexusGenInputs['ProfileRolesInput'] | null; // ProfileRolesInput + } updateUserPreferences: { // args userKeys?: NexusGenInputs['UserKeyInputType'][] | null; // [UserKeyInputType!] } diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index f6f89f6..226590b 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -31,9 +31,9 @@ interface BaseUser { location: String name: String! role: String - roles: [UserRole!]! + roles: [MakerRole!]! similar_makers: [User!]! - skills: [UserSkill!]! + skills: [MakerSkill!]! stories: [Story!]! tournaments: [Tournament!]! twitter: String @@ -96,6 +96,12 @@ type DonationsStats { touranments: String! } +type GenericMakerRole { + icon: String! + id: Int! + title: String! +} + type Hackathon { cover_image: String! description: String! @@ -115,6 +121,27 @@ type LnurlDetails { minSendable: Int } +type MakerRole { + icon: String! + id: Int! + level: RoleLevelEnum! + title: String! +} + +input MakerRoleInput { + id: Int! + level: RoleLevelEnum! +} + +type MakerSkill { + id: Int! + title: String! +} + +input MakerSkillInput { + id: Int! +} + type Mutation { confirmDonation(payment_request: String!, preimage: String!): Donation! confirmVote(payment_request: String!, preimage: String!): Vote! @@ -122,6 +149,7 @@ type Mutation { deleteStory(id: Int!): Story donate(amount_in_sat: Int!): Donation! updateProfileDetails(data: ProfileDetailsInput): MyProfile + updateProfileRoles(data: ProfileRolesInput): MyProfile updateUserPreferences(userKeys: [UserKeyInputType!]): MyProfile! vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote! } @@ -141,9 +169,9 @@ type MyProfile implements BaseUser { nostr_prv_key: String nostr_pub_key: String role: String - roles: [UserRole!]! + roles: [MakerRole!]! similar_makers: [User!]! - skills: [UserSkill!]! + skills: [MakerSkill!]! stories: [Story!]! tournaments: [Tournament!]! twitter: String @@ -193,6 +221,11 @@ input ProfileDetailsInput { website: String } +input ProfileRolesInput { + roles: [MakerRoleInput!]! + skills: [MakerSkillInput!]! +} + type Project { awards: [Award!]! category: Category! @@ -213,6 +246,8 @@ type Query { allCategories: [Category!]! allProjects(skip: Int = 0, take: Int = 50): [Project!]! getAllHackathons(sortBy: String, tag: Int): [Hackathon!]! + getAllMakersRoles: [GenericMakerRole!]! + getAllMakersSkills: [MakerSkill!]! getCategory(id: Int!): Category! getDonationsStats: DonationsStats! getFeed(skip: Int = 0, sortBy: String, tag: Int = 0, take: Int = 10): [Post!]! @@ -313,9 +348,9 @@ type User implements BaseUser { location: String name: String! role: String - roles: [UserRole!]! + roles: [MakerRole!]! similar_makers: [User!]! - skills: [UserSkill!]! + skills: [MakerSkill!]! stories: [Story!]! tournaments: [Tournament!]! twitter: String @@ -327,18 +362,6 @@ input UserKeyInputType { name: String! } -type UserRole { - icon: String! - id: Int! - level: RoleLevelEnum! - title: String! -} - -type UserSkill { - id: Int! - title: String! -} - enum VOTE_ITEM_TYPE { Bounty PostComment diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index 3d4d06b..7f6a815 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -26,13 +26,13 @@ const BaseUser = interfaceType({ t.string('bio') t.string('location') t.nonNull.list.nonNull.field('roles', { - type: UserRole, + type: MakerRole, resolve: (parent) => { return [] } }) t.nonNull.list.nonNull.field('skills', { - type: UserSkill, + type: MakerSkill, resolve: (parent) => { return [] } @@ -74,6 +74,8 @@ const BaseUser = interfaceType({ }, }) + + const RoleLevelEnum = enumType({ name: 'RoleLevelEnum', members: { @@ -85,8 +87,17 @@ const RoleLevelEnum = enumType({ }, }); -const UserRole = objectType({ - name: 'UserRole', +const GenericMakerRole = objectType({ + name: 'GenericMakerRole', + definition(t) { + t.nonNull.int('id'); + t.nonNull.string('title'); + t.nonNull.string('icon'); + } +}) + +const MakerRole = objectType({ + name: 'MakerRole', definition(t) { t.nonNull.int('id'); t.nonNull.string('title'); @@ -95,14 +106,40 @@ const UserRole = objectType({ } }) -const UserSkill = objectType({ - name: 'UserSkill', +const getAllMakersRoles = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('getAllMakersRoles', { + type: GenericMakerRole, + async resolve(parent, args, context) { + return [] + } + }) + } +}) + + +const MakerSkill = objectType({ + name: 'MakerSkill', definition(t) { t.nonNull.int('id'); t.nonNull.string('title'); } }) +const getAllMakersSkills = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('getAllMakersSkills', { + type: MakerSkill, + async resolve(parent, args, context) { + return [] + } + }) + } +}) + + const User = objectType({ name: 'User', definition(t) { @@ -312,6 +349,65 @@ const updateUserPreferences = extendType({ +const MakerRoleInput = inputObjectType({ + name: "MakerRoleInput", + definition(t) { + t.nonNull.int('id'); + t.nonNull.field('level', { type: RoleLevelEnum }) + } +}) + +const MakerSkillInput = inputObjectType({ + name: "MakerSkillInput", + definition(t) { + t.nonNull.int('id'); + } +}) + + +const ProfileRolesInput = inputObjectType({ + name: 'ProfileRolesInput', + definition(t) { + t.nonNull.list.nonNull.field('roles', { + type: MakerRoleInput, + }) + t.nonNull.list.nonNull.field('skills', { + type: MakerSkillInput, + }) + } +}) + +const updateProfileRoles = extendType({ + type: 'Mutation', + definition(t) { + t.field('updateProfileRoles', { + type: 'MyProfile', + args: { data: ProfileRolesInput }, + async resolve(_root, args, ctx) { + const user = await getUserByPubKey(ctx.userPubKey); + + // Do some validation + if (!user) + throw new Error("You have to login"); + // TODO: validate new data + + + // Preprocess & insert + + return prisma.user.update({ + where: { + id: user.id, + }, + // data: removeNulls(args.data) + }) + } + }) + }, +}) + + + + module.exports = { // Types @@ -324,7 +420,10 @@ module.exports = { me, profile, similarMakers, + getAllMakersRoles, + getAllMakersSkills, // Mutations updateProfileDetails, updateUserPreferences, + updateProfileRoles, } diff --git a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx index 870bf14..67d8a04 100644 --- a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx +++ b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx @@ -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() { } /> } /> + } /> } /> diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx new file mode 100644 index 0000000..35a09ca --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx @@ -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; + +const schema: yup.SchemaOf = 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({ + 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 + + if (!query.data || !query.data?.me) + return + + if (!query.data?.getAllMakersRoles || !query.data?.getAllMakersSkills) + return null; + + const onSubmit: SubmitHandler = 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 ( +
+
+ ( + + )} + /> +
+
+ reset()} + /> +
+
+ ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.stories.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.stories.tsx new file mode 100644 index 0000000..0bd3b91 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.stories.tsx @@ -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; + + +const Template: ComponentStory = (args) => + +export const Default = Template.bind({}); +Default.args = { + value: MOCK_DATA['user'].roles, + onChange: () => { } +} diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.tsx new file mode 100644 index 0000000..ed31dd4 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.tsx @@ -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 + +interface Props { + allRoles: Pick[]; + 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 ( + +

πŸŽ›οΈ Roles

+

Select your top 3 roles, and let other makers know what your level is.

+
    + {props.allRoles.map((role, idx) => { + const isActive = props.value.some(v => v.id === role.id); + + return + })} +
+ + {props.value.length > 0 &&
+
    + {props.value.map(role => { + const { title, icon } = props.allRoles.find(r => r.id === role.id)!; + + return <> +

    {icon} {title}

    +
    + {[RoleLevelEnum.Beginner, RoleLevelEnum.Hobbyist, RoleLevelEnum.Intermediate, RoleLevelEnum.Advanced, RoleLevelEnum.Pro].map(r => + + )}
    + + })} +
+
} +
+ ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.stories.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.stories.tsx new file mode 100644 index 0000000..4b08dbb --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.stories.tsx @@ -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; + + +const Template: ComponentStory = (args) => + +export const Default = Template.bind({}); +Default.args = { + value: MOCK_DATA['user'].skills, + onChange: () => { } +} diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.tsx new file mode 100644 index 0000000..ba85701 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.tsx @@ -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 + +interface Props { + allSkills: Pick[]; + 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 ( + +

🌈 Skills

+

Add some of your skills and let other makers know what you’re good at.

+ {/*
    + {props.allSkills.map((role, idx) => { + const isActive = props.value.some(v => v.id === role.id); + + return + })} +
*/} + +
+ ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/rolesSkills.graphql b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/rolesSkills.graphql new file mode 100644 index 0000000..e80ea58 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/rolesSkills.graphql @@ -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 + } + } +} diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index cbcc5d7..e5f3a8d 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -48,9 +48,9 @@ export type BaseUser = { location: Maybe; name: Scalars['String']; role: Maybe; - roles: Array; + roles: Array; similar_makers: Array; - skills: Array; + skills: Array; stories: Array; tournaments: Array; twitter: Maybe; @@ -115,6 +115,13 @@ export type DonationsStats = { touranments: Scalars['String']; }; +export type GenericMakerRole = { + __typename?: 'GenericMakerRole'; + icon: Scalars['String']; + id: Scalars['Int']; + title: Scalars['String']; +}; + export type Hackathon = { __typename?: 'Hackathon'; cover_image: Scalars['String']; @@ -136,6 +143,29 @@ export type LnurlDetails = { minSendable: Maybe; }; +export type MakerRole = { + __typename?: 'MakerRole'; + icon: Scalars['String']; + id: Scalars['Int']; + level: RoleLevelEnum; + title: Scalars['String']; +}; + +export type MakerRoleInput = { + id: Scalars['Int']; + level: RoleLevelEnum; +}; + +export type MakerSkill = { + __typename?: 'MakerSkill'; + id: Scalars['Int']; + title: Scalars['String']; +}; + +export type MakerSkillInput = { + id: Scalars['Int']; +}; + export type Mutation = { __typename?: 'Mutation'; confirmDonation: Donation; @@ -144,6 +174,7 @@ export type Mutation = { deleteStory: Maybe; donate: Donation; updateProfileDetails: Maybe; + updateProfileRoles: Maybe; updateUserPreferences: MyProfile; vote: Vote; }; @@ -181,6 +212,11 @@ export type MutationUpdateProfileDetailsArgs = { }; +export type MutationUpdateProfileRolesArgs = { + data: InputMaybe; +}; + + export type MutationUpdateUserPreferencesArgs = { userKeys: InputMaybe>; }; @@ -208,9 +244,9 @@ export type MyProfile = BaseUser & { nostr_prv_key: Maybe; nostr_pub_key: Maybe; role: Maybe; - roles: Array; + roles: Array; similar_makers: Array; - skills: Array; + skills: Array; stories: Array; tournaments: Array; twitter: Maybe; @@ -261,6 +297,11 @@ export type ProfileDetailsInput = { website: InputMaybe; }; +export type ProfileRolesInput = { + roles: Array; + skills: Array; +}; + export type Project = { __typename?: 'Project'; awards: Array; @@ -283,6 +324,8 @@ export type Query = { allCategories: Array; allProjects: Array; getAllHackathons: Array; + getAllMakersRoles: Array; + getAllMakersSkills: Array; getCategory: Category; getDonationsStats: DonationsStats; getFeed: Array; @@ -470,9 +513,9 @@ export type User = BaseUser & { location: Maybe; name: Scalars['String']; role: Maybe; - roles: Array; + roles: Array; similar_makers: Array; - skills: Array; + skills: Array; stories: Array; tournaments: Array; twitter: Maybe; @@ -484,20 +527,6 @@ export type UserKeyInputType = { name: Scalars['String']; }; -export type UserRole = { - __typename?: 'UserRole'; - icon: Scalars['String']; - id: Scalars['Int']; - level: RoleLevelEnum; - title: Scalars['String']; -}; - -export type UserSkill = { - __typename?: 'UserSkill'; - id: Scalars['Int']; - title: Scalars['String']; -}; - export enum Vote_Item_Type { Bounty = 'Bounty', PostComment = 'PostComment', @@ -635,6 +664,18 @@ export type UpdateUserPreferencesMutationVariables = Exact<{ export type UpdateUserPreferencesMutation = { __typename?: 'Mutation', updateUserPreferences: { __typename?: 'MyProfile', id: number, nostr_pub_key: string | null, nostr_prv_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } }; +export type MyProfileRolesSkillsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type MyProfileRolesSkillsQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', id: number, skills: Array<{ __typename?: 'MakerSkill', id: number, title: string }>, roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> } | null, getAllMakersRoles: Array<{ __typename?: 'GenericMakerRole', id: number, title: string, icon: string }>, getAllMakersSkills: Array<{ __typename?: 'MakerSkill', id: number, title: string }> }; + +export type UpdateUserRolesSkillsMutationVariables = Exact<{ + data: InputMaybe; +}>; + + +export type UpdateUserRolesSkillsMutation = { __typename?: 'Mutation', updateProfileRoles: { __typename?: 'MyProfile', id: number, skills: Array<{ __typename?: 'MakerSkill', id: number, title: string }>, roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> } | null }; + export type MyProfileAboutQueryVariables = Exact<{ [key: string]: never; }>; @@ -652,7 +693,7 @@ export type ProfileQueryVariables = Exact<{ }>; -export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }>, skills: Array<{ __typename?: 'UserSkill', id: number, title: string }>, roles: Array<{ __typename?: 'UserRole', id: number, title: string, icon: string, level: RoleLevelEnum }>, tournaments: Array<{ __typename?: 'Tournament', id: number, title: string, thumbnail_image: string, start_date: any, end_date: any }>, similar_makers: Array<{ __typename?: 'User', id: number, name: string, avatar: string, jobTitle: string | null }> } | null }; +export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }>, skills: Array<{ __typename?: 'MakerSkill', id: number, title: string }>, roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }>, tournaments: Array<{ __typename?: 'Tournament', id: number, title: string, thumbnail_image: string, start_date: any, end_date: any }>, similar_makers: Array<{ __typename?: 'User', id: number, name: string, avatar: string, jobTitle: string | null }> } | null }; export type CategoryPageQueryVariables = Exact<{ categoryId: Scalars['Int']; @@ -1512,6 +1553,102 @@ export function useUpdateUserPreferencesMutation(baseOptions?: Apollo.MutationHo export type UpdateUserPreferencesMutationHookResult = ReturnType; export type UpdateUserPreferencesMutationResult = Apollo.MutationResult; export type UpdateUserPreferencesMutationOptions = Apollo.BaseMutationOptions; +export const MyProfileRolesSkillsDocument = gql` + query MyProfileRolesSkills { + me { + id + skills { + id + title + } + roles { + id + title + icon + level + } + } + getAllMakersRoles { + id + title + icon + } + getAllMakersSkills { + id + title + } +} + `; + +/** + * __useMyProfileRolesSkillsQuery__ + * + * To run a query within a React component, call `useMyProfileRolesSkillsQuery` and pass it any options that fit your needs. + * When your component renders, `useMyProfileRolesSkillsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useMyProfileRolesSkillsQuery({ + * variables: { + * }, + * }); + */ +export function useMyProfileRolesSkillsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(MyProfileRolesSkillsDocument, options); + } +export function useMyProfileRolesSkillsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(MyProfileRolesSkillsDocument, options); + } +export type MyProfileRolesSkillsQueryHookResult = ReturnType; +export type MyProfileRolesSkillsLazyQueryHookResult = ReturnType; +export type MyProfileRolesSkillsQueryResult = Apollo.QueryResult; +export const UpdateUserRolesSkillsDocument = gql` + mutation UpdateUserRolesSkills($data: ProfileRolesInput) { + updateProfileRoles(data: $data) { + id + skills { + id + title + } + roles { + id + title + icon + level + } + } +} + `; +export type UpdateUserRolesSkillsMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateUserRolesSkillsMutation__ + * + * To run a mutation, you first call `useUpdateUserRolesSkillsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateUserRolesSkillsMutation` 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 [updateUserRolesSkillsMutation, { data, loading, error }] = useUpdateUserRolesSkillsMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useUpdateUserRolesSkillsMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateUserRolesSkillsDocument, options); + } +export type UpdateUserRolesSkillsMutationHookResult = ReturnType; +export type UpdateUserRolesSkillsMutationResult = Apollo.MutationResult; +export type UpdateUserRolesSkillsMutationOptions = Apollo.BaseMutationOptions; export const MyProfileAboutDocument = gql` query MyProfileAbout { me { diff --git a/src/mocks/data.ts b/src/mocks/data.ts index a660dcf..8e86892 100644 --- a/src/mocks/data.ts +++ b/src/mocks/data.ts @@ -1,7 +1,7 @@ import { hackathons } from "./data/hackathon"; import { posts, feed, generatePostComments } from "./data/posts"; import { categories, projects } from "./data/projects"; -import { user } from "./data/users"; +import { allMakersRoles, allMakersSkills, user } from "./data/users"; export const MOCK_DATA = { projects, @@ -10,5 +10,7 @@ export const MOCK_DATA = { feed, hackathons, generatePostComments: generatePostComments, - user: user + user: user, + allMakersRoles: allMakersRoles, + allMakersSkills: allMakersSkills, } \ No newline at end of file diff --git a/src/mocks/data/users.ts b/src/mocks/data/users.ts index 51e8a1e..eade93e 100644 --- a/src/mocks/data/users.ts +++ b/src/mocks/data/users.ts @@ -1,9 +1,101 @@ import { Chance } from "chance"; -import { MyProfile, RoleLevelEnum, User } from "src/graphql"; +import { GenericMakerRole, MakerSkill, MyProfile, RoleLevelEnum, User } from "src/graphql"; +import { randomItem, randomItems } from "src/utils/helperFunctions"; import { posts } from "./posts"; import { getCoverImage, getAvatarImage } from "./utils"; const chance = new Chance(); +export const allMakersRoles: GenericMakerRole[] = [ + { + id: 1, + title: "Frontend Dev", + icon: "πŸ’„" + }, + { + id: 2, + title: "Backend Dev", + icon: "πŸ’»οΈ" + }, { + id: 3, + title: "UI/UX Designer", + icon: "🌈️️" + }, + { + id: 4, + title: "Community Manager", + icon: "πŸŽ‰οΈοΈ" + }, + { + id: 5, + title: "Founder", + icon: "πŸ¦„οΈ" + }, + { + id: 6, + title: "Marketer", + icon: "🚨️" + }, + { + id: 7, + title: "Content Creator", + icon: "πŸŽ₯️" + }, + { + id: 8, + title: "Researcher", + icon: "πŸ”¬" + }, + { + id: 9, + title: "Data engineer", + icon: "πŸ’ΏοΈ" + }, + { + id: 10, + title: "Growth hacker", + icon: "πŸ“‰οΈ" + }, + { + id: 11, + title: "Technical Writer", + icon: "✍️️" + }, +] + +export const allMakersSkills: MakerSkill[] = [ + { + id: 1, + title: "Figma" + }, + { + id: 2, + title: "Prototyping" + }, { + id: 3, + title: "Writing" + }, { + id: 4, + title: "CSS" + }, { + id: 5, + title: "React.js" + }, { + id: 6, + title: "Wordpress" + }, { + id: 7, + title: "Principle app" + }, { + id: 8, + title: "UX design" + }, { + id: 9, + title: "User research" + }, { + id: 10, + title: "User testing" + }, +] export const user: User & MyProfile = { id: 123, @@ -32,56 +124,8 @@ export const user: User & MyProfile = { key: "66345134234235", name: "My Phoenix wallet key" },], - roles: [ - { - id: 12, - title: "Developer", - icon: "πŸ’»οΈ", - level: RoleLevelEnum.Pro, - }, - { - id: 14, - title: "Founder", - icon: "πŸ¦„οΈ", - level: RoleLevelEnum.Intermediate, - }, - { - id: 51, - title: "Community Manager", - icon: "πŸŽ‰οΈ", - level: RoleLevelEnum.Hobbyist, - }, - ], - skills: [ - { - id: 1, - title: chance.word(), - }, - { - id: 2, - title: chance.word(), - }, - { - id: 3, - title: chance.word(), - }, - { - id: 4, - title: chance.word(), - }, - { - id: 5, - title: chance.word(), - }, - { - id: 6, - title: chance.word(), - }, - { - id: 7, - title: chance.word(), - }, - ], + roles: randomItems(3, ...allMakersRoles).map(role => ({ ...role, level: randomItem(...Object.values(RoleLevelEnum)) })), + skills: randomItems(7, ...allMakersSkills), tournaments: [ { id: 1, @@ -127,3 +171,5 @@ export const user: User & MyProfile = { }, ] as User[] } + + diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 94b5557..9325296 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,6 +1,6 @@ import { graphql } from 'msw' -import { allCategories, getAllHackathons, getCategory, getFeed, getMyDrafts, getPostById, getProject, getTrendingPosts, hottestProjects, me, newProjects, popularTags, profile, projectsByCategory, searchProjects } from './resolvers' +import { allCategories, getAllHackathons, getAllMakersRoles, getAllMakersSkills, getCategory, getFeed, getMyDrafts, getPostById, getProject, getTrendingPosts, hottestProjects, me, newProjects, popularTags, profile, projectsByCategory, searchProjects } from './resolvers' import { NavCategoriesQuery, ExploreProjectsQuery, @@ -31,6 +31,7 @@ import { GetMyDraftsQuery, MyProfileAboutQuery, MyProfilePreferencesQuery, + MyProfileRolesSkillsQuery, } from 'src/graphql' const delay = (ms = 1000) => new Promise((res) => setTimeout(res, ms + Math.random() * 1000)) @@ -197,8 +198,6 @@ export const handlers = [ graphql.query('Me', async (req, res, ctx) => { await delay() - console.log("ME"); - return res( ctx.data({ me: me() @@ -226,6 +225,17 @@ export const handlers = [ }), + graphql.query('MyProfileRolesSkills', async (req, res, ctx) => { + await delay() + return res( + ctx.data({ + me: { ...me(), roles: [], skills: [] }, + getAllMakersRoles: getAllMakersRoles(), + getAllMakersSkills: getAllMakersSkills(), + }) + ) + }), + graphql.query('profile', async (req, res, ctx) => { await delay() diff --git a/src/mocks/resolvers.ts b/src/mocks/resolvers.ts index d09f614..80fa46b 100644 --- a/src/mocks/resolvers.ts +++ b/src/mocks/resolvers.ts @@ -84,6 +84,13 @@ export function profile() { return { ...MOCK_DATA['user'], __typename: 'User' } as User } +export function getAllMakersRoles() { + return MOCK_DATA['allMakersRoles'] +} + +export function getAllMakersSkills() { + return MOCK_DATA['allMakersSkills'] +} export function getMyDrafts(): Query['getMyDrafts'] { return MOCK_DATA['posts'].stories; } \ No newline at end of file