Merge pull request #130 from peakshift/dev

Linking wallets feature
This commit is contained in:
Mohammed Taher Ghazal
2022-08-23 23:29:52 +03:00
committed by GitHub
36 changed files with 1251 additions and 312 deletions

View File

@@ -28,15 +28,7 @@ declare global {
}
export interface NexusGenInputs {
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!
}
UpdateProfileInput: { // input type
ProfileDetailsInput: { // input type
avatar?: string | null; // String
bio?: string | null; // String
email?: string | null; // String
@@ -49,6 +41,18 @@ export interface NexusGenInputs {
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 {
@@ -137,6 +141,24 @@ export interface NexusGenObjects {
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!
@@ -198,8 +220,6 @@ export interface NexusGenObjects {
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
@@ -213,9 +233,14 @@ export interface NexusGenObjects {
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'];
}
@@ -313,9 +338,30 @@ export interface NexusGenFieldTypes {
createStory: NexusGenRootTypes['Story'] | null; // Story
deleteStory: NexusGenRootTypes['Story'] | null; // Story
donate: NexusGenRootTypes['Donation']; // Donation!
updateProfile: NexusGenRootTypes['User'] | null; // User
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!
@@ -352,7 +398,7 @@ export interface NexusGenFieldTypes {
getProject: NexusGenRootTypes['Project']; // Project!
getTrendingPosts: NexusGenRootTypes['Post'][]; // [Post!]!
hottestProjects: NexusGenRootTypes['Project'][]; // [Project!]!
me: NexusGenRootTypes['User'] | null; // User
me: NexusGenRootTypes['MyProfile'] | null; // MyProfile
newProjects: NexusGenRootTypes['Project'][]; // [Project!]!
officialTags: NexusGenRootTypes['Tag'][]; // [Tag!]!
popularTags: NexusGenRootTypes['Tag'][]; // [Tag!]!
@@ -408,8 +454,6 @@ export interface NexusGenFieldTypes {
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
@@ -424,6 +468,27 @@ export interface NexusGenFieldTypes {
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!
@@ -522,9 +587,30 @@ export interface NexusGenFieldTypeNames {
createStory: 'Story'
deleteStory: 'Story'
donate: 'Donation'
updateProfile: 'User'
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'
@@ -561,7 +647,7 @@ export interface NexusGenFieldTypeNames {
getProject: 'Project'
getTrendingPosts: 'Post'
hottestProjects: 'Project'
me: 'User'
me: 'MyProfile'
newProjects: 'Project'
officialTags: 'Tag'
popularTags: 'Tag'
@@ -617,8 +703,6 @@ export interface NexusGenFieldTypeNames {
linkedin: 'String'
location: 'String'
name: 'String'
nostr_prv_key: 'String'
nostr_pub_key: 'String'
role: 'String'
stories: 'Story'
twitter: 'String'
@@ -633,6 +717,27 @@ export interface NexusGenFieldTypeNames {
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'
@@ -664,8 +769,11 @@ export interface NexusGenArgTypes {
donate: { // args
amount_in_sat: number; // Int!
}
updateProfile: { // args
data?: NexusGenInputs['UpdateProfileInput'] | null; // UpdateProfileInput
updateProfileDetails: { // args
data?: NexusGenInputs['ProfileDetailsInput'] | null; // ProfileDetailsInput
}
updateUserPreferences: { // args
userKeys?: NexusGenInputs['UserKeyInputType'][] | null; // [UserKeyInputType!]
}
vote: { // args
amount_in_sat: number; // Int!
@@ -730,13 +838,16 @@ export interface NexusGenArgTypes {
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;
@@ -753,7 +864,7 @@ export type NexusGenUnionNames = keyof NexusGenUnions;
export type NexusGenObjectsUsingAbstractStrategyIsTypeOf = never;
export type NexusGenAbstractsUsingStrategyResolveType = "Post" | "PostBase";
export type NexusGenAbstractsUsingStrategyResolveType = "BaseUser" | "Post" | "PostBase";
export type NexusGenFeaturesConfig = {
abstractTypeStrategies: {

View File

@@ -18,6 +18,24 @@ type Award {
url: String!
}
interface BaseUser {
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
}
type Bounty implements PostBase {
applicants_count: Int!
applications: [BountyApplication!]!
@@ -99,10 +117,32 @@ type Mutation {
createStory(data: StoryInputType): Story
deleteStory(id: Int!): Story
donate(amount_in_sat: Int!): Donation!
updateProfile(data: UpdateProfileInput): User
updateProfileDetails(data: ProfileDetailsInput): MyProfile
updateUserPreferences(userKeys: [UserKeyInputType!]): MyProfile!
vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote!
}
type MyProfile implements BaseUser {
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
}
enum POST_TYPE {
Bounty
Question
@@ -131,6 +171,20 @@ type PostComment {
votes_count: Int!
}
input ProfileDetailsInput {
avatar: String
bio: String
email: String
github: String
jobTitle: String
lightning_address: String
linkedin: String
location: String
name: String
twitter: String
website: String
}
type Project {
awards: [Award!]!
category: Category!
@@ -160,7 +214,7 @@ type Query {
getProject(id: Int!): Project!
getTrendingPosts: [Post!]!
hottestProjects(skip: Int = 0, take: Int = 50): [Project!]!
me: User
me: MyProfile
newProjects(skip: Int = 0, take: Int = 50): [Project!]!
officialTags: [Tag!]!
popularTags: [Tag!]!
@@ -217,21 +271,7 @@ type Tag {
title: String!
}
input UpdateProfileInput {
avatar: String
bio: String
email: String
github: String
jobTitle: String
lightning_address: String
linkedin: String
location: String
name: String
twitter: String
website: String
}
type User {
type User implements BaseUser {
avatar: String!
bio: String
email: String
@@ -243,14 +283,17 @@ type User {
linkedin: String
location: String
name: String!
nostr_prv_key: String
nostr_pub_key: String
role: String
stories: [Story!]!
twitter: String
website: String
}
input UserKeyInputType {
key: String!
name: String!
}
enum VOTE_ITEM_TYPE {
Bounty
PostComment
@@ -268,4 +311,9 @@ type Vote {
paid: Boolean!
payment_hash: String!
payment_request: String!
}
type WalletKey {
key: String!
name: String!
}

View File

@@ -1,13 +1,14 @@
const { prisma } = require('../../../prisma');
const { objectType, extendType, intArg, nonNull, inputObjectType } = require("nexus");
const { objectType, extendType, intArg, nonNull, inputObjectType, interfaceType, list } = require("nexus");
const { getUserByPubKey } = require("../../../auth/utils/helperFuncs");
const { removeNulls } = require("./helpers");
const User = objectType({
name: 'User',
const BaseUser = interfaceType({
name: 'BaseUser',
definition(t) {
t.nonNull.int('id');
t.nonNull.string('name');
@@ -23,8 +24,7 @@ const User = objectType({
t.string('linkedin')
t.string('bio')
t.string('location')
t.string('nostr_prv_key')
t.string('nostr_pub_key')
t.nonNull.list.nonNull.field('stories', {
type: "Story",
@@ -32,6 +32,35 @@ const User = objectType({
return prisma.story.findMany({ where: { user_id: parent.id, is_published: true }, orderBy: { createdAt: "desc" } });
}
});
},
resolveType() {
return null
},
})
const User = objectType({
name: 'User',
definition(t) {
t.implements('BaseUser')
}
})
const MyProfile = objectType({
name: 'MyProfile',
definition(t) {
t.implements('BaseUser')
t.string('nostr_prv_key')
t.string('nostr_pub_key')
t.nonNull.list.nonNull.field('walletsKeys', {
type: "WalletKey",
resolve: (parent) => {
return prisma.user.findUnique({ where: { id: parent.id } }).userKeys();
}
});
}
})
@@ -40,7 +69,7 @@ const me = extendType({
type: "Query",
definition(t) {
t.field('me', {
type: "User",
type: "MyProfile",
async resolve(parent, args, context) {
const user = await getUserByPubKey(context.userPubKey)
return user
@@ -58,21 +87,14 @@ const profile = extendType({
id: nonNull(intArg())
},
async resolve(parent, { id }, ctx) {
const user = await getUserByPubKey(ctx.userPubKey);
const isSelf = user?.id === id;
const profile = await prisma.user.findFirst({
where: { id },
});
if (!isSelf)
profile.nostr_prv_key = null;
return profile;
return prisma.user.findUnique({ where: { id } })
}
})
}
})
const UpdateProfileInput = inputObjectType({
name: 'UpdateProfileInput',
const ProfileDetailsInput = inputObjectType({
name: 'ProfileDetailsInput',
definition(t) {
t.string('name');
t.string('avatar');
@@ -88,12 +110,12 @@ const UpdateProfileInput = inputObjectType({
}
})
const updateProfile = extendType({
const updateProfileDetails = extendType({
type: 'Mutation',
definition(t) {
t.field('updateProfile', {
type: 'User',
args: { data: UpdateProfileInput },
t.field('updateProfileDetails', {
type: 'MyProfile',
args: { data: ProfileDetailsInput },
async resolve(_root, args, ctx) {
const user = await getUserByPubKey(ctx.userPubKey);
@@ -117,14 +139,103 @@ const updateProfile = extendType({
})
const WalletKey = objectType({
name: 'WalletKey',
definition(t) {
t.nonNull.string('key');
t.nonNull.string('name');
}
})
const UserKeyInputType = inputObjectType({
name: 'UserKeyInputType',
definition(t) {
t.nonNull.string('key');
t.nonNull.string('name');
}
})
const updateUserPreferences = extendType({
type: 'Mutation',
definition(t) {
t.nonNull.field('updateUserPreferences', {
type: 'MyProfile',
args: { userKeys: list(nonNull(UserKeyInputType)) },
async resolve(_root, args, ctx) {
const user = await getUserByPubKey(ctx.userPubKey);
if (!user)
throw new Error("You have to login");
//Update the userkeys
//--------------------
// 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.userKeys.map(i => i.key)
}
},
},
select: {
key: true
}
})).map(i => i.key);
const newKeys = [];
for (let i = 0; i < args.userKeys.length; i++) {
const item = args.userKeys[i];
if (userKeys.includes(item.key))
newKeys.push(item);
}
if (newKeys.length === 0)
throw new Error("You can't delete all your wallets 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 prisma.user.findUnique({ where: { id: user.id } });
}
})
}
})
module.exports = {
// Types
BaseUser,
User,
UpdateProfileInput,
MyProfile,
WalletKey,
// Queries
me,
profile,
// Mutations
updateProfile,
}
updateProfileDetails,
updateUserPreferences,
}

View File

@@ -37,15 +37,11 @@ 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" });
// Remove old linking for this key if existing
await prisma.userKey.deleteMany({
where: { key }
})
await prisma.userKey.create({
data: {
key,
@@ -53,6 +49,7 @@ const loginHandler = async (req, res) => {
}
});
return res
.status(200)
.json({ status: "OK" })

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "UserKey" ADD COLUMN "name" TEXT NOT NULL DEFAULT E'New Key Name';

View File

@@ -0,0 +1,21 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.1645 2.44122L13.8999 1.89167L13.2918 1.93933L5.88553 2.51977L5.44117 2.5546L5.20243 2.931L2.07304 7.86481L1.81474 8.27205L2.01427 8.71108L4.91607 15.096L5.13423 15.5761L5.66001 15.616L13.2938 16.1965L13.9287 16.2448L14.1803 15.6599L17.0157 9.06762L17.1762 8.69453L16.9999 8.32862L14.1645 2.44122Z" fill="#C4C4C4" stroke="#333333" stroke-width="1.78281"/>
<g filter="url(#filter0_i_1824_6762)">
<path d="M13.3614 2.82806L5.95518 3.40851L2.82579 8.34232L5.72759 14.7273L13.3614 15.3077L16.1968 8.71547L13.3614 2.82806Z" fill="url(#paint0_linear_1824_6762)"/>
</g>
<defs>
<filter id="filter0_i_1824_6762" x="2.82579" y="2.82806" width="13.371" height="12.4796" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.445701" dy="1.3371"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.9375 0 0 0 0 0.9375 0 0 0 0 0.9375 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1824_6762"/>
</filter>
<linearGradient id="paint0_linear_1824_6762" x1="9.51131" y1="2.82806" x2="9.51131" y2="15.3077" gradientUnits="userSpaceOnUse">
<stop offset="0.286458" stop-color="#B7B7B7"/>
<stop offset="1" stop-color="#9B9B9B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -25,7 +25,7 @@ const btnStylesFill: UnionToObjectKeys<Props, 'color'> = {
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<Props, 'color'> = {

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { Link } from 'react-router-dom'
import { UnionToObjectKeys } from 'src/utils/types/utils'
@@ -6,7 +6,6 @@ interface Props {
onClick?: () => 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<Props, 'variant'> = {
blank: "bg-gray-900 bg-opacity-0 hover:bg-opacity-5 active:bg-opacity-10 active:scale-95 !border-0"
}
const IconButton = React.forwardRef<any, Props>(({
const IconButton = React.forwardRef<any, PropsWithChildren<Props>>(({
href,
size = "md",
className = "",

View File

@@ -153,20 +153,29 @@ export default function LoginPage() {
</div>
else
content = <div className="max-w-[326px] border-2 border-gray-200 rounded-16 p-16 flex flex-col gap-16 items-center" >
<p className="text-body1 font-bolder text-center">
Login with lightning
</p>
<QRCodeSVG
width={160}
height={160}
value={lnurl}
/>
content = <div className="max-w-[364px] border-2 border-gray-200 rounded-16 p-16 flex flex-col gap-24 items-center" >
<h2 className='text-h5 font-bold text-center'>Login with lightning </h2>
<a href={`lightning:${lnurl}`} >
<QRCodeSVG
width={240}
height={240}
value={lnurl}
bgColor='transparent'
imageSettings={{
src: '/assets/images/nut_3d.png',
width: 32,
height: 32,
excavate: true,
}}
/>
</a>
<p className="text-gray-600 text-body4 text-center">
Scan this code or copy + paste it to your lightning wallet. Or click to login with your browser's wallet.
</p>
<div className="flex flex-wrap gap-16">
<a href={lnurl}
<div className="w-full flex flex-col items-stretch gap-16">
<a href={`lightning:${lnurl}`}
className='grow block text-body4 text-center text-white font-bolder bg-primary-500 hover:bg-primary-600 rounded-10 px-16 py-12 active:scale-90 transition-transform'
>Click to connect <IoRocketOutline /></a>
<Button

View File

@@ -4,5 +4,7 @@ query Me {
name
avatar
join_date
jobTitle
bio
}
}

View File

@@ -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 {

View File

@@ -1,36 +0,0 @@
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';
interface Props {
}
export default function AccountCard({ }: Props) {
const dispatch = useAppDispatch()
const connectNewWallet = () => {
dispatch(openModal({ Modal: "LinkingAccountModal" }))
}
return (
<Card>
<p className="text-body2 font-bold">🔒 Linking Accounts</p>
<div className='mt-24 flex flex-col gap-16'>
<p className="text-body3 font-bold">Linked Wallets</p>
<p className="text-body4 text-gray-600">
These are the wallets that you can login to this account from.
<br />
You can add a new wallet from the button below.
</p>
<Button color='primary' className='' onClick={connectNewWallet}>
Connect new wallet
</Button>
</div>
</Card>
)
}

View File

@@ -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";
@@ -17,12 +16,8 @@ const links = [
text: "👾 My Profile",
path: 'my-profile',
},
// {
// text: "🙍‍♂️ Account",
// path: 'account',
// },
{
text: "⚙️ Preferences",
text: "⚙️ Settings & Preferences",
path: 'preferences',
}
]
@@ -30,24 +25,16 @@ 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);
const user = useAppSelector(state => state.user.me)
if (!userId || profileQuery.loading)
if (!user)
return <LoadingPage />
if (!profileQuery.data?.profile)
return <NotFoundPage />
return (
<>
<Helmet>
@@ -97,9 +84,8 @@ export default function EditProfilePage() {
<main className="md:col-span-3">
<Routes>
<Route index element={<Navigate to='my-profile' />} />
<Route path='my-profile' element={<UpdateMyProfileTab data={profileQuery.data.profile} />} />
<Route path='account' element={<AccountCard />} />
<Route path='preferences' element={<PreferencesTab nostr_prv_key={profileQuery.data.profile.nostr_prv_key} nostr_pub_key={profileQuery.data.profile.nostr_pub_key} isOwner={true} />
<Route path='my-profile' element={<UpdateMyProfileTab />} />
<Route path='preferences' element={<PreferencesTab />
} />
</Routes>
</main>

View File

@@ -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',

View File

@@ -0,0 +1,87 @@
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 WalletKey from './WalletKey';
export type WalletKeyType = MyProfile['walletsKeys'][number]
interface Props {
value: WalletKeyType[],
onChange: (newValue: WalletKeyType[]) => void
}
export default function LinkedAccountsCard({ value, onChange }: Props) {
const dispatch = useAppDispatch();
const connectNewWallet = () => {
dispatch(openModal({ Modal: "LinkingAccountModal" }))
}
const updateKeyName = (idx: number, newName: string) => {
onChange(value.map((item, i) => {
if (i === idx)
return {
...item,
name: newName
}
return item;
}))
}
const deleteKey = (idx: number,) => {
onChange([...value.slice(0, idx), ...value.slice(idx + 1)])
}
return (
<Card>
<p className="text-body2 font-bold">🔐 Linked Wallets</p>
<p className="text-body4 text-gray-600 mt-8">
These are the wallets that you can login to this account from. You can add up to 3 wallets.
</p>
<div className='mt-24 flex flex-col gap-16'>
<ul className="mt-8 relative flex flex-col gap-8">
{value.map((item, idx) =>
<WalletKey
key={idx}
walletKey={item}
canDelete={value.length > 1}
onRename={v => updateKeyName(idx, v)}
onDelete={() => deleteKey(idx)}
/>
)}
</ul>
{/* <div className="flex justify-end gap-8">
<Button
color='gray'
className=''
disabled={!keysState.hasNewChanges || updatingKeysStatus.loading}
onClick={cancelChanges}
>
Cancel
</Button>
<Button
color='black'
className=''
disabled={!keysState.hasNewChanges}
isLoading={updatingKeysStatus.loading}
onClick={saveChanges}
>
Save Changes
</Button>
</div> */}
</div>
{value.length < 3 &&
<Button color='none' size='sm' className='mt-16 text-gray-600 hover:bg-gray-50' onClick={connectNewWallet}>
+ Add another wallet
</Button>}
<p className="text-body5 text-gray-400 mt-24"><span className="font-bold">Note</span>: 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.</p>
</Card>
)
}

View File

@@ -0,0 +1,90 @@
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<HTMLInputElement>(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: "RemoveWalletKeyModal",
props: {
callbackAction: {
type: CONFIRM_DELETE_WALLET.type,
payload: { confirmed: false }
}
}
}))
}
return (
<li key={walletKey.key} className="flex gap-16 items-center">
<div className="input-wrapper relative min-w-0">
<span className="input-icon !pr-0">🔑</span>
<input
ref={ref}
disabled={!editMode}
type='text'
value={name}
className="input-text overflow-hidden text-ellipsis"
placeholder='e.g My Alby Key'
onChange={e => setName(e.target.value)}
/>
{!editMode && <Button size='sm' color='none' className='text-blue-400 shrink-0' onClick={() => toggleEditMode()}>Rename</Button>}
{editMode &&
<Button
size='sm'
color='none'
className='text-blue-400 shrink-0'
disabled={name.length === 0}
onClick={saveNameChanges}
>Save</Button>}
</div>
{canDelete && <IconButton
size='sm'
className='text-red-500 shrink-0'
onClick={() => handleDelete()}
><FiTrash2 /> </IconButton>}
</li>
)
}

View File

@@ -7,6 +7,8 @@ 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';
import { IoClose } from 'react-icons/io5';
@@ -57,7 +59,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,41 +74,49 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo
clipboard(lnurl);
}
const done = () => {
apolloClient.refetchQueries({
include: ['MyProfilePreferences']
})
onClose?.()
}
let content = <></>
if (error)
content = <div className="flex flex-col gap-24 items-center">
<p className="text-body3 text-red-500 font-bold">Something wrong happened...</p>
<a href='/login' className="text body4 text-gray-500 hover:underline">Refresh the page</a>
content = <div className="flex flex-col gap-24 items-center my-32">
<p className="text-body3 text-red-500 font-bold">Ooops...😵</p>
<p className="text-body4 text-gray-600 text-center">An error happened while fetching the link, please check your internet connection and try again.</p>
</div>
else if (loadingLnurl)
content = <div className="flex flex-col gap-24 items-center">
<Grid color="var(--primary)" width="150" />
<p className="text-body3 font-bold">Fetching Lnurl-Auth...</p>
content = <div className="flex flex-col gap-24 items-center my-32">
<Grid color="var(--primary)" width="80" />
<p className="text-body4 text-gray-600 font-bold">Fetching Lnurl-Auth Link...</p>
</div>
else
content =
<>
<p className="text-body1 font-bolder text-center">
Link your account
</p>
<QRCodeSVG
width={160}
height={160}
value={lnurl}
/>
<div className='flex flex-col gap-24 items-center mt-32 '>
<a href={`lightning:${lnurl}`} >
<QRCodeSVG
width={240}
height={240}
value={lnurl}
bgColor='transparent'
imageSettings={{
src: '/assets/images/nut_3d.png',
width: 32,
height: 32,
excavate: true
}}
/>
</a>
<p className="text-gray-600 text-body4 text-center">
Scan this code or copy + paste it to your other lightning wallet to be able to login later with it to this account.
<br />
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.
</p>
<div className="flex flex-col w-full gap-16">
{/* <a href={lnurl}
className='grow block text-body4 text-center text-white font-bolder bg-primary-500 hover:bg-primary-600 rounded-10 px-16 py-12 active:scale-90 transition-transform'
>Click to connect <IoRocketOutline /></a> */}
<Button
color='gray'
className='grow'
@@ -114,14 +125,13 @@ export default function LinkingAccountModal({ onClose, direction, ...props }: Mo
>{copied ? "Copied" : "Copy"} <FiCopy /></Button>
<Button
color='primary'
onClick={onClose}
onClick={done}
fullWidth
className='mt-16'
>
Done?
Done
</Button>
</div>
</>
</div>
@@ -132,8 +142,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-[442px] p-24 rounded-xl relative"
>
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold text-center'>Connect another wallet</h2>
{content}
</motion.div>
)

View File

@@ -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 (
<div className="grid grid-cols-1 md:grid-cols-3 gap-24">
<div className="col-span-2 flex flex-col gap-24">
<Card>
<p className="text-body2 font-bold"><Skeleton width="15ch" /></p>
<p className="text-body4 text-gray-600 mt-8">
<Skeleton width="70%" />
<Skeleton width="35%" />
</p>
<div className='mt-24 flex flex-col gap-16'>
<ul className="mt-8 relative flex flex-col gap-8">
{Array(3).fill(0).map((_, idx) =>
<li key={idx} className="flex flex-wrap gap-16 justify-between items-center text-body4 border-b py-12 px-16 border border-gray-200 rounded-16 focus-within:ring-1 ring-primary-200">
<div className='p-0 border-0 focus:border-0 focus:outline-none grow
focus:ring-0 placeholder:!text-gray-400' >
<Skeleton width='20ch'></Skeleton>
</div>
</li>
)}
</ul>
</div>
</Card>
<Card>
<p className="text-body2 font-bold"><Skeleton width="12ch" /></p>
<p className="text-body4 text-gray-600 mt-8">
<Skeleton width="80%" />
<Skeleton width="50%" />
<Skeleton width="65%" />
</p>
<div className="py-42"></div>
</Card>
</div>
<div className="">
</div>
</div>
)
}

View File

@@ -1,20 +1,105 @@
import { Nullable } from 'remirror';
import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard';
import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard';
import { UpdateUserPreferencesMutationVariables, useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql';
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';
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 {
isOwner?: boolean;
nostr_pub_key: Nullable<string>;
nostr_prv_key: Nullable<string>;
}
export default function PreferencesTab({ nostr_prv_key, nostr_pub_key, isOwner }: Props) {
export type IProfilePreferencesForm = NonNullable<UpdateUserPreferencesMutationVariables>;
const schema: yup.SchemaOf<IProfilePreferencesForm> = 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 { formState: { isDirty, }, handleSubmit, reset, control } = useForm<IProfilePreferencesForm>({
defaultValues: {
walletsKeys: []
},
resolver: yupResolver(schema),
});
const query = useMyProfilePreferencesQuery({
onCompleted: data => {
if (data.me) reset(data.me)
},
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 <PreferencesTabSkeleton />
if (!query.data?.me)
return <NotFoundPage />
const onSubmit: SubmitHandler<IProfilePreferencesForm> = 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 (
<div className="grid grid-cols-1 md:grid-cols-3 gap-24">
<div className="col-span-2">
<CommentsSettingsCard nostr_prv_key={nostr_prv_key} nostr_pub_key={nostr_pub_key} />
<div className="col-span-2 flex flex-col gap-24">
<Controller
control={control}
name="walletsKeys"
render={({ field: { onChange, value } }) => (
<LinkedAccountsCard value={value as any} onChange={v => {
onChange(v);
handleSubmit(onSubmit)();
}} />
)}
/>
<CommentsSettingsCard nostr_prv_key={query.data.me.nostr_prv_key} nostr_pub_key={query.data.me.nostr_pub_key} />
</div>
<div className="self-start sticky-side-element">
{/* <SaveChangesCard
isLoading={mutationStatus.loading}
isDirty={isDirty}
onSubmit={handleSubmit(onSubmit)}
onCancel={() => reset()}
/> */}
</div>
</div>
)

View File

@@ -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 (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[326px] p-24 rounded-xl relative"
>
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold text-center'>Remove key?</h2>
<div className="pt-16 flex flex-col gap-24 mt-16">
<p className="text-h1 text-center">🔑</p>
<p className="text-body4 text-gray-600 text-center font-light">Are you sure you want to remove this key from your account? Once deleted, you wont be able to recover it.</p>
<div className="grid grid-cols-2 gap-16">
<Button color='gray' onClick={onClose} >Cancel</Button>
<Button color='red' onClick={handleConfirm} >Remove</Button>
</div>
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,3 @@
import { lazyModal } from 'src/utils/helperFunctions';
export const { LazyComponent: RemoveWalletKeyModal } = lazyModal(() => import('./RemoveWalletKeyModal'))

View File

@@ -0,0 +1,23 @@
query MyProfilePreferences {
me {
id
walletsKeys {
key
name
}
nostr_prv_key
nostr_pub_key
}
}
mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
updateUserPreferences(userKeys: $walletsKeys) {
id
walletsKeys {
key
name
}
nostr_pub_key
nostr_prv_key
}
}

View File

@@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'
import Button from 'src/Components/Button/Button'
import Card from 'src/Components/Card/Card'
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'
import { useProfileQuery } from 'src/graphql'
import { trimText } from 'src/utils/helperFunctions'
import { useAppSelector } from 'src/utils/hooks'
import { createRoute } from 'src/utils/routing'
@@ -17,14 +16,10 @@ interface Props {
export default function SaveChangesCard(props: Props) {
const userId = useAppSelector(state => state.user.me?.id!)
const profileQuery = useProfileQuery({
variables: {
profileId: userId,
},
})
const user = useAppSelector(state => state.user.me)
if (!profileQuery.data?.profile)
if (!user)
return <></>
@@ -38,18 +33,18 @@ export default function SaveChangesCard(props: Props) {
<div className='hidden md:flex gap-8'>
<Link
className='shrink-0'
to={createRoute({ type: 'profile', id: profileQuery.data.profile.id, username: profileQuery.data.profile.name })}>
<Avatar width={48} src={profileQuery.data.profile.avatar!} />
to={createRoute({ type: 'profile', id: user.id, username: user.name })}>
<Avatar width={48} src={user.avatar!} />
</Link>
<div className='overflow-hidden'>
<p className={`text-body4 text-black font-medium overflow-hidden text-ellipsis`}>{profileQuery.data.profile ? trimText(profileQuery.data.profile.name, 30) : "Anonymouse"}</p>
{profileQuery.data.profile.jobTitle && <p className={`text-body6 text-gray-600`}>{profileQuery.data.profile.jobTitle}</p>}
<p className={`text-body4 text-black font-medium overflow-hidden text-ellipsis`}>{user ? trimText(user.name, 30) : "Anonymouse"}</p>
{user.jobTitle && <p className={`text-body6 text-gray-600`}>{user.jobTitle}</p>}
</div>
{/* {showTimeAgo && <p className={`${nameSize[size]} text-gray-500 ml-auto `}>
{dayjs().diff(props.date, 'hour') < 24 ? `${dayjs().diff(props.date, 'hour')}h ago` : undefined}
</p>} */}
</div>
<p className="hidden md:block text-body5">{trimText(profileQuery.data.profile.bio, 120)}</p>
<p className="hidden md:block text-body5">{trimText(user.bio, 120)}</p>
<div className="flex flex-col gap-16">
<Button
color="primary"

View File

@@ -0,0 +1,30 @@
import Card from 'src/Components/Card/Card';
import Skeleton from 'react-loading-skeleton';
export default function UpdateProfileAboutTabSkeleton() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-24">
<div className="col-span-2 flex flex-col gap-24">
<Card className="md:col-span-2" defaultPadding={false}>
<div className="bg-gray-100 relative h-[160px] rounded-t-16">
<div className="absolute left-24 bottom-0 translate-y-1/2">
<div className="w-[120px] aspect-square rounded-full bg-gray-50 border border-gray-200"></div>
</div>
</div>
<div className="p-16 md:p-24 mt-64">
<p className="text-body2 font-bold"><Skeleton width="12ch" /></p>
<p className="text-body4 text-gray-600 mt-8">
<Skeleton width="50%" />
</p>
<div className="py-[250px]"></div>
</div>
</Card>
</div>
<div className="">
</div>
</div>
)
}

View File

@@ -1,33 +1,23 @@
import { SubmitHandler, useForm } from "react-hook-form"
import Button from "src/Components/Button/Button";
import { User, useUpdateProfileAboutMutation } from "src/graphql";
import { User, useUpdateProfileAboutMutation, useMyProfileAboutQuery, UpdateProfileAboutMutationVariables } from "src/graphql";
import { NotificationsService } from "src/services/notifications.service";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
import { usePrompt } from "src/utils/hooks";
import { useAppDispatch, usePrompt } from "src/utils/hooks";
import SaveChangesCard from "../SaveChangesCard/SaveChangesCard";
import { toast } from "react-toastify";
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 {
data: Pick<User,
| 'name'
| 'email'
| 'lightning_address'
| 'jobTitle'
| 'avatar'
| 'website'
| 'github'
| 'twitter'
| 'linkedin'
| 'location'
| 'bio'
>,
onClose?: () => void;
}
type IFormInputs = Props['data'];
type IFormInputs = NonNullable<UpdateProfileAboutMutationVariables['data']>;
const schema: yup.SchemaOf<IFormInputs> = yup.object({
name: yup.string().trim().required().min(2),
@@ -63,21 +53,35 @@ const schema: yup.SchemaOf<IFormInputs> = yup.object({
}).required();
export default function UpdateMyProfileTab({ data, onClose }: Props) {
export default function UpdateMyProfileTab() {
const { register, formState: { errors, isDirty, }, handleSubmit, reset } = useForm<IFormInputs>({
defaultValues: data,
defaultValues: {},
resolver: yupResolver(schema),
mode: 'onBlur',
});
const profileQuery = useMyProfileAboutQuery({
onCompleted: data => {
if (data.me)
reset(data.me)
}
})
const [mutate, mutationStatus] = useUpdateProfileAboutMutation();
const dispatch = useAppDispatch()
usePrompt('You may have some unsaved changes. You still want to leave?', isDirty)
if (profileQuery.loading)
return <UpdateProfileAboutTabSkeleton />
if (!profileQuery.data?.me)
return <NotFoundPage />
const onSubmit: SubmitHandler<IFormInputs> = data => {
const toastId = toast.loading("Saving changes...", NotificationsService.defaultOptions)
@@ -98,9 +102,12 @@ export default function UpdateMyProfileTab({ data, onClose }: Props) {
website: data.website,
}
},
onCompleted: () => {
reset(data);
toast.update(toastId, { render: "Saved changes successfully", type: "success", ...NotificationsService.defaultOptions, isLoading: false });
onCompleted: ({ updateProfileDetails: data }) => {
if (data) {
dispatch(setUser(data))
reset(data);
toast.update(toastId, { render: "Saved changes successfully", type: "success", ...NotificationsService.defaultOptions, isLoading: false });
}
}
})
.catch(() => {
@@ -114,7 +121,7 @@ export default function UpdateMyProfileTab({ data, onClose }: Props) {
<Card className="md:col-span-2" defaultPadding={false}>
<div className="bg-gray-600 relative h-[160px] rounded-t-16">
<div className="absolute left-24 bottom-0 translate-y-1/2">
<Avatar src={data.avatar} width={120} />
<Avatar src={profileQuery.data.me.avatar} width={120} />
</div>
</div>
<div className="p-16 md:p-24 mt-64">

View File

@@ -0,0 +1,37 @@
query MyProfileAbout {
me {
id
name
avatar
join_date
role
email
jobTitle
lightning_address
website
twitter
github
linkedin
bio
location
}
}
mutation updateProfileAbout($data: ProfileDetailsInput) {
updateProfileDetails(data: $data) {
id
name
avatar
join_date
role
email
jobTitle
lightning_address
website
twitter
github
linkedin
bio
location
}
}

View File

@@ -1,18 +0,0 @@
mutation updateProfileAbout($data: UpdateProfileInput) {
updateProfile(data: $data) {
id
name
avatar
join_date
website
role
email
lightning_address
jobTitle
twitter
github
linkedin
bio
location
}
}

View File

@@ -24,7 +24,5 @@ query profile($profileId: Int!) {
icon
}
}
nostr_prv_key
nostr_pub_key
}
}

View File

@@ -35,6 +35,24 @@ export type Award = {
url: Scalars['String'];
};
export type BaseUser = {
avatar: Scalars['String'];
bio: Maybe<Scalars['String']>;
email: Maybe<Scalars['String']>;
github: Maybe<Scalars['String']>;
id: Scalars['Int'];
jobTitle: Maybe<Scalars['String']>;
join_date: Scalars['Date'];
lightning_address: Maybe<Scalars['String']>;
linkedin: Maybe<Scalars['String']>;
location: Maybe<Scalars['String']>;
name: Scalars['String'];
role: Maybe<Scalars['String']>;
stories: Array<Story>;
twitter: Maybe<Scalars['String']>;
website: Maybe<Scalars['String']>;
};
export type Bounty = PostBase & {
__typename?: 'Bounty';
applicants_count: Scalars['Int'];
@@ -121,7 +139,8 @@ export type Mutation = {
createStory: Maybe<Story>;
deleteStory: Maybe<Story>;
donate: Donation;
updateProfile: Maybe<User>;
updateProfileDetails: Maybe<MyProfile>;
updateUserPreferences: MyProfile;
vote: Vote;
};
@@ -153,8 +172,13 @@ export type MutationDonateArgs = {
};
export type MutationUpdateProfileArgs = {
data: InputMaybe<UpdateProfileInput>;
export type MutationUpdateProfileDetailsArgs = {
data: InputMaybe<ProfileDetailsInput>;
};
export type MutationUpdateUserPreferencesArgs = {
userKeys: InputMaybe<Array<UserKeyInputType>>;
};
@@ -164,6 +188,28 @@ export type MutationVoteArgs = {
item_type: Vote_Item_Type;
};
export type MyProfile = BaseUser & {
__typename?: 'MyProfile';
avatar: Scalars['String'];
bio: Maybe<Scalars['String']>;
email: Maybe<Scalars['String']>;
github: Maybe<Scalars['String']>;
id: Scalars['Int'];
jobTitle: Maybe<Scalars['String']>;
join_date: Scalars['Date'];
lightning_address: Maybe<Scalars['String']>;
linkedin: Maybe<Scalars['String']>;
location: Maybe<Scalars['String']>;
name: Scalars['String'];
nostr_prv_key: Maybe<Scalars['String']>;
nostr_pub_key: Maybe<Scalars['String']>;
role: Maybe<Scalars['String']>;
stories: Array<Story>;
twitter: Maybe<Scalars['String']>;
walletsKeys: Array<WalletKey>;
website: Maybe<Scalars['String']>;
};
export enum Post_Type {
Bounty = 'Bounty',
Question = 'Question',
@@ -193,6 +239,20 @@ export type PostComment = {
votes_count: Scalars['Int'];
};
export type ProfileDetailsInput = {
avatar: InputMaybe<Scalars['String']>;
bio: InputMaybe<Scalars['String']>;
email: InputMaybe<Scalars['String']>;
github: InputMaybe<Scalars['String']>;
jobTitle: InputMaybe<Scalars['String']>;
lightning_address: InputMaybe<Scalars['String']>;
linkedin: InputMaybe<Scalars['String']>;
location: InputMaybe<Scalars['String']>;
name: InputMaybe<Scalars['String']>;
twitter: InputMaybe<Scalars['String']>;
website: InputMaybe<Scalars['String']>;
};
export type Project = {
__typename?: 'Project';
awards: Array<Award>;
@@ -224,7 +284,7 @@ export type Query = {
getProject: Project;
getTrendingPosts: Array<Post>;
hottestProjects: Array<Project>;
me: Maybe<User>;
me: Maybe<MyProfile>;
newProjects: Array<Project>;
officialTags: Array<Tag>;
popularTags: Array<Tag>;
@@ -361,21 +421,7 @@ export type Tag = {
title: Scalars['String'];
};
export type UpdateProfileInput = {
avatar: InputMaybe<Scalars['String']>;
bio: InputMaybe<Scalars['String']>;
email: InputMaybe<Scalars['String']>;
github: InputMaybe<Scalars['String']>;
jobTitle: InputMaybe<Scalars['String']>;
lightning_address: InputMaybe<Scalars['String']>;
linkedin: InputMaybe<Scalars['String']>;
location: InputMaybe<Scalars['String']>;
name: InputMaybe<Scalars['String']>;
twitter: InputMaybe<Scalars['String']>;
website: InputMaybe<Scalars['String']>;
};
export type User = {
export type User = BaseUser & {
__typename?: 'User';
avatar: Scalars['String'];
bio: Maybe<Scalars['String']>;
@@ -388,14 +434,17 @@ export type User = {
linkedin: Maybe<Scalars['String']>;
location: Maybe<Scalars['String']>;
name: Scalars['String'];
nostr_prv_key: Maybe<Scalars['String']>;
nostr_pub_key: Maybe<Scalars['String']>;
role: Maybe<Scalars['String']>;
stories: Array<Story>;
twitter: Maybe<Scalars['String']>;
website: Maybe<Scalars['String']>;
};
export type UserKeyInputType = {
key: Scalars['String'];
name: Scalars['String'];
};
export enum Vote_Item_Type {
Bounty = 'Bounty',
PostComment = 'PostComment',
@@ -416,6 +465,12 @@ export type Vote = {
payment_request: Scalars['String'];
};
export type WalletKey = {
__typename?: 'WalletKey';
key: Scalars['String'];
name: Scalars['String'];
};
export type OfficialTagsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -436,7 +491,7 @@ export type SearchProjectsQuery = { __typename?: 'Query', searchProjects: Array<
export type MeQueryVariables = Exact<{ [key: string]: never; }>;
export type MeQuery = { __typename?: 'Query', me: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null };
export type MeQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', id: number, name: string, avatar: string, join_date: any, jobTitle: string | null, bio: string | null } | null };
export type DonationsStatsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -515,19 +570,36 @@ export type PostDetailsQueryVariables = Exact<{
export type PostDetailsQuery = { __typename?: 'Query', getPostById: { __typename?: 'Bounty', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, applications: Array<{ __typename?: 'BountyApplication', id: number, date: string, workplan: string, author: { __typename?: 'Author', id: number, name: string, avatar: string } }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, cover_image: string | null, is_published: boolean | null, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } };
export type MyProfilePreferencesQueryVariables = Exact<{ [key: string]: never; }>;
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<Array<UserKeyInputType> | UserKeyInputType>;
}>;
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; }>;
export type MyProfileAboutQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', 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 } | null };
export type UpdateProfileAboutMutationVariables = Exact<{
data: InputMaybe<ProfileDetailsInput>;
}>;
export type UpdateProfileAboutMutation = { __typename?: 'Mutation', updateProfileDetails: { __typename?: 'MyProfile', 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 } | null };
export type ProfileQueryVariables = Exact<{
profileId: Scalars['Int'];
}>;
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, nostr_prv_key: string | null, nostr_pub_key: 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 UpdateProfileAboutMutationVariables = Exact<{
data: InputMaybe<UpdateProfileInput>;
}>;
export type UpdateProfileAboutMutation = { __typename?: 'Mutation', updateProfile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, website: string | null, role: string | null, email: string | null, lightning_address: string | null, jobTitle: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: 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 }> }> } | null };
export type CategoryPageQueryVariables = Exact<{
categoryId: Scalars['Int'];
@@ -698,6 +770,8 @@ export const MeDocument = gql`
name
avatar
join_date
jobTitle
bio
}
}
`;
@@ -1306,9 +1380,88 @@ export function usePostDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
export type PostDetailsQueryHookResult = ReturnType<typeof usePostDetailsQuery>;
export type PostDetailsLazyQueryHookResult = ReturnType<typeof usePostDetailsLazyQuery>;
export type PostDetailsQueryResult = Apollo.QueryResult<PostDetailsQuery, PostDetailsQueryVariables>;
export const ProfileDocument = gql`
query profile($profileId: Int!) {
profile(id: $profileId) {
export const MyProfilePreferencesDocument = gql`
query MyProfilePreferences {
me {
id
walletsKeys {
key
name
}
nostr_prv_key
nostr_pub_key
}
}
`;
/**
* __useMyProfilePreferencesQuery__
*
* To run a query within a React component, call `useMyProfilePreferencesQuery` and pass it any options that fit your needs.
* When your component renders, `useMyProfilePreferencesQuery` 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 } = useMyProfilePreferencesQuery({
* variables: {
* },
* });
*/
export function useMyProfilePreferencesQuery(baseOptions?: Apollo.QueryHookOptions<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>(MyProfilePreferencesDocument, options);
}
export function useMyProfilePreferencesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>(MyProfilePreferencesDocument, options);
}
export type MyProfilePreferencesQueryHookResult = ReturnType<typeof useMyProfilePreferencesQuery>;
export type MyProfilePreferencesLazyQueryHookResult = ReturnType<typeof useMyProfilePreferencesLazyQuery>;
export type MyProfilePreferencesQueryResult = Apollo.QueryResult<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>;
export const UpdateUserPreferencesDocument = gql`
mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
updateUserPreferences(userKeys: $walletsKeys) {
id
walletsKeys {
key
name
}
nostr_pub_key
nostr_prv_key
}
}
`;
export type UpdateUserPreferencesMutationFn = Apollo.MutationFunction<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>;
/**
* __useUpdateUserPreferencesMutation__
*
* To run a mutation, you first call `useUpdateUserPreferencesMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateUserPreferencesMutation` 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 [updateUserPreferencesMutation, { data, loading, error }] = useUpdateUserPreferencesMutation({
* variables: {
* walletsKeys: // value for 'walletsKeys'
* },
* });
*/
export function useUpdateUserPreferencesMutation(baseOptions?: Apollo.MutationHookOptions<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>(UpdateUserPreferencesDocument, options);
}
export type UpdateUserPreferencesMutationHookResult = ReturnType<typeof useUpdateUserPreferencesMutation>;
export type UpdateUserPreferencesMutationResult = Apollo.MutationResult<UpdateUserPreferencesMutation>;
export type UpdateUserPreferencesMutationOptions = Apollo.BaseMutationOptions<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>;
export const MyProfileAboutDocument = gql`
query MyProfileAbout {
me {
id
name
avatar
@@ -1323,61 +1476,48 @@ export const ProfileDocument = gql`
linkedin
bio
location
stories {
id
title
createdAt
tags {
id
title
icon
}
}
nostr_prv_key
nostr_pub_key
}
}
`;
/**
* __useProfileQuery__
* __useMyProfileAboutQuery__
*
* To run a query within a React component, call `useProfileQuery` and pass it any options that fit your needs.
* When your component renders, `useProfileQuery` returns an object from Apollo Client that contains loading, error, and data properties
* To run a query within a React component, call `useMyProfileAboutQuery` and pass it any options that fit your needs.
* When your component renders, `useMyProfileAboutQuery` 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 } = useProfileQuery({
* const { data, loading, error } = useMyProfileAboutQuery({
* variables: {
* profileId: // value for 'profileId'
* },
* });
*/
export function useProfileQuery(baseOptions: Apollo.QueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
export function useMyProfileAboutQuery(baseOptions?: Apollo.QueryHookOptions<MyProfileAboutQuery, MyProfileAboutQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
return Apollo.useQuery<MyProfileAboutQuery, MyProfileAboutQueryVariables>(MyProfileAboutDocument, options);
}
export function useProfileLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
export function useMyProfileAboutLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfileAboutQuery, MyProfileAboutQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
return Apollo.useLazyQuery<MyProfileAboutQuery, MyProfileAboutQueryVariables>(MyProfileAboutDocument, options);
}
export type ProfileQueryHookResult = ReturnType<typeof useProfileQuery>;
export type ProfileLazyQueryHookResult = ReturnType<typeof useProfileLazyQuery>;
export type ProfileQueryResult = Apollo.QueryResult<ProfileQuery, ProfileQueryVariables>;
export type MyProfileAboutQueryHookResult = ReturnType<typeof useMyProfileAboutQuery>;
export type MyProfileAboutLazyQueryHookResult = ReturnType<typeof useMyProfileAboutLazyQuery>;
export type MyProfileAboutQueryResult = Apollo.QueryResult<MyProfileAboutQuery, MyProfileAboutQueryVariables>;
export const UpdateProfileAboutDocument = gql`
mutation updateProfileAbout($data: UpdateProfileInput) {
updateProfile(data: $data) {
mutation updateProfileAbout($data: ProfileDetailsInput) {
updateProfileDetails(data: $data) {
id
name
avatar
join_date
website
role
email
lightning_address
jobTitle
lightning_address
website
twitter
github
linkedin
@@ -1412,6 +1552,64 @@ export function useUpdateProfileAboutMutation(baseOptions?: Apollo.MutationHookO
export type UpdateProfileAboutMutationHookResult = ReturnType<typeof useUpdateProfileAboutMutation>;
export type UpdateProfileAboutMutationResult = Apollo.MutationResult<UpdateProfileAboutMutation>;
export type UpdateProfileAboutMutationOptions = Apollo.BaseMutationOptions<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>;
export const ProfileDocument = gql`
query profile($profileId: Int!) {
profile(id: $profileId) {
id
name
avatar
join_date
role
email
jobTitle
lightning_address
website
twitter
github
linkedin
bio
location
stories {
id
title
createdAt
tags {
id
title
icon
}
}
}
}
`;
/**
* __useProfileQuery__
*
* To run a query within a React component, call `useProfileQuery` and pass it any options that fit your needs.
* When your component renders, `useProfileQuery` 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 } = useProfileQuery({
* variables: {
* profileId: // value for 'profileId'
* },
* });
*/
export function useProfileQuery(baseOptions: Apollo.QueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
}
export function useProfileLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
}
export type ProfileQueryHookResult = ReturnType<typeof useProfileQuery>;
export type ProfileLazyQueryHookResult = ReturnType<typeof useProfileLazyQuery>;
export type ProfileQueryResult = Apollo.QueryResult<ProfileQuery, ProfileQueryVariables>;
export const CategoryPageDocument = gql`
query CategoryPage($categoryId: Int!) {
projectsByCategory(category_id: $categoryId) {

View File

@@ -1,12 +1,12 @@
import { User } from "src/graphql";
import { MyProfile, User } from "src/graphql";
import { posts } from "./posts";
export const user: User = {
export const user: User & MyProfile = {
id: 123,
email: "mtg0987654321@gmail.com",
avatar: "https://avatars.dicebear.com/api/bottts/Mtgmtg.svg",
bio: "Lorem asiop asklh kluiw wekjhl shkj kljhsva klu khsc klhlkbs mjklwqr kmlk sadlfui mewr qiumnk, asdjomi cskhsdf.",
name: "123123124asdfsadfsa8d7fsadfasdf",
name: "Mtg",
github: "MTG2000",
jobTitle: "Front-end Web Developer",
join_date: new Date(2021).toISOString(),
@@ -14,9 +14,19 @@ export const user: User = {
linkedin: "https://www.linkedin.com/in/mtg-softwares-dev/",
location: "Germany, Berlin",
role: "user",
twitter: "john-doe",
twitter: "mtg",
website: "https://mtg-dev.tech",
stories: posts.stories,
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
walletsKeys: [
{
key: "1645h234j2421zxvertw",
name: "My Alby wallet key"
},
{
key: "6643534534534534543",
name: "My Phoenix wallet key"
},
]
}

View File

@@ -29,6 +29,8 @@ import {
MeQuery,
ProfileQuery,
GetMyDraftsQuery,
MyProfileAboutQuery,
MyProfilePreferencesQuery,
} from 'src/graphql'
const delay = (ms = 1000) => new Promise((res) => setTimeout(res, ms + Math.random() * 1000))
@@ -195,6 +197,7 @@ export const handlers = [
graphql.query<MeQuery>('Me', async (req, res, ctx) => {
await delay()
console.log("ME");
return res(
ctx.data({
@@ -203,6 +206,27 @@ export const handlers = [
)
}),
graphql.query<MyProfileAboutQuery>('MyProfileAbout', async (req, res, ctx) => {
await delay()
return res(
ctx.data({
me: me(),
})
)
}),
graphql.query<MyProfilePreferencesQuery>('MyProfilePreferences', async (req, res, ctx) => {
await delay()
return res(
ctx.data({
me: me(),
})
)
}),
graphql.query<ProfileQuery>('profile', async (req, res, ctx) => {
await delay()

View File

@@ -1,5 +1,5 @@
import { MOCK_DATA } from "./data";
import { 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']
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'] {

View File

@@ -8,7 +8,8 @@ import { InsertLinkModal } from 'src/Components/Inputs/TextEditor/InsertLinkModa
import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatureCard, Claim_SubmittedCard } from "src/features/Projects/pages/ProjectPage/ClaimProject";
import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer";
import { ConfirmModal } from "src/Components/Modals/ConfirmModal";
import { LinkingAccountModal } from "src/features/Profiles/pages/EditProfilePage/AccountCard/LinkingAccountModal";
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,

View File

@@ -1,12 +1,14 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { MyProfile } from "src/graphql";
interface StoreState {
me: {
id: number;
name: string;
avatar: string;
join_date: string;
}
me: Pick<MyProfile,
| 'id'
| "name"
| "avatar"
| "bio"
| "jobTitle"
| 'join_date'>
| undefined // fetching user data if exist
| null // user not logged in