Skip to content

Commit 5ec36e5

Browse files
feat(web): email-updateable-at-info
1 parent d1a6020 commit 5ec36e5

File tree

4 files changed

+64
-11
lines changed

4 files changed

+64
-11
lines changed

web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx

+21-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { ISettings } from "../../../../index";
1414

1515
import EmailVerificationInfo from "./EmailVerificationInfo";
1616
import FormContact from "./FormContact";
17+
import { isUndefined } from "src/utils";
18+
import InfoCard from "components/InfoCard";
19+
import { timeLeftUntil } from "utils/date";
1720

1821
const FormContainer = styled.form`
1922
width: 100%;
@@ -33,7 +36,13 @@ const ButtonContainer = styled.div`
3336
const FormContactContainer = styled.div`
3437
display: flex;
3538
flex-direction: column;
39+
`;
40+
41+
const StyledInfoCard = styled(InfoCard)`
42+
width: fit-content;
43+
font-size: 14px;
3644
margin-bottom: 8px;
45+
word-wrap: break-word;
3746
`;
3847

3948
const FormContactDetails: React.FC<ISettings> = ({ toggleIsSettingsOpen }) => {
@@ -44,6 +53,10 @@ const FormContactDetails: React.FC<ISettings> = ({ toggleIsSettingsOpen }) => {
4453

4554
const isEditingEmail = user?.email !== emailInput;
4655

56+
const isEmailUpdateable = user?.email
57+
? !isUndefined(user?.emailUpdateableAt) && new Date(user.emailUpdateableAt).getTime() < new Date().getTime()
58+
: true;
59+
4760
useEffect(() => {
4861
if (!user || !userExists) return;
4962

@@ -53,11 +66,12 @@ const FormContactDetails: React.FC<ISettings> = ({ toggleIsSettingsOpen }) => {
5366
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
5467
e.preventDefault();
5568
if (!address) {
56-
throw new Error("Missing address");
69+
return;
5770
}
5871

5972
// if user exists then update email
6073
if (userExists) {
74+
if (!isEmailUpdateable) return;
6175
const data = {
6276
newEmail: emailInput,
6377
};
@@ -108,11 +122,15 @@ const FormContactDetails: React.FC<ISettings> = ({ toggleIsSettingsOpen }) => {
108122
isEditing={isEditingEmail}
109123
/>
110124
</FormContactContainer>
111-
125+
{!isEmailUpdateable ? (
126+
<StyledInfoCard msg={`You can update email again ${timeLeftUntil(user?.emailUpdateableAt!)}`} />
127+
) : null}
112128
<ButtonContainer>
113129
<Button
114130
text="Save"
115-
disabled={!isEditingEmail || !emailIsValid || isAddingUser || isFetchingUser || isUpdatingUser}
131+
disabled={
132+
!isEditingEmail || !emailIsValid || isAddingUser || isFetchingUser || isUpdatingUser || !isEmailUpdateable
133+
}
116134
/>
117135
</ButtonContainer>
118136
<EmailVerificationInfo toggleIsSettingsOpen={toggleIsSettingsOpen} />

web/src/layout/Header/navbar/Menu/Settings/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const StyledTabs = styled(Tabs)`
5252
padding: 0 ${responsiveSize(8, 32, 300)};
5353
width: 86vw;
5454
max-width: 660px;
55-
55+
align-self: center;
5656
${landscapeStyle(
5757
() => css`
5858
width: ${responsiveSize(300, 424, 300)};

web/src/utils/atlas/fetchUser.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@ import { toast } from "react-toastify";
33

44
import { OPTIONS } from "utils/wrapWithToast";
55

6-
type GetUserResponse = {
7-
user: {
8-
email: string;
9-
isEmailVerified: string;
10-
};
11-
};
12-
136
export type User = {
147
email: string;
158
isEmailVerified: string;
9+
emailUpdateableAt: string | null;
1610
};
1711

12+
type GetUserResponse = {
13+
user: User;
14+
};
1815
const query = gql`
1916
query GetUser {
2017
user {
2118
email
2219
isEmailVerified
20+
emailUpdateableAt
2321
}
2422
}
2523
`;

web/src/utils/date.ts

+37
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,40 @@ export function formatDate(unixTimestamp: number, withTime = false): string {
3535
: { month: "long", day: "2-digit", year: "numeric" };
3636
return date.toLocaleDateString("en-US", options);
3737
}
38+
39+
/**
40+
* Calculates the time left until a specified date and formats it.
41+
*
42+
* @param {string} isoString - An ISO 8601 formatted date string (e.g., "2024-10-29T09:52:08.580Z").
43+
* @returns {string} A human-readable string indicating the time left until the specified date.
44+
* @example
45+
* console.log(timeLeftUntil("2024-10-29T09:52:08.580Z"));
46+
* // Outputs: "in x secs", "in x mins", "in x hrs", or "after October 29, 2024"
47+
*/
48+
export function timeLeftUntil(isoString: string): string {
49+
const targetDate = new Date(isoString);
50+
const now = new Date();
51+
const timeDifference = targetDate.getTime() - now.getTime();
52+
53+
if (timeDifference <= 0) {
54+
return "The date has already passed.";
55+
}
56+
57+
const secondsLeft = Math.floor(timeDifference / 1000);
58+
const minutesLeft = Math.floor(secondsLeft / 60);
59+
const hoursLeft = Math.floor(minutesLeft / 60);
60+
const daysLeft = Math.floor(hoursLeft / 24);
61+
62+
if (secondsLeft < 60) {
63+
return `in ${secondsLeft} secs`;
64+
} else if (minutesLeft < 60) {
65+
return `in ${minutesLeft} mins`;
66+
} else if (hoursLeft < 24) {
67+
return `in ${hoursLeft} hrs`;
68+
} else if (daysLeft < 2) {
69+
return `in ${daysLeft} days`;
70+
} else {
71+
const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" };
72+
return `after ${targetDate.toLocaleDateString("en-US", options)}`;
73+
}
74+
}

0 commit comments

Comments
 (0)