-
-
+
+ (
+ {
+ onChange(v);
+ handleSubmit(onSubmit)();
+ }} />
+ )}
+ />
+
+
+
+ {/* reset()}
+ /> */}
)
diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/RemoveWalletKeyModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/RemoveWalletKeyModal.tsx
new file mode 100644
index 0000000..53f9d46
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/RemoveWalletKeyModal.tsx
@@ -0,0 +1,48 @@
+import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
+import { motion } from 'framer-motion'
+import { IoClose } from 'react-icons/io5'
+import Button from 'src/Components/Button/Button'
+import { useAppDispatch } from 'src/utils/hooks'
+import { PayloadAction } from '@reduxjs/toolkit'
+
+interface Props extends ModalCard {
+ callbackAction: PayloadAction<{ confirmed: boolean }>
+}
+
+
+
+export default function RemoveWalletKeyModal({
+ onClose, direction, callbackAction,
+}: Props) {
+
+ const dispatch = useAppDispatch();
+
+ const handleConfirm = () => {
+ const action = Object.assign({}, callbackAction);
+ action.payload = { confirmed: true }
+ dispatch(action)
+ onClose?.();
+ }
+
+ return (
+
+
+ Remove key?
+
+
🔑
+
Are you sure you want to remove this key from your account? Once deleted, you won’t be able to recover it.
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/index.ts b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/index.ts
new file mode 100644
index 0000000..c056033
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/index.ts
@@ -0,0 +1,3 @@
+import { lazyModal } from 'src/utils/helperFunctions';
+
+export const { LazyComponent: RemoveWalletKeyModal } = lazyModal(() => import('./RemoveWalletKeyModal'))
\ No newline at end of file
diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql
new file mode 100644
index 0000000..aad0a49
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql
@@ -0,0 +1,24 @@
+query MyProfilePreferences {
+ me {
+ id
+ walletsKeys {
+ key
+ name
+ is_current
+ }
+ nostr_prv_key
+ nostr_pub_key
+ }
+}
+
+mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
+ updateUserPreferences(userKeys: $walletsKeys) {
+ id
+ walletsKeys {
+ key
+ name
+ }
+ nostr_pub_key
+ nostr_prv_key
+ }
+}
diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.Skeleton.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.Skeleton.tsx
new file mode 100644
index 0000000..b84e4ce
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.Skeleton.tsx
@@ -0,0 +1,44 @@
+import React from 'react'
+import Card from 'src/Components/Card/Card';
+import Skeleton from 'react-loading-skeleton';
+import { random } from 'src/utils/helperFunctions';
+
+export default function RolesSkillsTabSkeleton() {
+ return (
+
+
+
+
+
+
+
+
+ {Array(10).fill(0).map((_, idx) => {
+
+ return
+
+ })}
+
+
+
+
+
+
+
+
+
+ {Array(8).fill(0).map((_, idx) => -
+
+
)}
+
+
+
+
+
+
+
+ )
+}
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..b60c2bb
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx
@@ -0,0 +1,140 @@
+
+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 { gql, NetworkStatus, useApolloClient } from '@apollo/client';
+import { usePrompt } from 'src/utils/hooks';
+import { UpdateUserRolesSkillsMutationVariables, useMyProfileRolesSkillsQuery, useUpdateUserRolesSkillsMutation, UserRolesSkillsFragmentDoc } from 'src/graphql'
+import UpdateRolesCard from "./UpdateRolesCard/UpdateRolesCard";
+import UpdateSkillsCard from "./UpdateSkillsCard/UpdateSkillsCard";
+import RolesSkillsTabSkeleton from "./RolesSkillsTab.Skeleton";
+
+
+interface Props {
+}
+
+
+
+export type IRolesSkillsForm = NonNullable
;
+
+const schema: yup.SchemaOf = yup.object({
+ roles: yup.array().of(
+ yup.object().shape({
+ id: yup.number().required(),
+ level: 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 apolloClient = useApolloClient()
+ const [mutate, mutationStatus] = useUpdateUserRolesSkillsMutation({
+ onCompleted: ({ updateProfileRoles: data }) => {
+ apolloClient.writeFragment({
+ id: `User:${data?.id}`,
+ data: {
+ roles: data?.roles,
+ skills: data?.skills
+ },
+ fragment: UserRolesSkillsFragmentDoc,
+ })
+ }
+ });
+
+
+
+
+ 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: {
+ roles: data.roles.map(v => ({ id: v.id, level: v.level })),
+ skills: data.skills.map(v => ({ id: v.id })),
+ }
+ },
+ 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 (
+
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+
+ console.log(e))}
+ onCancel={() => 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..1b15db3
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateRolesCard/UpdateRolesCard.tsx
@@ -0,0 +1,82 @@
+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/SkillsInput.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/SkillsInput.tsx
new file mode 100644
index 0000000..c410c4d
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/SkillsInput.tsx
@@ -0,0 +1,130 @@
+
+
+import Select from 'react-select';
+import { OnChangeValue, StylesConfig, components, OptionProps } from "react-select";
+import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
+import { FiSearch } from 'react-icons/fi';
+import { useState } from 'react';
+import { MyProfileRolesSkillsQuery } from 'src/graphql';
+
+
+
+type Skill = MyProfileRolesSkillsQuery['getAllMakersSkills'][number]
+
+interface Props {
+ classes?: {
+ container?: string
+ input?: string
+ }
+ placeholder?: string,
+ onSelect?: (selectedUser: Skill) => void
+ options: Skill[]
+}
+
+
+
+
+// const OptionComponent = (props: OptionProps) => {
+// return (
+//
+//
+//
+//
+//
+// {props.data.name}
+//
+//
+// {props.data.jobTitle}
+//
+//
+//
+
+//
+// );
+// };
+
+
+const colourStyles: StylesConfig = {
+
+ control: (styles, state) => ({
+ ...styles,
+ padding: '5px 16px',
+ borderRadius: 12,
+ // border: 'none',
+ // boxShadow: 'none',
+
+ ":hover": {
+ cursor: "pointer"
+ },
+ ":focus-within": {
+ '--tw-border-opacity': '1',
+ borderColor: 'rgb(179 160 255 / var(--tw-border-opacity))',
+ outlineColor: '#9E88FF',
+ '--tw-ring-offset-shadow': 'var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)',
+ '--tw-ring-shadow': 'var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)',
+ boxShadow: 'var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)',
+ '--tw-ring-color': 'rgb(179 160 255 / var(--tw-ring-opacity))',
+ '--tw-ring-opacity': '0.5'
+ }
+
+ }),
+ multiValueRemove: (styles) => ({
+ ...styles,
+ ":hover": {
+ background: 'none'
+ }
+ }),
+ indicatorsContainer: () => ({ display: 'none' }),
+ clearIndicator: () => ({ display: 'none' }),
+ indicatorSeparator: () => ({ display: "none" }),
+ input: (styles, state) => ({
+ ...styles,
+ " input": {
+ boxShadow: 'none !important'
+ },
+ }),
+ multiValue: styles => ({
+ ...styles,
+ padding: '4px 12px',
+ borderRadius: 48,
+ fontWeight: 500
+ }),
+ valueContainer: (styles) => ({
+ ...styles,
+ paddingLeft: 0,
+ paddingRight: 0,
+ })
+}
+
+
+export default function SkillsInput({
+ classes,
+ ...props }: Props) {
+
+ const handleChange = (newValue: OnChangeValue,) => {
+ if (newValue)
+ props.onSelect?.(newValue);
+ }
+
+ return (
+
+
+ )
+}
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..07c1a01
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/UpdateSkillsCard.tsx
@@ -0,0 +1,54 @@
+import React, { useMemo } from 'react'
+import { GrClose } from 'react-icons/gr'
+import Card from 'src/Components/Card/Card'
+import InfoCard from 'src/Components/InfoCard/InfoCard'
+import { MakerSkill } from 'src/graphql'
+import SkillsInput from './SkillsInput'
+
+type Value = Pick
+
+interface Props {
+ allSkills: Pick[];
+ value: Value[],
+ onChange: (newValue: Value[]) => void
+}
+
+export default function UpdateSkillsCard(props: Props) {
+
+ const add = (newValue: Value) => {
+ props.onChange([...props.value, newValue])
+ }
+
+ const idToValue = useMemo(() => {
+ const map = new Map();
+ for (let i = 0; i < props.allSkills.length; i++) {
+ const element = props.allSkills[i];
+ map.set(element.id, element);
+ }
+ return map;
+ }, [props.allSkills])
+
+ const remove = (id: number) => {
+ props.onChange(props.value.filter(v => v.id !== id))
+ }
+
+
+ return (
+
+ 🌈 Skills
+ Add some of your skills and let other makers know what you’re good at.
+
+ !props.value.some(v => v.id === skill.id))} onSelect={add} />
+
+ {props.value.length > 0 &&
+ {props.value.map((skill) => -
+ {idToValue.get(skill.id)?.title}
+
)}
+
}
+
+
+ ℹ️ Can't find a specific skill? You can suggest it to be added here
+
+
+ )
+}
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..0f1957d
--- /dev/null
+++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/rolesSkills.graphql
@@ -0,0 +1,44 @@
+fragment UserRolesSkills on BaseUser {
+ skills {
+ id
+ title
+ }
+ roles {
+ id
+ title
+ icon
+ level
+ }
+}
+
+query MyProfileRolesSkills {
+ me {
+ id
+ ...UserRolesSkills
+ }
+ 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/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx b/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx
index 3f616c7..941814c 100644
--- a/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx
+++ b/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx
@@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'
import Button from 'src/Components/Button/Button'
import Card from 'src/Components/Card/Card'
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'
-import { useProfileQuery } from 'src/graphql'
import { trimText } from 'src/utils/helperFunctions'
import { useAppSelector } from 'src/utils/hooks'
import { createRoute } from 'src/utils/routing'
@@ -17,14 +16,10 @@ interface Props {
export default function SaveChangesCard(props: Props) {
- const userId = useAppSelector(state => state.user.me?.id!)
- const profileQuery = useProfileQuery({
- variables: {
- profileId: userId,
- },
- })
+ const user = useAppSelector(state => state.user.me)
- if (!profileQuery.data?.profile)
+
+ if (!user)
return <>>
@@ -38,18 +33,18 @@ export default function SaveChangesCard(props: Props) {
-
+ to={createRoute({ type: 'profile', id: user.id, username: user.name })}>
+
-
{profileQuery.data.profile ? trimText(profileQuery.data.profile.name, 30) : "Anonymouse"}
- {profileQuery.data.profile.jobTitle &&
{profileQuery.data.profile.jobTitle}
}
+
{user ? trimText(user.name, 30) : "Anonymouse"}
+ {user.jobTitle &&
{user.jobTitle}
}
{/* {showTimeAgo &&
{dayjs().diff(props.date, 'hour') < 24 ? `${dayjs().diff(props.date, 'hour')}h ago` : undefined}
} */}
- {trimText(profileQuery.data.profile.bio, 120)}
+ {trimText(user.bio, 120)}