feat: wired linking account api and form state, updated card ui

This commit is contained in:
MTG2000
2022-08-17 13:31:13 +03:00
parent 5a7bac8325
commit 1be2bb4fae
7 changed files with 150 additions and 104 deletions

View File

@@ -339,7 +339,7 @@ export interface NexusGenFieldTypes {
deleteStory: NexusGenRootTypes['Story'] | null; // Story deleteStory: NexusGenRootTypes['Story'] | null; // Story
donate: NexusGenRootTypes['Donation']; // Donation! donate: NexusGenRootTypes['Donation']; // Donation!
updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile
updateUserPreferences: NexusGenRootTypes['MyProfile'][]; // [MyProfile!]! updateUserPreferences: NexusGenRootTypes['MyProfile']; // MyProfile!
vote: NexusGenRootTypes['Vote']; // Vote! vote: NexusGenRootTypes['Vote']; // Vote!
} }
MyProfile: { // field return type MyProfile: { // field return type

View File

@@ -118,7 +118,7 @@ type Mutation {
deleteStory(id: Int!): Story deleteStory(id: Int!): Story
donate(amount_in_sat: Int!): Donation! donate(amount_in_sat: Int!): Donation!
updateProfileDetails(data: ProfileDetailsInput): MyProfile updateProfileDetails(data: ProfileDetailsInput): MyProfile
updateUserPreferences(userKeys: [UserKeyInputType!]): [MyProfile!]! updateUserPreferences(userKeys: [UserKeyInputType!]): MyProfile!
vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote! vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote!
} }

View File

@@ -161,7 +161,7 @@ const UserKeyInputType = inputObjectType({
const updateUserPreferences = extendType({ const updateUserPreferences = extendType({
type: 'Mutation', type: 'Mutation',
definition(t) { definition(t) {
t.nonNull.list.nonNull.field('updateUserPreferences', { t.nonNull.field('updateUserPreferences', {
type: 'MyProfile', type: 'MyProfile',
args: { userKeys: list(nonNull(UserKeyInputType)) }, args: { userKeys: list(nonNull(UserKeyInputType)) },
async resolve(_root, args, ctx) { async resolve(_root, args, ctx) {
@@ -182,7 +182,7 @@ const updateUserPreferences = extendType({
equals: user.id, equals: user.id,
}, },
key: { key: {
in: args.data.map(i => i.key) in: args.userKeys.map(i => i.key)
} }
}, },
}, },
@@ -192,15 +192,15 @@ const updateUserPreferences = extendType({
})).map(i => i.key); })).map(i => i.key);
const newKeys = []; const newKeys = [];
for (let i = 0; i < args.data.length; i++) { for (let i = 0; i < args.userKeys.length; i++) {
const item = args.data[i]; const item = args.userKeys[i];
if (userKeys.includes(item.key)) if (userKeys.includes(item.key))
newKeys.push(item); newKeys.push(item);
} }
if (newKeys.length === 0) if (newKeys.length === 0)
throw new Error("You can't delete all your keys") throw new Error("You can't delete all your wallets keys")
await prisma.userKey.deleteMany({ await prisma.userKey.deleteMany({
where: { where: {

View File

@@ -4,84 +4,63 @@ import { openModal } from 'src/redux/features/modals.slice';
import Card from 'src/Components/Card/Card'; import Card from 'src/Components/Card/Card';
import { MyProfile } from 'src/graphql'; import { MyProfile } from 'src/graphql';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
import { useReducer } from 'react'; import { useReducer, useRef } from 'react';
import { Nullable } from 'remirror';
type Value = MyProfile['walletsKeys']
interface Props { interface Props {
walletsKeys: MyProfile['walletsKeys'] value: Value,
onChange: (newValue: Value) => void
} }
type State = {
hasNewChanges: boolean,
keys: Array<{ key: string, name: string }>,
oldKeys: Array<{ key: string, name: string }>
}
// 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;
// }),
type Action = // }
| { // case 'cancel':
type: 'set' // return {
payload: State['keys'] // hasNewChanges: false,
} // keys: [...state.oldKeys],
| { // oldKeys: state.oldKeys,
type: 'delete', // }
payload: { idx: number } // }
} // }
| {
type: 'update',
payload: {
idx: number,
value: string,
}
}
| {
type: 'cancel'
}
function reducer(state: State, action: Action): State { export default function LinkedAccountsCard({ value, onChange }: Props) {
switch (action.type) {
case 'set':
return {
hasNewChanges: false,
keys: [...action.payload],
oldKeys: [...action.payload],
}
case 'delete':
if (state.keys.length === 1)
return state;
return {
hasNewChanges: true,
oldKeys: state.oldKeys,
keys: [...state.keys.slice(0, action.payload.idx), ...state.keys.slice(action.payload.idx + 1)]
};
case 'update':
return {
hasNewChanges: true,
oldKeys: state.oldKeys,
keys: state.keys.map((item, idx) => {
if (idx === action.payload.idx)
return {
...item,
name: action.payload.value
}
return item;
}),
}
case 'cancel':
return {
hasNewChanges: false,
keys: [...state.oldKeys],
oldKeys: state.oldKeys,
}
}
}
export default function LinkedAccountsCard({ walletsKeys }: Props) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [keysState, keysDispatch] = useReducer(reducer, { keys: [], oldKeys: [], hasNewChanges: false, }); const inputsRefs = useRef<Nullable<HTMLInputElement>[]>([]);
// const [keysState, keysDispatch] = useReducer(reducer, { keys: [], oldKeys: [], hasNewChanges: false, });
// const [updateKeys, updatingKeysStatus] = useUpdateUserWalletsKeysMutation({ // const [updateKeys, updatingKeysStatus] = useUpdateUserWalletsKeysMutation({
// onCompleted: data => { // onCompleted: data => {
@@ -96,16 +75,19 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) {
dispatch(openModal({ Modal: "LinkingAccountModal" })) dispatch(openModal({ Modal: "LinkingAccountModal" }))
} }
const saveChanges = () => { const updateKeyName = (idx: number, newName: string) => {
// updateKeys({ onChange(value.map((item, i) => {
// variables: { if (i === idx)
// data: keysState.keys.map(v => ({ key: v.key, name: v.name })) return {
// } ...item,
// }) name: newName
}
return item;
}))
} }
const cancelChanges = () => { const deleteKey = (idx: number,) => {
keysDispatch({ type: 'cancel' }); onChange([...value.slice(0, idx), ...value.slice(idx + 1)])
} }
@@ -117,24 +99,22 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) {
</p> </p>
<div className='mt-24 flex flex-col gap-16'> <div className='mt-24 flex flex-col gap-16'>
<ul className="mt-8 relative flex flex-col gap-8"> <ul className="mt-8 relative flex flex-col gap-8">
{walletsKeys.map((item, idx) => {value.map((item, idx) =>
<li key={item.key} className="flex justify-between items-center text-body4 border-b py-12 px-16 border border-gray-200 rounded-16"> <li key={item.key} 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">
<input <input
ref={el => inputsRefs.current[idx] = el}
type="text" type="text"
value={item.name} value={item.name}
onChange={e => { onChange={e => {
keysDispatch({ updateKeyName(idx, e.target.value)
type: 'update',
payload: {
idx,
value: e.target.value
}
})
}} }}
className='p-0 border-0 focus:border-0 focus:outline-none grow className='p-0 border-0 focus:border-0 focus:outline-none grow
focus:ring-0 placeholder:!text-gray-400' /> focus:ring-0 placeholder:!text-gray-400' />
{walletsKeys.length > 1 && <Button size='sm' color='none' className='text-red-500 !p-0' onClick={() => keysDispatch({ type: 'delete', payload: { idx } })}>Delete key</Button>} <div className='flex gap-8 ml-auto'>
<Button size='sm' color='none' className='text-blue-400 !p-0' onClick={() => inputsRefs.current[idx]?.focus()}>Rename</Button>
{value.length > 1 && <Button size='sm' color='none' className='text-red-500 !p-0' onClick={() => deleteKey(idx)}>Delete key</Button>}
</div>
</li> </li>
)} )}
</ul> </ul>
@@ -157,7 +137,7 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) {
Save Changes Save Changes
</Button> </Button>
</div> */} </div> */}
{walletsKeys.length < 3 && {value.length < 3 &&
<Button color='white' className='mt-16' onClick={connectNewWallet}> <Button color='white' className='mt-16' onClick={connectNewWallet}>
Connect new wallet Connect new wallet
</Button>} </Button>}

View File

@@ -1,18 +1,45 @@
import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard'; import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard';
import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard'; import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard';
import { useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql'; import { UpdateUserPreferencesMutationVariables, useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql';
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'; import LoadingPage from 'src/Components/LoadingPage/LoadingPage';
import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage"; import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import SaveChangesCard from '../SaveChangesCard/SaveChangesCard';
import { toast } from 'react-toastify';
import { NotificationsService } from 'src/services';
interface Props { interface 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() { export default function PreferencesTab() {
const query = useMyProfilePreferencesQuery(); const { register, formState: { errors, isDirty, }, handleSubmit, reset, control } = useForm<IProfilePreferencesForm>({
defaultValues: {
walletsKeys: []
},
resolver: yupResolver(schema),
mode: 'onBlur',
});
const query = useMyProfilePreferencesQuery({
onCompleted: data => {
if (data.me) reset(data.me)
}
});
const [mutate, mutationStatus] = useUpdateUserPreferencesMutation(); const [mutate, mutationStatus] = useUpdateUserPreferencesMutation();
if (query.loading) if (query.loading)
@@ -21,12 +48,51 @@ export default function PreferencesTab() {
if (!query.data?.me) if (!query.data?.me)
return <NotFoundPage /> 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 ( return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-24"> <div className="grid grid-cols-1 md:grid-cols-3 gap-24">
<div className="col-span-2 flex flex-col gap-24"> <div className="col-span-2 flex flex-col gap-24">
<LinkedAccountsCard walletsKeys={query.data.me.walletsKeys} /> <Controller
control={control}
name="walletsKeys"
render={({ field: { onChange, value } }) => (
<LinkedAccountsCard value={value as any} onChange={onChange} />
)}
/>
<CommentsSettingsCard nostr_prv_key={query.data.me.nostr_prv_key} nostr_pub_key={query.data.me.nostr_pub_key} /> <CommentsSettingsCard nostr_prv_key={query.data.me.nostr_prv_key} nostr_pub_key={query.data.me.nostr_pub_key} />
</div> </div>
<div className="self-start sticky-side-element">
<SaveChangesCard
isLoading={mutationStatus.loading}
isDirty={isDirty}
onSubmit={handleSubmit(onSubmit)}
onCancel={() => reset()}
/>
</div>
</div> </div>
) )
} }

View File

@@ -9,8 +9,8 @@ query MyProfilePreferences {
} }
} }
mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) { mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
updateUserPreferences(userKeys: $userKeys) { updateUserPreferences(userKeys: $walletsKeys) {
walletsKeys { walletsKeys {
key key
name name

View File

@@ -140,7 +140,7 @@ export type Mutation = {
deleteStory: Maybe<Story>; deleteStory: Maybe<Story>;
donate: Donation; donate: Donation;
updateProfileDetails: Maybe<MyProfile>; updateProfileDetails: Maybe<MyProfile>;
updateUserPreferences: Array<MyProfile>; updateUserPreferences: MyProfile;
vote: Vote; vote: Vote;
}; };
@@ -576,11 +576,11 @@ export type MyProfilePreferencesQueryVariables = Exact<{ [key: string]: never; }
export type MyProfilePreferencesQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', nostr_prv_key: string | null, nostr_pub_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } | null }; export type MyProfilePreferencesQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', nostr_prv_key: string | null, nostr_pub_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } | null };
export type UpdateUserPreferencesMutationVariables = Exact<{ export type UpdateUserPreferencesMutationVariables = Exact<{
userKeys: InputMaybe<Array<UserKeyInputType> | UserKeyInputType>; walletsKeys: InputMaybe<Array<UserKeyInputType> | UserKeyInputType>;
}>; }>;
export type UpdateUserPreferencesMutation = { __typename?: 'Mutation', updateUserPreferences: Array<{ __typename?: 'MyProfile', nostr_pub_key: string | null, nostr_prv_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> }> }; export type UpdateUserPreferencesMutation = { __typename?: 'Mutation', updateUserPreferences: { __typename?: 'MyProfile', nostr_pub_key: string | null, nostr_prv_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } };
export type MyProfileAboutQueryVariables = Exact<{ [key: string]: never; }>; export type MyProfileAboutQueryVariables = Exact<{ [key: string]: never; }>;
@@ -1420,8 +1420,8 @@ export type MyProfilePreferencesQueryHookResult = ReturnType<typeof useMyProfile
export type MyProfilePreferencesLazyQueryHookResult = ReturnType<typeof useMyProfilePreferencesLazyQuery>; export type MyProfilePreferencesLazyQueryHookResult = ReturnType<typeof useMyProfilePreferencesLazyQuery>;
export type MyProfilePreferencesQueryResult = Apollo.QueryResult<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>; export type MyProfilePreferencesQueryResult = Apollo.QueryResult<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>;
export const UpdateUserPreferencesDocument = gql` export const UpdateUserPreferencesDocument = gql`
mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) { mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
updateUserPreferences(userKeys: $userKeys) { updateUserPreferences(userKeys: $walletsKeys) {
walletsKeys { walletsKeys {
key key
name name
@@ -1446,7 +1446,7 @@ export type UpdateUserPreferencesMutationFn = Apollo.MutationFunction<UpdateUser
* @example * @example
* const [updateUserPreferencesMutation, { data, loading, error }] = useUpdateUserPreferencesMutation({ * const [updateUserPreferencesMutation, { data, loading, error }] = useUpdateUserPreferencesMutation({
* variables: { * variables: {
* userKeys: // value for 'userKeys' * walletsKeys: // value for 'walletsKeys'
* }, * },
* }); * });
*/ */