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 ? + + : + <> + +
+ + +
+ {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"}