Skip to content

Commit 7f9804c

Browse files
committed
Enhance login functionality to support new account creation. Updated LoginRequest and LoginResponse types to include 'create' option for newEmailBehavior. Modified processLoginRequest to handle new account creation and added corresponding LoginOutcome. Updated UI components to reflect email requirement for accessing the map and display messages for new account creation.
1 parent bc7f455 commit 7f9804c

File tree

10 files changed

+83
-45
lines changed

10 files changed

+83
-45
lines changed

backend/src/api/AuthenticationController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type LoginRequest = {
3030
requireVerifiedEmail?: boolean;
3131

3232
// If the user is already logged in, and the email is different, should we update the email on a named account?
33-
newEmailBehavior?: 'update' | 'reject';
33+
newEmailBehavior?: 'update' | 'reject' | 'create';
3434
};
3535

3636
type LoginResponse = {

backend/src/business/users/UserService.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export async function processLoginRequest(
225225
apiBase: string,
226226
returnToPath?: string,
227227
requireVerifiedEmail = false,
228-
newEmailBehavior: 'update' | 'reject' = 'update'
228+
newEmailBehavior: 'update' | 'reject' | 'create' = 'update'
229229
): Promise<LoginOutcome> {
230230
const userRepository = getRepository(User);
231231

@@ -265,7 +265,27 @@ export async function processLoginRequest(
265265
);
266266
return LoginOutcome.SentLinkToExistingAccount;
267267
} else {
268-
if (newEmailBehavior === 'update' || currentUser.isAnonymous) {
268+
if (newEmailBehavior === 'create' && !currentUser.isAnonymous) {
269+
// Create a new account for the new email if current account is non-anonymous
270+
const newUser = new User();
271+
newUser.email = normalizeEmail(requestedEmail);
272+
newUser.isEmailVerified = false;
273+
newUser.ipAddress = currentUser.ipAddress; // Keep the same IP for tracking
274+
await userRepository.save(newUser);
275+
276+
if (requireVerifiedEmail) {
277+
// We require a verified email for the new account
278+
await sendMagicLink(
279+
required(newUser.email, 'email'),
280+
newUser.id,
281+
apiBase,
282+
returnToPath
283+
);
284+
return LoginOutcome.SentLinkToVerifyEmail;
285+
}
286+
287+
return LoginOutcome.CreatedNewAccount as LoginOutcome;
288+
} else if (newEmailBehavior === 'update' || currentUser.isAnonymous) {
269289
// Either account is anonymous or the current user is changing their email
270290
// Stays logged in, but updates account info
271291
currentUser.email = normalizeEmail(requestedEmail);

backend/src/enum/LoginOutcome.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ enum LoginOutcome {
44
SentLinkToExistingAccount = 'sent_link_to_existing_account',
55
SentLinkToVerifyEmail = 'sent_link_to_verify_email',
66
AccountDoesNotExist = 'account_does_not_exist',
7+
CreatedNewAccount = 'created_new_account',
78
}
89

910
export default LoginOutcome;

frontend/src/screens/App/screens/AnnouncementBanner/AnnouncementRegistry.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import React from 'react';
22
import Announcment from './Announcement';
33

44
const ANNOUNCEMENTS_REGISTRY: Announcment[] = [
5+
{
6+
id: 'email-required',
7+
expiresAt: new Date('2026-01-18'),
8+
render: () => (
9+
<React.Fragment>
10+
Due to high usage, an email address is temporarily required to access
11+
the map.
12+
</React.Fragment>
13+
),
14+
},
515
{
616
id: 'zoom',
717
expiresAt: new Date('2025-01-31'),

frontend/src/screens/App/screens/MapPane/components/MainMap/MainMap.less

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,4 @@
66
// width: 100%;
77
height: 100%;
88
}
9-
10-
.loginOverlay {
11-
height: 100%;
12-
display: flex;
13-
align-items: center;
14-
justify-content: center;
15-
background-color: #f5f5f5;
16-
}
17-
18-
.loginContent {
19-
text-align: center;
20-
padding: 2rem;
21-
background: white;
22-
border-radius: 8px;
23-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
24-
max-width: 400px;
25-
width: 100%;
26-
27-
h2 {
28-
margin: 0 0 1rem 0;
29-
color: #333;
30-
}
31-
32-
p {
33-
margin: 0 0 1.5rem 0;
34-
color: #666;
35-
}
36-
}
379
}

frontend/src/screens/App/screens/MapPane/components/MainMap/index.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,25 @@ function withRouterRef<
162162

163163
if (!canShowMap) {
164164
return (
165-
<div className={stylesheet.loginOverlay}>
166-
<div className={stylesheet.loginContent}>
165+
<div
166+
style={{
167+
height: '100%',
168+
display: 'flex',
169+
alignItems: 'center',
170+
justifyContent: 'center',
171+
backgroundColor: '#f5f5f5',
172+
padding: '2rem',
173+
}}
174+
>
175+
<div
176+
style={{
177+
textAlign: 'center',
178+
maxWidth: '400px',
179+
}}
180+
>
167181
<h2>Login Required</h2>
168182
<p>Please log in to view the map.</p>
169-
<LoginForm />
183+
<LoginForm newEmailBehavior="create" />
170184
</div>
171185
</div>
172186
);

frontend/src/screens/App/screens/Welcome/index.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import classNames from 'classnames';
1212

1313
import stylesheet from './welcome.less';
1414

15+
// Set to false to disable email requirement
16+
const REQUIRE_EMAIL = true;
17+
1518
interface Props {
1619
isOpen: boolean;
1720
onRequestClose: () => void;
@@ -25,11 +28,13 @@ export default function Welcome({
2528

2629
const { isLoggedInToNonAnonymousAccount, isLoadingMe } = useLoginStore();
2730

28-
// Only allow closing if user is logged in to a non-anonymous account
29-
const canClose = isLoggedInToNonAnonymousAccount && !isLoadingMe;
31+
// Only require login if REQUIRE_EMAIL is true
32+
const isEmailRequirementMet = REQUIRE_EMAIL
33+
? isLoggedInToNonAnonymousAccount && !isLoadingMe
34+
: true;
3035

3136
const handleClose = (): void => {
32-
if (canClose) {
37+
if (isEmailRequirementMet) {
3338
onRequestClose();
3439
}
3540
};
@@ -40,7 +45,7 @@ export default function Welcome({
4045
className={stylesheet.welcomeModal}
4146
onRequestClose={handleClose}
4247
shouldCloseOnOverlayClick={false}
43-
shouldCloseOnEsc={canClose}
48+
shouldCloseOnEsc={isEmailRequirementMet}
4449
size="large"
4550
isCloseButtonVisible={false}
4651
carouselProps={{
@@ -62,14 +67,14 @@ export default function Welcome({
6267
<strong>Zoom in! Every dot&nbsp;is&nbsp;a&nbsp;photo.</strong>
6368
</p>
6469

65-
{!canClose ? (
70+
{REQUIRE_EMAIL && !isEmailRequirementMet ? (
6671
<div className={stylesheet.loginSection}>
6772
<p>
6873
<strong>
6974
An email address is temporarily required to access the site.
7075
</strong>
7176
</p>
72-
<LoginForm />
77+
<LoginForm newEmailBehavior="create" />
7378
</div>
7479
) : (
7580
<div
@@ -134,7 +139,7 @@ export default function Welcome({
134139
</a>
135140
</p>
136141
</div>
137-
{canClose && (
142+
{(!REQUIRE_EMAIL || isEmailRequirementMet) && (
138143
<div
139144
className={classNames(
140145
stylesheet.buttonContainer,

frontend/src/shared/components/LoginForm/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function LoginForm({
1212
newEmailBehavior,
1313
}: {
1414
requireVerifiedEmail?: boolean;
15-
newEmailBehavior?: 'update' | 'reject';
15+
newEmailBehavior?: 'update' | 'reject' | 'create';
1616
}): JSX.Element {
1717
const {
1818
emailAddress,
@@ -23,6 +23,7 @@ export default function LoginForm({
2323
isVerifyEmailMessageVisible,
2424
isEmailUpdatedMessageVisible,
2525
isAccountDoesNotExistMessageVisible,
26+
isNewAccountCreatedMessageVisible,
2627
isLoadingMe,
2728
} = useLoginStore();
2829

@@ -83,6 +84,11 @@ export default function LoginForm({
8384
No account found for <i>{emailAddress}</i>.
8485
</p>
8586
)}
87+
{isNewAccountCreatedMessageVisible && (
88+
<p className={stylesheet.resultMessage}>
89+
A new account has been created for <i>{emailAddress}</i>.
90+
</p>
91+
)}
8692
</form>
8793
);
8894
}

frontend/src/shared/stores/LoginStore.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface State {
1515
isVerifyEmailMessageVisible: boolean;
1616
isEmailUpdatedMessageVisible: boolean;
1717
isAccountDoesNotExistMessageVisible: boolean;
18+
isNewAccountCreatedMessageVisible: boolean;
1819
isLoadingMe: boolean;
1920
}
2021

@@ -26,7 +27,7 @@ interface Actions {
2627
newEmailBehavior,
2728
}: {
2829
requireVerifiedEmail: boolean;
29-
newEmailBehavior?: 'update' | 'reject';
30+
newEmailBehavior?: 'update' | 'reject' | 'create';
3031
}) => void;
3132
logout: () => void;
3233
}
@@ -40,6 +41,7 @@ const useLoginStore = create(
4041
isVerifyEmailMessageVisible: false,
4142
isEmailUpdatedMessageVisible: false,
4243
isAccountDoesNotExistMessageVisible: false,
44+
isNewAccountCreatedMessageVisible: false,
4345
isLoadingMe: false,
4446

4547
initialize: () => {
@@ -57,6 +59,7 @@ const useLoginStore = create(
5759
draft.isFollowMagicLinkMessageVisible = false;
5860
draft.isVerifyEmailMessageVisible = false;
5961
draft.isEmailUpdatedMessageVisible = false;
62+
draft.isNewAccountCreatedMessageVisible = false;
6063
});
6164
})
6265
.catch((err: unknown) => {
@@ -95,7 +98,8 @@ const useLoginStore = create(
9598
);
9699
if (
97100
outcome === LoginOutcome.AlreadyAuthenticated ||
98-
outcome === LoginOutcome.UpdatedEmailOnAuthenticatedAccount
101+
outcome === LoginOutcome.UpdatedEmailOnAuthenticatedAccount ||
102+
outcome === LoginOutcome.CreatedNewAccount
99103
) {
100104
set((draft) => {
101105
// We stay logged into the current account and can proceed
@@ -109,6 +113,11 @@ const useLoginStore = create(
109113
draft.isEmailUpdatedMessageVisible = true;
110114
});
111115
}
116+
if (outcome === LoginOutcome.CreatedNewAccount) {
117+
set((draft) => {
118+
draft.isNewAccountCreatedMessageVisible = true;
119+
});
120+
}
112121
} else if (outcome === LoginOutcome.SentLinkToExistingAccount) {
113122
set((draft) => {
114123
// The user must follow the link to log into another account, or verify their email

frontend/src/shared/utils/AuthenticationApi.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ export enum LoginOutcome {
66
SentLinkToExistingAccount = 'sent_link_to_existing_account',
77
SentLinkToVerifyEmail = 'sent_link_to_verify_email',
88
AccountDoesNotExist = 'account_does_not_exist',
9+
CreatedNewAccount = 'created_new_account',
910
}
1011

1112
export async function processLoginRequest(
1213
requestedEmail: string,
1314
returnToPath?: string,
14-
newEmailBehavior?: 'update' | 'reject',
15+
newEmailBehavior?: 'update' | 'reject' | 'create',
1516
requireVerifiedEmail = false
1617
): Promise<LoginOutcome> {
1718
const resp = await api.post<{ outcome: LoginOutcome }>(

0 commit comments

Comments
 (0)