mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-18 14:54:23 +01:00
feat: wired linking account api and form state, updated card ui
This commit is contained in:
@@ -339,7 +339,7 @@ export interface NexusGenFieldTypes {
|
||||
deleteStory: NexusGenRootTypes['Story'] | null; // Story
|
||||
donate: NexusGenRootTypes['Donation']; // Donation!
|
||||
updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile
|
||||
updateUserPreferences: NexusGenRootTypes['MyProfile'][]; // [MyProfile!]!
|
||||
updateUserPreferences: NexusGenRootTypes['MyProfile']; // MyProfile!
|
||||
vote: NexusGenRootTypes['Vote']; // Vote!
|
||||
}
|
||||
MyProfile: { // field return type
|
||||
|
||||
@@ -118,7 +118,7 @@ type Mutation {
|
||||
deleteStory(id: Int!): Story
|
||||
donate(amount_in_sat: Int!): Donation!
|
||||
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!
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ const UserKeyInputType = inputObjectType({
|
||||
const updateUserPreferences = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('updateUserPreferences', {
|
||||
t.nonNull.field('updateUserPreferences', {
|
||||
type: 'MyProfile',
|
||||
args: { userKeys: list(nonNull(UserKeyInputType)) },
|
||||
async resolve(_root, args, ctx) {
|
||||
@@ -182,7 +182,7 @@ const updateUserPreferences = extendType({
|
||||
equals: user.id,
|
||||
},
|
||||
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);
|
||||
|
||||
const newKeys = [];
|
||||
for (let i = 0; i < args.data.length; i++) {
|
||||
const item = args.data[i];
|
||||
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 keys")
|
||||
throw new Error("You can't delete all your wallets keys")
|
||||
|
||||
await prisma.userKey.deleteMany({
|
||||
where: {
|
||||
|
||||
@@ -4,84 +4,63 @@ import { openModal } from 'src/redux/features/modals.slice';
|
||||
import Card from 'src/Components/Card/Card';
|
||||
import { MyProfile } from 'src/graphql';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { useReducer } from 'react';
|
||||
import { useReducer, useRef } from 'react';
|
||||
import { Nullable } from 'remirror';
|
||||
|
||||
|
||||
type Value = MyProfile['walletsKeys']
|
||||
|
||||
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 =
|
||||
| {
|
||||
type: 'set'
|
||||
payload: State['keys']
|
||||
}
|
||||
| {
|
||||
type: 'delete',
|
||||
payload: { idx: number }
|
||||
}
|
||||
| {
|
||||
type: 'update',
|
||||
payload: {
|
||||
idx: number,
|
||||
value: string,
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'cancel'
|
||||
}
|
||||
// }
|
||||
// case 'cancel':
|
||||
// return {
|
||||
// hasNewChanges: false,
|
||||
// keys: [...state.oldKeys],
|
||||
// oldKeys: state.oldKeys,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case 'set':
|
||||
return {
|
||||
hasNewChanges: false,
|
||||
keys: [...action.payload],
|
||||
oldKeys: [...action.payload],
|
||||
}
|
||||
case 'delete':
|
||||
if (state.keys.length === 1)
|
||||
return state;
|
||||
return {
|
||||
hasNewChanges: true,
|
||||
oldKeys: state.oldKeys,
|
||||
keys: [...state.keys.slice(0, action.payload.idx), ...state.keys.slice(action.payload.idx + 1)]
|
||||
};
|
||||
case 'update':
|
||||
return {
|
||||
hasNewChanges: true,
|
||||
oldKeys: state.oldKeys,
|
||||
keys: state.keys.map((item, idx) => {
|
||||
if (idx === action.payload.idx)
|
||||
return {
|
||||
...item,
|
||||
name: action.payload.value
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
|
||||
}
|
||||
case 'cancel':
|
||||
return {
|
||||
hasNewChanges: false,
|
||||
keys: [...state.oldKeys],
|
||||
oldKeys: state.oldKeys,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function LinkedAccountsCard({ walletsKeys }: Props) {
|
||||
export default function LinkedAccountsCard({ value, onChange }: Props) {
|
||||
|
||||
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({
|
||||
// onCompleted: data => {
|
||||
@@ -96,16 +75,19 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) {
|
||||
dispatch(openModal({ Modal: "LinkingAccountModal" }))
|
||||
}
|
||||
|
||||
const saveChanges = () => {
|
||||
// updateKeys({
|
||||
// variables: {
|
||||
// data: keysState.keys.map(v => ({ key: v.key, name: v.name }))
|
||||
// }
|
||||
// })
|
||||
const updateKeyName = (idx: number, newName: string) => {
|
||||
onChange(value.map((item, i) => {
|
||||
if (i === idx)
|
||||
return {
|
||||
...item,
|
||||
name: newName
|
||||
}
|
||||
return item;
|
||||
}))
|
||||
}
|
||||
|
||||
const cancelChanges = () => {
|
||||
keysDispatch({ type: 'cancel' });
|
||||
const deleteKey = (idx: number,) => {
|
||||
onChange([...value.slice(0, idx), ...value.slice(idx + 1)])
|
||||
}
|
||||
|
||||
|
||||
@@ -117,24 +99,22 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) {
|
||||
</p>
|
||||
<div className='mt-24 flex flex-col gap-16'>
|
||||
<ul className="mt-8 relative flex flex-col gap-8">
|
||||
{walletsKeys.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">
|
||||
{value.map((item, idx) =>
|
||||
<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
|
||||
ref={el => inputsRefs.current[idx] = el}
|
||||
type="text"
|
||||
value={item.name}
|
||||
onChange={e => {
|
||||
keysDispatch({
|
||||
type: 'update',
|
||||
payload: {
|
||||
idx,
|
||||
value: e.target.value
|
||||
}
|
||||
})
|
||||
updateKeyName(idx, e.target.value)
|
||||
}}
|
||||
className='p-0 border-0 focus:border-0 focus:outline-none grow
|
||||
focus:ring-0 placeholder:!text-gray-400' />
|
||||
|
||||
{walletsKeys.length > 1 && <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>
|
||||
)}
|
||||
</ul>
|
||||
@@ -157,7 +137,7 @@ export default function LinkedAccountsCard({ walletsKeys }: Props) {
|
||||
Save Changes
|
||||
</Button>
|
||||
</div> */}
|
||||
{walletsKeys.length < 3 &&
|
||||
{value.length < 3 &&
|
||||
<Button color='white' className='mt-16' onClick={connectNewWallet}>
|
||||
Connect new wallet ⚡
|
||||
</Button>}
|
||||
|
||||
@@ -1,18 +1,45 @@
|
||||
import LinkedAccountsCard from './LinkedAccountsCard/LinkedAccountsCard';
|
||||
import CommentsSettingsCard from './CommentsSettingsCard/CommentsSettingsCard';
|
||||
import { useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql';
|
||||
import { UpdateUserPreferencesMutationVariables, useMyProfilePreferencesQuery, useUpdateUserPreferencesMutation } from 'src/graphql';
|
||||
import LoadingPage from 'src/Components/LoadingPage/LoadingPage';
|
||||
import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage";
|
||||
|
||||
import * as yup from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import SaveChangesCard from '../SaveChangesCard/SaveChangesCard';
|
||||
import { toast } from 'react-toastify';
|
||||
import { NotificationsService } from 'src/services';
|
||||
|
||||
|
||||
interface Props {
|
||||
}
|
||||
|
||||
export type IProfilePreferencesForm = NonNullable<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 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();
|
||||
|
||||
if (query.loading)
|
||||
@@ -21,12 +48,51 @@ export default function PreferencesTab() {
|
||||
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 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} />
|
||||
</div>
|
||||
<div className="self-start sticky-side-element">
|
||||
<SaveChangesCard
|
||||
isLoading={mutationStatus.loading}
|
||||
isDirty={isDirty}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
onCancel={() => reset()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ query MyProfilePreferences {
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) {
|
||||
updateUserPreferences(userKeys: $userKeys) {
|
||||
mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
|
||||
updateUserPreferences(userKeys: $walletsKeys) {
|
||||
walletsKeys {
|
||||
key
|
||||
name
|
||||
|
||||
@@ -140,7 +140,7 @@ export type Mutation = {
|
||||
deleteStory: Maybe<Story>;
|
||||
donate: Donation;
|
||||
updateProfileDetails: Maybe<MyProfile>;
|
||||
updateUserPreferences: Array<MyProfile>;
|
||||
updateUserPreferences: MyProfile;
|
||||
vote: Vote;
|
||||
};
|
||||
|
||||
@@ -576,11 +576,11 @@ export type MyProfilePreferencesQueryVariables = Exact<{ [key: string]: never; }
|
||||
export type MyProfilePreferencesQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', nostr_prv_key: string | null, nostr_pub_key: string | null, walletsKeys: Array<{ __typename?: 'WalletKey', key: string, name: string }> } | null };
|
||||
|
||||
export type UpdateUserPreferencesMutationVariables = Exact<{
|
||||
userKeys: InputMaybe<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; }>;
|
||||
|
||||
@@ -1420,8 +1420,8 @@ export type MyProfilePreferencesQueryHookResult = ReturnType<typeof useMyProfile
|
||||
export type MyProfilePreferencesLazyQueryHookResult = ReturnType<typeof useMyProfilePreferencesLazyQuery>;
|
||||
export type MyProfilePreferencesQueryResult = Apollo.QueryResult<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>;
|
||||
export const UpdateUserPreferencesDocument = gql`
|
||||
mutation UpdateUserPreferences($userKeys: [UserKeyInputType!]) {
|
||||
updateUserPreferences(userKeys: $userKeys) {
|
||||
mutation UpdateUserPreferences($walletsKeys: [UserKeyInputType!]) {
|
||||
updateUserPreferences(userKeys: $walletsKeys) {
|
||||
walletsKeys {
|
||||
key
|
||||
name
|
||||
@@ -1446,7 +1446,7 @@ export type UpdateUserPreferencesMutationFn = Apollo.MutationFunction<UpdateUser
|
||||
* @example
|
||||
* const [updateUserPreferencesMutation, { data, loading, error }] = useUpdateUserPreferencesMutation({
|
||||
* variables: {
|
||||
* userKeys: // value for 'userKeys'
|
||||
* walletsKeys: // value for 'walletsKeys'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user