From e9bcff88d64df03c990fb27ab0e0f09eab615c77 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 8 Aug 2022 19:49:26 +0300 Subject: [PATCH 01/57] add logs --- api/functions/login/login.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/functions/login/login.js b/api/functions/login/login.js index 7dcdbf2..6f0d695 100644 --- a/api/functions/login/login.js +++ b/api/functions/login/login.js @@ -106,6 +106,7 @@ const loginHandler = async (req, res) => { return res.status(200).json({ status: "OK" }) } catch (error) { + console.log(error); return res.status(400).json({ status: 'ERROR', reason: 'Unexpected error happened, please try again' }) } } From cb11f056d97e27a0fa6d69469731c05b21535e82 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 8 Aug 2022 21:26:42 +0300 Subject: [PATCH 02/57] reenable "account" setting --- .../Profiles/pages/EditProfilePage/EditProfilePage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx index 787c952..8382d21 100644 --- a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx +++ b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx @@ -16,10 +16,10 @@ const links = [ text: "👾 My Profile", path: 'my-profile', }, - // { - // text: "đŸ™â€â™‚ī¸ Account", - // path: 'account', - // }, + { + text: "đŸ™â€â™‚ī¸ Account", + path: 'account', + }, { text: "âš™ī¸ Preferences", path: 'preferences', From 30dc59ddedb70622d8c5bdb35af3aad36d10606d Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 8 Aug 2022 21:52:48 +0300 Subject: [PATCH 03/57] fix: allow linking already linked key --- api/functions/login/login.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/api/functions/login/login.js b/api/functions/login/login.js index 6f0d695..1b1d99b 100644 --- a/api/functions/login/login.js +++ b/api/functions/login/login.js @@ -38,16 +38,13 @@ const loginHandler = async (req, res) => { if (existingKeys.length >= 3) return res.status(400).json({ status: 'ERROR', reason: "Can only link up to 3 wallets" }) - if (existingKeys.includes(key)) - return res.status(400).json({ status: 'ERROR', reason: "Wallet already linked" }) - - - await prisma.userKey.create({ - data: { - key, - user_id, - } - }); + if (!existingKeys.includes(key)) + await prisma.userKey.create({ + data: { + key, + user_id, + } + }); return res .status(200) @@ -106,7 +103,6 @@ const loginHandler = async (req, res) => { return res.status(200).json({ status: "OK" }) } catch (error) { - console.log(error); return res.status(400).json({ status: 'ERROR', reason: 'Unexpected error happened, please try again' }) } } From 4dfa4800dae25a6337d5c2905f40ff60374d2e9d Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Tue, 9 Aug 2022 13:05:21 +0300 Subject: [PATCH 04/57] feat: add update/delete wallet keys api & widgets --- api/functions/graphql/nexus-typegen.ts | 23 ++ api/functions/graphql/schema.graphql | 12 ++ api/functions/graphql/types/users.js | 111 +++++++++- .../migration.sql | 2 + prisma/schema.prisma | 3 +- .../AccountCard/AccountCard.tsx | 162 +++++++++++++- .../LinkingAccountModal.tsx | 13 +- .../LinkingAccountModal/myWalletsKeys.graphql | 13 ++ src/graphql/index.tsx | 197 +++++++++++++----- 9 files changed, 479 insertions(+), 57 deletions(-) create mode 100644 prisma/migrations/20220809081452_add_name_to_user_key/migration.sql create mode 100644 src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/myWalletsKeys.graphql diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index 78a7cfd..b3b9805 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -49,6 +49,10 @@ export interface NexusGenInputs { twitter?: string | null; // String website?: string | null; // String } + UserKeyInputType: { // input type + key: string; // String! + name: string; // String! + } } export interface NexusGenEnums { @@ -204,6 +208,10 @@ export interface NexusGenObjects { twitter?: string | null; // String website?: string | null; // String } + UserKey: { // root type + key: string; // String! + name: string; // String! + } Vote: { // root type amount_in_sat: number; // Int! id: number; // Int! @@ -314,6 +322,7 @@ export interface NexusGenFieldTypes { deleteStory: NexusGenRootTypes['Story'] | null; // Story donate: NexusGenRootTypes['Donation']; // Donation! updateProfile: NexusGenRootTypes['User'] | null; // User + updateUserWalletKeys: NexusGenRootTypes['UserKey'][]; // [UserKey!]! vote: NexusGenRootTypes['Vote']; // Vote! } PostComment: { // field return type @@ -353,6 +362,7 @@ export interface NexusGenFieldTypes { getTrendingPosts: NexusGenRootTypes['Post'][]; // [Post!]! hottestProjects: NexusGenRootTypes['Project'][]; // [Project!]! me: NexusGenRootTypes['User'] | null; // User + myWalletsKeys: NexusGenRootTypes['UserKey'][]; // [UserKey!]! newProjects: NexusGenRootTypes['Project'][]; // [Project!]! officialTags: NexusGenRootTypes['Tag'][]; // [Tag!]! popularTags: NexusGenRootTypes['Tag'][]; // [Tag!]! @@ -415,6 +425,10 @@ export interface NexusGenFieldTypes { twitter: string | null; // String website: string | null; // String } + UserKey: { // field return type + key: string; // String! + name: string; // String! + } Vote: { // field return type amount_in_sat: number; // Int! id: number; // Int! @@ -523,6 +537,7 @@ export interface NexusGenFieldTypeNames { deleteStory: 'Story' donate: 'Donation' updateProfile: 'User' + updateUserWalletKeys: 'UserKey' vote: 'Vote' } PostComment: { // field return type name @@ -562,6 +577,7 @@ export interface NexusGenFieldTypeNames { getTrendingPosts: 'Post' hottestProjects: 'Project' me: 'User' + myWalletsKeys: 'UserKey' newProjects: 'Project' officialTags: 'Tag' popularTags: 'Tag' @@ -624,6 +640,10 @@ export interface NexusGenFieldTypeNames { twitter: 'String' website: 'String' } + UserKey: { // field return type name + key: 'String' + name: 'String' + } Vote: { // field return type name amount_in_sat: 'Int' id: 'Int' @@ -667,6 +687,9 @@ export interface NexusGenArgTypes { updateProfile: { // args data?: NexusGenInputs['UpdateProfileInput'] | null; // UpdateProfileInput } + updateUserWalletKeys: { // args + data?: NexusGenInputs['UserKeyInputType'][] | null; // [UserKeyInputType!] + } vote: { // args amount_in_sat: number; // Int! item_id: number; // Int! diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index b2994b5..e199a9e 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -100,6 +100,7 @@ type Mutation { deleteStory(id: Int!): Story donate(amount_in_sat: Int!): Donation! updateProfile(data: UpdateProfileInput): User + updateUserWalletKeys(data: [UserKeyInputType!]): [UserKey!]! vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote! } @@ -161,6 +162,7 @@ type Query { getTrendingPosts: [Post!]! hottestProjects(skip: Int = 0, take: Int = 50): [Project!]! me: User + myWalletsKeys: [UserKey!]! newProjects(skip: Int = 0, take: Int = 50): [Project!]! officialTags: [Tag!]! popularTags: [Tag!]! @@ -251,6 +253,16 @@ type User { website: String } +type UserKey { + key: String! + name: String! +} + +input UserKeyInputType { + key: String! + name: String! +} + enum VOTE_ITEM_TYPE { Bounty PostComment diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index 824cc76..e04dd1f 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -1,6 +1,6 @@ const { prisma } = require('../../../prisma'); -const { objectType, extendType, intArg, nonNull, inputObjectType } = require("nexus"); +const { objectType, extendType, intArg, nonNull, inputObjectType, list } = require("nexus"); const { getUserByPubKey } = require("../../../auth/utils/helperFuncs"); const { removeNulls } = require("./helpers"); @@ -117,14 +117,123 @@ const updateProfile = extendType({ }) +const UserKey = objectType({ + name: 'UserKey', + definition(t) { + t.nonNull.string('key'); + t.nonNull.string('name'); + } +}) + +const myWalletsKeys = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('myWalletsKeys', { + type: "UserKey", + + async resolve(_, __, ctx) { + const user = await getUserByPubKey(ctx.userPubKey); + + if (!user) + throw new Error("You have to login"); + + return prisma.userKey.findMany({ + where: { + user_id: user.id, + } + }) + } + }) + } +}) + +const UserKeyInputType = inputObjectType({ + name: 'UserKeyInputType', + definition(t) { + t.nonNull.string('key'); + t.nonNull.string('name'); + } +}) + + + +const updateUserWalletKeys = extendType({ + type: 'Mutation', + definition(t) { + t.nonNull.list.nonNull.field('updateUserWalletKeys', { + type: 'UserKey', + args: { data: list(nonNull(UserKeyInputType)) }, + async resolve(_root, args, ctx) { + + + + const user = await getUserByPubKey(ctx.userPubKey); + if (!user) + throw new Error("You have to login"); + + + + // Check if all the sent keys belong to the user + const userKeys = (await prisma.userKey.findMany({ + where: { + AND: { + user_id: { + equals: user.id, + }, + key: { + in: args.data.map(i => i.key) + } + }, + }, + select: { + key: true + } + })).map(i => i.key); + + const newKeys = []; + for (let i = 0; i < args.data.length; i++) { + const item = args.data[i]; + if (userKeys.includes(item.key)) + newKeys.push(item); + } + + + if (newKeys.length === 0) + throw new Error("You can't delete all your keys") + + await prisma.userKey.deleteMany({ + where: { + user_id: user.id + } + }) + + await prisma.userKey.createMany({ + data: newKeys.map(i => ({ + user_id: user.id, + key: i.key, + name: i.name, + })) + }) + + return newKeys; + + } + }) + } +}) + + module.exports = { // Types User, UpdateProfileInput, + UserKey, // Queries me, profile, + myWalletsKeys, // Mutations updateProfile, + updateUserWalletKeys, } \ No newline at end of file diff --git a/prisma/migrations/20220809081452_add_name_to_user_key/migration.sql b/prisma/migrations/20220809081452_add_name_to_user_key/migration.sql new file mode 100644 index 0000000..e04904a --- /dev/null +++ b/prisma/migrations/20220809081452_add_name_to_user_key/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "UserKey" ADD COLUMN "name" TEXT NOT NULL DEFAULT E'New Key Name'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5042441..b623fdc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -68,7 +68,8 @@ model User { } model UserKey { - key String @id + key String @id + name String @default("New Key Name") user User? @relation(fields: [user_id], references: [id]) user_id Int? diff --git a/src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.tsx b/src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.tsx index b27b49e..dccd63b 100644 --- a/src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.tsx +++ b/src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.tsx @@ -1,20 +1,119 @@ import Button from 'src/Components/Button/Button'; import { useAppDispatch } from 'src/utils/hooks'; import { openModal } from 'src/redux/features/modals.slice'; +import { useMyWalletsKeysQuery, useUpdateUserWalletsKeysMutation } from 'src/graphql'; +import Skeleton from 'react-loading-skeleton'; +import { useReducer } from 'react'; interface Props { } + +type State = { + hasNewChanges: boolean, + keys: Array<{ key: string, name: string }>, + oldKeys: Array<{ key: string, name: string }> +} + + +type Action = + | { + type: 'set' + payload: State['keys'] + } + | { + type: 'delete', + payload: { idx: number } + } + | { + type: 'update', + payload: { + idx: number, + value: string, + } + } + | { + type: 'cancel' + } + +function reducer(state: State, action: Action): State { + switch (action.type) { + case 'set': + return { + hasNewChanges: false, + keys: [...action.payload], + oldKeys: [...action.payload], + } + case 'delete': + if (state.keys.length === 1) + return state; + return { + hasNewChanges: true, + oldKeys: state.oldKeys, + keys: [...state.keys.slice(0, action.payload.idx), ...state.keys.slice(action.payload.idx + 1)] + }; + case 'update': + return { + hasNewChanges: true, + oldKeys: state.oldKeys, + keys: state.keys.map((item, idx) => { + if (idx === action.payload.idx) + return { + ...item, + name: action.payload.value + } + return item; + }), + + } + case 'cancel': + return { + hasNewChanges: false, + keys: [...state.oldKeys], + oldKeys: state.oldKeys, + } + } +} + export default function AccountCard({ }: Props) { - const dispatch = useAppDispatch() + const dispatch = useAppDispatch(); + const [keysState, keysDispatch] = useReducer(reducer, { keys: [], oldKeys: [], hasNewChanges: false, }); + const myKeysQuery = useMyWalletsKeysQuery({ + onCompleted: data => { + keysDispatch({ + type: 'set', + payload: data.myWalletsKeys + }) + } + }); + const [updateKeys, updatingKeysStatus] = useUpdateUserWalletsKeysMutation({ + onCompleted: data => { + keysDispatch({ + type: "set", + payload: data.updateUserWalletKeys + }) + } + }) const connectNewWallet = () => { dispatch(openModal({ Modal: "LinkingAccountModal" })) } + const saveChanges = () => { + updateKeys({ + variables: { + data: keysState.keys.map(v => ({ key: v.key, name: v.name })) + } + }) + } + + const cancelChanges = () => { + keysDispatch({ type: 'cancel' }); + } + return (
@@ -28,9 +127,64 @@ export default function AccountCard({ }: Props) {
You can add a new wallet from the button below.

- + { + myKeysQuery.loading ? +
    + {Array(2).fill(0).map((_, idx) => +
  • + +
  • + )} +
+ : + <> +
    + {keysState.keys.map((item, idx) => +
  • + { + keysDispatch({ + type: 'update', + payload: { + idx, + value: e.target.value + } + }) + }} + className='p-0 border-0 focus:border-0 focus:outline-none grow + focus:ring-0 placeholder:!text-gray-400' /> + + + +
  • + )} +
+
+ + +
+ {keysState.keys.length < 3 && } + + }
diff --git a/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/LinkingAccountModal.tsx index 038040d..757ccee 100644 --- a/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/LinkingAccountModal.tsx +++ b/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/LinkingAccountModal.tsx @@ -7,6 +7,7 @@ import { QRCodeSVG } from 'qrcode.react'; import Button from "src/Components/Button/Button"; import { FiCopy } from "react-icons/fi"; import useCopyToClipboard from "src/utils/hooks/useCopyToClipboard"; +import { useApolloClient } from '@apollo/client'; @@ -57,7 +58,8 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo const [copied, setCopied] = useState(false); const { loadingLnurl, data: { lnurl }, error } = useLnurlQuery(); - const clipboard = useCopyToClipboard() + const clipboard = useCopyToClipboard(); + const apolloClient = useApolloClient(); @@ -71,6 +73,13 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo clipboard(lnurl); } + const done = () => { + apolloClient.refetchQueries({ + include: ['MyWalletsKeys'] + }) + onClose?.() + } + let content = <> @@ -114,7 +123,7 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo >{copied ? "Copied" : "Copy"} - - - )} - -
- - -
- {keysState.keys.length < 3 && } - - } - - - ) -} diff --git a/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/myWalletsKeys.graphql b/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/myWalletsKeys.graphql deleted file mode 100644 index c5bd961..0000000 --- a/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/myWalletsKeys.graphql +++ /dev/null @@ -1,13 +0,0 @@ -query MyWalletsKeys { - myWalletsKeys { - key - name - } -} - -mutation UpdateUserWalletsKeys($data: [UserKeyInputType!]) { - updateUserWalletKeys(data: $data) { - key - name - } -} diff --git a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx index ac3939c..6a9a6a0 100644 --- a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx +++ b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx @@ -7,7 +7,6 @@ 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 AccountCard from "./AccountCard/AccountCard"; import PreferencesTab from "./PreferencesTab/PreferencesTab"; import Card from "src/Components/Card/Card"; @@ -18,11 +17,7 @@ const links = [ path: 'my-profile', }, { - text: "đŸ™â€â™‚ī¸ Account", - path: 'account', - }, - { - text: "âš™ī¸ Preferences", + text: "âš™ī¸ Settings & Preferences", path: 'preferences', } ] @@ -30,24 +25,10 @@ const links = [ export default function EditProfilePage() { - const userId = useAppSelector(state => state.user.me?.id) - const profileQuery = useProfileQuery({ - variables: { - profileId: userId!, - }, - skip: !userId, - }) - const isMediumScreen = useMediaQuery(MEDIA_QUERIES.isMedium) + const isMediumScreen = useMediaQuery(MEDIA_QUERIES.isMedium); - - if (!userId || profileQuery.loading) - return - - if (!profileQuery.data?.profile) - return - return ( <> @@ -97,9 +78,8 @@ export default function EditProfilePage() {
} /> - } /> - } /> - + } /> + } />
diff --git a/src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.stories.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.stories.tsx similarity index 90% rename from src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.stories.tsx rename to src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.stories.tsx index 7463ed6..319570a 100644 --- a/src/features/Profiles/pages/EditProfilePage/AccountCard/AccountCard.stories.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.stories.tsx @@ -1,5 +1,5 @@ import { ComponentStory, ComponentMeta } from '@storybook/react'; -import AccountCard from './AccountCard'; +import AccountCard from './LinkedAccountsCard'; export default { title: 'Profiles/Profile Page/Account Card', diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx new file mode 100644 index 0000000..0526825 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx @@ -0,0 +1,168 @@ +import Button from 'src/Components/Button/Button'; +import { useAppDispatch } from 'src/utils/hooks'; +import { openModal } from 'src/redux/features/modals.slice'; +import Card from 'src/Components/Card/Card'; +import { MyProfile } from 'src/graphql'; +import Skeleton from 'react-loading-skeleton'; +import { useReducer } from 'react'; + + +interface Props { + walletsKeys: MyProfile['walletsKeys'] +} + + +type State = { + hasNewChanges: boolean, + keys: Array<{ key: string, name: string }>, + oldKeys: Array<{ key: string, name: string }> +} + + +type Action = + | { + type: 'set' + payload: State['keys'] + } + | { + type: 'delete', + payload: { idx: number } + } + | { + type: 'update', + payload: { + idx: number, + value: string, + } + } + | { + type: 'cancel' + } + +function reducer(state: State, action: Action): State { + switch (action.type) { + case 'set': + return { + hasNewChanges: false, + keys: [...action.payload], + oldKeys: [...action.payload], + } + case 'delete': + if (state.keys.length === 1) + return state; + return { + hasNewChanges: true, + oldKeys: state.oldKeys, + keys: [...state.keys.slice(0, action.payload.idx), ...state.keys.slice(action.payload.idx + 1)] + }; + case 'update': + return { + hasNewChanges: true, + oldKeys: state.oldKeys, + keys: state.keys.map((item, idx) => { + if (idx === action.payload.idx) + return { + ...item, + name: action.payload.value + } + return item; + }), + + } + case 'cancel': + return { + hasNewChanges: false, + keys: [...state.oldKeys], + oldKeys: state.oldKeys, + } + } +} + +export default function LinkedAccountsCard({ walletsKeys }: Props) { + + const dispatch = useAppDispatch(); + const [keysState, keysDispatch] = useReducer(reducer, { keys: [], oldKeys: [], hasNewChanges: false, }); + + // const [updateKeys, updatingKeysStatus] = useUpdateUserWalletsKeysMutation({ + // onCompleted: data => { + // keysDispatch({ + // type: "set", + // payload: data.updateUserWalletKeys + // }) + // } + // }) + + const connectNewWallet = () => { + dispatch(openModal({ Modal: "LinkingAccountModal" })) + } + + const saveChanges = () => { + // updateKeys({ + // variables: { + // data: keysState.keys.map(v => ({ key: v.key, name: v.name })) + // } + // }) + } + + const cancelChanges = () => { + keysDispatch({ type: 'cancel' }); + } + + + return ( + +

🔐 Linked Accounts

+

+ These are the wallets that you can login to this account from. You can add a new wallet below. +

+
+
    + {walletsKeys.map((item, idx) => +
  • + { + keysDispatch({ + type: 'update', + payload: { + idx, + value: e.target.value + } + }) + }} + className='p-0 border-0 focus:border-0 focus:outline-none grow + focus:ring-0 placeholder:!text-gray-400' /> + + {walletsKeys.length > 1 && } +
  • + )} +
+ {/*
+ + +
*/} + {walletsKeys.length < 3 && + } + +
+
+ ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx similarity index 100% rename from src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/LinkingAccountModal.tsx rename to src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx diff --git a/src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/index.ts b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/index.ts similarity index 100% rename from src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal/index.ts rename to src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/index.ts diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index 2711363..2fb17e8 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -1,20 +1,31 @@ -import { Nullable } from 'remirror'; +import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard'; import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard'; +import { useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql'; +import LoadingPage from 'src/Components/LoadingPage/LoadingPage'; +import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage"; interface Props { - isOwner?: boolean; - nostr_pub_key: Nullable; - nostr_prv_key: Nullable; - } -export default function PreferencesTab({ nostr_prv_key, nostr_pub_key, isOwner }: Props) { + + +export default function PreferencesTab() { + + const query = useMyProfilePreferencesQuery(); + const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); + + if (query.loading) + return + + if (!query.data?.me) + return return (
-
- +
+ +
) 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..d30db02 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql @@ -0,0 +1,21 @@ +query MyProfilePreferences { + me { + walletsKeys { + key + name + } + nostr_prv_key + nostr_pub_key + } +} + +mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) { + updateUserPreferences(userKeys: $userKeys) { + walletsKeys { + key + name + } + nostr_pub_key + nostr_prv_key + } +} diff --git a/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx b/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx index 3f616c7..0011cd5 100644 --- a/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx +++ b/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx @@ -17,14 +17,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 +34,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)}

} +
+ + {value.length > 1 && } +
)} @@ -157,7 +137,7 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) { Save Changes
*/} - {walletsKeys.length < 3 && + {value.length < 3 && } diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index 2fb17e8..62ad3be 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -1,18 +1,45 @@ import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard'; import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard'; -import { useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql'; +import { UpdateUserPreferencesMutationVariables, useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql'; import LoadingPage from 'src/Components/LoadingPage/LoadingPage'; 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'; + interface Props { } +export type IProfilePreferencesForm = NonNullable; +const schema: yup.SchemaOf = yup.object({ + walletsKeys: yup.array().of(yup.object().shape({ + name: yup.string().required(), + key: yup.string().trim().required(), + }).required()) + .required(), +}).required(); export default function PreferencesTab() { - const query = useMyProfilePreferencesQuery(); + const { register, formState: { errors, isDirty, }, handleSubmit, reset, control } = useForm({ + defaultValues: { + walletsKeys: [] + }, + resolver: yupResolver(schema), + mode: 'onBlur', + }); + + const query = useMyProfilePreferencesQuery({ + onCompleted: data => { + if (data.me) reset(data.me) + } + }); const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); if (query.loading) @@ -21,12 +48,51 @@ export default function PreferencesTab() { if (!query.data?.me) return + + const onSubmit: SubmitHandler = data => { + if (!Array.isArray(data.walletsKeys)) + return; + + const toastId = toast.loading("Saving changes...", NotificationsService.defaultOptions) + + + mutate({ + variables: { + walletsKeys: data.walletsKeys.map(({ key, name }) => ({ key, name })), + }, + onCompleted: ({ updateUserPreferences }) => { + if (updateUserPreferences) { + reset(updateUserPreferences); + 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/PreferencesTab/profilePreferences.graphql b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql index d30db02..6077304 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql @@ -9,8 +9,8 @@ query MyProfilePreferences { } } -mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) { - updateUserPreferences(userKeys: $userKeys) { +mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) { + updateUserPreferences(userKeys: $walletsKeys) { walletsKeys { key name diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index d6fede7..d6c2f98 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -140,7 +140,7 @@ export type Mutation = { deleteStory: Maybe; donate: Donation; updateProfileDetails: Maybe; - updateUserPreferences: Array; + updateUserPreferences: MyProfile; vote: Vote; }; @@ -576,11 +576,11 @@ export type MyProfilePreferencesQueryVariables = Exact<{ [key: string]: never; } export type MyProfilePreferencesQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', nostr_prv_key: string | null, nostr_pub_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } | null }; export type UpdateUserPreferencesMutationVariables = Exact<{ - userKeys: InputMaybe | UserKeyInputType>; + walletsKeys: InputMaybe | UserKeyInputType>; }>; -export type UpdateUserPreferencesMutation = { __typename?: 'Mutation', updateUserPreferences: Array<{ __typename?: 'MyProfile', nostr_pub_key: string | null, nostr_prv_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> }> }; +export type UpdateUserPreferencesMutation = { __typename?: 'Mutation', updateUserPreferences: { __typename?: 'MyProfile', nostr_pub_key: string | null, nostr_prv_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } }; export type MyProfileAboutQueryVariables = Exact<{ [key: string]: never; }>; @@ -1420,8 +1420,8 @@ export type MyProfilePreferencesQueryHookResult = ReturnType; export type MyProfilePreferencesQueryResult = Apollo.QueryResult; export const UpdateUserPreferencesDocument = gql` - mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) { - updateUserPreferences(userKeys: $userKeys) { + mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) { + updateUserPreferences(userKeys: $walletsKeys) { walletsKeys { key name @@ -1446,7 +1446,7 @@ export type UpdateUserPreferencesMutationFn = Apollo.MutationFunction Date: Wed, 17 Aug 2022 13:39:14 +0300 Subject: [PATCH 08/57] fix: invalidate the preferences query on linking new account --- .../PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx index 757ccee..81257c6 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx @@ -75,7 +75,7 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo const done = () => { apolloClient.refetchQueries({ - include: ['MyWalletsKeys'] + include: ['MyProfilePreferences'] }) onClose?.() } From fc3bd116b8a0468c5a305f48269d60564e5fad7b Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 17 Aug 2022 13:53:22 +0300 Subject: [PATCH 09/57] update: linking account modal ui --- api/functions/graphql/nexus-typegen.ts | 917 ------------------ .../LinkingAccountModal.tsx | 27 +- 2 files changed, 13 insertions(+), 931 deletions(-) delete mode 100644 api/functions/graphql/nexus-typegen.ts diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts deleted file mode 100644 index bd8c67e..0000000 --- a/api/functions/graphql/nexus-typegen.ts +++ /dev/null @@ -1,917 +0,0 @@ -/** - * This file was generated by Nexus Schema - * Do not make changes to this file directly - */ - - -import type { core } from "nexus" -declare global { - interface NexusGenCustomInputMethods { - /** - * Date custom scalar type - */ - date(fieldName: FieldName, opts?: core.CommonInputFieldConfig): void // "Date"; - } -} -declare global { - interface NexusGenCustomOutputMethods { - /** - * Date custom scalar type - */ - date(fieldName: FieldName, ...opts: core.ScalarOutSpread): void // "Date"; - } -} - - -declare global { - interface NexusGen extends NexusGenTypes {} -} - -export interface NexusGenInputs { - ProfileDetailsInput: { // input type - avatar?: string | null; // String - bio?: string | null; // String - email?: string | null; // String - github?: string | null; // String - jobTitle?: string | null; // String - lightning_address?: string | null; // String - linkedin?: string | null; // String - location?: string | null; // String - name?: string | null; // String - twitter?: string | null; // String - website?: string | null; // String - } - StoryInputType: { // input type - body: string; // String! - cover_image?: string | null; // String - id?: number | null; // Int - is_published?: boolean | null; // Boolean - tags: string[]; // [String!]! - title: string; // String! - } - UserKeyInputType: { // input type - key: string; // String! - name: string; // String! - } -} - -export interface NexusGenEnums { - POST_TYPE: "Bounty" | "Question" | "Story" - VOTE_ITEM_TYPE: "Bounty" | "PostComment" | "Project" | "Question" | "Story" | "User" -} - -export interface NexusGenScalars { - String: string - Int: number - Float: number - Boolean: boolean - ID: string - Date: any -} - -export interface NexusGenObjects { - Author: { // root type - avatar: string; // String! - id: number; // Int! - join_date: NexusGenScalars['Date']; // Date! - lightning_address?: string | null; // String - name: string; // String! - } - Award: { // root type - id: number; // Int! - image: string; // String! - title: string; // String! - url: string; // String! - } - Bounty: { // root type - applicants_count: number; // Int! - applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]! - body: string; // String! - cover_image?: string | null; // String - createdAt: NexusGenScalars['Date']; // Date! - deadline: string; // String! - excerpt: string; // String! - id: number; // Int! - is_published?: boolean | null; // Boolean - reward_amount: number; // Int! - title: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } - BountyApplication: { // root type - author: NexusGenRootTypes['Author']; // Author! - date: string; // String! - id: number; // Int! - workplan: string; // String! - } - Category: { // root type - cover_image?: string | null; // String - icon?: string | null; // String - id: number; // Int! - title: string; // String! - } - Donation: { // root type - amount: number; // Int! - createdAt: NexusGenScalars['Date']; // Date! - id: number; // Int! - paid: boolean; // Boolean! - payment_hash: string; // String! - payment_request: string; // String! - } - DonationsStats: { // root type - applications: string; // String! - donations: string; // String! - prizes: string; // String! - touranments: string; // String! - } - Hackathon: { // root type - cover_image: string; // String! - description: string; // String! - end_date: NexusGenScalars['Date']; // Date! - id: number; // Int! - location: string; // String! - start_date: NexusGenScalars['Date']; // Date! - title: string; // String! - website: string; // String! - } - LnurlDetails: { // root type - commentAllowed?: number | null; // Int - maxSendable?: number | null; // Int - metadata?: string | null; // String - minSendable?: number | null; // Int - } - Mutation: {}; - MyProfile: { // root type - avatar: string; // String! - bio?: string | null; // String - email?: string | null; // String - github?: string | null; // String - id: number; // Int! - jobTitle?: string | null; // String - join_date: NexusGenScalars['Date']; // Date! - lightning_address?: string | null; // String - linkedin?: string | null; // String - location?: string | null; // String - name: string; // String! - nostr_prv_key?: string | null; // String - nostr_pub_key?: string | null; // String - role?: string | null; // String - twitter?: string | null; // String - website?: string | null; // String - } - PostComment: { // root type - author: NexusGenRootTypes['Author']; // Author! - body: string; // String! - created_at: NexusGenScalars['Date']; // Date! - id: number; // Int! - parentId?: number | null; // Int - votes_count: number; // Int! - } - Project: { // root type - cover_image: string; // String! - description: string; // String! - id: number; // Int! - lightning_address?: string | null; // String - lnurl_callback_url?: string | null; // String - screenshots: string[]; // [String!]! - thumbnail_image: string; // String! - title: string; // String! - votes_count: number; // Int! - website: string; // String! - } - Query: {}; - Question: { // root type - body: string; // String! - createdAt: NexusGenScalars['Date']; // Date! - excerpt: string; // String! - id: number; // Int! - is_published?: boolean | null; // Boolean - title: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } - Story: { // root type - body: string; // String! - cover_image?: string | null; // String - createdAt: NexusGenScalars['Date']; // Date! - excerpt: string; // String! - id: number; // Int! - is_published?: boolean | null; // Boolean - title: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } - Tag: { // root type - description?: string | null; // String - icon?: string | null; // String - id: number; // Int! - isOfficial?: boolean | null; // Boolean - title: string; // String! - } - User: { // root type - avatar: string; // String! - bio?: string | null; // String - email?: string | null; // String - github?: string | null; // String - id: number; // Int! - jobTitle?: string | null; // String - join_date: NexusGenScalars['Date']; // Date! - lightning_address?: string | null; // String - linkedin?: string | null; // String - location?: string | null; // String - name: string; // String! - role?: string | null; // String - twitter?: string | null; // String - website?: string | null; // String - } - Vote: { // root type - amount_in_sat: number; // Int! - id: number; // Int! - item_id: number; // Int! - item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! - paid: boolean; // Boolean! - payment_hash: string; // String! - payment_request: string; // String! - } - WalletKey: { // root type - key: string; // String! - name: string; // String! - } -} - -export interface NexusGenInterfaces { - BaseUser: NexusGenRootTypes['MyProfile'] | NexusGenRootTypes['User']; - PostBase: NexusGenRootTypes['Bounty'] | NexusGenRootTypes['Question'] | NexusGenRootTypes['Story']; -} - -export interface NexusGenUnions { - Post: NexusGenRootTypes['Bounty'] | NexusGenRootTypes['Question'] | NexusGenRootTypes['Story']; -} - -export type NexusGenRootTypes = NexusGenInterfaces & NexusGenObjects & NexusGenUnions - -export type NexusGenAllTypes = NexusGenRootTypes & NexusGenScalars & NexusGenEnums - -export interface NexusGenFieldTypes { - Author: { // field return type - avatar: string; // String! - id: number; // Int! - join_date: NexusGenScalars['Date']; // Date! - lightning_address: string | null; // String - name: string; // String! - } - Award: { // field return type - id: number; // Int! - image: string; // String! - project: NexusGenRootTypes['Project']; // Project! - title: string; // String! - url: string; // String! - } - Bounty: { // field return type - applicants_count: number; // Int! - applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]! - author: NexusGenRootTypes['Author']; // Author! - body: string; // String! - cover_image: string | null; // String - createdAt: NexusGenScalars['Date']; // Date! - deadline: string; // String! - excerpt: string; // String! - id: number; // Int! - is_published: boolean | null; // Boolean - reward_amount: number; // Int! - tags: NexusGenRootTypes['Tag'][]; // [Tag!]! - title: string; // String! - type: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } - BountyApplication: { // field return type - author: NexusGenRootTypes['Author']; // Author! - date: string; // String! - id: number; // Int! - workplan: string; // String! - } - Category: { // field return type - apps_count: number; // Int! - cover_image: string | null; // String - icon: string | null; // String - id: number; // Int! - project: NexusGenRootTypes['Project'][]; // [Project!]! - title: string; // String! - votes_sum: number; // Int! - } - Donation: { // field return type - amount: number; // Int! - by: NexusGenRootTypes['User'] | null; // User - createdAt: NexusGenScalars['Date']; // Date! - id: number; // Int! - paid: boolean; // Boolean! - payment_hash: string; // String! - payment_request: string; // String! - } - DonationsStats: { // field return type - applications: string; // String! - donations: string; // String! - prizes: string; // String! - touranments: string; // String! - } - Hackathon: { // field return type - cover_image: string; // String! - description: string; // String! - end_date: NexusGenScalars['Date']; // Date! - id: number; // Int! - location: string; // String! - start_date: NexusGenScalars['Date']; // Date! - tags: NexusGenRootTypes['Tag'][]; // [Tag!]! - title: string; // String! - website: string; // String! - } - LnurlDetails: { // field return type - commentAllowed: number | null; // Int - maxSendable: number | null; // Int - metadata: string | null; // String - minSendable: number | null; // Int - } - Mutation: { // field return type - confirmDonation: NexusGenRootTypes['Donation']; // Donation! - confirmVote: NexusGenRootTypes['Vote']; // Vote! - createStory: NexusGenRootTypes['Story'] | null; // Story - deleteStory: NexusGenRootTypes['Story'] | null; // Story - donate: NexusGenRootTypes['Donation']; // Donation! - updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile - updateUserPreferences: NexusGenRootTypes['MyProfile']; // MyProfile! - vote: NexusGenRootTypes['Vote']; // Vote! - } - MyProfile: { // field return type - avatar: string; // String! - bio: string | null; // String - email: string | null; // String - github: string | null; // String - id: number; // Int! - jobTitle: string | null; // String - join_date: NexusGenScalars['Date']; // Date! - lightning_address: string | null; // String - linkedin: string | null; // String - location: string | null; // String - name: string; // String! - nostr_prv_key: string | null; // String - nostr_pub_key: string | null; // String - role: string | null; // String - stories: NexusGenRootTypes['Story'][]; // [Story!]! - twitter: string | null; // String - walletsKeys: NexusGenRootTypes['WalletKey'][]; // [WalletKey!]! - website: string | null; // String - } - PostComment: { // field return type - author: NexusGenRootTypes['Author']; // Author! - body: string; // String! - created_at: NexusGenScalars['Date']; // Date! - id: number; // Int! - parentId: number | null; // Int - votes_count: number; // Int! - } - Project: { // field return type - awards: NexusGenRootTypes['Award'][]; // [Award!]! - category: NexusGenRootTypes['Category']; // Category! - cover_image: string; // String! - description: string; // String! - id: number; // Int! - lightning_address: string | null; // String - lnurl_callback_url: string | null; // String - screenshots: string[]; // [String!]! - tags: NexusGenRootTypes['Tag'][]; // [Tag!]! - thumbnail_image: string; // String! - title: string; // String! - votes_count: number; // Int! - website: string; // String! - } - Query: { // field return type - allCategories: NexusGenRootTypes['Category'][]; // [Category!]! - allProjects: NexusGenRootTypes['Project'][]; // [Project!]! - getAllHackathons: NexusGenRootTypes['Hackathon'][]; // [Hackathon!]! - getCategory: NexusGenRootTypes['Category']; // Category! - getDonationsStats: NexusGenRootTypes['DonationsStats']; // DonationsStats! - getFeed: NexusGenRootTypes['Post'][]; // [Post!]! - getLnurlDetailsForProject: NexusGenRootTypes['LnurlDetails']; // LnurlDetails! - getMyDrafts: NexusGenRootTypes['Post'][]; // [Post!]! - getPostById: NexusGenRootTypes['Post']; // Post! - getProject: NexusGenRootTypes['Project']; // Project! - getTrendingPosts: NexusGenRootTypes['Post'][]; // [Post!]! - hottestProjects: NexusGenRootTypes['Project'][]; // [Project!]! - me: NexusGenRootTypes['MyProfile'] | null; // MyProfile - newProjects: NexusGenRootTypes['Project'][]; // [Project!]! - officialTags: NexusGenRootTypes['Tag'][]; // [Tag!]! - popularTags: NexusGenRootTypes['Tag'][]; // [Tag!]! - profile: NexusGenRootTypes['User'] | null; // User - projectsByCategory: NexusGenRootTypes['Project'][]; // [Project!]! - searchProjects: NexusGenRootTypes['Project'][]; // [Project!]! - } - Question: { // field return type - author: NexusGenRootTypes['Author']; // Author! - body: string; // String! - createdAt: NexusGenScalars['Date']; // Date! - excerpt: string; // String! - id: number; // Int! - is_published: boolean | null; // Boolean - tags: NexusGenRootTypes['Tag'][]; // [Tag!]! - title: string; // String! - type: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } - Story: { // field return type - author: NexusGenRootTypes['Author']; // Author! - body: string; // String! - comments: NexusGenRootTypes['PostComment'][]; // [PostComment!]! - comments_count: number; // Int! - cover_image: string | null; // String - createdAt: NexusGenScalars['Date']; // Date! - excerpt: string; // String! - id: number; // Int! - is_published: boolean | null; // Boolean - tags: NexusGenRootTypes['Tag'][]; // [Tag!]! - title: string; // String! - type: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } - Tag: { // field return type - description: string | null; // String - icon: string | null; // String - id: number; // Int! - isOfficial: boolean | null; // Boolean - title: string; // String! - } - User: { // field return type - avatar: string; // String! - bio: string | null; // String - email: string | null; // String - github: string | null; // String - id: number; // Int! - jobTitle: string | null; // String - join_date: NexusGenScalars['Date']; // Date! - lightning_address: string | null; // String - linkedin: string | null; // String - location: string | null; // String - name: string; // String! - role: string | null; // String - stories: NexusGenRootTypes['Story'][]; // [Story!]! - twitter: string | null; // String - website: string | null; // String - } - Vote: { // field return type - amount_in_sat: number; // Int! - id: number; // Int! - item_id: number; // Int! - item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! - paid: boolean; // Boolean! - payment_hash: string; // String! - payment_request: string; // String! - } - WalletKey: { // field return type - key: string; // String! - name: string; // String! - } - BaseUser: { // field return type - avatar: string; // String! - bio: string | null; // String - email: string | null; // String - github: string | null; // String - id: number; // Int! - jobTitle: string | null; // String - join_date: NexusGenScalars['Date']; // Date! - lightning_address: string | null; // String - linkedin: string | null; // String - location: string | null; // String - name: string; // String! - role: string | null; // String - stories: NexusGenRootTypes['Story'][]; // [Story!]! - twitter: string | null; // String - website: string | null; // String - } - PostBase: { // field return type - body: string; // String! - createdAt: NexusGenScalars['Date']; // Date! - excerpt: string; // String! - id: number; // Int! - is_published: boolean | null; // Boolean - title: string; // String! - updatedAt: NexusGenScalars['Date']; // Date! - votes_count: number; // Int! - } -} - -export interface NexusGenFieldTypeNames { - Author: { // field return type name - avatar: 'String' - id: 'Int' - join_date: 'Date' - lightning_address: 'String' - name: 'String' - } - Award: { // field return type name - id: 'Int' - image: 'String' - project: 'Project' - title: 'String' - url: 'String' - } - Bounty: { // field return type name - applicants_count: 'Int' - applications: 'BountyApplication' - author: 'Author' - body: 'String' - cover_image: 'String' - createdAt: 'Date' - deadline: 'String' - excerpt: 'String' - id: 'Int' - is_published: 'Boolean' - reward_amount: 'Int' - tags: 'Tag' - title: 'String' - type: 'String' - updatedAt: 'Date' - votes_count: 'Int' - } - BountyApplication: { // field return type name - author: 'Author' - date: 'String' - id: 'Int' - workplan: 'String' - } - Category: { // field return type name - apps_count: 'Int' - cover_image: 'String' - icon: 'String' - id: 'Int' - project: 'Project' - title: 'String' - votes_sum: 'Int' - } - Donation: { // field return type name - amount: 'Int' - by: 'User' - createdAt: 'Date' - id: 'Int' - paid: 'Boolean' - payment_hash: 'String' - payment_request: 'String' - } - DonationsStats: { // field return type name - applications: 'String' - donations: 'String' - prizes: 'String' - touranments: 'String' - } - Hackathon: { // field return type name - cover_image: 'String' - description: 'String' - end_date: 'Date' - id: 'Int' - location: 'String' - start_date: 'Date' - tags: 'Tag' - title: 'String' - website: 'String' - } - LnurlDetails: { // field return type name - commentAllowed: 'Int' - maxSendable: 'Int' - metadata: 'String' - minSendable: 'Int' - } - Mutation: { // field return type name - confirmDonation: 'Donation' - confirmVote: 'Vote' - createStory: 'Story' - deleteStory: 'Story' - donate: 'Donation' - updateProfileDetails: 'MyProfile' - updateUserPreferences: 'MyProfile' - vote: 'Vote' - } - MyProfile: { // field return type name - avatar: 'String' - bio: 'String' - email: 'String' - github: 'String' - id: 'Int' - jobTitle: 'String' - join_date: 'Date' - lightning_address: 'String' - linkedin: 'String' - location: 'String' - name: 'String' - nostr_prv_key: 'String' - nostr_pub_key: 'String' - role: 'String' - stories: 'Story' - twitter: 'String' - walletsKeys: 'WalletKey' - website: 'String' - } - PostComment: { // field return type name - author: 'Author' - body: 'String' - created_at: 'Date' - id: 'Int' - parentId: 'Int' - votes_count: 'Int' - } - Project: { // field return type name - awards: 'Award' - category: 'Category' - cover_image: 'String' - description: 'String' - id: 'Int' - lightning_address: 'String' - lnurl_callback_url: 'String' - screenshots: 'String' - tags: 'Tag' - thumbnail_image: 'String' - title: 'String' - votes_count: 'Int' - website: 'String' - } - Query: { // field return type name - allCategories: 'Category' - allProjects: 'Project' - getAllHackathons: 'Hackathon' - getCategory: 'Category' - getDonationsStats: 'DonationsStats' - getFeed: 'Post' - getLnurlDetailsForProject: 'LnurlDetails' - getMyDrafts: 'Post' - getPostById: 'Post' - getProject: 'Project' - getTrendingPosts: 'Post' - hottestProjects: 'Project' - me: 'MyProfile' - newProjects: 'Project' - officialTags: 'Tag' - popularTags: 'Tag' - profile: 'User' - projectsByCategory: 'Project' - searchProjects: 'Project' - } - Question: { // field return type name - author: 'Author' - body: 'String' - createdAt: 'Date' - excerpt: 'String' - id: 'Int' - is_published: 'Boolean' - tags: 'Tag' - title: 'String' - type: 'String' - updatedAt: 'Date' - votes_count: 'Int' - } - Story: { // field return type name - author: 'Author' - body: 'String' - comments: 'PostComment' - comments_count: 'Int' - cover_image: 'String' - createdAt: 'Date' - excerpt: 'String' - id: 'Int' - is_published: 'Boolean' - tags: 'Tag' - title: 'String' - type: 'String' - updatedAt: 'Date' - votes_count: 'Int' - } - Tag: { // field return type name - description: 'String' - icon: 'String' - id: 'Int' - isOfficial: 'Boolean' - title: 'String' - } - User: { // field return type name - avatar: 'String' - bio: 'String' - email: 'String' - github: 'String' - id: 'Int' - jobTitle: 'String' - join_date: 'Date' - lightning_address: 'String' - linkedin: 'String' - location: 'String' - name: 'String' - role: 'String' - stories: 'Story' - twitter: 'String' - website: 'String' - } - Vote: { // field return type name - amount_in_sat: 'Int' - id: 'Int' - item_id: 'Int' - item_type: 'VOTE_ITEM_TYPE' - paid: 'Boolean' - payment_hash: 'String' - payment_request: 'String' - } - WalletKey: { // field return type name - key: 'String' - name: 'String' - } - BaseUser: { // field return type name - avatar: 'String' - bio: 'String' - email: 'String' - github: 'String' - id: 'Int' - jobTitle: 'String' - join_date: 'Date' - lightning_address: 'String' - linkedin: 'String' - location: 'String' - name: 'String' - role: 'String' - stories: 'Story' - twitter: 'String' - website: 'String' - } - PostBase: { // field return type name - body: 'String' - createdAt: 'Date' - excerpt: 'String' - id: 'Int' - is_published: 'Boolean' - title: 'String' - updatedAt: 'Date' - votes_count: 'Int' - } -} - -export interface NexusGenArgTypes { - Mutation: { - confirmDonation: { // args - payment_request: string; // String! - preimage: string; // String! - } - confirmVote: { // args - payment_request: string; // String! - preimage: string; // String! - } - createStory: { // args - data?: NexusGenInputs['StoryInputType'] | null; // StoryInputType - } - deleteStory: { // args - id: number; // Int! - } - donate: { // args - amount_in_sat: number; // Int! - } - updateProfileDetails: { // args - data?: NexusGenInputs['ProfileDetailsInput'] | null; // ProfileDetailsInput - } - updateUserPreferences: { // args - userKeys?: NexusGenInputs['UserKeyInputType'][] | null; // [UserKeyInputType!] - } - vote: { // args - amount_in_sat: number; // Int! - item_id: number; // Int! - item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! - } - } - Query: { - allProjects: { // args - skip?: number | null; // Int - take: number | null; // Int - } - getAllHackathons: { // args - sortBy?: string | null; // String - tag?: number | null; // Int - } - getCategory: { // args - id: number; // Int! - } - getFeed: { // args - skip?: number | null; // Int - sortBy?: string | null; // String - tag?: number | null; // Int - take: number | null; // Int - } - getLnurlDetailsForProject: { // args - project_id: number; // Int! - } - getMyDrafts: { // args - type: NexusGenEnums['POST_TYPE']; // POST_TYPE! - } - getPostById: { // args - id: number; // Int! - type: NexusGenEnums['POST_TYPE']; // POST_TYPE! - } - getProject: { // args - id: number; // Int! - } - hottestProjects: { // args - skip?: number | null; // Int - take: number | null; // Int - } - newProjects: { // args - skip?: number | null; // Int - take: number | null; // Int - } - profile: { // args - id: number; // Int! - } - projectsByCategory: { // args - category_id: number; // Int! - skip?: number | null; // Int - take: number | null; // Int - } - searchProjects: { // args - search: string; // String! - skip?: number | null; // Int - take: number | null; // Int - } - } -} - -export interface NexusGenAbstractTypeMembers { - Post: "Bounty" | "Question" | "Story" - BaseUser: "MyProfile" | "User" - PostBase: "Bounty" | "Question" | "Story" -} - -export interface NexusGenTypeInterfaces { - Bounty: "PostBase" - MyProfile: "BaseUser" - Question: "PostBase" - Story: "PostBase" - User: "BaseUser" -} - -export type NexusGenObjectNames = keyof NexusGenObjects; - -export type NexusGenInputNames = keyof NexusGenInputs; - -export type NexusGenEnumNames = keyof NexusGenEnums; - -export type NexusGenInterfaceNames = keyof NexusGenInterfaces; - -export type NexusGenScalarNames = keyof NexusGenScalars; - -export type NexusGenUnionNames = keyof NexusGenUnions; - -export type NexusGenObjectsUsingAbstractStrategyIsTypeOf = never; - -export type NexusGenAbstractsUsingStrategyResolveType = "BaseUser" | "Post" | "PostBase"; - -export type NexusGenFeaturesConfig = { - abstractTypeStrategies: { - isTypeOf: false - resolveType: true - __typename: false - } -} - -export interface NexusGenTypes { - context: any; - inputTypes: NexusGenInputs; - rootTypes: NexusGenRootTypes; - inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars; - argTypes: NexusGenArgTypes; - fieldTypes: NexusGenFieldTypes; - fieldTypeNames: NexusGenFieldTypeNames; - allTypes: NexusGenAllTypes; - typeInterfaces: NexusGenTypeInterfaces; - objectNames: NexusGenObjectNames; - inputNames: NexusGenInputNames; - enumNames: NexusGenEnumNames; - interfaceNames: NexusGenInterfaceNames; - scalarNames: NexusGenScalarNames; - unionNames: NexusGenUnionNames; - allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames']; - allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames']; - allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes'] - abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames']; - abstractTypeMembers: NexusGenAbstractTypeMembers; - objectsUsingAbstractStrategyIsTypeOf: NexusGenObjectsUsingAbstractStrategyIsTypeOf; - abstractsUsingStrategyResolveType: NexusGenAbstractsUsingStrategyResolveType; - features: NexusGenFeaturesConfig; -} - - -declare global { - interface NexusGenPluginTypeConfig { - } - interface NexusGenPluginInputTypeConfig { - } - interface NexusGenPluginFieldConfig { - } - interface NexusGenPluginInputFieldConfig { - } - interface NexusGenPluginSchemaConfig { - } - interface NexusGenPluginArgConfig { - } -} \ No newline at end of file diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx index 81257c6..db40eba 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx @@ -8,6 +8,7 @@ import Button from "src/Components/Button/Button"; import { FiCopy } from "react-icons/fi"; import useCopyToClipboard from "src/utils/hooks/useCopyToClipboard"; import { useApolloClient } from '@apollo/client'; +import { IoClose } from 'react-icons/io5'; @@ -84,30 +85,27 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo let content = <> if (error) - content =
-

Something wrong happened...

- Refresh the page + content =
+

Ooops...đŸ˜ĩ

+

An error happened while fetching the link, please check your internet connection and try again.

else if (loadingLnurl) - content =
- -

Fetching Lnurl-Auth...

+ content =
+ +

Fetching Lnurl-Auth Link...

else content = - <> -

- Link your account ⚡ -

+

- Scan this code or copy + paste it to your other lightning wallet to be able to login later with it to this account. + Scan this QR code with your other lightning wallet & you will be able to use it to login to this account.
When done, click the button below to close this modal.

@@ -125,12 +123,11 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo color='primary' onClick={done} fullWidth - className='mt-16' > Done?
- +
@@ -141,8 +138,10 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo initial='initial' animate="animate" exit='exit' - className="modal-card w-full max-w-[326px] bg-white border-2 border-gray-200 rounded-16 p-16 flex flex-col gap-16 items-center" + className="modal-card max-w-[364px] p-24 rounded-xl relative" > + +

Link new ⚡ wallet

{content} ) From 470776b041e5e1f1f2c3e0defa9b08bc89cfb7b4 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 17 Aug 2022 14:10:49 +0300 Subject: [PATCH 10/57] fix: updating network status on change --- api/functions/graphql/nexus-typegen.ts | 917 ++++++++++++++++++ .../PreferencesTab/PreferencesTab.tsx | 7 +- 2 files changed, 922 insertions(+), 2 deletions(-) create mode 100644 api/functions/graphql/nexus-typegen.ts diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts new file mode 100644 index 0000000..bd8c67e --- /dev/null +++ b/api/functions/graphql/nexus-typegen.ts @@ -0,0 +1,917 @@ +/** + * This file was generated by Nexus Schema + * Do not make changes to this file directly + */ + + +import type { core } from "nexus" +declare global { + interface NexusGenCustomInputMethods { + /** + * Date custom scalar type + */ + date(fieldName: FieldName, opts?: core.CommonInputFieldConfig): void // "Date"; + } +} +declare global { + interface NexusGenCustomOutputMethods { + /** + * Date custom scalar type + */ + date(fieldName: FieldName, ...opts: core.ScalarOutSpread): void // "Date"; + } +} + + +declare global { + interface NexusGen extends NexusGenTypes {} +} + +export interface NexusGenInputs { + ProfileDetailsInput: { // input type + avatar?: string | null; // String + bio?: string | null; // String + email?: string | null; // String + github?: string | null; // String + jobTitle?: string | null; // String + lightning_address?: string | null; // String + linkedin?: string | null; // String + location?: string | null; // String + name?: string | null; // String + twitter?: string | null; // String + website?: string | null; // String + } + StoryInputType: { // input type + body: string; // String! + cover_image?: string | null; // String + id?: number | null; // Int + is_published?: boolean | null; // Boolean + tags: string[]; // [String!]! + title: string; // String! + } + UserKeyInputType: { // input type + key: string; // String! + name: string; // String! + } +} + +export interface NexusGenEnums { + POST_TYPE: "Bounty" | "Question" | "Story" + VOTE_ITEM_TYPE: "Bounty" | "PostComment" | "Project" | "Question" | "Story" | "User" +} + +export interface NexusGenScalars { + String: string + Int: number + Float: number + Boolean: boolean + ID: string + Date: any +} + +export interface NexusGenObjects { + Author: { // root type + avatar: string; // String! + id: number; // Int! + join_date: NexusGenScalars['Date']; // Date! + lightning_address?: string | null; // String + name: string; // String! + } + Award: { // root type + id: number; // Int! + image: string; // String! + title: string; // String! + url: string; // String! + } + Bounty: { // root type + applicants_count: number; // Int! + applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]! + body: string; // String! + cover_image?: string | null; // String + createdAt: NexusGenScalars['Date']; // Date! + deadline: string; // String! + excerpt: string; // String! + id: number; // Int! + is_published?: boolean | null; // Boolean + reward_amount: number; // Int! + title: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } + BountyApplication: { // root type + author: NexusGenRootTypes['Author']; // Author! + date: string; // String! + id: number; // Int! + workplan: string; // String! + } + Category: { // root type + cover_image?: string | null; // String + icon?: string | null; // String + id: number; // Int! + title: string; // String! + } + Donation: { // root type + amount: number; // Int! + createdAt: NexusGenScalars['Date']; // Date! + id: number; // Int! + paid: boolean; // Boolean! + payment_hash: string; // String! + payment_request: string; // String! + } + DonationsStats: { // root type + applications: string; // String! + donations: string; // String! + prizes: string; // String! + touranments: string; // String! + } + Hackathon: { // root type + cover_image: string; // String! + description: string; // String! + end_date: NexusGenScalars['Date']; // Date! + id: number; // Int! + location: string; // String! + start_date: NexusGenScalars['Date']; // Date! + title: string; // String! + website: string; // String! + } + LnurlDetails: { // root type + commentAllowed?: number | null; // Int + maxSendable?: number | null; // Int + metadata?: string | null; // String + minSendable?: number | null; // Int + } + Mutation: {}; + MyProfile: { // root type + avatar: string; // String! + bio?: string | null; // String + email?: string | null; // String + github?: string | null; // String + id: number; // Int! + jobTitle?: string | null; // String + join_date: NexusGenScalars['Date']; // Date! + lightning_address?: string | null; // String + linkedin?: string | null; // String + location?: string | null; // String + name: string; // String! + nostr_prv_key?: string | null; // String + nostr_pub_key?: string | null; // String + role?: string | null; // String + twitter?: string | null; // String + website?: string | null; // String + } + PostComment: { // root type + author: NexusGenRootTypes['Author']; // Author! + body: string; // String! + created_at: NexusGenScalars['Date']; // Date! + id: number; // Int! + parentId?: number | null; // Int + votes_count: number; // Int! + } + Project: { // root type + cover_image: string; // String! + description: string; // String! + id: number; // Int! + lightning_address?: string | null; // String + lnurl_callback_url?: string | null; // String + screenshots: string[]; // [String!]! + thumbnail_image: string; // String! + title: string; // String! + votes_count: number; // Int! + website: string; // String! + } + Query: {}; + Question: { // root type + body: string; // String! + createdAt: NexusGenScalars['Date']; // Date! + excerpt: string; // String! + id: number; // Int! + is_published?: boolean | null; // Boolean + title: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } + Story: { // root type + body: string; // String! + cover_image?: string | null; // String + createdAt: NexusGenScalars['Date']; // Date! + excerpt: string; // String! + id: number; // Int! + is_published?: boolean | null; // Boolean + title: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } + Tag: { // root type + description?: string | null; // String + icon?: string | null; // String + id: number; // Int! + isOfficial?: boolean | null; // Boolean + title: string; // String! + } + User: { // root type + avatar: string; // String! + bio?: string | null; // String + email?: string | null; // String + github?: string | null; // String + id: number; // Int! + jobTitle?: string | null; // String + join_date: NexusGenScalars['Date']; // Date! + lightning_address?: string | null; // String + linkedin?: string | null; // String + location?: string | null; // String + name: string; // String! + role?: string | null; // String + twitter?: string | null; // String + website?: string | null; // String + } + Vote: { // root type + amount_in_sat: number; // Int! + id: number; // Int! + item_id: number; // Int! + item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! + paid: boolean; // Boolean! + payment_hash: string; // String! + payment_request: string; // String! + } + WalletKey: { // root type + key: string; // String! + name: string; // String! + } +} + +export interface NexusGenInterfaces { + BaseUser: NexusGenRootTypes['MyProfile'] | NexusGenRootTypes['User']; + PostBase: NexusGenRootTypes['Bounty'] | NexusGenRootTypes['Question'] | NexusGenRootTypes['Story']; +} + +export interface NexusGenUnions { + Post: NexusGenRootTypes['Bounty'] | NexusGenRootTypes['Question'] | NexusGenRootTypes['Story']; +} + +export type NexusGenRootTypes = NexusGenInterfaces & NexusGenObjects & NexusGenUnions + +export type NexusGenAllTypes = NexusGenRootTypes & NexusGenScalars & NexusGenEnums + +export interface NexusGenFieldTypes { + Author: { // field return type + avatar: string; // String! + id: number; // Int! + join_date: NexusGenScalars['Date']; // Date! + lightning_address: string | null; // String + name: string; // String! + } + Award: { // field return type + id: number; // Int! + image: string; // String! + project: NexusGenRootTypes['Project']; // Project! + title: string; // String! + url: string; // String! + } + Bounty: { // field return type + applicants_count: number; // Int! + applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]! + author: NexusGenRootTypes['Author']; // Author! + body: string; // String! + cover_image: string | null; // String + createdAt: NexusGenScalars['Date']; // Date! + deadline: string; // String! + excerpt: string; // String! + id: number; // Int! + is_published: boolean | null; // Boolean + reward_amount: number; // Int! + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! + title: string; // String! + type: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } + BountyApplication: { // field return type + author: NexusGenRootTypes['Author']; // Author! + date: string; // String! + id: number; // Int! + workplan: string; // String! + } + Category: { // field return type + apps_count: number; // Int! + cover_image: string | null; // String + icon: string | null; // String + id: number; // Int! + project: NexusGenRootTypes['Project'][]; // [Project!]! + title: string; // String! + votes_sum: number; // Int! + } + Donation: { // field return type + amount: number; // Int! + by: NexusGenRootTypes['User'] | null; // User + createdAt: NexusGenScalars['Date']; // Date! + id: number; // Int! + paid: boolean; // Boolean! + payment_hash: string; // String! + payment_request: string; // String! + } + DonationsStats: { // field return type + applications: string; // String! + donations: string; // String! + prizes: string; // String! + touranments: string; // String! + } + Hackathon: { // field return type + cover_image: string; // String! + description: string; // String! + end_date: NexusGenScalars['Date']; // Date! + id: number; // Int! + location: string; // String! + start_date: NexusGenScalars['Date']; // Date! + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! + title: string; // String! + website: string; // String! + } + LnurlDetails: { // field return type + commentAllowed: number | null; // Int + maxSendable: number | null; // Int + metadata: string | null; // String + minSendable: number | null; // Int + } + Mutation: { // field return type + confirmDonation: NexusGenRootTypes['Donation']; // Donation! + confirmVote: NexusGenRootTypes['Vote']; // Vote! + createStory: NexusGenRootTypes['Story'] | null; // Story + deleteStory: NexusGenRootTypes['Story'] | null; // Story + donate: NexusGenRootTypes['Donation']; // Donation! + updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile + updateUserPreferences: NexusGenRootTypes['MyProfile']; // MyProfile! + vote: NexusGenRootTypes['Vote']; // Vote! + } + MyProfile: { // field return type + avatar: string; // String! + bio: string | null; // String + email: string | null; // String + github: string | null; // String + id: number; // Int! + jobTitle: string | null; // String + join_date: NexusGenScalars['Date']; // Date! + lightning_address: string | null; // String + linkedin: string | null; // String + location: string | null; // String + name: string; // String! + nostr_prv_key: string | null; // String + nostr_pub_key: string | null; // String + role: string | null; // String + stories: NexusGenRootTypes['Story'][]; // [Story!]! + twitter: string | null; // String + walletsKeys: NexusGenRootTypes['WalletKey'][]; // [WalletKey!]! + website: string | null; // String + } + PostComment: { // field return type + author: NexusGenRootTypes['Author']; // Author! + body: string; // String! + created_at: NexusGenScalars['Date']; // Date! + id: number; // Int! + parentId: number | null; // Int + votes_count: number; // Int! + } + Project: { // field return type + awards: NexusGenRootTypes['Award'][]; // [Award!]! + category: NexusGenRootTypes['Category']; // Category! + cover_image: string; // String! + description: string; // String! + id: number; // Int! + lightning_address: string | null; // String + lnurl_callback_url: string | null; // String + screenshots: string[]; // [String!]! + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! + thumbnail_image: string; // String! + title: string; // String! + votes_count: number; // Int! + website: string; // String! + } + Query: { // field return type + allCategories: NexusGenRootTypes['Category'][]; // [Category!]! + allProjects: NexusGenRootTypes['Project'][]; // [Project!]! + getAllHackathons: NexusGenRootTypes['Hackathon'][]; // [Hackathon!]! + getCategory: NexusGenRootTypes['Category']; // Category! + getDonationsStats: NexusGenRootTypes['DonationsStats']; // DonationsStats! + getFeed: NexusGenRootTypes['Post'][]; // [Post!]! + getLnurlDetailsForProject: NexusGenRootTypes['LnurlDetails']; // LnurlDetails! + getMyDrafts: NexusGenRootTypes['Post'][]; // [Post!]! + getPostById: NexusGenRootTypes['Post']; // Post! + getProject: NexusGenRootTypes['Project']; // Project! + getTrendingPosts: NexusGenRootTypes['Post'][]; // [Post!]! + hottestProjects: NexusGenRootTypes['Project'][]; // [Project!]! + me: NexusGenRootTypes['MyProfile'] | null; // MyProfile + newProjects: NexusGenRootTypes['Project'][]; // [Project!]! + officialTags: NexusGenRootTypes['Tag'][]; // [Tag!]! + popularTags: NexusGenRootTypes['Tag'][]; // [Tag!]! + profile: NexusGenRootTypes['User'] | null; // User + projectsByCategory: NexusGenRootTypes['Project'][]; // [Project!]! + searchProjects: NexusGenRootTypes['Project'][]; // [Project!]! + } + Question: { // field return type + author: NexusGenRootTypes['Author']; // Author! + body: string; // String! + createdAt: NexusGenScalars['Date']; // Date! + excerpt: string; // String! + id: number; // Int! + is_published: boolean | null; // Boolean + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! + title: string; // String! + type: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } + Story: { // field return type + author: NexusGenRootTypes['Author']; // Author! + body: string; // String! + comments: NexusGenRootTypes['PostComment'][]; // [PostComment!]! + comments_count: number; // Int! + cover_image: string | null; // String + createdAt: NexusGenScalars['Date']; // Date! + excerpt: string; // String! + id: number; // Int! + is_published: boolean | null; // Boolean + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! + title: string; // String! + type: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } + Tag: { // field return type + description: string | null; // String + icon: string | null; // String + id: number; // Int! + isOfficial: boolean | null; // Boolean + title: string; // String! + } + User: { // field return type + avatar: string; // String! + bio: string | null; // String + email: string | null; // String + github: string | null; // String + id: number; // Int! + jobTitle: string | null; // String + join_date: NexusGenScalars['Date']; // Date! + lightning_address: string | null; // String + linkedin: string | null; // String + location: string | null; // String + name: string; // String! + role: string | null; // String + stories: NexusGenRootTypes['Story'][]; // [Story!]! + twitter: string | null; // String + website: string | null; // String + } + Vote: { // field return type + amount_in_sat: number; // Int! + id: number; // Int! + item_id: number; // Int! + item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! + paid: boolean; // Boolean! + payment_hash: string; // String! + payment_request: string; // String! + } + WalletKey: { // field return type + key: string; // String! + name: string; // String! + } + BaseUser: { // field return type + avatar: string; // String! + bio: string | null; // String + email: string | null; // String + github: string | null; // String + id: number; // Int! + jobTitle: string | null; // String + join_date: NexusGenScalars['Date']; // Date! + lightning_address: string | null; // String + linkedin: string | null; // String + location: string | null; // String + name: string; // String! + role: string | null; // String + stories: NexusGenRootTypes['Story'][]; // [Story!]! + twitter: string | null; // String + website: string | null; // String + } + PostBase: { // field return type + body: string; // String! + createdAt: NexusGenScalars['Date']; // Date! + excerpt: string; // String! + id: number; // Int! + is_published: boolean | null; // Boolean + title: string; // String! + updatedAt: NexusGenScalars['Date']; // Date! + votes_count: number; // Int! + } +} + +export interface NexusGenFieldTypeNames { + Author: { // field return type name + avatar: 'String' + id: 'Int' + join_date: 'Date' + lightning_address: 'String' + name: 'String' + } + Award: { // field return type name + id: 'Int' + image: 'String' + project: 'Project' + title: 'String' + url: 'String' + } + Bounty: { // field return type name + applicants_count: 'Int' + applications: 'BountyApplication' + author: 'Author' + body: 'String' + cover_image: 'String' + createdAt: 'Date' + deadline: 'String' + excerpt: 'String' + id: 'Int' + is_published: 'Boolean' + reward_amount: 'Int' + tags: 'Tag' + title: 'String' + type: 'String' + updatedAt: 'Date' + votes_count: 'Int' + } + BountyApplication: { // field return type name + author: 'Author' + date: 'String' + id: 'Int' + workplan: 'String' + } + Category: { // field return type name + apps_count: 'Int' + cover_image: 'String' + icon: 'String' + id: 'Int' + project: 'Project' + title: 'String' + votes_sum: 'Int' + } + Donation: { // field return type name + amount: 'Int' + by: 'User' + createdAt: 'Date' + id: 'Int' + paid: 'Boolean' + payment_hash: 'String' + payment_request: 'String' + } + DonationsStats: { // field return type name + applications: 'String' + donations: 'String' + prizes: 'String' + touranments: 'String' + } + Hackathon: { // field return type name + cover_image: 'String' + description: 'String' + end_date: 'Date' + id: 'Int' + location: 'String' + start_date: 'Date' + tags: 'Tag' + title: 'String' + website: 'String' + } + LnurlDetails: { // field return type name + commentAllowed: 'Int' + maxSendable: 'Int' + metadata: 'String' + minSendable: 'Int' + } + Mutation: { // field return type name + confirmDonation: 'Donation' + confirmVote: 'Vote' + createStory: 'Story' + deleteStory: 'Story' + donate: 'Donation' + updateProfileDetails: 'MyProfile' + updateUserPreferences: 'MyProfile' + vote: 'Vote' + } + MyProfile: { // field return type name + avatar: 'String' + bio: 'String' + email: 'String' + github: 'String' + id: 'Int' + jobTitle: 'String' + join_date: 'Date' + lightning_address: 'String' + linkedin: 'String' + location: 'String' + name: 'String' + nostr_prv_key: 'String' + nostr_pub_key: 'String' + role: 'String' + stories: 'Story' + twitter: 'String' + walletsKeys: 'WalletKey' + website: 'String' + } + PostComment: { // field return type name + author: 'Author' + body: 'String' + created_at: 'Date' + id: 'Int' + parentId: 'Int' + votes_count: 'Int' + } + Project: { // field return type name + awards: 'Award' + category: 'Category' + cover_image: 'String' + description: 'String' + id: 'Int' + lightning_address: 'String' + lnurl_callback_url: 'String' + screenshots: 'String' + tags: 'Tag' + thumbnail_image: 'String' + title: 'String' + votes_count: 'Int' + website: 'String' + } + Query: { // field return type name + allCategories: 'Category' + allProjects: 'Project' + getAllHackathons: 'Hackathon' + getCategory: 'Category' + getDonationsStats: 'DonationsStats' + getFeed: 'Post' + getLnurlDetailsForProject: 'LnurlDetails' + getMyDrafts: 'Post' + getPostById: 'Post' + getProject: 'Project' + getTrendingPosts: 'Post' + hottestProjects: 'Project' + me: 'MyProfile' + newProjects: 'Project' + officialTags: 'Tag' + popularTags: 'Tag' + profile: 'User' + projectsByCategory: 'Project' + searchProjects: 'Project' + } + Question: { // field return type name + author: 'Author' + body: 'String' + createdAt: 'Date' + excerpt: 'String' + id: 'Int' + is_published: 'Boolean' + tags: 'Tag' + title: 'String' + type: 'String' + updatedAt: 'Date' + votes_count: 'Int' + } + Story: { // field return type name + author: 'Author' + body: 'String' + comments: 'PostComment' + comments_count: 'Int' + cover_image: 'String' + createdAt: 'Date' + excerpt: 'String' + id: 'Int' + is_published: 'Boolean' + tags: 'Tag' + title: 'String' + type: 'String' + updatedAt: 'Date' + votes_count: 'Int' + } + Tag: { // field return type name + description: 'String' + icon: 'String' + id: 'Int' + isOfficial: 'Boolean' + title: 'String' + } + User: { // field return type name + avatar: 'String' + bio: 'String' + email: 'String' + github: 'String' + id: 'Int' + jobTitle: 'String' + join_date: 'Date' + lightning_address: 'String' + linkedin: 'String' + location: 'String' + name: 'String' + role: 'String' + stories: 'Story' + twitter: 'String' + website: 'String' + } + Vote: { // field return type name + amount_in_sat: 'Int' + id: 'Int' + item_id: 'Int' + item_type: 'VOTE_ITEM_TYPE' + paid: 'Boolean' + payment_hash: 'String' + payment_request: 'String' + } + WalletKey: { // field return type name + key: 'String' + name: 'String' + } + BaseUser: { // field return type name + avatar: 'String' + bio: 'String' + email: 'String' + github: 'String' + id: 'Int' + jobTitle: 'String' + join_date: 'Date' + lightning_address: 'String' + linkedin: 'String' + location: 'String' + name: 'String' + role: 'String' + stories: 'Story' + twitter: 'String' + website: 'String' + } + PostBase: { // field return type name + body: 'String' + createdAt: 'Date' + excerpt: 'String' + id: 'Int' + is_published: 'Boolean' + title: 'String' + updatedAt: 'Date' + votes_count: 'Int' + } +} + +export interface NexusGenArgTypes { + Mutation: { + confirmDonation: { // args + payment_request: string; // String! + preimage: string; // String! + } + confirmVote: { // args + payment_request: string; // String! + preimage: string; // String! + } + createStory: { // args + data?: NexusGenInputs['StoryInputType'] | null; // StoryInputType + } + deleteStory: { // args + id: number; // Int! + } + donate: { // args + amount_in_sat: number; // Int! + } + updateProfileDetails: { // args + data?: NexusGenInputs['ProfileDetailsInput'] | null; // ProfileDetailsInput + } + updateUserPreferences: { // args + userKeys?: NexusGenInputs['UserKeyInputType'][] | null; // [UserKeyInputType!] + } + vote: { // args + amount_in_sat: number; // Int! + item_id: number; // Int! + item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! + } + } + Query: { + allProjects: { // args + skip?: number | null; // Int + take: number | null; // Int + } + getAllHackathons: { // args + sortBy?: string | null; // String + tag?: number | null; // Int + } + getCategory: { // args + id: number; // Int! + } + getFeed: { // args + skip?: number | null; // Int + sortBy?: string | null; // String + tag?: number | null; // Int + take: number | null; // Int + } + getLnurlDetailsForProject: { // args + project_id: number; // Int! + } + getMyDrafts: { // args + type: NexusGenEnums['POST_TYPE']; // POST_TYPE! + } + getPostById: { // args + id: number; // Int! + type: NexusGenEnums['POST_TYPE']; // POST_TYPE! + } + getProject: { // args + id: number; // Int! + } + hottestProjects: { // args + skip?: number | null; // Int + take: number | null; // Int + } + newProjects: { // args + skip?: number | null; // Int + take: number | null; // Int + } + profile: { // args + id: number; // Int! + } + projectsByCategory: { // args + category_id: number; // Int! + skip?: number | null; // Int + take: number | null; // Int + } + searchProjects: { // args + search: string; // String! + skip?: number | null; // Int + take: number | null; // Int + } + } +} + +export interface NexusGenAbstractTypeMembers { + Post: "Bounty" | "Question" | "Story" + BaseUser: "MyProfile" | "User" + PostBase: "Bounty" | "Question" | "Story" +} + +export interface NexusGenTypeInterfaces { + Bounty: "PostBase" + MyProfile: "BaseUser" + Question: "PostBase" + Story: "PostBase" + User: "BaseUser" +} + +export type NexusGenObjectNames = keyof NexusGenObjects; + +export type NexusGenInputNames = keyof NexusGenInputs; + +export type NexusGenEnumNames = keyof NexusGenEnums; + +export type NexusGenInterfaceNames = keyof NexusGenInterfaces; + +export type NexusGenScalarNames = keyof NexusGenScalars; + +export type NexusGenUnionNames = keyof NexusGenUnions; + +export type NexusGenObjectsUsingAbstractStrategyIsTypeOf = never; + +export type NexusGenAbstractsUsingStrategyResolveType = "BaseUser" | "Post" | "PostBase"; + +export type NexusGenFeaturesConfig = { + abstractTypeStrategies: { + isTypeOf: false + resolveType: true + __typename: false + } +} + +export interface NexusGenTypes { + context: any; + inputTypes: NexusGenInputs; + rootTypes: NexusGenRootTypes; + inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars; + argTypes: NexusGenArgTypes; + fieldTypes: NexusGenFieldTypes; + fieldTypeNames: NexusGenFieldTypeNames; + allTypes: NexusGenAllTypes; + typeInterfaces: NexusGenTypeInterfaces; + objectNames: NexusGenObjectNames; + inputNames: NexusGenInputNames; + enumNames: NexusGenEnumNames; + interfaceNames: NexusGenInterfaceNames; + scalarNames: NexusGenScalarNames; + unionNames: NexusGenUnionNames; + allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames']; + allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames']; + allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes'] + abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames']; + abstractTypeMembers: NexusGenAbstractTypeMembers; + objectsUsingAbstractStrategyIsTypeOf: NexusGenObjectsUsingAbstractStrategyIsTypeOf; + abstractsUsingStrategyResolveType: NexusGenAbstractsUsingStrategyResolveType; + features: NexusGenFeaturesConfig; +} + + +declare global { + interface NexusGenPluginTypeConfig { + } + interface NexusGenPluginInputTypeConfig { + } + interface NexusGenPluginFieldConfig { + } + interface NexusGenPluginInputFieldConfig { + } + interface NexusGenPluginSchemaConfig { + } + interface NexusGenPluginArgConfig { + } +} \ No newline at end of file diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index 62ad3be..cd60575 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -10,6 +10,7 @@ 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'; interface Props { @@ -38,11 +39,13 @@ export default function PreferencesTab() { const query = useMyProfilePreferencesQuery({ onCompleted: data => { if (data.me) reset(data.me) - } + }, + notifyOnNetworkStatusChange: true, }); + const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); - if (query.loading) + if (query.networkStatus === NetworkStatus.loading) return if (!query.data?.me) From 9d1a4785c01074cf861c084ab914f714f12e2fcb Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 17 Aug 2022 14:17:34 +0300 Subject: [PATCH 11/57] update: add leave prompt to preferneces page --- .../EditProfilePage/PreferencesTab/PreferencesTab.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index cd60575..d62909f 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -11,6 +11,7 @@ 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'; interface Props { @@ -28,7 +29,7 @@ const schema: yup.SchemaOf = yup.object({ export default function PreferencesTab() { - const { register, formState: { errors, isDirty, }, handleSubmit, reset, control } = useForm({ + const { formState: { isDirty, }, handleSubmit, reset, control } = useForm({ defaultValues: { walletsKeys: [] }, @@ -42,9 +43,11 @@ export default function PreferencesTab() { }, notifyOnNetworkStatusChange: true, }); - const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); + usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) + + if (query.networkStatus === NetworkStatus.loading) return From fd69deeb5c7d9a149923485a12e4b619ef95e0bd Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 17 Aug 2022 14:21:18 +0300 Subject: [PATCH 12/57] update: rename 'linked accounts' to 'linked wallets' --- .../PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx index 1f36d6e..d4d179c 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx @@ -93,7 +93,7 @@ export default function LinkedAccountsCard({ value, onChange }: Props) { return ( -

🔐 Linked Accounts

+

🔐 Linked Wallets

These are the wallets that you can login to this account from. You can add a new wallet below.

From 3ebd12b0298c5773b60a3a75663a55d2f7b48ff2 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 17 Aug 2022 16:36:19 +0300 Subject: [PATCH 13/57] feat: added skeletons to profile tabs, added api mocsk to profile --- .../PostCard/Header/Header.Skeleton.tsx | 3 +- .../pages/EditProfilePage/EditProfilePage.tsx | 6 +++ .../LinkedAccountsCard/LinkedAccountsCard.tsx | 49 ------------------- .../PreferencesTab.Skeleton.tsx | 45 +++++++++++++++++ .../PreferencesTab/PreferencesTab.tsx | 15 ++++-- .../PreferencesTab/profilePreferences.graphql | 2 + .../SaveChangesCard/SaveChangesCard.tsx | 1 - .../UpdateMyProfileTab.Skeleton.tsx | 30 ++++++++++++ .../UpdateMyProfileTab/UpdateMyProfileTab.tsx | 3 +- src/graphql/index.tsx | 6 ++- src/mocks/data/users.ts | 4 +- src/mocks/handlers.ts | 24 +++++++++ src/mocks/resolvers.ts | 11 +++-- 13 files changed, 134 insertions(+), 65 deletions(-) create mode 100644 src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.Skeleton.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.Skeleton.tsx diff --git a/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx b/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx index 9917276..41b4a0e 100644 --- a/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx +++ b/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx @@ -1,5 +1,4 @@ -import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'; -import dayjs from 'dayjs' + import Skeleton from 'react-loading-skeleton'; interface Props { diff --git a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx index 6a9a6a0..870bf14 100644 --- a/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx +++ b/src/features/Profiles/pages/EditProfilePage/EditProfilePage.tsx @@ -29,6 +29,12 @@ export default function EditProfilePage() { const isMediumScreen = useMediaQuery(MEDIA_QUERIES.isMedium); + const user = useAppSelector(state => state.user.me) + + + if (!user) + return + return ( <> diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx index d4d179c..b05d1e3 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx @@ -17,59 +17,10 @@ interface Props { -// function reducer(state: State, action: Action): State { -// switch (action.type) { -// case 'set': -// return { -// hasNewChanges: false, -// keys: [...action.payload], -// oldKeys: [...action.payload], -// } -// case 'delete': -// if (state.keys.length === 1) -// return state; -// return { -// hasNewChanges: true, -// oldKeys: state.oldKeys, -// keys: [...state.keys.slice(0, action.payload.idx), ...state.keys.slice(action.payload.idx + 1)] -// }; -// case 'update': -// return { -// hasNewChanges: true, -// oldKeys: state.oldKeys, -// keys: state.keys.map((item, idx) => { -// if (idx === action.payload.idx) -// return { -// ...item, -// name: action.payload.value -// } -// return item; -// }), - -// } -// case 'cancel': -// return { -// hasNewChanges: false, -// keys: [...state.oldKeys], -// oldKeys: state.oldKeys, -// } -// } -// } - export default function LinkedAccountsCard({ value, onChange }: Props) { const dispatch = useAppDispatch(); const inputsRefs = useRef[]>([]); - // const [keysState, keysDispatch] = useReducer(reducer, { keys: [], oldKeys: [], hasNewChanges: false, }); - - // const [updateKeys, updatingKeysStatus] = useUpdateUserWalletsKeysMutation({ - // onCompleted: data => { - // keysDispatch({ - // type: "set", - // payload: data.updateUserWalletKeys - // }) - // } - // }) const connectNewWallet = () => { dispatch(openModal({ Modal: "LinkingAccountModal" })) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.Skeleton.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.Skeleton.tsx new file mode 100644 index 0000000..2e236a1 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.Skeleton.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import Card from 'src/Components/Card/Card'; +import Skeleton from 'react-loading-skeleton'; + +export default function PreferencesTabSkeleton() { + return ( +
+
+ +

+

+ + +

+ +
+
    + {Array(3).fill(0).map((_, idx) => +
  • +
    + +
    +
  • + )} +
+ +
+
+ +

+

+ + + +

+
+
+
+
+ +
+
+ ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index d62909f..657d75d 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -1,9 +1,8 @@ import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard'; import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard'; import { UpdateUserPreferencesMutationVariables, useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql'; -import LoadingPage from 'src/Components/LoadingPage/LoadingPage'; import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage"; - +import PreferencesTabSkeleton from './PreferencesTab.Skeleton' import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; @@ -12,6 +11,7 @@ import { toast } from 'react-toastify'; import { NotificationsService } from 'src/services'; import { NetworkStatus } from '@apollo/client'; import { usePrompt } from 'src/utils/hooks'; +import { useEffect } from 'react'; interface Props { @@ -45,11 +45,16 @@ export default function PreferencesTab() { }); const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); - usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) + useEffect(() => { + console.log("MOUNTED"); + + }, []) + + // usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) - if (query.networkStatus === NetworkStatus.loading) - return + if (query.loading) + return if (!query.data?.me) return diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql index 6077304..0f23571 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/profilePreferences.graphql @@ -1,5 +1,6 @@ query MyProfilePreferences { me { + id walletsKeys { key name @@ -11,6 +12,7 @@ query MyProfilePreferences { mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) { updateUserPreferences(userKeys: $walletsKeys) { + id walletsKeys { key name diff --git a/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx b/src/features/Profiles/pages/EditProfilePage/SaveChangesCard/SaveChangesCard.tsx index 0011cd5..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' diff --git a/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.Skeleton.tsx b/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.Skeleton.tsx new file mode 100644 index 0000000..51e7e45 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.Skeleton.tsx @@ -0,0 +1,30 @@ +import Card from 'src/Components/Card/Card'; +import Skeleton from 'react-loading-skeleton'; + +export default function UpdateProfileAboutTabSkeleton() { + return ( +
+
+ +
+
+
+
+
+ +
+ +

+

+ +

+
+
+
+
+
+ +
+
+ ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.tsx b/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.tsx index 14dbe90..2af4dbe 100644 --- a/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/UpdateMyProfileTab/UpdateMyProfileTab.tsx @@ -12,6 +12,7 @@ import Card from "src/Components/Card/Card"; import LoadingPage from "src/Components/LoadingPage/LoadingPage"; import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage"; import { setUser } from "src/redux/features/user.slice"; +import UpdateProfileAboutTabSkeleton from "./UpdateMyProfileTab.Skeleton"; interface Props { } @@ -75,7 +76,7 @@ export default function UpdateMyProfileTab() { if (profileQuery.loading) - return + return if (!profileQuery.data?.me) return diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index d6c2f98..f9f95ac 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -573,14 +573,14 @@ export type PostDetailsQuery = { __typename?: 'Query', getPostById: { __typename export type MyProfilePreferencesQueryVariables = Exact<{ [key: string]: never; }>; -export type MyProfilePreferencesQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', nostr_prv_key: string | null, nostr_pub_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } | null }; +export type MyProfilePreferencesQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', id: number, nostr_prv_key: string | null, nostr_pub_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } | null }; export type UpdateUserPreferencesMutationVariables = Exact<{ walletsKeys: InputMaybe | UserKeyInputType>; }>; -export type UpdateUserPreferencesMutation = { __typename?: 'Mutation', updateUserPreferences: { __typename?: 'MyProfile', nostr_pub_key: string | null, nostr_prv_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } }; +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 MyProfileAboutQueryVariables = Exact<{ [key: string]: never; }>; @@ -1383,6 +1383,7 @@ export type PostDetailsQueryResult = Apollo.QueryResult new Promise((res) => setTimeout(res, ms + Math.random() * 1000)) @@ -195,6 +197,7 @@ export const handlers = [ graphql.query('Me', async (req, res, ctx) => { await delay() + console.log("ME"); return res( ctx.data({ @@ -203,6 +206,27 @@ export const handlers = [ ) }), + + graphql.query('MyProfileAbout', async (req, res, ctx) => { + await delay() + return res( + ctx.data({ + me: me(), + }) + ) + }), + + graphql.query('MyProfilePreferences', async (req, res, ctx) => { + await delay() + return res( + ctx.data({ + me: me(), + }) + ) + }), + + + graphql.query('profile', async (req, res, ctx) => { await delay() diff --git a/src/mocks/resolvers.ts b/src/mocks/resolvers.ts index f9802bf..d09f614 100644 --- a/src/mocks/resolvers.ts +++ b/src/mocks/resolvers.ts @@ -1,5 +1,5 @@ import { MOCK_DATA } from "./data"; -import { MyProfile, Query, QueryGetFeedArgs, QueryGetPostByIdArgs } from 'src/graphql' +import { MyProfile, Query, QueryGetFeedArgs, QueryGetPostByIdArgs, User } from 'src/graphql' import { Chance } from "chance"; import { tags } from "./data/tags"; import { hackathons } from "./data/hackathon"; @@ -72,11 +72,16 @@ export function getAllHackathons() { } export function me() { - return MOCK_DATA['user'] as MyProfile + return { + ...MOCK_DATA['user'], + __typename: "MyProfile", + } as MyProfile } + + export function profile() { - return MOCK_DATA['user'] + return { ...MOCK_DATA['user'], __typename: 'User' } as User } export function getMyDrafts(): Query['getMyDrafts'] { From 4f2688f35377ef2d3d688905d73301595fabe2de Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 17 Aug 2022 17:15:01 +0300 Subject: [PATCH 14/57] update: make lnurl QRs clickable --- src/features/Auth/pages/LoginPage/LoginPage.tsx | 12 +++++++----- .../LinkingAccountModal/LinkingAccountModal.tsx | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/features/Auth/pages/LoginPage/LoginPage.tsx b/src/features/Auth/pages/LoginPage/LoginPage.tsx index f3b9ccb..012c9b6 100644 --- a/src/features/Auth/pages/LoginPage/LoginPage.tsx +++ b/src/features/Auth/pages/LoginPage/LoginPage.tsx @@ -157,11 +157,13 @@ export default function LoginPage() {

Login with lightning ⚡

- + + +

Scan this code or copy + paste it to your lightning wallet. Or click to login with your browser's wallet.

diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx index db40eba..019679d 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx @@ -99,11 +99,13 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo else content =
- + + +

Scan this QR code with your other lightning wallet & you will be able to use it to login to this account.
From dba13a3e92303330dd82ed36ac4a3c7d74068478 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Thu, 18 Aug 2022 15:06:52 +0300 Subject: [PATCH 15/57] update: fix refreshing preferences page on key addition bug, add unsaved_changes prompt to prefs tab, udpate QR styling --- public/assets/icons/nut.svg | 21 +++++++++++++++++++ .../Auth/pages/LoginPage/LoginPage.tsx | 18 ++++++++++------ .../LinkingAccountModal.tsx | 11 ++++++++-- .../PreferencesTab/PreferencesTab.tsx | 9 ++------ 4 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 public/assets/icons/nut.svg diff --git a/public/assets/icons/nut.svg b/public/assets/icons/nut.svg new file mode 100644 index 0000000..b7590c0 --- /dev/null +++ b/public/assets/icons/nut.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/features/Auth/pages/LoginPage/LoginPage.tsx b/src/features/Auth/pages/LoginPage/LoginPage.tsx index 012c9b6..8e92475 100644 --- a/src/features/Auth/pages/LoginPage/LoginPage.tsx +++ b/src/features/Auth/pages/LoginPage/LoginPage.tsx @@ -153,21 +153,27 @@ export default function LoginPage() {

else - content =
-

- Login with lightning ⚡ -

- + content =
+ +

Login with lightning ⚡

+

Scan this code or copy + paste it to your lightning wallet. Or click to login with your browser's wallet.

-
+
Click to connect diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx index 019679d..483eb6f 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx @@ -98,12 +98,19 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo else content = -
- +
+

diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index 657d75d..87967b4 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -11,7 +11,6 @@ import { toast } from 'react-toastify'; import { NotificationsService } from 'src/services'; import { NetworkStatus } from '@apollo/client'; import { usePrompt } from 'src/utils/hooks'; -import { useEffect } from 'react'; interface Props { @@ -45,15 +44,11 @@ export default function PreferencesTab() { }); const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); - useEffect(() => { - console.log("MOUNTED"); - }, []) - - // usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) + usePrompt('You may have some unsaved changes. You still want to leave?', isDirty) - if (query.loading) + if (query.networkStatus === NetworkStatus.loading) return if (!query.data?.me) From eb7eeb538226c3d9f86d9b797eaea87298db418f Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Thu, 18 Aug 2022 18:55:24 +0300 Subject: [PATCH 16/57] update: new component design --- public/assets/images/nut_3d.png | Bin 0 -> 9125 bytes src/Components/IconButton/IconButton.tsx | 5 +- .../Auth/pages/LoginPage/LoginPage.tsx | 15 +-- .../LinkedAccountsCard/LinkedAccountsCard.tsx | 45 +++------ .../LinkedAccountsCard/WalletKey.tsx | 94 ++++++++++++++++++ .../LinkingAccountModal.tsx | 25 ++--- .../PreferencesTab/PreferencesTab.tsx | 6 +- 7 files changed, 134 insertions(+), 56 deletions(-) create mode 100644 public/assets/images/nut_3d.png create mode 100644 src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx diff --git a/public/assets/images/nut_3d.png b/public/assets/images/nut_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..955694b66794ede819d84eaacf9c88f5f3e5f9ba GIT binary patch literal 9125 zcmV;WBU;>vP)%$f54`|q+K-taxo?97~h%9-yyUp-|-4$&bx zM2F}Q9isY?CI1g;=FFMR_U_%=v9hxAD4aGaFE6*Ns;cZ7P(?)rcQrUKFDolshr`b& zPo7*R2MK+v0r~jjk6Y~AxwAKzI~-eg0B|I>jyNvBUaG1prLwXL`zm$JJtJf6bLMBEK%n>}#gz{M!D ziRaz>lJP@=*7?Kg#pt>S^ZUBYg zECetw4;?ymR!&aNN3tL3pacZWw*bWXVBYySF2%linDJpiJgG1M(ExCx1XP7VFr>l+ zfycIu<7#ZSjWYgb%b-Dn>@&_d+Wag#JNq@+4|GrgvS!Viyo!pl8!IcT$AU4P zH8Xw2SBO?m8j!Gh(l(>Inp;g!B6#dF+yle~K`|ZI^PFJt-XJK>d3JV=oOar20z32D zf`Wp{`T6;qr2*-n0A%&*)g3A-s{RMQ&(-+we68Pu(%^%kkAqdxP24VWz9vI>(wURpJoq zGIi?I7TdRPpSo+;t}B{0Z8}@_kq$T@OP4Oa0v|XDOe#`LaG7UsEfzyA8`k+3W8%0AEm1LT7b zKIj8qehQfV9jGaf3Dt(J&|qlwWJ*R=NIhvg5d@<;U8|AlR0bGuI+dD;fF7jdeVNkAs>GDVKzkidNB8njT<*MCUblA z=pitKp3TsVH^I>M!}OZGpe{|^puMG3cEOYc(@@(rWV-F_T|iX1ExyD^%&~}fo=7C* zd*A!s+7>NZ^zGNL-+HMB?N>mad+s^rlO9K2=_G(bw^lZ6*dVQ2x0b%&Rxy5&O#p1%?6c1nxQ&62qmB6T%P(c&!i7pIxDga2 z92Ec#;~j^G-Wy5V53Qm!pO!CQ0SVh8{rmTqJiJdVD-c(h_j+|O+*~_0-)qN~Bc(#A zZHhY1#A#1#RgwWUh=iPX-g&2j(J!E8O{0};bVPH!0kFF;1@0WC!~25zvpLgYHrWuo_z*n#E21RgE3El0nK$Z zOfZzUL>1!dax$?=lRSWNm>hM~QPR0{XU|6PHxhwZvShI=U9wDaa&r`*Jm0WkBWz3# zKrEJy9XqPlN6Dk-2ESj{uV1h16oNBXBFm&4K_$~Gd?rClBrNs0JSLEQ?{RP{Xv{;*Vr|Ff=_k! z2pEv7m`HcGLx&F1xl#qQW*P?6* zC5}wrwQHy6E^Bv~@7HcGDVazbj)LvIi>hopByIx$ZP%`yk|s@dUBy7$R&7PwHot|= zN?F<7EHsEmQFN@-f@QrW1bVm>AO8$gK#L?kRDBdSGj+ia&&tYH-V|G85g6MA>ZvCJ z!^U7R@1cEo^UXKa@1*rmNxX*AL)D@UI+;o-Sh;eg{QKYkmM^~eLN+3%*$G2m1@LXC zDZZaf*Zk2A0fR-_HS&ULGY2X3yu8L}AZ?^ipFW{4vujmN4)3j)@@UzD1q+x&)xX~X z`!`Y2P`xDNn37{o0fSyaYsyjpXh2AYAJ4u6y05KT6$;V`ipjlt^@K5RtQa+8#thlC zd6V=g>7f|R2>yfk2!o-|TO$@i{?2`@eI2A`8dTY*l7i|C2y}l*wN?F! zRb^$lFHuafEVpI4mC1tIaKLBztX=wOj`(-OgV(aq4wOw-W>JQs_1sdWM*;hb7)A7L_9WWeQk+3u{jwi#9qfyPvYoa1! zy0^W1AEU~idip8Zwso5VjQ1iDrz$M&2ea(H`|gvubKg;n`_6azN#DMGmEqN@h(>+U zqD35mBAYjDMy#{jQ*A!eEL9iQNgtz|UImPFwHf<3fFvas;OO|pW3s`}K)L&^<~32( zrNOh*aT7yAv}pp5?uUJxfrhlX-hjXcJOqXetZ|0PD1EK=(8me3O4p7!U^G?+nNq-f z6%G3%k36E>(WT3l%6HHDuAFkpDW1Da5Z`|LZF%UShm`uF9T_=tq>=^#z!psac+I?d z@2N62I>sWDKuVsrHF2F;8Z2&zn&rqNk5p2Xo#WKicLc0hP{JSt%8b1gV^he7Frhw; z!Ka_%@P|?#K%lF~Vt0f7=!D_B+q&bx^>D~!x_XSnX)}2KzylAci#BfDAXh*t`t&*0 ztMJa9I{?jaMgsZOfmdr{@UTzZ=x3uE@a0cj-n-RXr&@cc&^^_VAPjy+28*WiTYa*`O4fuv9 z;dOmnCqMv3G5nH8!jWt+3Fm`B32B6u%gCFcJUZo381eOrY2Uy4YU$Clrw1yd?uoZf zlnv`Quy0x}xZnb>wa=S3Pe~OGHj{%)cToM2bc<%>PUdPGLdntf^U3z?S~XnXWa9*E zS|?exdX=dQ zl6&sCM?U%F6990OoI7fi6ciRJ^~2{=?NN16_3cC|UneRFBt0{o2sHcHwaOwe^u)OD zfcoisdG6VS6pW6RS;jqSt`%_|jaV86i9Z7d{-ulnL1cXuKISa#NtxZ84%vYUDuWKa z%og|w3@_c)CQX~jxEsbPb)<*$eEH>ax&8LrmHxl^=9^V6i$FgA{0s8L6HiD!^zQia zHzRK8Doo)wZQ4v;fBkg@1PwCZL*0DD5gni&8!5M25BrI9`!Zut<;OI%JG4`4)~r?U zb=>x3Naa{5Rg%f@RM(rwbnE%b=%Vd0%mpKU<9WbXhR_$FkTil1X73g z9aMg3I)XmZ@X}H_=9pe;z{1X*yEx)Re)qfIsrY2Vgb6C0!iqoe;Da*rg_&~BIcLkL zQRjNG!MpFi%brKovI$@bRAGK|r(dIG%N8mq}2X-rOmF` z{>~MD25Yap{IaqSbXm`WN5%9zRn1#(y(NG8)1Oo-WT>e#1l_c&O#0(umq8Pr_P-?hYKeMe zgE0f~O;=(Y4SSQ#UPZR3QfQ6_lK4wqXI zY+M6oIa0#}BGLKagAY{eMIVVbc!6n;XP59vFJMx>Q5gUVE)_k@@?J zFV2$39(znV2II$%cirhM#1I{s?d9}=mmuPglnyKXt7JgvOR2KX0YwFSThz}$Wpy@o&T*RS7vtDe&Xkepn1#1Tilj5>^AkVyOEc!`>!mQuMQk^xXW zjb5wI**Mp%T_cVSPG4R8eht+4V-Y|K3k&;UcdA(ysE{KLVvgvrRj}yj;m%4?V^SqImbEuQ`7bY)+LT4UChsguIORw~Cd`L^2-eQ%O0ue?${gQ|`C9-!<- zzxicDh75V$YM6N%r!n}XXJ)04gg7t)l3=*FsZ`t6tzXMlbg*e2#)IP6XVwMZd{G1t z+{pBBGK{#sRb!%GLaK2+6-oe&FJlG{Mj)}1iiVu(3=CEA!drj)o9h2SOIOmur`-FS zd(lU+UCurCTm=H{#pW%WDe~{(=L-?1td<5K1Y=FRb?bI!_wL<9=iV{~CC*y}hAGh4 z|M9O+KUF|5smM6tB}mIg^a>6iKHLc`;rYy&^A4k+DSLMBxnatbDV5Sdg!=sy4Wq!3 zjvyEgI1(t=-yNrtB9h`VOaI24TWtjOsXRyC$7yu|K$1!8#`p?^E3F5iG2n94m-hXk z@|4XWQI&E0RkLRKD%C+%MQO&NugR~rpo)Q`lAn zHG@c8bROhGaR0^|v%TMKHv`L_6$Z6KhYnHQ6KY(V>m2bqsPt_fAUL_mtG(8TAu`3= zl{_7~dw}raj&G0Fm2||O!5;@I(0))Q(JgjH_U%$4%X^q8+`M_qDoD~*a1QoJ1Jd;A z(>tKvQ}K;iVVgnHYO3~>h&qb=7Cz^&C2krFy>AZVO*=^&sRs!f+4E|2;_cLcWTW5& zvvsJ^++M}~4^W*wE)LE&Sf16VXT;wdA-`m)m)WX}08kyh|Ni?b-r#fS02H(=P`Rjb z_LD4K%8C6~1B@-wfP@6+kpN;k4vR8TQDKSDW$}CGx&SDA=ggU+C4$7i^fDQPksnTi zHS+!qlP6EEQGg&I8H9o^?qee_*P`uYe8T|w8#{X#J2=23mGP$Jl)0nS*-Cg!DPfL> zkunYcCg@s#@f`XF8gynW5|YKJ^=wqOb7nxqxA-tXW|_WL6W#pFCJqQoj#`x(aA&J z2jhoEQ}ttMKtkTEGaA4=)T))`b?5qwjcfH`K+Jt@<+_Z+ewZrKYKloz_VZ++bBwdW zRO}Pa=V$p0w+~f1SuqcfYcX1FdB+AQtVpx`eAh^4D@mcmaCEvJXR8KbLs}?JD*Nh) z@AF8ptdItxxpU`Uh-b4Or;zbA%In)v=$M?+8J2etN?GyuUyF~$Bcm@(S#<8T<7PVJh^DVGvBxlTSVgY5!VX&w&|~ zjK+-{d6JLLabAIGvF>QsRraVbW zD8rplBL@@SIj`k-sv<dE(O|M=N`?LgO|_|HoW+tXgc~o7~0(rRWuDu2^S5q}$FV-&m&)Wp1{nUI` z0>N5G|07ZxcF)sNfS9c#X{a!Nq=)KHM=pow%-;6y-5W_Xr>Bkvo#h--3b*$ysRtq8 z?2T6W5?bqfjG^#6OG|*RpV|5oF)iJ5gHw2ok?ZtkoU?IGdukfI#!bprTz%D5Qdm$J z;F;#H2ZkG%;<>k>KWLNrUFbMVcGaW(;*E}PM*h>ql_3y{}56F6+LiB{Sa?SEjuuJuYoK zve;9nPF3Z!l^pJBf}vHrw#DMt(+1x!i(AQ#URH%AL8*|yc_O8BSTO+Ta!<4q&L~HY z*JX6h;Wx!-MI05*I0g2k8zR_kV0bn}K8cFvW9y6a670K`?cLi8hB;e*ph#4$>N0a* zFF-T^Y0Av^Jp0VEs&TQC?aZ^zQW=L}y{y=}f=^R_dB+`}#l1cY46N|nuoS^zA|_QG zS02!qeG`eg<(fHxvKW^%e7!%8 zrhY_K#bHTw@yA>`Mk>lH(%2pm!%nY6hhEBV1=^5`FrubM6$U1yin3CxJd;zF;XIfO zs*OFVK&Y>}$`}mZZzKpw|MyeXqF>gC5VCRBWnaTaIfCRyrG*W|N?rVrLr18Z*|p<(6C2 zfIUxLTfS|z5<$mE-JeKIn>1-s+8`MBCn{AO;ocaPj$rFgxXe!^EJ>xp5=KS*j|%gw z9ty*fSd|rio)z=kzNjZn40xyg`Ookm_b6AMIXOQjOBlwSJPYRdaJ&u26RcVWX)u!7 zNWZb5S zH7OQEXc0m({c&A9Bc%YIvz_v)$5tIfIHnz@OP zeGn_oSW5`7sM_tPy&{cC>dPNL#?~jBu_6&7HDvKaDu;dM`T-t z>u#1hk^%$|{fc|F_!lQ)MkGX4!vRQw#w;vRCaEF`>-IF<3@rp>S#?|LB9%(YR8{Fd ze-7Yd7Jmd-wwG8g|wE6PN^P8hi0T665!f z@efFc50X}QwKK(MPF*#Sc3bOAWU@P%M=e_5t`fv<=Qgd|1g35WZ;J;6{QKpQAwyn~ zdXSzmzY-NtqoZ}-uU1{Yv6p~hk5fm^)zLElhyAQ;>S;o3cYLxFFC@KQMXp@bhJ(Q^ ztJci`V*%{Xuce-(09m(g9S5d;!DFf|_GLtjy7-`wB9)S0P^ho*DIUN8)gnUS;j0Zn zdKjn4SOP*R;cT!(A`w%~ltz6=PEOAGrKP3YrJiKI&0Uq*R;hu#@G>IImY`vdH}Ddw ze6TFlv3hd~CPycmW=@&dk8mR+b2%QsneHaFN`^j$RE;kwDOoA|KnbH#FX>ePxZ|=% zbp6~PBeo%|vV7?YONSpY`sd;r$5hp_L;L!Bw6#f6oW$$lOW{UnS@AYgt(lTcK7=TK zpGVJlfB@aE#2r1SnWt6~-M!nF5(!mGBsSQKRatdVM{bD=LUg&kfa*$lf0TlaDp;<` z?N*kkjkF@q@ueg29=x+j(g5TEvSP)GZTP4KI;BS9N3u_xpqRSHv=?GoAvF~J*pOB; zev?=-d7F;UO7}~8N(Dls0`4{RsPq~v`u^~9LfaNxe^IASo$75jObT191rp;pvg?(@ zc68snn3Z&nLgE~LjTP9(NtI82FqjF2B_{CJR6w}DnJ?j09K>g?ofTBYv2WiCDH&c+ zQ1F#B5QPDm;nqaVijtUQH1Us zu(K&DQBhUpt1P({>a9WlCshCtxW9CLH-~EN9Wrp@tS7D^=-8HEm5uN7%40Q!q%MnC z5-&guyJTh+qaKfYbM29j3o=zzWZf()Xwdz4se0X?pWpI6X<$l3>u0}Tn~O>Bi?e9C zW(h~N+q%|~4i>&X-E&QH-BsmTg}o?$A6ChU*U)=7 zLG}YB!v$z5&9OV(D+n>gR7C`awuLiponX>QQ|tZ$Z@esz17&)j6*^A_#tA1v<7fAl z?Ntj7FyBOq>wl=ti;5%mbLgHv^ZGb%5r?0m-WRuT-#()y8tO`sdFosg8QIOYsNm%Q z;3(Y!&HsX?|8q^1yK*SgE$g_TG1M7HUNAe#C1G0@6u_Tv!ttt{qUC;(_s1i@@!JYy zszdz`TXcR&t>egr(cqF&*X#d3VopNUj0-oMbVSL}%WJ5br3J%UyKC33GuyUpyHfTO zrL6Vf!GoKD+5d%)n~c*Qsjkx#a;nub-~Q5iOw74Hy3T$o4<--kY)*OOJUhLq1(MO~ zId6p5>T>FJ0?KLCY{d-LI5mubaO3mTI$4OQ<0K{Up)mlljKfw*M$HJFGpB;_R|1?1EIom{mWS53Ql}N`5a?$G5tCz1?vu1ixQPE#)ySf5QJrb47PZccbAd5^VlN+P8Q!?@z=c!dd9UJ3c z=hzl)GyM4*dWAX%s9YsAJmwNj8brN_avT_`q=Kb-QGWmWlq#zdjqy4(ikWf%C?h~L z!L!)1di9#w5c5Z~6A7-!vj$zbTo#)1+pV+!v4hK%43o)Rc1cfZP#%vfHTt@2PjVbU zoHes@RhgcCpr;*#!AJ#&o)xPj@^tnCVBCspAC?0|^;&9t(V|7jrN}IIlao$5sYS)! zy{CdRTw|*@wq9}fSs{b&FfG_7%H98{ptAzHv&dXiCA`R`Y1mDQGwv*NK+W;$kZKR0 zCqu3MN)8}p=8DvjU~{_TgZOXq&cW%)*bWQFC4oxB>unD}+6ui6FjtaeFCLA3W2(U1 z9JLCBcC};eh-*9abLj=@%p?#PRI3RH}eE9Izh-C)hkZU~-05kaC1Db@HWmy^^_dMax zRMPPd0fIN^Nlad=%f94uh`#}eneV{1*| void; onKeyDown?: (v: any) => void href?: string; - children: JSX.Element className?: string size?: "sm" | 'md' | 'lg' variant?: 'blank' | 'fill' @@ -26,7 +25,7 @@ const baseBtnStyles: UnionToObjectKeys = { blank: "bg-gray-900 bg-opacity-0 hover:bg-opacity-5 active:bg-opacity-10 active:scale-95 !border-0" } -const IconButton = React.forwardRef(({ +const IconButton = React.forwardRef>(({ href, size = "md", className = "", diff --git a/src/features/Auth/pages/LoginPage/LoginPage.tsx b/src/features/Auth/pages/LoginPage/LoginPage.tsx index 8e92475..3407622 100644 --- a/src/features/Auth/pages/LoginPage/LoginPage.tsx +++ b/src/features/Auth/pages/LoginPage/LoginPage.tsx @@ -156,17 +156,18 @@ export default function LoginPage() { content =

Login with lightning ⚡

- + diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx index b05d1e3..6f38f33 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx @@ -3,16 +3,14 @@ import { useAppDispatch } from 'src/utils/hooks'; import { openModal } from 'src/redux/features/modals.slice'; import Card from 'src/Components/Card/Card'; import { MyProfile } from 'src/graphql'; -import Skeleton from 'react-loading-skeleton'; -import { useReducer, useRef } from 'react'; -import { Nullable } from 'remirror'; +import WalletKey from './WalletKey'; -type Value = MyProfile['walletsKeys'] +export type WalletKeyType = MyProfile['walletsKeys'][number] interface Props { - value: Value, - onChange: (newValue: Value) => void + value: WalletKeyType[], + onChange: (newValue: WalletKeyType[]) => void } @@ -20,7 +18,6 @@ interface Props { export default function LinkedAccountsCard({ value, onChange }: Props) { const dispatch = useAppDispatch(); - const inputsRefs = useRef[]>([]); const connectNewWallet = () => { dispatch(openModal({ Modal: "LinkingAccountModal" })) @@ -46,27 +43,18 @@ export default function LinkedAccountsCard({ value, onChange }: Props) {

🔐 Linked Wallets

- These are the wallets that you can login to this account from. You can add a new wallet below. + These are the wallets that you can login to this account from. You can add up to 3 wallets.

    {value.map((item, idx) => -
  • - inputsRefs.current[idx] = el} - type="text" - value={item.name} - onChange={e => { - updateKeyName(idx, e.target.value) - }} - className='p-0 border-0 focus:border-0 focus:outline-none grow - focus:ring-0 placeholder:!text-gray-400' /> - -
    - - {value.length > 1 && } -
    -
  • + 1} + onRename={v => updateKeyName(idx, v)} + onDelete={() => deleteKey(idx)} + /> )}
{/*
@@ -88,12 +76,11 @@ export default function LinkedAccountsCard({ value, onChange }: Props) { Save Changes
*/} - {value.length < 3 && - } -
+ {value.length < 3 && + }
) } diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx new file mode 100644 index 0000000..63990a5 --- /dev/null +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx @@ -0,0 +1,94 @@ +import { useToggle } from '@react-hookz/web'; +import { createAction } from '@reduxjs/toolkit'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { FiTrash2 } from 'react-icons/fi'; +import Button from 'src/Components/Button/Button'; +import IconButton from 'src/Components/IconButton/IconButton'; +import { useReduxEffect } from 'src/utils/hooks/useReduxEffect'; +import { WalletKeyType } from './LinkedAccountsCard' +import { useAppDispatch } from "src/utils/hooks"; +import { openModal } from "src/redux/features/modals.slice"; + +interface Props { + walletKey: WalletKeyType, + canDelete: boolean; + onRename: (newName: string) => void + onDelete: () => void +} + + + +export default function WalletKey({ walletKey, canDelete, onRename, onDelete }: Props) { + + const ref = useRef(null!); + const [name, setName] = useState(walletKey.name); + const [editMode, toggleEditMode] = useToggle(false); + const dispatch = useAppDispatch(); + + + const CONFIRM_DELETE_WALLET = useMemo(() => createAction<{ confirmed?: boolean }>(`CONFIRM_DELETE_WALLET_${walletKey.key.slice(0, 10)}`)({}), [walletKey.key]) + + const saveNameChanges = () => { + toggleEditMode(); + onRename(name); + } + + const onConfirmDelete = useCallback(({ payload: { confirmed } }: typeof CONFIRM_DELETE_WALLET) => { + if (confirmed) + onDelete() + }, [onDelete]) + + useReduxEffect(onConfirmDelete, CONFIRM_DELETE_WALLET.type); + + useEffect(() => { + if (editMode) + ref.current.focus() + }, [editMode]) + + const handleDelete = () => { + dispatch(openModal({ + Modal: "ConfirmModal", + props: { + callbackAction: { + type: CONFIRM_DELETE_WALLET.type, + payload: {} + }, + actionName: "Remove", + title: "Remove key", + message: "Are you sure you want to remove this key from your account? Once deleted, you won’t be able to recover it.", + color: "red" + } + })) + } + + return ( +
  • +
    + 🔑 + setName(e.target.value)} + /> + {!editMode && } + {editMode && + } +
    + {canDelete && handleDelete()} + > } +
  • + ) +} diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx index 483eb6f..bfeb522 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal/LinkingAccountModal.tsx @@ -99,29 +99,24 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo else content =
    - +

    - Scan this QR code with your other lightning wallet & you will be able to use it to login to this account. -
    - When done, click the button below to close this modal. + Scan this code or copy + paste it to your lightning wallet to connect another account to your maker profile. You can also click the QR code to open your WebLN wallet. When done, click the button below to close this modal.

    - {/* Click to connect */}
    @@ -147,10 +142,10 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo initial='initial' animate="animate" exit='exit' - className="modal-card max-w-[364px] p-24 rounded-xl relative" + className="modal-card max-w-[442px] p-24 rounded-xl relative" > -

    Link new ⚡ wallet

    +

    Connect another âšĄī¸ wallet

    {content} ) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index 87967b4..1261e7f 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -33,7 +33,6 @@ export default function PreferencesTab() { walletsKeys: [] }, resolver: yupResolver(schema), - mode: 'onBlur', }); const query = useMyProfilePreferencesQuery({ @@ -86,7 +85,10 @@ export default function PreferencesTab() { control={control} name="walletsKeys" render={({ field: { onChange, value } }) => ( - + { + onChange(v); + handleSubmit(onSubmit)(); + }} /> )} /> From b929600d1f1d556acd7b4d9573fb81abefc012fd Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Thu, 18 Aug 2022 19:01:47 +0300 Subject: [PATCH 17/57] fix: hide the "save-changes" card on the prefernces profile tab --- .../pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx index 1261e7f..ac7ba73 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/PreferencesTab.tsx @@ -94,12 +94,12 @@ export default function PreferencesTab() {
    - reset()} - /> + /> */}
    ) From 1a73b403ae1beac46d069a74d61f0df289c5329a Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Fri, 19 Aug 2022 09:34:38 +0300 Subject: [PATCH 18/57] feat: built remove_key modal --- api/functions/graphql/types/users.js | 5 +- src/Components/Button/Button.tsx | 2 +- .../LinkedAccountsCard/WalletKey.tsx | 10 ++-- .../RemoveWalletKeyModal.tsx | 48 +++++++++++++++++++ .../RemoveWalletKeyModal/index.ts | 3 ++ src/redux/features/modals.slice.ts | 11 ++++- 6 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/RemoveWalletKeyModal.tsx create mode 100644 src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal/index.ts diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index 81f1231..1d796c1 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -56,8 +56,9 @@ const MyProfile = objectType({ t.nonNull.list.nonNull.field('walletsKeys', { type: "WalletKey", - resolve: (parent) => { - return prisma.user.findUnique({ where: { id: parent.id } }).userKeys(); + resolve: async (parent) => { + const keys = await prisma.user.findUnique({ where: { id: parent.id } }).userKeys(); + return keys.map(k => ({ ...k, key: k.key.slice(0, 10) + '...' })) } }); } diff --git a/src/Components/Button/Button.tsx b/src/Components/Button/Button.tsx index 2c58f20..8d4c378 100644 --- a/src/Components/Button/Button.tsx +++ b/src/Components/Button/Button.tsx @@ -25,7 +25,7 @@ const btnStylesFill: UnionToObjectKeys = { gray: 'bg-gray-100 hover:bg-gray-200 text-gray-900 active:bg-gray-300', white: 'border border-gray-300 text-gray-900 bg-gray-25 hover:bg-gray-50', black: 'text-white bg-black hover:bg-gray-900', - red: "bg-red-600 hover:bg-red-500 active:bg-red-700 text-white", + red: "bg-red-500 hover:bg-red-600 active:bg-red-700 text-white", } const loadingColor: UnionToObjectKeys = { diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx index 63990a5..de2f77d 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/WalletKey.tsx @@ -47,16 +47,12 @@ export default function WalletKey({ walletKey, canDelete, onRename, onDelete }: const handleDelete = () => { dispatch(openModal({ - Modal: "ConfirmModal", + Modal: "RemoveWalletKeyModal", props: { callbackAction: { type: CONFIRM_DELETE_WALLET.type, - payload: {} - }, - actionName: "Remove", - title: "Remove key", - message: "Are you sure you want to remove this key from your account? Once deleted, you won’t be able to recover it.", - color: "red" + payload: { confirmed: false } + } } })) } 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/redux/features/modals.slice.ts b/src/redux/features/modals.slice.ts index ed94a76..c718a4f 100644 --- a/src/redux/features/modals.slice.ts +++ b/src/redux/features/modals.slice.ts @@ -9,6 +9,7 @@ import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatur import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer"; import { ConfirmModal } from "src/Components/Modals/ConfirmModal"; import { LinkingAccountModal } from "src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkingAccountModal"; +import { RemoveWalletKeyModal } from "src/features/Profiles/pages/EditProfilePage/PreferencesTab/RemoveWalletKeyModal"; import { ComponentProps } from "react"; import { generateId } from "src/utils/helperFunctions"; @@ -24,19 +25,27 @@ export enum Direction { export const ALL_MODALS = { + //Projects ProjectDetailsCard, + + // Auth Login_ScanningWalletCard, Login_NativeWalletCard, Login_SuccessCard, Login_ExternalWalletCard, - VoteCard, Claim_GenerateSignatureCard, Claim_CopySignatureCard, Claim_SubmittedCard, Claim_FundWithdrawCard, + + // Misc ConfirmModal, + VoteCard, NoWeblnModal, + + // User Wallets Keys LinkingAccountModal, + RemoveWalletKeyModal, // Text Editor Modals InsertImageModal, From be06f553d2c8407aac00e1b062b61024b1980cb5 Mon Sep 17 00:00:00 2001 From: Mohammed Taher Ghazal <50504183+MTG2000@users.noreply.github.com> Date: Fri, 19 Aug 2022 19:06:10 +0300 Subject: [PATCH 19/57] Fix: return key --- api/functions/graphql/types/users.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index 1d796c1..b633464 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -56,9 +56,9 @@ const MyProfile = objectType({ t.nonNull.list.nonNull.field('walletsKeys', { type: "WalletKey", - resolve: async (parent) => { - const keys = await prisma.user.findUnique({ where: { id: parent.id } }).userKeys(); - return keys.map(k => ({ ...k, key: k.key.slice(0, 10) + '...' })) + resolve: (parent) => { + return prisma.user.findUnique({ where: { id: parent.id } }).userKeys(); + } }); } @@ -238,4 +238,4 @@ module.exports = { // Mutations updateProfileDetails, updateUserPreferences, -} \ No newline at end of file +} From b8e6437a1e323f492a47f50dd3f51ab39cf695b7 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Fri, 19 Aug 2022 20:29:28 +0300 Subject: [PATCH 20/57] feat: use old account for same wallet --- api/functions/login/login.js | 50 ++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/api/functions/login/login.js b/api/functions/login/login.js index 0475d89..2a30585 100644 --- a/api/functions/login/login.js +++ b/api/functions/login/login.js @@ -65,26 +65,44 @@ const loginHandler = async (req, res) => { const user = await getUserByPubKey(key) if (user === null) { - const nostr_prv_key = generatePrivateKey(); - const nostr_pub_key = getPublicKey(nostr_prv_key); + // Check if user had a previous account using this wallet - const createdUser = await prisma.user.create({ - data: { - pubKey: key, - name: key, - avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`, - nostr_prv_key, - nostr_pub_key, - }, - }) - await prisma.userKey.create({ - data: { - key, - name: "My original wallet key", - user_id: createdUser.id, + const oldAccount = await prisma.user.findFirst({ + where: { + pubKey: key } }); + if (oldAccount) { + await prisma.userKey.create({ + data: { + key, + name: "My original wallet key", + user_id: oldAccount.id, + } + }); + } else { + const nostr_prv_key = generatePrivateKey(); + const nostr_pub_key = getPublicKey(nostr_prv_key); + + const createdUser = await prisma.user.create({ + data: { + pubKey: key, + name: key, + avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`, + nostr_prv_key, + nostr_pub_key, + }, + }) + await prisma.userKey.create({ + data: { + key, + name: "My original wallet key", + user_id: createdUser.id, + } + }); + } + } // calc the hash of k1 From 80b7053f88825cb0685eb59205e2d08a596749a2 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Sun, 21 Aug 2022 13:44:35 +0300 Subject: [PATCH 21/57] feat: profile roles, skills, tournaments, similar makers cards/stories/schemas --- api/functions/graphql/nexus-typegen.ts | 92 ++++++++++++++ api/functions/graphql/schema.graphql | 45 +++++++ api/functions/graphql/types/tournaments.js | 55 +++++++++ api/functions/graphql/types/users.js | 91 +++++++++++++- .../Components/TrendingCard/TrendingCard.tsx | 2 +- .../CommentsSettingsCard.stories.tsx | 5 +- .../LinkedAccountsCard.stories.tsx | 6 +- .../pages/ProfilePage/AboutCard/AboutCard.tsx | 44 ++++--- .../pages/ProfilePage/ProfilePage.tsx | 46 +++++-- .../RolesCard/RolesCard.stories.tsx | 31 +++++ .../pages/ProfilePage/RolesCard/RolesCard.tsx | 58 +++++++++ .../SimilarMakersCard.stories.tsx | 22 ++++ .../SimilarMakersCard/SimilarMakersCard.tsx | 38 ++++++ .../SkillsCard/SkillsCard.stories.tsx | 31 +++++ .../ProfilePage/SkillsCard/SkillsCard.tsx | 29 +++++ .../TournamentsCard.stories.tsx | 31 +++++ .../TournamentsCard/TournamentsCard.tsx | 45 +++++++ .../pages/ProfilePage/profile.graphql | 23 ++++ .../pages/ProfilePage/styles.module.scss | 9 ++ src/graphql/index.tsx | 78 +++++++++++- src/mocks/data/users.ts | 113 +++++++++++++++++- 21 files changed, 856 insertions(+), 38 deletions(-) create mode 100644 api/functions/graphql/types/tournaments.js create mode 100644 src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.stories.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.stories.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.stories.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.stories.tsx create mode 100644 src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.tsx diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index bd8c67e..4c8413f 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -57,6 +57,7 @@ export interface NexusGenInputs { export interface NexusGenEnums { POST_TYPE: "Bounty" | "Question" | "Story" + RoleLevelEnum: 3 | 0 | 1 | 2 | 4 VOTE_ITEM_TYPE: "Bounty" | "PostComment" | "Project" | "Question" | "Story" | "User" } @@ -208,6 +209,16 @@ export interface NexusGenObjects { isOfficial?: boolean | null; // Boolean title: string; // String! } + Tournament: { // root type + cover_image: string; // String! + description: string; // String! + end_date: NexusGenScalars['Date']; // Date! + id: number; // Int! + start_date: NexusGenScalars['Date']; // Date! + thumbnail_image: string; // String! + title: string; // String! + website: string; // String! + } User: { // root type avatar: string; // String! bio?: string | null; // String @@ -224,6 +235,16 @@ 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! @@ -357,7 +378,11 @@ export interface NexusGenFieldTypes { nostr_prv_key: string | null; // String nostr_pub_key: string | null; // String role: string | null; // String + roles: NexusGenRootTypes['UserRole'][]; // [UserRole!]! + similar_makers: NexusGenRootTypes['User'][]; // [User!]! + skills: NexusGenRootTypes['UserSkill'][]; // [UserSkill!]! stories: NexusGenRootTypes['Story'][]; // [Story!]! + tournaments: NexusGenRootTypes['Tournament'][]; // [Tournament!]! twitter: string | null; // String walletsKeys: NexusGenRootTypes['WalletKey'][]; // [WalletKey!]! website: string | null; // String @@ -405,6 +430,7 @@ export interface NexusGenFieldTypes { profile: NexusGenRootTypes['User'] | null; // User projectsByCategory: NexusGenRootTypes['Project'][]; // [Project!]! searchProjects: NexusGenRootTypes['Project'][]; // [Project!]! + similarMakers: NexusGenRootTypes['User'][]; // [User!]! } Question: { // field return type author: NexusGenRootTypes['Author']; // Author! @@ -442,6 +468,17 @@ export interface NexusGenFieldTypes { isOfficial: boolean | null; // Boolean title: string; // String! } + Tournament: { // field return type + cover_image: string; // String! + description: string; // String! + end_date: NexusGenScalars['Date']; // Date! + id: number; // Int! + start_date: NexusGenScalars['Date']; // Date! + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! + thumbnail_image: string; // String! + title: string; // String! + website: string; // String! + } User: { // field return type avatar: string; // String! bio: string | null; // String @@ -455,10 +492,24 @@ export interface NexusGenFieldTypes { location: string | null; // String name: string; // String! role: string | null; // String + roles: NexusGenRootTypes['UserRole'][]; // [UserRole!]! + similar_makers: NexusGenRootTypes['User'][]; // [User!]! + skills: NexusGenRootTypes['UserSkill'][]; // [UserSkill!]! 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! @@ -485,7 +536,11 @@ export interface NexusGenFieldTypes { location: string | null; // String name: string; // String! role: string | null; // String + roles: NexusGenRootTypes['UserRole'][]; // [UserRole!]! + similar_makers: NexusGenRootTypes['User'][]; // [User!]! + skills: NexusGenRootTypes['UserSkill'][]; // [UserSkill!]! stories: NexusGenRootTypes['Story'][]; // [Story!]! + tournaments: NexusGenRootTypes['Tournament'][]; // [Tournament!]! twitter: string | null; // String website: string | null; // String } @@ -606,7 +661,11 @@ export interface NexusGenFieldTypeNames { nostr_prv_key: 'String' nostr_pub_key: 'String' role: 'String' + roles: 'UserRole' + similar_makers: 'User' + skills: 'UserSkill' stories: 'Story' + tournaments: 'Tournament' twitter: 'String' walletsKeys: 'WalletKey' website: 'String' @@ -654,6 +713,7 @@ export interface NexusGenFieldTypeNames { profile: 'User' projectsByCategory: 'Project' searchProjects: 'Project' + similarMakers: 'User' } Question: { // field return type name author: 'Author' @@ -691,6 +751,17 @@ export interface NexusGenFieldTypeNames { isOfficial: 'Boolean' title: 'String' } + Tournament: { // field return type name + cover_image: 'String' + description: 'String' + end_date: 'Date' + id: 'Int' + start_date: 'Date' + tags: 'Tag' + thumbnail_image: 'String' + title: 'String' + website: 'String' + } User: { // field return type name avatar: 'String' bio: 'String' @@ -704,10 +775,24 @@ export interface NexusGenFieldTypeNames { location: 'String' name: 'String' role: 'String' + roles: 'UserRole' + similar_makers: 'User' + skills: 'UserSkill' 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' @@ -734,7 +819,11 @@ export interface NexusGenFieldTypeNames { location: 'String' name: 'String' role: 'String' + roles: 'UserRole' + similar_makers: 'User' + skills: 'UserSkill' stories: 'Story' + tournaments: 'Tournament' twitter: 'String' website: 'String' } @@ -833,6 +922,9 @@ export interface NexusGenArgTypes { skip?: number | null; // Int take: number | null; // Int } + similarMakers: { // args + id: number; // Int! + } } } diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index 8a3947c..f6f89f6 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -31,7 +31,11 @@ interface BaseUser { location: String name: String! role: String + roles: [UserRole!]! + similar_makers: [User!]! + skills: [UserSkill!]! stories: [Story!]! + tournaments: [Tournament!]! twitter: String website: String } @@ -137,7 +141,11 @@ type MyProfile implements BaseUser { nostr_prv_key: String nostr_pub_key: String role: String + roles: [UserRole!]! + similar_makers: [User!]! + skills: [UserSkill!]! stories: [Story!]! + tournaments: [Tournament!]! twitter: String walletsKeys: [WalletKey!]! website: String @@ -221,6 +229,7 @@ type Query { profile(id: Int!): User projectsByCategory(category_id: Int!, skip: Int = 0, take: Int = 10): [Project!]! searchProjects(search: String!, skip: Int = 0, take: Int = 50): [Project!]! + similarMakers(id: Int!): [User!]! } type Question implements PostBase { @@ -237,6 +246,14 @@ type Question implements PostBase { votes_count: Int! } +enum RoleLevelEnum { + Advanced + Beginner + Hobbyist + Intermediate + Pro +} + type Story implements PostBase { author: Author! body: String! @@ -271,6 +288,18 @@ type Tag { title: String! } +type Tournament { + cover_image: String! + description: String! + end_date: Date! + id: Int! + start_date: Date! + tags: [Tag!]! + thumbnail_image: String! + title: String! + website: String! +} + type User implements BaseUser { avatar: String! bio: String @@ -284,7 +313,11 @@ type User implements BaseUser { location: String name: String! role: String + roles: [UserRole!]! + similar_makers: [User!]! + skills: [UserSkill!]! stories: [Story!]! + tournaments: [Tournament!]! twitter: String website: String } @@ -294,6 +327,18 @@ 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/tournaments.js b/api/functions/graphql/types/tournaments.js new file mode 100644 index 0000000..9f04081 --- /dev/null +++ b/api/functions/graphql/types/tournaments.js @@ -0,0 +1,55 @@ +const { + intArg, + objectType, + stringArg, + extendType, + nonNull, +} = require('nexus'); +const { prisma } = require('../../../prisma'); + + + +const Tournament = objectType({ + name: 'Tournament', + definition(t) { + t.nonNull.int('id'); + t.nonNull.string('title'); + t.nonNull.string('description'); + t.nonNull.string('thumbnail_image'); + t.nonNull.string('cover_image'); + t.nonNull.date('start_date'); + t.nonNull.date('end_date'); + t.nonNull.string('website'); + t.nonNull.list.nonNull.field('tags', { + type: "Tag", + resolve: (parent) => { + // return prisma.hackathon.findUnique({ where: { id: parent.id } }).tags(); + return []; + } + }); + } +}) + +const getAllTournaments = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('getAllTournaments', { + type: Tournament, + args: { + sortBy: stringArg(), + tag: intArg(), + }, + resolve(_, args) { + const { sortBy, tag } = args; + return []; + } + }) + } +}) + +module.exports = { + // Types + Tournament, + // Queries + getAllTournaments, +} \ No newline at end of file diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index b633464..3d4d06b 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -1,8 +1,9 @@ const { prisma } = require('../../../prisma'); -const { objectType, extendType, intArg, nonNull, inputObjectType, interfaceType, list } = require("nexus"); +const { objectType, extendType, intArg, nonNull, inputObjectType, interfaceType, list, enumType } = require("nexus"); const { getUserByPubKey } = require("../../../auth/utils/helperFuncs"); const { removeNulls } = require("./helpers"); +const { Tournament } = require('./tournaments'); @@ -24,6 +25,39 @@ const BaseUser = interfaceType({ t.string('linkedin') t.string('bio') t.string('location') + t.nonNull.list.nonNull.field('roles', { + type: UserRole, + resolve: (parent) => { + return [] + } + }) + t.nonNull.list.nonNull.field('skills', { + type: UserSkill, + resolve: (parent) => { + return [] + } + }) + t.nonNull.list.nonNull.field('tournaments', { + type: Tournament, + resolve: (parent) => { + return [] + } + }) + t.nonNull.list.nonNull.field('similar_makers', { + type: "User", + resolve(parent,) { + return prisma.user.findMany({ + where: { + AND: { + id: { + not: parent.id + } + } + }, + take: 3, + }) + } + }) t.nonNull.list.nonNull.field('stories', { @@ -40,6 +74,35 @@ const BaseUser = interfaceType({ }, }) +const RoleLevelEnum = enumType({ + name: 'RoleLevelEnum', + members: { + Beginner: 0, + Hobbyist: 1, + Intermediate: 2, + Advanced: 3, + Pro: 4, + }, +}); + +const UserRole = objectType({ + name: 'UserRole', + definition(t) { + t.nonNull.int('id'); + t.nonNull.string('title'); + t.nonNull.string('icon'); + t.nonNull.field('level', { type: RoleLevelEnum }) + } +}) + +const UserSkill = objectType({ + name: 'UserSkill', + definition(t) { + t.nonNull.int('id'); + t.nonNull.string('title'); + } +}) + const User = objectType({ name: 'User', definition(t) { @@ -93,6 +156,30 @@ const profile = extendType({ } }) +const similarMakers = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('similarMakers', { + type: "User", + args: { + id: nonNull(intArg()) + }, + async resolve(parent, { id }, ctx) { + return prisma.user.findMany({ + where: { + AND: { + id: { + not: id + } + } + }, + take: 3, + }) + } + }) + } +}) + const ProfileDetailsInput = inputObjectType({ name: 'ProfileDetailsInput', definition(t) { @@ -228,6 +315,7 @@ const updateUserPreferences = extendType({ module.exports = { // Types + BaseUser, User, MyProfile, @@ -235,6 +323,7 @@ module.exports = { // Queries me, profile, + similarMakers, // Mutations updateProfileDetails, updateUserPreferences, diff --git a/src/features/Posts/Components/TrendingCard/TrendingCard.tsx b/src/features/Posts/Components/TrendingCard/TrendingCard.tsx index 99f5cfd..8404bf5 100644 --- a/src/features/Posts/Components/TrendingCard/TrendingCard.tsx +++ b/src/features/Posts/Components/TrendingCard/TrendingCard.tsx @@ -14,7 +14,7 @@ export default function TrendingCard() { return ( -

    Trending on BOLT.FUN

    +

    Trending on BOLT🔩FUN

      { trendingPosts.loading ? diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/CommentsSettingsCard/CommentsSettingsCard.stories.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/CommentsSettingsCard/CommentsSettingsCard.stories.tsx index d06fa34..1f0538f 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/CommentsSettingsCard/CommentsSettingsCard.stories.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/CommentsSettingsCard/CommentsSettingsCard.stories.tsx @@ -3,7 +3,7 @@ import { MOCK_DATA } from 'src/mocks/data'; import CommentsSettingsCard from './CommentsSettingsCard'; export default { - title: 'Profiles/Profile Page/Comments Settings Card', + title: 'Profiles/Edit Profile Page/Comments Settings Card', component: CommentsSettingsCard, argTypes: { backgroundColor: { control: 'color' }, @@ -16,5 +16,6 @@ const Template: ComponentStory = (args) => = (args) => { } } diff --git a/src/features/Profiles/pages/ProfilePage/AboutCard/AboutCard.tsx b/src/features/Profiles/pages/ProfilePage/AboutCard/AboutCard.tsx index 8be108c..c995476 100644 --- a/src/features/Profiles/pages/ProfilePage/AboutCard/AboutCard.tsx +++ b/src/features/Profiles/pages/ProfilePage/AboutCard/AboutCard.tsx @@ -29,24 +29,28 @@ export default function AboutCard({ user, isOwner }: Props) { hasValue: user.website, text: user.website?.replace(/(^\w+:|^)\/\//, '').replace(/\/$/, ""), icon: FiGlobe, + colors: "bg-gray-100 text-gray-900", url: user.website && withHttp(user.website) }, { hasValue: user.twitter, text: user.twitter, icon: FiTwitter, + colors: "bg-blue-100 text-blue-500", url: `https://twitter.com/@${user.twitter}` }, { hasValue: user.github, text: user.github, icon: FiGithub, + colors: "bg-pink-100 text-pink-600", url: `https://github.com/${user.github}` }, { hasValue: user.linkedin, text: "LinkedIn", icon: FiLinkedin, + colors: "bg-sky-100 text-cyan-600", url: user.linkedin && withHttp(user.linkedin), } ]; @@ -65,37 +69,41 @@ export default function AboutCard({ user, isOwner }: Props) {
    -

    - {user.name} -

    +
    +

    + {user.name} +

    - {links.some(link => link.hasValue) &&
    + {

    + {user.jobTitle ? user.jobTitle : "No job title added"} +

    } +
    + {
    {links.filter(link => link.hasValue || isOwner).map((link, idx) => link.hasValue ? - {link.text} - : -

    - --- -

    )} + + + : + (isOwner && +

    + --- +

    ))}
    } - {(user.jobTitle || isOwner) &&

    - {user.jobTitle ? user.jobTitle : "No job title added"} -

    } - {(user.lightning_address || isOwner) &&

    + {

    {user.lightning_address ? `⚡${user.lightning_address}` : "⚡ No lightning address"}

    } - {(user.bio || isOwner) &&

    + {

    {user.bio ? user.bio : "No bio added"}

    }
    diff --git a/src/features/Profiles/pages/ProfilePage/ProfilePage.tsx b/src/features/Profiles/pages/ProfilePage/ProfilePage.tsx index 7cdd415..2f01217 100644 --- a/src/features/Profiles/pages/ProfilePage/ProfilePage.tsx +++ b/src/features/Profiles/pages/ProfilePage/ProfilePage.tsx @@ -4,9 +4,15 @@ import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage" import { useProfileQuery } from "src/graphql" import AboutCard from "./AboutCard/AboutCard" import { Helmet } from 'react-helmet' -import { useAppSelector } from 'src/utils/hooks'; +import { useAppSelector, useMediaQuery } from 'src/utils/hooks'; import styles from './styles.module.scss' import StoriesCard from "./StoriesCard/StoriesCard" +import RolesCard from "./RolesCard/RolesCard" +import SkillsCard from "./SkillsCard/SkillsCard" +import TournamentsCard from "./TournamentsCard/TournamentsCard" +import { MEDIA_QUERIES } from "src/utils/theme" +import TrendingCard from "src/features/Posts/Components/TrendingCard/TrendingCard" +import SimilarMakersCard from "./SimilarMakersCard/SimilarMakersCard" export default function ProfilePage() { @@ -17,7 +23,10 @@ export default function ProfilePage() { }, skip: isNaN(Number(id)), }) - const isOwner = useAppSelector(state => Boolean(state.user.me?.id && state.user.me?.id === profileQuery.data?.profile?.id)) + const { isOwner } = useAppSelector(state => ({ + isOwner: Boolean(state.user.me?.id && state.user.me?.id === profileQuery.data?.profile?.id), + })) + const isMediumScreen = useMediaQuery(MEDIA_QUERIES.isMedium) if (profileQuery.loading) @@ -39,12 +48,33 @@ export default function ProfilePage() {
    - -
    - - -
    - + {isMediumScreen ? + <> + +
    + + + +
    + + + : + <> +
    + + + + +
    + + }
    ) diff --git a/src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.stories.tsx b/src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.stories.tsx new file mode 100644 index 0000000..d38ce5b --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.stories.tsx @@ -0,0 +1,31 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { MOCK_DATA } from 'src/mocks/data'; +import RolesCard from './RolesCard'; + +export default { + title: 'Profiles/Profile Page/Roles Card', + component: RolesCard, + argTypes: { + backgroundColor: { control: 'color' }, + }, + +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
    + +export const Default = Template.bind({}); +Default.args = { + roles: MOCK_DATA['user'].roles +} + +export const Empty = Template.bind({}); +Empty.args = { + roles: [], +} + +export const EmptyOwner = Template.bind({}); +EmptyOwner.args = { + roles: [], + isOwner: true +} \ No newline at end of file diff --git a/src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.tsx b/src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.tsx new file mode 100644 index 0000000..0389e42 --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/RolesCard/RolesCard.tsx @@ -0,0 +1,58 @@ +import Card from 'src/Components/Card/Card' +import Button from 'src/Components/Button/Button' +import { RoleLevelEnum, User } from 'src/graphql'; + + + +interface Props { + roles: User['roles'][number][] + isOwner?: boolean; +} + +export default function RolesCard({ roles, isOwner }: Props) { + + return ( + +

    đŸŽ›ī¸ Roles

    +
    + {roles.length === 0 && <> +

    No roles added

    + {isOwner && } + } +
      + { + roles + .map(role => { + + let levelInt = 0; + if (role.level === RoleLevelEnum.Hobbyist) levelInt = 1; + if (role.level === RoleLevelEnum.Intermediate) levelInt = 2; + if (role.level === RoleLevelEnum.Advanced) levelInt = 3; + if (role.level === RoleLevelEnum.Pro) levelInt = 4; + return { + ...role, + level: levelInt + } + }) + .sort((a, b) => b.level - a.level) + .map((role) =>
    • + {role.icon} +
      +

      + {role.title} +

      +
      + {Array(5).fill(0).map((_, idx) => { + return
      + })} +
      +
      +
    • )} +
    +
    +
    + ) +} diff --git a/src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.stories.tsx b/src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.stories.tsx new file mode 100644 index 0000000..fa60ff8 --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.stories.tsx @@ -0,0 +1,22 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { MOCK_DATA } from 'src/mocks/data'; + +import SimilarMakersCard from './SimilarMakersCard'; + +export default { + title: 'Profiles/Profile Page/Similar Makers Card', + component: SimilarMakersCard, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
    + +export const Default = Template.bind({}); +Default.args = { + makers: MOCK_DATA['user'].similar_makers +} + + diff --git a/src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.tsx b/src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.tsx new file mode 100644 index 0000000..0136b4a --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/SimilarMakersCard/SimilarMakersCard.tsx @@ -0,0 +1,38 @@ + +import { Link } from 'react-router-dom' +import Card from 'src/Components/Card/Card' +import Avatar from 'src/features/Profiles/Components/Avatar/Avatar' +import { User } from 'src/graphql' +import { createRoute } from 'src/utils/routing' + +interface Props { + makers: Pick[] +} + +export default function SimilarMakersCard({ makers }: Props) { + + + return ( + +

    Similar makers

    +
      + {makers.map(maker => { + return +
    • + +
      +

      {maker.name}

      +

      {maker.jobTitle}

      +
      +
    • + + })} +
    +
    + ) +} diff --git a/src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.stories.tsx b/src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.stories.tsx new file mode 100644 index 0000000..bf859c8 --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.stories.tsx @@ -0,0 +1,31 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { MOCK_DATA } from 'src/mocks/data'; +import SkillsCard from './SkillsCard'; + +export default { + title: 'Profiles/Profile Page/Skills Card', + component: SkillsCard, + argTypes: { + backgroundColor: { control: 'color' }, + }, + +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
    + +export const Default = Template.bind({}); +Default.args = { + skills: MOCK_DATA['user'].skills +} + +export const Empty = Template.bind({}); +Empty.args = { + skills: [], +} + +export const EmptyOwner = Template.bind({}); +EmptyOwner.args = { + skills: [], + isOwner: true +} \ No newline at end of file diff --git a/src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.tsx b/src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.tsx new file mode 100644 index 0000000..3cfb05a --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/SkillsCard/SkillsCard.tsx @@ -0,0 +1,29 @@ +import Card from 'src/Components/Card/Card' +import Button from 'src/Components/Button/Button' +import { RoleLevelEnum, User } from 'src/graphql'; + + + +interface Props { + skills: User['skills'][number][] + isOwner?: boolean; +} + +export default function SkillsCard({ skills, isOwner }: Props) { + + return ( + +

    🌈 Skills

    +
    + {skills.length === 0 && <> +

    No skills added

    + {isOwner && } + } +
      + { + skills.map((skill) =>
    • {skill.title}
    • )} +
    +
    +
    + ) +} diff --git a/src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.stories.tsx b/src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.stories.tsx new file mode 100644 index 0000000..ef3155e --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.stories.tsx @@ -0,0 +1,31 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { MOCK_DATA } from 'src/mocks/data'; +import TournamentsCard from './TournamentsCard'; + +export default { + title: 'Profiles/Profile Page/Tournaments Card', + component: TournamentsCard, + argTypes: { + backgroundColor: { control: 'color' }, + }, + +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
    + +export const Default = Template.bind({}); +Default.args = { + tournaments: MOCK_DATA['user'].tournaments +} + +export const Empty = Template.bind({}); +Empty.args = { + tournaments: [], +} + +export const EmptyOwner = Template.bind({}); +EmptyOwner.args = { + tournaments: [], + isOwner: true +} \ No newline at end of file diff --git a/src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.tsx b/src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.tsx new file mode 100644 index 0000000..ef96f2e --- /dev/null +++ b/src/features/Profiles/pages/ProfilePage/TournamentsCard/TournamentsCard.tsx @@ -0,0 +1,45 @@ +import Card from 'src/Components/Card/Card' +import Button from 'src/Components/Button/Button' +import { RoleLevelEnum, User } from 'src/graphql'; + + + +interface Props { + tournaments: Pick[] + isOwner?: boolean; +} + +export default function TournamentsCard({ tournaments, isOwner }: Props) { + + return ( + +

    🏆 Tournaments

    +
    + {tournaments.length === 0 && <> +

    No tournaments entered.

    + } +
      + { + tournaments.map((tournament) => { + + const isLive = ((new Date() < new Date(tournament.end_date)) && (new Date() > new Date(tournament.start_date))); + + return
    • + +
      +

      {tournament.title}

      +

      • {isLive ? "Live" : "Completed"}

      +
      +
    • + })} +
    +
    +
    + ) +} diff --git a/src/features/Profiles/pages/ProfilePage/profile.graphql b/src/features/Profiles/pages/ProfilePage/profile.graphql index 60feb92..8bdaa0e 100644 --- a/src/features/Profiles/pages/ProfilePage/profile.graphql +++ b/src/features/Profiles/pages/ProfilePage/profile.graphql @@ -24,5 +24,28 @@ query profile($profileId: Int!) { icon } } + skills { + id + title + } + roles { + id + title + icon + level + } + tournaments { + id + title + thumbnail_image + start_date + end_date + } + similar_makers { + id + name + avatar + jobTitle + } } } diff --git a/src/features/Profiles/pages/ProfilePage/styles.module.scss b/src/features/Profiles/pages/ProfilePage/styles.module.scss index 9552bd1..f663255 100644 --- a/src/features/Profiles/pages/ProfilePage/styles.module.scss +++ b/src/features/Profiles/pages/ProfilePage/styles.module.scss @@ -5,12 +5,21 @@ grid-template-areas: "main"; > aside:first-of-type { + display: flex; + flex-direction: column; + gap: 24px; grid-area: aside1; } > main { + display: flex; + flex-direction: column; + gap: 24px; grid-area: main; } > aside:last-of-type { + display: flex; + flex-direction: column; + gap: 24px; grid-area: aside2; } diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index f9f95ac..cbcc5d7 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -48,7 +48,11 @@ export type BaseUser = { location: Maybe; name: Scalars['String']; role: Maybe; + roles: Array; + similar_makers: Array; + skills: Array; stories: Array; + tournaments: Array; twitter: Maybe; website: Maybe; }; @@ -204,7 +208,11 @@ export type MyProfile = BaseUser & { nostr_prv_key: Maybe; nostr_pub_key: Maybe; role: Maybe; + roles: Array; + similar_makers: Array; + skills: Array; stories: Array; + tournaments: Array; twitter: Maybe; walletsKeys: Array; website: Maybe; @@ -291,6 +299,7 @@ export type Query = { profile: Maybe; projectsByCategory: Array; searchProjects: Array; + similarMakers: Array; }; @@ -370,6 +379,11 @@ export type QuerySearchProjectsArgs = { take?: InputMaybe; }; + +export type QuerySimilarMakersArgs = { + id: Scalars['Int']; +}; + export type Question = PostBase & { __typename?: 'Question'; author: Author; @@ -385,6 +399,14 @@ export type Question = PostBase & { votes_count: Scalars['Int']; }; +export enum RoleLevelEnum { + Advanced = 'Advanced', + Beginner = 'Beginner', + Hobbyist = 'Hobbyist', + Intermediate = 'Intermediate', + Pro = 'Pro' +} + export type Story = PostBase & { __typename?: 'Story'; author: Author; @@ -421,6 +443,19 @@ export type Tag = { title: Scalars['String']; }; +export type Tournament = { + __typename?: 'Tournament'; + cover_image: Scalars['String']; + description: Scalars['String']; + end_date: Scalars['Date']; + id: Scalars['Int']; + start_date: Scalars['Date']; + tags: Array; + thumbnail_image: Scalars['String']; + title: Scalars['String']; + website: Scalars['String']; +}; + export type User = BaseUser & { __typename?: 'User'; avatar: Scalars['String']; @@ -435,7 +470,11 @@ export type User = BaseUser & { location: Maybe; name: Scalars['String']; role: Maybe; + roles: Array; + similar_makers: Array; + skills: Array; stories: Array; + tournaments: Array; twitter: Maybe; website: Maybe; }; @@ -445,6 +484,20 @@ 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', @@ -599,7 +652,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 }> }> } | 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?: '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 CategoryPageQueryVariables = Exact<{ categoryId: Scalars['Int']; @@ -1579,6 +1632,29 @@ export const ProfileDocument = gql` icon } } + skills { + id + title + } + roles { + id + title + icon + level + } + tournaments { + id + title + thumbnail_image + start_date + end_date + } + similar_makers { + id + name + avatar + jobTitle + } } } `; diff --git a/src/mocks/data/users.ts b/src/mocks/data/users.ts index 54c15f3..51e8a1e 100644 --- a/src/mocks/data/users.ts +++ b/src/mocks/data/users.ts @@ -1,5 +1,9 @@ -import { MyProfile, User } from "src/graphql"; +import { Chance } from "chance"; +import { MyProfile, RoleLevelEnum, User } from "src/graphql"; import { posts } from "./posts"; +import { getCoverImage, getAvatarImage } from "./utils"; + +const chance = new Chance(); export const user: User & MyProfile = { id: 123, @@ -19,8 +23,107 @@ export const user: User & MyProfile = { stories: posts.stories, nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf", nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf", - walletsKeys: [{ - key: "1645h234j2421zxvertw", - name: "My alby wallet key" - }] + walletsKeys: [ + { + key: "1645h234j2421zxvertw", + name: "My Alby wallet key" + }, + { + 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(), + }, + ], + tournaments: [ + { + id: 1, + title: "BreezConf", + description: chance.paragraph(), + cover_image: getCoverImage(), + thumbnail_image: getCoverImage(), + start_date: new Date(2021, 3).toISOString(), + end_date: new Date(2021, 4).toISOString(), + tags: [], + website: "https://breez-conf.com" + }, + { + id: 2, + title: "Shock the Web 3", + description: chance.paragraph(), + cover_image: getCoverImage(), + thumbnail_image: getCoverImage(), + start_date: new Date(2022, 7).toISOString(), + end_date: new Date(2022, 11).toISOString(), + tags: [], + website: "https://shock-the-web.com" + }, + ], + similar_makers: [ + { + id: 144, + name: "Johns Beharry", + jobTitle: "Manager", + avatar: getAvatarImage(), + }, + { + id: 155, + name: "Edward P", + jobTitle: "Front-end Developer", + avatar: getAvatarImage(), + }, + { + id: 166, + name: "Mohammed T", + jobTitle: "Front-end Developer", + avatar: getAvatarImage(), + }, + ] as User[] } From 6dd34dd253641879e4fb49c4ffcf93c707aca9d0 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Sun, 21 Aug 2022 14:32:09 +0300 Subject: [PATCH 22/57] update: add warn msg to adding new account --- .../LinkedAccountsCard/LinkedAccountsCard.tsx | 1 + src/mocks/data/users.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx index 6f38f33..207a4f6 100644 --- a/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx +++ b/src/features/Profiles/pages/EditProfilePage/PreferencesTab/LinkedAccountsCard/LinkedAccountsCard.tsx @@ -81,6 +81,7 @@ export default function LinkedAccountsCard({ value, onChange }: Props) { } +

    Note: if you link a wallet that was used to create another account previously, you won't be able to login to that account until you remove it from here.

    ) } diff --git a/src/mocks/data/users.ts b/src/mocks/data/users.ts index 54c15f3..c292050 100644 --- a/src/mocks/data/users.ts +++ b/src/mocks/data/users.ts @@ -19,8 +19,14 @@ export const user: User & MyProfile = { stories: posts.stories, nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf", nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf", - walletsKeys: [{ - key: "1645h234j2421zxvertw", - name: "My alby wallet key" - }] + walletsKeys: [ + { + key: "1645h234j2421zxvertw", + name: "My Alby wallet key" + }, + { + key: "6643534534534534543", + name: "My Phoenix wallet key" + }, + ] } From bb0fbfa572ed04e3110a4f7c965126abd7676dce Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 22 Aug 2022 13:34:30 +0300 Subject: [PATCH 23/57] 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 From bf7825b4fe050846eeadc9098861a66b8b829fa8 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 22 Aug 2022 14:12:15 +0300 Subject: [PATCH 24/57] feat: update user skills card, skills input component --- .../RolesSkillsTab/RolesSkillsTab.tsx | 11 ++ .../UpdateSkillsCard/SkillsInput.tsx | 130 ++++++++++++++++++ .../UpdateSkillsCard/UpdateSkillsCard.tsx | 58 ++++---- src/redux/features/modals.slice.ts | 2 +- 4 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/UpdateSkillsCard/SkillsInput.tsx diff --git a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx index 35a09ca..fac8d8c 100644 --- a/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx +++ b/src/features/Profiles/pages/EditProfilePage/RolesSkillsTab/RolesSkillsTab.tsx @@ -11,6 +11,7 @@ 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"; +import UpdateSkillsCard from "./UpdateSkillsCard/UpdateSkillsCard"; interface Props { @@ -97,6 +98,16 @@ export default function PreferencesTab() { onChange={onChange} /> )} /> + ( + + )} + />
    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 ( +
    +