Skip to content

Commit f3d2ec4

Browse files
committed
17-React Firebase: Link Social Logins
1 parent 2b28b83 commit f3d2ec4

File tree

4 files changed

+240
-1
lines changed

4 files changed

+240
-1
lines changed

src/components/Account/index.js

+203-1
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,223 @@
1-
import React from 'react';
1+
import React, { Component } from 'react';
22

33
import { AuthUserContext, withAuthorization } from '../Session';
4+
import { withFirebase } from '../Firebase';
45
import { PasswordForgetForm } from '../PasswordForget';
56
import PasswordChangeForm from '../PasswordChange';
67

8+
const SIGN_IN_METHODS = [
9+
{
10+
id: 'password',
11+
provider: null,
12+
},
13+
{
14+
id: 'google.com',
15+
provider: 'googleProvider',
16+
},
17+
{
18+
id: 'facebook.com',
19+
provider: 'facebookProvider',
20+
},
21+
{
22+
id: 'twitter.com',
23+
provider: 'twitterProvider',
24+
},
25+
];
26+
727
const AccountPage = () => (
828
<AuthUserContext.Consumer>
929
{authUser => (
1030
<div>
1131
<h1>Account: {authUser.email}</h1>
1232
<PasswordForgetForm />
1333
<PasswordChangeForm />
34+
<LoginManagement authUser={authUser} />
1435
</div>
1536
)}
1637
</AuthUserContext.Consumer>
1738
);
1839

40+
class LoginManagementBase extends Component {
41+
constructor(props) {
42+
super(props);
43+
44+
this.state = {
45+
activeSignInMethods: [],
46+
error: null,
47+
};
48+
}
49+
50+
componentDidMount() {
51+
this.fetchSignInMethods();
52+
}
53+
54+
fetchSignInMethods = () => {
55+
this.props.firebase.auth
56+
.fetchSignInMethodsForEmail(this.props.authUser.email)
57+
.then(activeSignInMethods =>
58+
this.setState({ activeSignInMethods, error: null }),
59+
)
60+
.catch(error => this.setState({ error }));
61+
};
62+
63+
onSocialLoginLink = provider => {
64+
this.props.firebase.auth.currentUser
65+
.linkWithPopup(this.props.firebase[provider])
66+
.then(this.fetchSignInMethods)
67+
.catch(error => this.setState({ error }));
68+
};
69+
70+
onDefaultLoginLink = password => {
71+
const credential = this.props.firebase.emailAuthProvider.credential(
72+
this.props.authUser.email,
73+
password,
74+
);
75+
76+
this.props.firebase.auth.currentUser
77+
.linkAndRetrieveDataWithCredential(credential)
78+
.then(this.fetchSignInMethods)
79+
.catch(error => this.setState({ error }));
80+
};
81+
82+
onUnlink = providerId => {
83+
this.props.firebase.auth.currentUser
84+
.unlink(providerId)
85+
.then(this.fetchSignInMethods)
86+
.catch(error => this.setState({ error }));
87+
};
88+
89+
render() {
90+
const { activeSignInMethods, error } = this.state;
91+
92+
return (
93+
<div>
94+
Sign In Methods:
95+
<ul>
96+
{SIGN_IN_METHODS.map(signInMethod => {
97+
const onlyOneLeft = activeSignInMethods.length === 1;
98+
const isEnabled = activeSignInMethods.includes(
99+
signInMethod.id,
100+
);
101+
102+
return (
103+
<li key={signInMethod.id}>
104+
{signInMethod.id === 'password' ? (
105+
<DefaultLoginToggle
106+
onlyOneLeft={onlyOneLeft}
107+
isEnabled={isEnabled}
108+
signInMethod={signInMethod}
109+
onLink={this.onDefaultLoginLink}
110+
onUnlink={this.onUnlink}
111+
/>
112+
) : (
113+
<SocialLoginToggle
114+
onlyOneLeft={onlyOneLeft}
115+
isEnabled={isEnabled}
116+
signInMethod={signInMethod}
117+
onLink={this.onSocialLoginLink}
118+
onUnlink={this.onUnlink}
119+
/>
120+
)}
121+
</li>
122+
);
123+
})}
124+
</ul>
125+
{error && error.message}
126+
</div>
127+
);
128+
}
129+
}
130+
131+
const SocialLoginToggle = ({
132+
onlyOneLeft,
133+
isEnabled,
134+
signInMethod,
135+
onLink,
136+
onUnlink,
137+
}) =>
138+
isEnabled ? (
139+
<button
140+
type="button"
141+
onClick={() => onUnlink(signInMethod.id)}
142+
disabled={onlyOneLeft}
143+
>
144+
Deactivate {signInMethod.id}
145+
</button>
146+
) : (
147+
<button
148+
type="button"
149+
onClick={() => onLink(signInMethod.provider)}
150+
>
151+
Link {signInMethod.id}
152+
</button>
153+
);
154+
155+
class DefaultLoginToggle extends Component {
156+
constructor(props) {
157+
super(props);
158+
159+
this.state = { passwordOne: '', passwordTwo: '' };
160+
}
161+
162+
onSubmit = event => {
163+
event.preventDefault();
164+
165+
this.props.onLink(this.state.passwordOne);
166+
this.setState({ passwordOne: '', passwordTwo: '' });
167+
};
168+
169+
onChange = event => {
170+
this.setState({ [event.target.name]: event.target.value });
171+
};
172+
173+
render() {
174+
const {
175+
onlyOneLeft,
176+
isEnabled,
177+
signInMethod,
178+
onUnlink,
179+
} = this.props;
180+
181+
const { passwordOne, passwordTwo } = this.state;
182+
183+
const isInvalid =
184+
passwordOne !== passwordTwo || passwordOne === '';
185+
186+
return isEnabled ? (
187+
<button
188+
type="button"
189+
onClick={() => onUnlink(signInMethod.id)}
190+
disabled={onlyOneLeft}
191+
>
192+
Deactivate {signInMethod.id}
193+
</button>
194+
) : (
195+
<form onSubmit={this.onSubmit}>
196+
<input
197+
name="passwordOne"
198+
value={passwordOne}
199+
onChange={this.onChange}
200+
type="password"
201+
placeholder="New Password"
202+
/>
203+
<input
204+
name="passwordTwo"
205+
value={passwordTwo}
206+
onChange={this.onChange}
207+
type="password"
208+
placeholder="Confirm New Password"
209+
/>
210+
211+
<button disabled={isInvalid} type="submit">
212+
Link {signInMethod.id}
213+
</button>
214+
</form>
215+
);
216+
}
217+
}
218+
219+
const LoginManagement = withFirebase(LoginManagementBase);
220+
19221
const condition = authUser => !!authUser;
20222

21223
export default withAuthorization(condition)(AccountPage);

src/components/Firebase/firebase.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Firebase {
1515
constructor() {
1616
app.initializeApp(config);
1717

18+
this.emailAuthProvider = app.auth.EmailAuthProvider;
1819
this.auth = app.auth();
1920
this.db = app.database();
2021

src/components/SignIn/index.js

+22
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ const INITIAL_STATE = {
2525
error: null,
2626
};
2727

28+
const ERROR_CODE_ACCOUNT_EXISTS =
29+
'auth/account-exists-with-different-credential';
30+
31+
const ERROR_MSG_ACCOUNT_EXISTS = `
32+
An account with an E-Mail address to
33+
this social account already exists. Try to login from
34+
this account instead and associate your social accounts on
35+
your personal account page.
36+
`;
37+
2838
class SignInFormBase extends Component {
2939
constructor(props) {
3040
super(props);
@@ -111,6 +121,10 @@ class SignInGoogleBase extends Component {
111121
});
112122
})
113123
.catch(error => {
124+
if (error.code === ERROR_CODE_ACCOUNT_EXISTS) {
125+
error.message = ERROR_MSG_ACCOUNT_EXISTS;
126+
}
127+
114128
this.setState({ error });
115129
});
116130

@@ -158,6 +172,10 @@ class SignInFacebookBase extends Component {
158172
});
159173
})
160174
.catch(error => {
175+
if (error.code === ERROR_CODE_ACCOUNT_EXISTS) {
176+
error.message = ERROR_MSG_ACCOUNT_EXISTS;
177+
}
178+
161179
this.setState({ error });
162180
});
163181

@@ -205,6 +223,10 @@ class SignInTwitterBase extends Component {
205223
});
206224
})
207225
.catch(error => {
226+
if (error.code === ERROR_CODE_ACCOUNT_EXISTS) {
227+
error.message = ERROR_MSG_ACCOUNT_EXISTS;
228+
}
229+
208230
this.setState({ error });
209231
});
210232

src/components/SignUp/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ const INITIAL_STATE = {
2121
error: null,
2222
};
2323

24+
const ERROR_CODE_ACCOUNT_EXISTS = 'auth/email-already-in-use';
25+
26+
const ERROR_MSG_ACCOUNT_EXISTS = `
27+
An account with this E-Mail address already exists.
28+
Try to login with this account instead. If you think the
29+
account is already used from one of the social logins, try
30+
to sign in with one of them. Afterward, associate your accounts
31+
on your personal account page.
32+
`;
33+
2434
class SignUpFormBase extends Component {
2535
constructor(props) {
2636
super(props);
@@ -56,6 +66,10 @@ class SignUpFormBase extends Component {
5666
});
5767
})
5868
.catch(error => {
69+
if (error.code === ERROR_CODE_ACCOUNT_EXISTS) {
70+
error.message = ERROR_MSG_ACCOUNT_EXISTS;
71+
}
72+
5973
this.setState({ error });
6074
});
6175

0 commit comments

Comments
 (0)