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.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"}