From fccd6f61da42a5d76b13b5992f73c4d13aaf1ad1 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Fri, 19 Apr 2024 15:37:20 +0200
Subject: [PATCH 01/13] feat(SPV-679): upsert and serach contacts with actual
backend functionality
---
src/api/requests/contact.ts | 45 +++++++++++--------
src/api/types/contact.ts | 13 ++++--
.../ContactsTable.tsx/ContactsTable.tsx | 6 +--
.../ContactsTable.tsx/JustAddedContcatMsg.tsx | 2 +-
.../ContactsTable.tsx/StatusBadge.tsx | 11 ++++-
.../ContactAddModal/ContactAddModal.tsx | 5 ++-
.../_modals/VerifyModal/VerifyModal.tsx | 10 ++---
7 files changed, 57 insertions(+), 35 deletions(-)
diff --git a/src/api/requests/contact.ts b/src/api/requests/contact.ts
index a96c700..5849305 100644
--- a/src/api/requests/contact.ts
+++ b/src/api/requests/contact.ts
@@ -2,22 +2,17 @@
import { timeoutPromise } from '@/utils/timeoutPromise';
import { PaginationParams } from '../types';
import { Contact } from '../types/contact';
+import axios from 'axios';
export const searchContacts = async (_pagination?: PaginationParams) => {
- await timeoutPromise(500);
- return contacts;
+ const { data: response } = await axios.get(`/contact/search`);
+ return response;
};
-export const addContact = async (paymail: string, name: string) => {
- await timeoutPromise(1000);
- contacts = [
- ...contacts,
- {
- paymail,
- name,
- status: 'awaiting-acceptance',
- },
- ];
+export const upsertContact = async (paymail: string, fullName: string) => {
+ await axios.put(`/contact/${encodeURIPaymail(paymail)}`, {
+ fullName,
+ });
};
export const rejectContact = async (paymail: string) => {
@@ -30,7 +25,7 @@ export const acceptContact = async (paymail: string) => {
await timeoutPromise(1000);
contacts = contacts.map((contact) => {
if (contact.paymail === paymail) {
- contact.status = 'not-confirmed';
+ contact.status = 'unconfirmed';
}
return { ...contact };
});
@@ -51,22 +46,36 @@ export const confirmContactWithTOTP = async (paymail: string, _totp: number) =>
});
};
+const encodeURIPaymail = (paymail: string) => {
+ // Remove control characters from the paymail
+ function* iterator() {
+ for (let i = 0; i < paymail.length; i++) {
+ const code = paymail.charCodeAt(i);
+ if (code > 32 && code !== 127) {
+ yield code;
+ }
+ }
+ }
+ const sanitizedPaymail = String.fromCharCode(...iterator());
+ return encodeURIComponent(sanitizedPaymail);
+};
+
/// Mocked contacts
let contacts: Contact[] = [
{
paymail: 'bob@example.com',
- name: 'Bob',
+ fullName: 'Bob',
status: 'confirmed',
},
{
paymail: 'tester@example.com',
- name: 'Tester',
- status: 'awaiting-acceptance',
+ fullName: 'Tester',
+ status: 'awaiting',
},
{
paymail: 'theguy@example.com',
- name: 'The Guy',
- status: 'not-confirmed',
+ fullName: 'The Guy',
+ status: 'unconfirmed',
},
];
diff --git a/src/api/types/contact.ts b/src/api/types/contact.ts
index bd202ad..047397a 100644
--- a/src/api/types/contact.ts
+++ b/src/api/types/contact.ts
@@ -1,12 +1,17 @@
-export const ContactAwaitingAcceptance = 'awaiting-acceptance';
-export const ContactNotConfirmed = 'not-confirmed';
+export const ContactAwaitingAcceptance = 'awaiting';
+export const ContactNotConfirmed = 'unconfirmed';
export const ContactConfirmed = 'confirmed';
+export const ContactRejected = 'rejected';
-export type ContactStatus = typeof ContactAwaitingAcceptance | typeof ContactNotConfirmed | typeof ContactConfirmed;
+export type ContactStatus =
+ | typeof ContactAwaitingAcceptance
+ | typeof ContactNotConfirmed
+ | typeof ContactConfirmed
+ | typeof ContactRejected;
export type Contact = {
paymail: string;
- name: string;
+ fullName: string;
status: ContactStatus;
//TODO: Add more fields (...metadata)
diff --git a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
index ddfe9eb..c9c902d 100644
--- a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
@@ -57,10 +57,10 @@ export const ContactsTable: FC = () => {
- {sortedContacts.map(({ paymail, status, name }) => (
+ {sortedContacts.map(({ paymail, status, fullName }) => (
{paymail}
- {name}
+ {fullName}
@@ -101,7 +101,7 @@ export const ContactsTable: FC = () => {
setJustAddedContact(false);
}}
>
- {justAddedContact && contactForVerification.status === 'not-confirmed' && }
+ {justAddedContact && contactForVerification.status === 'unconfirmed' && }
)}
>
diff --git a/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx b/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx
index 7bb3bd1..f59165d 100644
--- a/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx
@@ -7,7 +7,7 @@ export const JustAddedContactMsg: FC = () => {
return (
You've successfully accepted the contact.
- Until confirmed, it will be displayed as .
+ Until confirmed, it will be displayed as .
You can confirm it right now or return to this process later by using the "Show code" button.
);
diff --git a/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx b/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
index 1490195..c43a1cc 100644
--- a/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
@@ -1,4 +1,10 @@
-import { ContactAwaitingAcceptance, ContactConfirmed, ContactNotConfirmed, ContactStatus } from '@/api/types/contact';
+import {
+ ContactAwaitingAcceptance,
+ ContactConfirmed,
+ ContactNotConfirmed,
+ ContactStatus,
+ ContactRejected,
+} from '@/api/types/contact';
import { FC } from 'react';
import { Chip, ChipProps } from '@mui/material';
@@ -7,7 +13,7 @@ type StatusBadgeProps = {
};
export const StatusBadge: FC = ({ status }) => {
- const { label, color } = contactStatuses[status];
+ const { label, color } = contactStatuses[status] ?? { label: 'Unknown', color: 'error' };
return ;
};
@@ -16,4 +22,5 @@ const contactStatuses: Record = ({ open, onSubmitted, o
setLoading(true);
setError(false);
try {
- addContact(paymail, name);
+ await upsertContact(paymail, name);
onSuccess();
} catch (error) {
+ console.log('error', error);
setError(true);
} finally {
setLoading(false);
diff --git a/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx b/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
index 47dcbdc..b10b751 100644
--- a/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
@@ -13,7 +13,7 @@ type VerifyModalProps = {
};
export const VerifyModal: FC> = ({ children, peer, onConfirmed, onClose }) => {
- const { name, paymail, status } = peer;
+ const { fullName, paymail, status } = peer;
const yourTOTP = useYourTOTP(paymail);
const peerTOTP = usePeerTOTP(paymail, onConfirmed);
@@ -24,7 +24,7 @@ export const VerifyModal: FC> = ({ children,
return (
> = ({ children,
>
{children}
-
+
{status === ContactConfirmed ? (
- {name} is your trusted contact.
+ {fullName} is your trusted contact.
) : (
-
+
)}
From 1d0061767b777217d224b264bb4aaa6f609a8530 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Mon, 22 Apr 2024 12:48:51 +0200
Subject: [PATCH 02/13] feat(SPV-679): modals for upserting a contact
---
src/api/requests/contact.ts | 55 ++++-----------
src/api/types/contact.ts | 12 +++-
.../ContactsTable.tsx/ContactsTable.tsx | 67 ++++++++++--------
.../ContactsList/_modals/ContactAdd.tsx | 4 +-
.../ContactsList/_modals/ContactEdit.tsx | 29 ++++++++
.../ContactUpsertModal/ContactAddModal.tsx | 24 +++++++
.../ContactUpsertModal/ContactEditModal.tsx | 34 ++++++++++
.../ContactUpsertModal.tsx} | 68 +++++++++++++------
.../_modals/ContactUpsertModal/index.ts | 2 +
.../ContactUpsertModal/useContactFields.tsx | 18 +++++
.../_modals/VerifyModal/PeerTOTP.tsx | 6 +-
.../_modals/VerifyModal/VerifyModal.tsx | 4 +-
.../_modals/VerifyModal/YourTOTP.tsx | 8 +--
src/components/ContactsList/_modals/index.ts | 4 +-
src/components/Input/Input.styles.ts | 2 +-
src/components/Input/PaymailInput.tsx | 26 +++----
src/components/Input/types.ts | 1 +
src/components/Modal/Modal.styles.ts | 2 +
src/styles/colors.ts | 1 +
src/styles/variables.ts | 10 +--
src/utils/helpers/validatePhone.ts | 5 ++
21 files changed, 258 insertions(+), 124 deletions(-)
create mode 100644 src/components/ContactsList/_modals/ContactEdit.tsx
create mode 100644 src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx
create mode 100644 src/components/ContactsList/_modals/ContactUpsertModal/ContactEditModal.tsx
rename src/components/ContactsList/_modals/{ContactAddModal/ContactAddModal.tsx => ContactUpsertModal/ContactUpsertModal.tsx} (53%)
create mode 100644 src/components/ContactsList/_modals/ContactUpsertModal/index.ts
create mode 100644 src/components/ContactsList/_modals/ContactUpsertModal/useContactFields.tsx
create mode 100644 src/utils/helpers/validatePhone.ts
diff --git a/src/api/requests/contact.ts b/src/api/requests/contact.ts
index 5849305..af96e5a 100644
--- a/src/api/requests/contact.ts
+++ b/src/api/requests/contact.ts
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { timeoutPromise } from '@/utils/timeoutPromise';
import { PaginationParams } from '../types';
-import { Contact } from '../types/contact';
+import { Contact, ContactMetadata } from '../types/contact';
import axios from 'axios';
export const searchContacts = async (_pagination?: PaginationParams) => {
@@ -9,40 +8,30 @@ export const searchContacts = async (_pagination?: PaginationParams) => {
return response;
};
-export const upsertContact = async (paymail: string, fullName: string) => {
+export const upsertContact = async (paymail: string, fullName: string, metadata?: ContactMetadata) => {
await axios.put(`/contact/${encodeURIPaymail(paymail)}`, {
fullName,
+ metadata,
});
};
export const rejectContact = async (paymail: string) => {
- console.log('reject');
- await timeoutPromise(1000);
- contacts = contacts.filter((contact) => contact.paymail !== paymail);
+ await axios.patch(`/contact/rejected/${encodeURIPaymail(paymail)}`);
};
export const acceptContact = async (paymail: string) => {
- await timeoutPromise(1000);
- contacts = contacts.map((contact) => {
- if (contact.paymail === paymail) {
- contact.status = 'unconfirmed';
- }
- return { ...contact };
- });
+ await axios.patch(`/contact/accepted/${encodeURIPaymail(paymail)}`);
};
-export const getTOTP = async (_paymail: string) => {
- await timeoutPromise(1000);
- return Math.floor(10 + Math.random() * 89);
+export const getTOTP = async (contact: Contact) => {
+ const res = await axios.post(`/contact/totp`, contact);
+ return res.data.passcode;
};
-export const confirmContactWithTOTP = async (paymail: string, _totp: number) => {
- await timeoutPromise(1000);
- contacts = contacts.map((contact) => {
- if (contact.paymail === paymail) {
- contact.status = 'confirmed';
- }
- return { ...contact };
+export const confirmContactWithTOTP = async (contact: Contact, totp: number) => {
+ await axios.patch(`/contact/confirmed`, {
+ passcode: totp,
+ contact,
});
};
@@ -59,23 +48,3 @@ const encodeURIPaymail = (paymail: string) => {
const sanitizedPaymail = String.fromCharCode(...iterator());
return encodeURIComponent(sanitizedPaymail);
};
-
-/// Mocked contacts
-
-let contacts: Contact[] = [
- {
- paymail: 'bob@example.com',
- fullName: 'Bob',
- status: 'confirmed',
- },
- {
- paymail: 'tester@example.com',
- fullName: 'Tester',
- status: 'awaiting',
- },
- {
- paymail: 'theguy@example.com',
- fullName: 'The Guy',
- status: 'unconfirmed',
- },
-];
diff --git a/src/api/types/contact.ts b/src/api/types/contact.ts
index 047397a..1c3644f 100644
--- a/src/api/types/contact.ts
+++ b/src/api/types/contact.ts
@@ -10,9 +10,17 @@ export type ContactStatus =
| typeof ContactRejected;
export type Contact = {
+ created_at: string;
+ updated_at: string;
+ deleted_at: string;
+ metadata?: ContactMetadata;
+ id: string;
+ pubKey: string;
paymail: string;
- fullName: string;
status: ContactStatus;
+ fullName: string;
+};
- //TODO: Add more fields (...metadata)
+export type ContactMetadata = {
+ phoneNumber?: string;
};
diff --git a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
index c9c902d..6f91951 100644
--- a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
@@ -12,7 +12,7 @@ import {
import { SetPaymailButton } from '@/components/TransferForm/SetPaymailButton';
import { FC, useMemo, useState } from 'react';
import { StatusBadge } from './StatusBadge';
-import { VerifyModal } from '../_modals';
+import { ContactEdit, VerifyModal } from '../_modals';
import { SmallButton } from '@/components/Button';
import { AcceptReject } from '../AcceptReject';
import { useContacts } from '@/providers';
@@ -57,37 +57,44 @@ export const ContactsTable: FC = () => {
- {sortedContacts.map(({ paymail, status, fullName }) => (
-
- {paymail}
- {fullName}
-
-
-
-
- {status !== ContactAwaitingAcceptance ? (
- {
- openVerificationWindow(paymail);
- }}
- >
- Show code
-
- ) : (
- {
+ const { paymail, fullName, status } = contact;
+ return (
+
+ {paymail}
+ {fullName}
+
+
+
+
+
+ {status !== ContactAwaitingAcceptance ? (
+ {
+ openVerificationWindow(paymail);
+ }}
+ >
+ Show code
+
+ ) : (
+ {
+ openVerificationWindow(paymail, true);
+ refresh();
+ }}
+ onReject={refresh}
+ />
+ )}
+ {
- openVerificationWindow(paymail, true);
- refresh();
- }}
- onReject={refresh}
+ variant={status === ContactConfirmed ? 'accept' : 'primary'}
/>
- )}
-
-
-
- ))}
+
+
+ );
+ })}
)}
diff --git a/src/components/ContactsList/_modals/ContactAdd.tsx b/src/components/ContactsList/_modals/ContactAdd.tsx
index 7117f14..081af60 100644
--- a/src/components/ContactsList/_modals/ContactAdd.tsx
+++ b/src/components/ContactsList/_modals/ContactAdd.tsx
@@ -1,6 +1,6 @@
import { FC, useState } from 'react';
import { Button } from '@/components/Button';
-import { ContactAddModal } from './ContactAddModal/ContactAddModal';
+import { ContactAddModal } from './ContactUpsertModal';
import { useContacts } from '@/providers';
export const ContactAdd: FC = () => {
@@ -17,7 +17,7 @@ export const ContactAdd: FC = () => {
- {open && setOpen(false)} />}
+ {open && setOpen(false)} />}
>
);
};
diff --git a/src/components/ContactsList/_modals/ContactEdit.tsx b/src/components/ContactsList/_modals/ContactEdit.tsx
new file mode 100644
index 0000000..b893f53
--- /dev/null
+++ b/src/components/ContactsList/_modals/ContactEdit.tsx
@@ -0,0 +1,29 @@
+import { FC, useState } from 'react';
+import { SmallButton } from '@/components/Button';
+import { ContactEditModal } from './ContactUpsertModal';
+import { useContacts } from '@/providers';
+import { Contact } from '@/api';
+import EditIcon from '@mui/icons-material/Edit';
+
+type ContactEditProps = {
+ contact: Contact;
+};
+
+export const ContactEdit: FC = ({ contact }) => {
+ const [open, setOpen] = useState(false);
+ const { refresh } = useContacts();
+
+ const onSubmitted = () => {
+ setOpen(false);
+ refresh();
+ };
+
+ return (
+ <>
+ setOpen(true)}>
+
+
+ {open && setOpen(false)} />}
+ >
+ );
+};
diff --git a/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx b/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx
new file mode 100644
index 0000000..612c0d6
--- /dev/null
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx
@@ -0,0 +1,24 @@
+import { FC } from 'react';
+import { ContactUpsertModal } from './ContactUpsertModal';
+import { useContactFields } from './useContactFields';
+
+type ContactAddModalProps = {
+ onSubmitted: () => void;
+ onCancel: () => void;
+};
+
+export const ContactAddModal: FC = ({ onSubmitted, onCancel }) => {
+ const fields = useContactFields();
+ return (
+
+ );
+};
diff --git a/src/components/ContactsList/_modals/ContactUpsertModal/ContactEditModal.tsx b/src/components/ContactsList/_modals/ContactUpsertModal/ContactEditModal.tsx
new file mode 100644
index 0000000..974c833
--- /dev/null
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/ContactEditModal.tsx
@@ -0,0 +1,34 @@
+import { FC, useEffect } from 'react';
+import { ContactUpsertModal } from './ContactUpsertModal';
+import { useContactFields } from './useContactFields';
+import { Contact } from '@/api';
+
+type ContactEditModalProps = {
+ contact: Contact;
+ onSubmitted: () => void;
+ onCancel: () => void;
+};
+
+export const ContactEditModal: FC = ({ onSubmitted, onCancel, contact }) => {
+ const fields = useContactFields();
+ const { setPaymail, setName, setPhone } = fields;
+
+ useEffect(() => {
+ setPaymail(contact.paymail);
+ setName(contact.fullName);
+ setPhone(contact?.metadata?.phoneNumber ?? '');
+ }, [contact, setName, setPaymail, setPhone]);
+
+ return (
+
+ );
+};
diff --git a/src/components/ContactsList/_modals/ContactAddModal/ContactAddModal.tsx b/src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx
similarity index 53%
rename from src/components/ContactsList/_modals/ContactAddModal/ContactAddModal.tsx
rename to src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx
index f085af9..1c533b3 100644
--- a/src/components/ContactsList/_modals/ContactAddModal/ContactAddModal.tsx
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx
@@ -7,36 +7,58 @@ import { Input } from '@/components/Input';
import { upsertContact } from '@/api/requests/contact';
import { modalCloseTimeout } from '@/components/Modal/modalCloseTimeout';
import { ErrorBar } from '@/components/ErrorBar';
+import { isValidPhone } from '@/utils/helpers/validatePhone';
+import { ContactFields } from './useContactFields';
-type ContactAddModalProps = {
- open: boolean;
+type ContactUpsertModal = {
onSubmitted: () => void;
onCancel: () => void;
+ sucessMsg: string;
+ errorMsg: string;
+ modalTitle: string;
+ modalSubtitle: string;
+ fields: ContactFields;
+ disabledPaymailInput: boolean;
};
-export const ContactAddModal: FC = ({ open, onSubmitted, onCancel }) => {
- const [paymail, setPaymail] = useState('');
- const [name, setName] = useState('');
+export const ContactUpsertModal: FC = ({
+ onSubmitted,
+ onCancel,
+ fields,
+ errorMsg,
+ sucessMsg,
+ modalTitle,
+ modalSubtitle,
+ disabledPaymailInput,
+}) => {
const [loading, setLoading] = useState(false);
- const [error, setError] = useState(false);
-
+ const [error, setError] = useState(undefined);
const [successMessage, setSuccessMessage] = useState('');
+ const { name, paymail, phone, setName, setPaymail, setPhone } = fields;
const onSuccess = async () => {
- setSuccessMessage('Contact added successfully!');
+ setSuccessMessage(sucessMsg);
await modalCloseTimeout();
onSubmitted();
};
const onSubmit = async () => {
+ if (phone && !isValidPhone(phone)) {
+ setError('Wrong phone number.');
+ return;
+ }
+ if (!paymail || !name) {
+ setError('Both Paymail and Name are required');
+ return;
+ }
setLoading(true);
- setError(false);
+ setError(undefined);
try {
- await upsertContact(paymail, name);
+ await upsertContact(paymail, name, phone ? { phoneNumber: phone } : undefined);
onSuccess();
} catch (error) {
- console.log('error', error);
- setError(true);
+ console.error('error', error);
+ setError(errorMsg);
} finally {
setLoading(false);
}
@@ -44,12 +66,12 @@ export const ContactAddModal: FC = ({ open, onSubmitted, o
return (
= ({ open, onSubmitted, o
onCloseByEsc={onCancel}
>
{loading && }
- {error && }
+ {error && }
diff --git a/src/components/ContactsList/_modals/ContactUpsertModal/index.ts b/src/components/ContactsList/_modals/ContactUpsertModal/index.ts
new file mode 100644
index 0000000..6ea2bc6
--- /dev/null
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/index.ts
@@ -0,0 +1,2 @@
+export * from './ContactAddModal';
+export * from './ContactEditModal';
diff --git a/src/components/ContactsList/_modals/ContactUpsertModal/useContactFields.tsx b/src/components/ContactsList/_modals/ContactUpsertModal/useContactFields.tsx
new file mode 100644
index 0000000..c7f17e8
--- /dev/null
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/useContactFields.tsx
@@ -0,0 +1,18 @@
+import { useState } from 'react';
+
+export const useContactFields = () => {
+ const [paymail, setPaymail] = useState('');
+ const [name, setName] = useState('');
+ const [phone, setPhone] = useState('');
+
+ return {
+ paymail,
+ setPaymail,
+ name,
+ setName,
+ phone,
+ setPhone,
+ };
+};
+
+export type ContactFields = ReturnType;
diff --git a/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx b/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
index f3cf9fe..b34b5bc 100644
--- a/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
@@ -1,11 +1,11 @@
-import { confirmContactWithTOTP } from '@/api';
+import { Contact, confirmContactWithTOTP } from '@/api';
import { ErrorBar } from '@/components/ErrorBar';
import { Input } from '@/components/Input';
import { FC, useMemo, useState } from 'react';
const TOTP_VALID_REGEX = /^\d{2}$/;
-export const usePeerTOTP = (peerPaymail: string, onConfirmed: () => void) => {
+export const usePeerTOTP = (peer: Contact, onConfirmed: () => void) => {
const [value, setValue] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
@@ -18,7 +18,7 @@ export const usePeerTOTP = (peerPaymail: string, onConfirmed: () => void) => {
setLoading(true);
setError(false);
try {
- await confirmContactWithTOTP(peerPaymail, parseInt(value));
+ await confirmContactWithTOTP(peer, parseInt(value));
} catch {
setError(true);
} finally {
diff --git a/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx b/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
index b10b751..36cc592 100644
--- a/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
@@ -14,8 +14,8 @@ type VerifyModalProps = {
export const VerifyModal: FC> = ({ children, peer, onConfirmed, onClose }) => {
const { fullName, paymail, status } = peer;
- const yourTOTP = useYourTOTP(paymail);
- const peerTOTP = usePeerTOTP(paymail, onConfirmed);
+ const yourTOTP = useYourTOTP(peer);
+ const peerTOTP = usePeerTOTP(peer, onConfirmed);
if (status == ContactAwaitingAcceptance) {
return null;
diff --git a/src/components/ContactsList/_modals/VerifyModal/YourTOTP.tsx b/src/components/ContactsList/_modals/VerifyModal/YourTOTP.tsx
index 494e260..36b5df8 100644
--- a/src/components/ContactsList/_modals/VerifyModal/YourTOTP.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/YourTOTP.tsx
@@ -2,10 +2,10 @@ import { FC, useCallback, useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { variables } from '@/styles';
import { colors, sizes } from '@/styles';
-import { getTOTP } from '@/api';
+import { Contact, getTOTP } from '@/api';
import { CircularProgress } from '@mui/material';
-export const useYourTOTP = (peerPaymai: string) => {
+export const useYourTOTP = (peer: Contact) => {
const [totp, setTotp] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
@@ -14,13 +14,13 @@ export const useYourTOTP = (peerPaymai: string) => {
setLoading(true);
setError(false);
try {
- setTotp(await getTOTP(peerPaymai));
+ setTotp(await getTOTP(peer));
} catch {
setError(true);
} finally {
setLoading(false);
}
- }, [peerPaymai]);
+ }, [peer]);
useEffect(() => {
fetchTotp();
diff --git a/src/components/ContactsList/_modals/index.ts b/src/components/ContactsList/_modals/index.ts
index 3ab09e2..30befdc 100644
--- a/src/components/ContactsList/_modals/index.ts
+++ b/src/components/ContactsList/_modals/index.ts
@@ -1,2 +1,4 @@
-export * from './ContactAddModal/ContactAddModal';
+export * from './ContactUpsertModal/ContactUpsertModal';
export * from './VerifyModal/VerifyModal';
+export * from './ContactAdd';
+export * from './ContactEdit';
diff --git a/src/components/Input/Input.styles.ts b/src/components/Input/Input.styles.ts
index 6cfe86f..7d0c6ac 100644
--- a/src/components/Input/Input.styles.ts
+++ b/src/components/Input/Input.styles.ts
@@ -46,7 +46,7 @@ export const InputStyled = styled.input`
border: 2px solid
${({ inputOnLightBackground }) => (inputOnLightBackground ? colors.darkPrimary : colors.lightPrimary)};
border-radius: ${variables.doubleBorderRadius};
- background-color: ${colors.inputBackground};
+ background-color: ${({ disabled }) => (disabled ? colors.disabledInputBackground : colors.inputBackground)};
color: ${({ inputOnLightBackground }) => (inputOnLightBackground ? colors.darkPrimary : colors.lightPrimary)};
${media.sm} {
diff --git a/src/components/Input/PaymailInput.tsx b/src/components/Input/PaymailInput.tsx
index 0f080e8..4587b3c 100644
--- a/src/components/Input/PaymailInput.tsx
+++ b/src/components/Input/PaymailInput.tsx
@@ -6,18 +6,20 @@ import { usePaymailDomain } from '@/hooks/usePaymailDomain';
import { InputLinkButton } from './Input.styles';
import ContactsIcon from '@mui/icons-material/Contacts';
-export const PaymailInput = forwardRef(({ showContactsButton, ...props }, ref) => {
- const paymailDomain = usePaymailDomain();
+export const PaymailInput = forwardRef(
+ ({ showContactsButton, labelSuffix = '', ...props }, ref) => {
+ const paymailDomain = usePaymailDomain();
- return (
-
- {showContactsButton && (
-
-
-
- )}
-
- );
-});
+ return (
+
+ {showContactsButton && (
+
+
+
+ )}
+
+ );
+ },
+);
PaymailInput.displayName = 'PaymailInput';
diff --git a/src/components/Input/types.ts b/src/components/Input/types.ts
index f2f45de..8cbd9c5 100644
--- a/src/components/Input/types.ts
+++ b/src/components/Input/types.ts
@@ -15,4 +15,5 @@ export type CoinsInputProps = Omit;
export type PaymailInputProps = {
showContactsButton?: boolean;
+ labelSuffix?: string;
} & Omit;
diff --git a/src/components/Modal/Modal.styles.ts b/src/components/Modal/Modal.styles.ts
index 90b2508..53ce5ff 100644
--- a/src/components/Modal/Modal.styles.ts
+++ b/src/components/Modal/Modal.styles.ts
@@ -57,6 +57,7 @@ export const ModalMainContent = styled.main`
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
+ padding: ${sizes(4)};
`;
export const ModalHeadline = styled.h3`
@@ -76,6 +77,7 @@ export const ButtonsWrapper = styled.footer`
justify-content: center;
margin: ${sizes(12)} 0 ${sizes(6)};
z-index: 1;
+ padding: ${sizes(4)};
`;
export const ModalButton = styled(Button)`
diff --git a/src/styles/colors.ts b/src/styles/colors.ts
index ddf511c..89d3a75 100644
--- a/src/styles/colors.ts
+++ b/src/styles/colors.ts
@@ -8,6 +8,7 @@ export const colors = {
userMenuBackground: 'rgba(255, 255, 255, 0.4)',
dashboardTileBackground: 'rgba(0, 0, 0, 0.6)',
modalWrapperBackground: 'rgba(255, 255, 255, 0.4)',
+ disabledInputBackground: 'rgba(180, 180, 180, 1)',
primary: '#13145e',
lightPrimary: '#ffffff',
darkPrimary: '#000',
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index a6a4e27..6712edc 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -14,11 +14,11 @@ export const variables = {
},
shadow: {
tileShadow: '0 0 40px rgba(8, 7, 16, 0.6)',
- buttonShadow: '0 5px 15px rgba(0, 0, 0, 0.3)',
- buttonEffectShadow: '0 5px 35px rgba(0, 0, 0, 0.3)',
- smallButtonShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
- smallButtonEffectShadow: '0 1px 7px rgba(0, 0, 0, 0.3)',
- inputEffectShadow: '0 5px 15px rgba(0, 0, 0, 0.3)',
+ buttonShadow: '0 5px 5px rgba(0, 0, 0, 0.15)',
+ buttonEffectShadow: '0 5px 15px rgba(0, 0, 0, 0.15)',
+ smallButtonShadow: '0 1px 3px rgba(0, 0, 0, 0.15)',
+ smallButtonEffectShadow: '0 1px 7px rgba(0, 0, 0, 0.15)',
+ inputEffectShadow: '0 5px 15px rgba(0, 0, 0, 0.15)',
},
transition: {
baseEffect: 'all 0.2s ease-in-out',
diff --git a/src/utils/helpers/validatePhone.ts b/src/utils/helpers/validatePhone.ts
new file mode 100644
index 0000000..fea7ea8
--- /dev/null
+++ b/src/utils/helpers/validatePhone.ts
@@ -0,0 +1,5 @@
+const phoneRegex = /^\+?([0-9]{1,3})\)?[-. ]?([0-9]{1,4})[-. ]?([0-9]{1,4})[-. ]?([0-9]{1,4})$/;
+
+export const isValidPhone = (phone: string) => {
+ return phoneRegex.test(phone);
+};
From a823206140fc142488d3b26fbb0a72c0ae30790b Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Mon, 22 Apr 2024 14:06:42 +0200
Subject: [PATCH 03/13] feat(SPV-679): styles
---
.../ContactsTable.tsx/ContactsTable.tsx | 5 ++++-
src/styles/index.ts | 1 +
src/styles/textWrapper.tsx | 8 ++++++++
src/views/Contacts/Contacts.tsx | 13 +++++++++++--
4 files changed, 24 insertions(+), 3 deletions(-)
create mode 100644 src/styles/textWrapper.tsx
diff --git a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
index 6f91951..984727d 100644
--- a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
@@ -19,6 +19,7 @@ import { useContacts } from '@/providers';
import { ErrorBar } from '@/components/ErrorBar';
import { useSortedContacts } from './useSortedContacts';
import { JustAddedContactMsg } from './JustAddedContcatMsg';
+import { TextWrapper } from '@/styles';
export const ContactsTable: FC = () => {
const { contacts, loading, error, refresh } = useContacts();
@@ -61,7 +62,9 @@ export const ContactsTable: FC = () => {
const { paymail, fullName, status } = contact;
return (
- {paymail}
+
+ {paymail}
+
{fullName}
diff --git a/src/styles/index.ts b/src/styles/index.ts
index f13c669..7e343e6 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -5,3 +5,4 @@ export * from './sizes';
export * from './breakpoints';
export * from './keyframes';
export * from './variables';
+export * from './textWrapper';
diff --git a/src/styles/textWrapper.tsx b/src/styles/textWrapper.tsx
new file mode 100644
index 0000000..b8eff71
--- /dev/null
+++ b/src/styles/textWrapper.tsx
@@ -0,0 +1,8 @@
+import styled from '@emotion/styled';
+
+export const TextWrapper = styled.div`
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: left;
+`;
diff --git a/src/views/Contacts/Contacts.tsx b/src/views/Contacts/Contacts.tsx
index 8409cce..a625e7f 100644
--- a/src/views/Contacts/Contacts.tsx
+++ b/src/views/Contacts/Contacts.tsx
@@ -4,6 +4,8 @@ import { AccountSummary } from '@/components/AccountSummary';
import { TransferForm } from '@/components/TransferForm';
import { ContactsList } from '@/components/ContactsList';
import { useWebsocket } from '@/hooks';
+import styled from '@emotion/styled';
+import { sizes } from '@/styles';
export const Contacts = () => {
const lgMatch = useMediaMatch('lg');
@@ -16,10 +18,17 @@ export const Contacts = () => {
-
-
+
+
+
+
>
);
};
+
+const StickyColumn = styled('div')`
+ position: sticky;
+ top: ${sizes(4)};
+`;
From 92ae1ea64d7895b0696adb929ab356462a9de889 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Mon, 22 Apr 2024 15:25:19 +0200
Subject: [PATCH 04/13] feat(SPV-679): showing a badge for input paymail with
status in the contacts
---
.../ContactsTable.tsx/StatusBadge.tsx | 21 ++++++++--
src/components/Input/PaymailInput.tsx | 3 +-
src/components/TransferForm/TransferForm.tsx | 39 +++++++++++++++++--
src/providers/contacts/provider.tsx | 6 ++-
4 files changed, 59 insertions(+), 10 deletions(-)
diff --git a/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx b/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
index c43a1cc..36ac88e 100644
--- a/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
@@ -7,20 +7,33 @@ import {
} from '@/api/types/contact';
import { FC } from 'react';
import { Chip, ChipProps } from '@mui/material';
+import styled from '@emotion/styled';
type StatusBadgeProps = {
- status: ContactStatus;
+ status: ContactStatus | 'unknown';
};
-export const StatusBadge: FC = ({ status }) => {
+export const StatusBadge: FC> = ({ status, ...props }) => {
const { label, color } = contactStatuses[status] ?? { label: 'Unknown', color: 'error' };
- return ;
+ return (
+
+
+
+ );
};
-const contactStatuses: Record = {
+const contactStatuses: Record = {
[ContactAwaitingAcceptance]: { label: 'Pending', color: 'primary' },
[ContactNotConfirmed]: { label: 'Untrusted', color: 'secondary' },
[ContactConfirmed]: { label: 'Trusted', color: 'success' },
[ContactRejected]: { label: 'Rejected', color: 'error' },
+ unknown: { label: 'Unknown', color: 'warning' },
};
+
+const ChipContainer = styled.div`
+ & div span {
+ line-height: 1;
+ transform: translateY(1.5px);
+ }
+`;
diff --git a/src/components/Input/PaymailInput.tsx b/src/components/Input/PaymailInput.tsx
index 4587b3c..3fd3321 100644
--- a/src/components/Input/PaymailInput.tsx
+++ b/src/components/Input/PaymailInput.tsx
@@ -7,7 +7,7 @@ import { InputLinkButton } from './Input.styles';
import ContactsIcon from '@mui/icons-material/Contacts';
export const PaymailInput = forwardRef(
- ({ showContactsButton, labelSuffix = '', ...props }, ref) => {
+ ({ showContactsButton, labelSuffix = '', children, ...props }, ref) => {
const paymailDomain = usePaymailDomain();
return (
@@ -17,6 +17,7 @@ export const PaymailInput = forwardRef(
)}
+ {children}
);
},
diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx
index 3a33e1f..9763904 100644
--- a/src/components/TransferForm/TransferForm.tsx
+++ b/src/components/TransferForm/TransferForm.tsx
@@ -1,9 +1,9 @@
import { DashboardTile } from '@/components/DashboardTile';
import SendIcon from '@mui/icons-material/Send';
import { Button } from '@/components/Button';
-import { SrOnlySpan } from '@/styles';
+import { SrOnlySpan, sizes } from '@/styles';
import { Column, Row } from '@/styles/grid';
-import { ChangeEvent, FormEvent, useCallback, useState, FC } from 'react';
+import { ChangeEvent, FormEvent, useCallback, useState, FC, useEffect } from 'react';
import { Loader } from '@/components/Loader';
import { TransactionConfirmModal, TransactionData } from '@/components/Modal/_modals/TransactionConfirmModal';
import { EMAIL_REGEX } from '@/utils/constants';
@@ -13,6 +13,11 @@ import { CoinsInput } from '../Input/CoinsInput';
import { PaymailInput } from '../Input/PaymailInput';
import { useSubscribePaymailEvent } from './setPaymailEvent';
import { usePaymailInputAnimation } from './paymailInputAnimation';
+import { debounce } from 'lodash';
+import { useContacts } from '@/providers';
+import { ContactStatus } from '@/api';
+import { StatusBadge } from '../ContactsList/ContactsTable.tsx/StatusBadge';
+import styled from '@emotion/styled';
type TransferFormProps = {
showContactsButton?: boolean;
@@ -85,6 +90,26 @@ export const TransferForm: FC = ({ showContactsButton }) => {
setAmount(value);
}
};
+
+ const { contacts } = useContacts();
+ const [paymailStatus, setPaymailStatus] = useState();
+ const checkPaymailInContacts = useCallback(
+ (value: string) => {
+ if (!value) {
+ setPaymailStatus(undefined);
+ return;
+ }
+ value = value.toLowerCase();
+ const found = contacts?.find((el) => el.paymail === value);
+ setPaymailStatus(found?.status ?? 'unknown');
+ },
+ [contacts],
+ );
+ const debouncedValidateInput = debounce(checkPaymailInContacts, 500);
+ useEffect(() => {
+ debouncedValidateInput(paymail);
+ }, [debouncedValidateInput, paymail]);
+
return (
}>
{loading && }
@@ -95,13 +120,17 @@ export const TransferForm: FC = ({ showContactsButton }) => {
- setPaymail(event.target.value)}
value={paymail}
showContactsButton={showContactsButton}
/>
+
{errors && }
@@ -139,3 +168,7 @@ export const TransferForm: FC = ({ showContactsButton }) => {
);
};
+
+const StyledPaymailInput = styled(PaymailInput)`
+ margin-bottom: ${sizes(1)};
+`;
diff --git a/src/providers/contacts/provider.tsx b/src/providers/contacts/provider.tsx
index bab35a1..d83e12c 100644
--- a/src/providers/contacts/provider.tsx
+++ b/src/providers/contacts/provider.tsx
@@ -2,6 +2,7 @@ import { searchContacts } from '@/api/requests/contact';
import { Contact } from '@/api/types/contact';
import { usePikeEnabled } from '@/hooks/useFeatureFlags';
import { FC, PropsWithChildren, createContext, useCallback, useEffect, useMemo, useState } from 'react';
+import { useAuthorization } from '../authorization';
type ContactsContextValue = {
contacts: Contact[] | null;
@@ -17,9 +18,10 @@ export const ContactsProvider: FC = ({ children }) => {
const [error, setError] = useState(false);
const [contacts, setContacts] = useState(null);
const enabled = usePikeEnabled();
+ const { authorization } = useAuthorization();
const load = useCallback(async () => {
- if (!enabled) {
+ if (!enabled || !authorization) {
return;
}
setLoading(true);
@@ -32,7 +34,7 @@ export const ContactsProvider: FC = ({ children }) => {
} finally {
setLoading(false);
}
- }, [enabled]);
+ }, [authorization, enabled]);
const refresh = useCallback(() => {
load();
From 35d9d43c527d9091669a0e790d89372a1faa3917 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Tue, 23 Apr 2024 14:08:52 +0200
Subject: [PATCH 05/13] feat(SPV-679): totp
---
src/api/requests/contact.ts | 2 +-
src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/api/requests/contact.ts b/src/api/requests/contact.ts
index af96e5a..cc81feb 100644
--- a/src/api/requests/contact.ts
+++ b/src/api/requests/contact.ts
@@ -28,7 +28,7 @@ export const getTOTP = async (contact: Contact) => {
return res.data.passcode;
};
-export const confirmContactWithTOTP = async (contact: Contact, totp: number) => {
+export const confirmContactWithTOTP = async (contact: Contact, totp: string) => {
await axios.patch(`/contact/confirmed`, {
passcode: totp,
contact,
diff --git a/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx b/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
index b34b5bc..397e9e7 100644
--- a/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
@@ -3,7 +3,7 @@ import { ErrorBar } from '@/components/ErrorBar';
import { Input } from '@/components/Input';
import { FC, useMemo, useState } from 'react';
-const TOTP_VALID_REGEX = /^\d{2}$/;
+const TOTP_VALID_REGEX = /^\d{4}$/;
export const usePeerTOTP = (peer: Contact, onConfirmed: () => void) => {
const [value, setValue] = useState('');
@@ -18,7 +18,7 @@ export const usePeerTOTP = (peer: Contact, onConfirmed: () => void) => {
setLoading(true);
setError(false);
try {
- await confirmContactWithTOTP(peer, parseInt(value));
+ await confirmContactWithTOTP(peer, value);
} catch {
setError(true);
} finally {
From b8820ce0f5d747e0f83dbf2a8e90009fe0462e2f Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Thu, 25 Apr 2024 10:30:25 +0200
Subject: [PATCH 06/13] feat(SPV-679): ux adjustments
---
.../ContactsTable.tsx/ContactsTable.tsx | 2 +-
.../ContactsTable.tsx/JustAddedContcatMsg.tsx | 4 +++-
.../ContactsTable.tsx/StatusBadge.tsx | 1 -
.../ContactUpsertModal/ContactEditModal.tsx | 2 +-
.../_modals/VerifyModal/VerifyModal.tsx | 1 -
src/components/TransferForm/TransferForm.tsx | 18 +++++++++++-------
src/styles/colors.ts | 2 +-
7 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
index 984727d..e70a30f 100644
--- a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
@@ -70,7 +70,7 @@ export const ContactsTable: FC = () => {
-
+ {status !== ContactAwaitingAcceptance && }
{status !== ContactAwaitingAcceptance ? (
{
return (
You've successfully accepted the contact.
- Until confirmed, it will be displayed as .
+ Until confirmed, it will be displayed as
+
You can confirm it right now or return to this process later by using the "Show code" button.
);
@@ -15,6 +16,7 @@ export const JustAddedContactMsg: FC = () => {
const Container = styled.div`
padding: 20px;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
`;
const SuccessInfo = styled.div`
diff --git a/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx b/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
index 36ac88e..45c60a4 100644
--- a/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/StatusBadge.tsx
@@ -34,6 +34,5 @@ const contactStatuses: Record = ({ onSubmitted, onCan
modalTitle="Edit contact"
modalSubtitle=""
fields={fields}
- disabledPaymailInput={false}
+ disabledPaymailInput={true}
/>
);
};
diff --git a/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx b/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
index 36cc592..60b3b97 100644
--- a/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/VerifyModal.tsx
@@ -59,7 +59,6 @@ export const VerifyModal: FC> = ({ children,
};
const Container = styled.div`
- padding: 20px;
max-width: 80vw;
width: 800px;
`;
diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx
index 9763904..13b9894 100644
--- a/src/components/TransferForm/TransferForm.tsx
+++ b/src/components/TransferForm/TransferForm.tsx
@@ -3,7 +3,7 @@ import SendIcon from '@mui/icons-material/Send';
import { Button } from '@/components/Button';
import { SrOnlySpan, sizes } from '@/styles';
import { Column, Row } from '@/styles/grid';
-import { ChangeEvent, FormEvent, useCallback, useState, FC, useEffect } from 'react';
+import { ChangeEvent, FormEvent, useCallback, useState, FC, useEffect, useMemo } from 'react';
import { Loader } from '@/components/Loader';
import { TransactionConfirmModal, TransactionData } from '@/components/Modal/_modals/TransactionConfirmModal';
import { EMAIL_REGEX } from '@/utils/constants';
@@ -95,7 +95,7 @@ export const TransferForm: FC = ({ showContactsButton }) => {
const [paymailStatus, setPaymailStatus] = useState();
const checkPaymailInContacts = useCallback(
(value: string) => {
- if (!value) {
+ if (!value || !value.match(EMAIL_REGEX)) {
setPaymailStatus(undefined);
return;
}
@@ -105,7 +105,7 @@ export const TransferForm: FC = ({ showContactsButton }) => {
},
[contacts],
);
- const debouncedValidateInput = debounce(checkPaymailInContacts, 500);
+ const debouncedValidateInput = useMemo(() => debounce(checkPaymailInContacts, 1000), [checkPaymailInContacts]);
useEffect(() => {
debouncedValidateInput(paymail);
}, [debouncedValidateInput, paymail]);
@@ -127,10 +127,14 @@ export const TransferForm: FC = ({ showContactsButton }) => {
value={paymail}
showContactsButton={showContactsButton}
/>
-
+
+ {paymailStatus != null && (
+ <>
+ This is contact
+ >
+ )}
+
+
{errors && }
diff --git a/src/styles/colors.ts b/src/styles/colors.ts
index 89d3a75..aa39409 100644
--- a/src/styles/colors.ts
+++ b/src/styles/colors.ts
@@ -8,7 +8,7 @@ export const colors = {
userMenuBackground: 'rgba(255, 255, 255, 0.4)',
dashboardTileBackground: 'rgba(0, 0, 0, 0.6)',
modalWrapperBackground: 'rgba(255, 255, 255, 0.4)',
- disabledInputBackground: 'rgba(180, 180, 180, 1)',
+ disabledInputBackground: 'rgba(200, 200, 200, 1)',
primary: '#13145e',
lightPrimary: '#ffffff',
darkPrimary: '#000',
From 4a17dca8d9557e8a45ecbdf925457ffc9365d013 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Thu, 25 Apr 2024 10:58:58 +0200
Subject: [PATCH 07/13] feat(SPV-679): text adjustment
---
src/components/TransferForm/TransferForm.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx
index 13b9894..07691d7 100644
--- a/src/components/TransferForm/TransferForm.tsx
+++ b/src/components/TransferForm/TransferForm.tsx
@@ -130,7 +130,8 @@ export const TransferForm: FC = ({ showContactsButton }) => {
{paymailStatus != null && (
<>
- This is contact
+ This is {' '}
+ {paymailStatus !== 'unknown' ? 'contact' : 'paymail'}
>
)}
From 9e5d27cae8c44d847096020725134a2e57414e78 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Thu, 25 Apr 2024 14:21:46 +0200
Subject: [PATCH 08/13] feat(SPV-679): autocomplete
---
src/components/Input/Input.tsx | 4 +-
src/components/Input/PaymailAutocomplete.tsx | 88 ++++++++++++++++++++
src/components/Input/types.ts | 9 +-
src/components/TransferForm/TransferForm.tsx | 10 +--
4 files changed, 103 insertions(+), 8 deletions(-)
create mode 100644 src/components/Input/PaymailAutocomplete.tsx
diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx
index 1e4f2c7..217f1a7 100644
--- a/src/components/Input/Input.tsx
+++ b/src/components/Input/Input.tsx
@@ -4,9 +4,9 @@ import { InputStyled, InputWrapper, LabelStyled } from './Input.styles';
import { InputProps } from './types';
export const Input = forwardRef(
- ({ labelText, className, id, customPlaceholder, inputOnLightBackground, children, ...rest }, ref) => {
+ ({ labelText, className, id, customPlaceholder, inputOnLightBackground, children, rootProps, ...rest }, ref) => {
return (
-
+
(
+ ({ children, paymailValue, onPaymailChange, ...props }, ref) => {
+ const { contacts } = useContacts();
+
+ const contactsPaymails = useMemo(() => contacts?.map((c) => c.paymail) ?? [], [contacts]);
+
+ const isSelected = useMemo(
+ () => contactsPaymails.find((c) => c === paymailValue),
+ [contactsPaymails, paymailValue],
+ );
+
+ const { getRootProps, getInputProps, getListboxProps, getOptionProps, groupedOptions } = useAutocomplete({
+ id: 'paymail-autocomplete',
+ options: !isSelected && paymailValue != '' ? contactsPaymails : [],
+ autoHighlight: true,
+ inputValue: paymailValue,
+ onInputChange: (_, value) => {
+ onPaymailChange(value);
+ },
+ freeSolo: true,
+ openOnFocus: false,
+ });
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { onMouseDown: _, ...rootProps } = getRootProps(); //omit onMouseDown to allow a free select of text in the input
+
+ return (
+
+ {children}
+
+ {groupedOptions.length > 0 ? (
+
+ {groupedOptions.map((contactPaymail, index) => {
+ const p = contactPaymail as string;
+ return (
+
+ {p}
+
+ );
+ })}
+
+ ) : null}
+
+ );
+ },
+);
+
+PaymailAutocomplete.displayName = 'PaymailAutocomplete';
+
+const Listbox = styled('ul')(() => ({
+ width: '100%',
+ margin: 0,
+ padding: 0,
+ zIndex: 1,
+ position: 'absolute',
+ listStyle: 'none',
+ backgroundColor: colors.primaryBackground,
+ overflowX: 'hidden',
+ overflowY: 'auto',
+ scrollbarWidth: 'thin',
+ scrollbarColor: `${colors.secondaryBackground} ${colors.primaryBackground}`,
+ boxShadow: '0 2px 4px rgba(0,0,0,.25)',
+ borderRadius: sizes(1),
+
+ maxHeight: 200,
+ border: '1px solid rgba(0,0,0,.25)',
+ '& li.Mui-focused': {
+ backgroundColor: '#4a8df6',
+ color: 'white',
+ cursor: 'pointer',
+ },
+ '& li:active': {
+ backgroundColor: '#2977f5',
+ color: 'white',
+ },
+ '& li': {
+ padding: sizes(1),
+ },
+}));
diff --git a/src/components/Input/types.ts b/src/components/Input/types.ts
index 8cbd9c5..78bfb24 100644
--- a/src/components/Input/types.ts
+++ b/src/components/Input/types.ts
@@ -1,4 +1,4 @@
-import { InputHTMLAttributes } from 'react';
+import { HTMLAttributes, InputHTMLAttributes } from 'react';
export type InputProps = {
labelText: string;
@@ -7,6 +7,7 @@ export type InputProps = {
withIcon?: boolean;
customPlaceholder?: string;
inputOnLightBackground?: boolean;
+ rootProps?: HTMLAttributes;
} & InputHTMLAttributes;
export type PasswordInputProps = Omit;
@@ -16,4 +17,10 @@ export type CoinsInputProps = Omit;
export type PaymailInputProps = {
showContactsButton?: boolean;
labelSuffix?: string;
+ rootProps?: HTMLAttributes;
} & Omit;
+
+export type PaymailAutocompleteProps = {
+ paymailValue?: string;
+ onPaymailChange: (value: string) => void;
+} & Omit;
diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx
index 07691d7..0f73a46 100644
--- a/src/components/TransferForm/TransferForm.tsx
+++ b/src/components/TransferForm/TransferForm.tsx
@@ -10,7 +10,6 @@ import { EMAIL_REGEX } from '@/utils/constants';
import { ErrorBar } from '@/components/ErrorBar';
import { convertSatToBsv } from '@/utils/helpers/convertSatToBsv';
import { CoinsInput } from '../Input/CoinsInput';
-import { PaymailInput } from '../Input/PaymailInput';
import { useSubscribePaymailEvent } from './setPaymailEvent';
import { usePaymailInputAnimation } from './paymailInputAnimation';
import { debounce } from 'lodash';
@@ -18,6 +17,7 @@ import { useContacts } from '@/providers';
import { ContactStatus } from '@/api';
import { StatusBadge } from '../ContactsList/ContactsTable.tsx/StatusBadge';
import styled from '@emotion/styled';
+import { PaymailAutocomplete } from '../Input/PaymailAutocomplete';
type TransferFormProps = {
showContactsButton?: boolean;
@@ -120,11 +120,11 @@ export const TransferForm: FC = ({ showContactsButton }) => {
- setPaymail(event.target.value)}
- value={paymail}
+ onPaymailChange={(value) => setPaymail(value)}
+ paymailValue={paymail}
showContactsButton={showContactsButton}
/>
@@ -174,6 +174,6 @@ export const TransferForm: FC
= ({ showContactsButton }) => {
);
};
-const StyledPaymailInput = styled(PaymailInput)`
+const StyledPaymailAutocomplete = styled(PaymailAutocomplete)`
margin-bottom: ${sizes(1)};
`;
From e83da8f0e0cd8837f651d065de56ff248b8a82f6 Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Thu, 25 Apr 2024 15:16:50 +0200
Subject: [PATCH 09/13] feat(SPV-679): styles
---
src/components/TransferForm/TransferForm.tsx | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx
index 0f73a46..ea4d9d3 100644
--- a/src/components/TransferForm/TransferForm.tsx
+++ b/src/components/TransferForm/TransferForm.tsx
@@ -127,14 +127,14 @@ export const TransferForm: FC = ({ showContactsButton }) => {
paymailValue={paymail}
showContactsButton={showContactsButton}
/>
-
+
{paymailStatus != null && (
<>
This is {' '}
{paymailStatus !== 'unknown' ? 'contact' : 'paymail'}
>
)}
-
+
@@ -177,3 +177,10 @@ export const TransferForm: FC = ({ showContactsButton }) => {
const StyledPaymailAutocomplete = styled(PaymailAutocomplete)`
margin-bottom: ${sizes(1)};
`;
+
+const StyledStatusWrapper = styled.div`
+ height: 15px;
+ margin-bottom: 30px;
+ padding-right: 20px;
+ text-align: right;
+`;
From 47af0f5096665334f279be3cca6b38c344e0d92f Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Thu, 25 Apr 2024 15:43:13 +0200
Subject: [PATCH 10/13] feat(SPV-679): not showing paymailstatus if pikeEnabled
is false
---
src/components/TransferForm/TransferForm.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx
index ea4d9d3..a5eb6b8 100644
--- a/src/components/TransferForm/TransferForm.tsx
+++ b/src/components/TransferForm/TransferForm.tsx
@@ -18,6 +18,7 @@ import { ContactStatus } from '@/api';
import { StatusBadge } from '../ContactsList/ContactsTable.tsx/StatusBadge';
import styled from '@emotion/styled';
import { PaymailAutocomplete } from '../Input/PaymailAutocomplete';
+import { usePikeEnabled } from '@/hooks/useFeatureFlags';
type TransferFormProps = {
showContactsButton?: boolean;
@@ -31,6 +32,7 @@ export const TransferForm: FC = ({ showContactsButton }) => {
const [loading, setLoading] = useState(false);
const [transactionData, setTransactionData] = useState(null);
const [errors, setErrors] = useState('');
+ const pikeEnabled = usePikeEnabled();
const sendButtonDisabled = !paymail || !amount;
const cancelButtonDisabled = !paymail && !amount;
@@ -128,7 +130,7 @@ export const TransferForm: FC = ({ showContactsButton }) => {
showContactsButton={showContactsButton}
/>
- {paymailStatus != null && (
+ {pikeEnabled && paymailStatus != null && (
<>
This is {' '}
{paymailStatus !== 'unknown' ? 'contact' : 'paymail'}
From 4bf1d97761a701352d035152f3608f760bde480d Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Fri, 26 Apr 2024 10:23:30 +0200
Subject: [PATCH 11/13] feat(SPV-679): adjust to the review
---
.../_modals/ContactUpsertModal/ContactUpsertModal.tsx | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx b/src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx
index 1c533b3..c1025be 100644
--- a/src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/ContactUpsertModal.tsx
@@ -9,6 +9,7 @@ import { modalCloseTimeout } from '@/components/Modal/modalCloseTimeout';
import { ErrorBar } from '@/components/ErrorBar';
import { isValidPhone } from '@/utils/helpers/validatePhone';
import { ContactFields } from './useContactFields';
+import { EMAIL_REGEX } from '@/utils/constants';
type ContactUpsertModal = {
onSubmitted: () => void;
@@ -32,7 +33,7 @@ export const ContactUpsertModal: FC = ({
disabledPaymailInput,
}) => {
const [loading, setLoading] = useState(false);
- const [error, setError] = useState(undefined);
+ const [error, setError] = useState(null);
const [successMessage, setSuccessMessage] = useState('');
const { name, paymail, phone, setName, setPaymail, setPhone } = fields;
@@ -51,8 +52,12 @@ export const ContactUpsertModal: FC = ({
setError('Both Paymail and Name are required');
return;
}
+ if (!paymail.match(EMAIL_REGEX)) {
+ setError('Invalid paymail address!');
+ return;
+ }
setLoading(true);
- setError(undefined);
+ setError(null);
try {
await upsertContact(paymail, name, phone ? { phoneNumber: phone } : undefined);
onSuccess();
From 7e6b0aa8e7df8c0fd3b2bb8d522d5c5f7a6b59ef Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Fri, 26 Apr 2024 10:42:37 +0200
Subject: [PATCH 12/13] feat(SPV-679): two digits totp
---
src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx b/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
index 397e9e7..da5ed9d 100644
--- a/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
+++ b/src/components/ContactsList/_modals/VerifyModal/PeerTOTP.tsx
@@ -3,7 +3,7 @@ import { ErrorBar } from '@/components/ErrorBar';
import { Input } from '@/components/Input';
import { FC, useMemo, useState } from 'react';
-const TOTP_VALID_REGEX = /^\d{4}$/;
+const TOTP_VALID_REGEX = /^\d{2}$/;
export const usePeerTOTP = (peer: Contact, onConfirmed: () => void) => {
const [value, setValue] = useState('');
From 89088e714a5910311239a2014101810bd05f2e5a Mon Sep 17 00:00:00 2001
From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com>
Date: Fri, 26 Apr 2024 11:44:59 +0200
Subject: [PATCH 13/13] feat(SPV-679): minor adjustments
---
.../ContactsList/ContactsTable.tsx/ContactsTable.tsx | 4 ++--
.../ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx | 4 +++-
.../_modals/ContactUpsertModal/ContactAddModal.tsx | 2 +-
.../_modals/ContactUpsertModal/ContactEditModal.tsx | 2 +-
.../_modals/ContactUpsertModal/ContactUpsertModal.tsx | 6 +++---
5 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
index e70a30f..1663ff9 100644
--- a/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/ContactsTable.tsx
@@ -1,4 +1,4 @@
-import { ContactAwaitingAcceptance, ContactConfirmed } from '@/api/types/contact';
+import { ContactAwaitingAcceptance, ContactConfirmed, ContactNotConfirmed } from '@/api/types/contact';
import { Loader } from '@/components/Loader';
import {
LargeTd,
@@ -111,7 +111,7 @@ export const ContactsTable: FC = () => {
setJustAddedContact(false);
}}
>
- {justAddedContact && contactForVerification.status === 'unconfirmed' && }
+ {justAddedContact && contactForVerification.status === ContactNotConfirmed && }
)}
>
diff --git a/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx b/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx
index a01a1cd..866c084 100644
--- a/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx
+++ b/src/components/ContactsList/ContactsTable.tsx/JustAddedContcatMsg.tsx
@@ -2,12 +2,14 @@ import { FC } from 'react';
import styled from '@emotion/styled';
import { colors } from '@/styles';
import { StatusBadge } from './StatusBadge';
+import { ContactNotConfirmed } from '@/api';
export const JustAddedContactMsg: FC = () => {
return (
You've successfully accepted the contact.
- Until confirmed, it will be displayed as
+ Until confirmed, it will be displayed as{' '}
+
You can confirm it right now or return to this process later by using the "Show code" button.
diff --git a/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx b/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx
index 612c0d6..f45219c 100644
--- a/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx
+++ b/src/components/ContactsList/_modals/ContactUpsertModal/ContactAddModal.tsx
@@ -13,7 +13,7 @@ export const ContactAddModal: FC = ({ onSubmitted, onCance
= ({ onSubmitted, onCan
void;
onCancel: () => void;
- sucessMsg: string;
+ successMsg: string;
errorMsg: string;
modalTitle: string;
modalSubtitle: string;
@@ -27,7 +27,7 @@ export const ContactUpsertModal: FC = ({
onCancel,
fields,
errorMsg,
- sucessMsg,
+ successMsg,
modalTitle,
modalSubtitle,
disabledPaymailInput,
@@ -38,7 +38,7 @@ export const ContactUpsertModal: FC = ({
const { name, paymail, phone, setName, setPaymail, setPhone } = fields;
const onSuccess = async () => {
- setSuccessMessage(sucessMsg);
+ setSuccessMessage(successMsg);
await modalCloseTimeout();
onSubmitted();
};