Chore: Migrate LoginServices to Hooks (#4216)
* Chore: Migrate LoginServices to Hooks * fix lint * change theme to colors * back the separator as props * refactor loginservice to folder * refactor component login services * Tests to LoginService's components * finished loginservices * Chore: Migrate LoginServices to Hooks * fix lint * change theme to colors * back the separator as props * refactor loginservice to folder * refactor component login services * Tests to LoginService's components * finished loginservices * fix location * refactor TLoginStyle * fix imports * refactor lets to ref Co-authored-by: Gleidson Daniel Silva <gleidson10daniel@hotmail.com>
This commit is contained in:
parent
a5a5c52058
commit
bc09527d78
|
@ -1,444 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Animated, Easing, Linking, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Base64 } from 'js-base64';
|
|
||||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
|
||||||
|
|
||||||
import { TSupportedThemes, withTheme } from '../theme';
|
|
||||||
import sharedStyles from '../views/Styles';
|
|
||||||
import { themes } from '../lib/constants';
|
|
||||||
import Button from './Button';
|
|
||||||
import OrSeparator from './OrSeparator';
|
|
||||||
import Touch from '../lib/methods/helpers/touch';
|
|
||||||
import I18n from '../i18n';
|
|
||||||
import { random } from '../lib/methods/helpers';
|
|
||||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
|
||||||
import { CustomIcon, TIconsName } from './CustomIcon';
|
|
||||||
import { IServices } from '../selectors/login';
|
|
||||||
import { OutsideParamList } from '../stacks/types';
|
|
||||||
import { IApplicationState } from '../definitions';
|
|
||||||
import { Services } from '../lib/services';
|
|
||||||
|
|
||||||
const BUTTON_HEIGHT = 48;
|
|
||||||
const SERVICE_HEIGHT = 58;
|
|
||||||
const BORDER_RADIUS = 2;
|
|
||||||
const SERVICES_COLLAPSED_HEIGHT = 174;
|
|
||||||
|
|
||||||
const LOGIN_STYPE_POPUP = 'popup';
|
|
||||||
const LOGIN_STYPE_REDIRECT = 'redirect';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
serviceButton: {
|
|
||||||
borderRadius: BORDER_RADIUS,
|
|
||||||
marginBottom: 10
|
|
||||||
},
|
|
||||||
serviceButtonContainer: {
|
|
||||||
borderRadius: BORDER_RADIUS,
|
|
||||||
width: '100%',
|
|
||||||
height: BUTTON_HEIGHT,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingHorizontal: 15
|
|
||||||
},
|
|
||||||
serviceIcon: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: 15,
|
|
||||||
top: 12,
|
|
||||||
width: 24,
|
|
||||||
height: 24
|
|
||||||
},
|
|
||||||
serviceText: {
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
fontSize: 16
|
|
||||||
},
|
|
||||||
serviceName: {
|
|
||||||
...sharedStyles.textSemibold
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
marginBottom: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface IOpenOAuth {
|
|
||||||
url: string;
|
|
||||||
ssoToken?: string;
|
|
||||||
authType?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IItemService {
|
|
||||||
name: string;
|
|
||||||
service: string;
|
|
||||||
authType: string;
|
|
||||||
buttonColor: string;
|
|
||||||
buttonLabelColor: string;
|
|
||||||
clientConfig: { provider: string };
|
|
||||||
serverURL: string;
|
|
||||||
authorizePath: string;
|
|
||||||
clientId: string;
|
|
||||||
scope: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IOauthProvider {
|
|
||||||
[key: string]: () => void;
|
|
||||||
facebook: () => void;
|
|
||||||
github: () => void;
|
|
||||||
gitlab: () => void;
|
|
||||||
google: () => void;
|
|
||||||
linkedin: () => void;
|
|
||||||
'meteor-developer': () => void;
|
|
||||||
twitter: () => void;
|
|
||||||
wordpress: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILoginServicesProps {
|
|
||||||
navigation: StackNavigationProp<OutsideParamList>;
|
|
||||||
server: string;
|
|
||||||
services: IServices;
|
|
||||||
Gitlab_URL: string;
|
|
||||||
CAS_enabled: boolean;
|
|
||||||
CAS_login_url: string;
|
|
||||||
separator: boolean;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILoginServicesState {
|
|
||||||
collapsed: boolean;
|
|
||||||
servicesHeight: Animated.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServicesState> {
|
|
||||||
private _animation?: Animated.CompositeAnimation | void;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
collapsed: true,
|
|
||||||
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressFacebook = () => {
|
|
||||||
logEvent(events.ENTER_WITH_FACEBOOK);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.facebook;
|
|
||||||
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
|
||||||
const redirect_uri = `${server}/_oauth/facebook?close`;
|
|
||||||
const scope = 'email';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&display=touch`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressGithub = () => {
|
|
||||||
logEvent(events.ENTER_WITH_GITHUB);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.github;
|
|
||||||
const endpoint = `https://github.com/login?client_id=${clientId}&return_to=${encodeURIComponent('/login/oauth/authorize')}`;
|
|
||||||
const redirect_uri = `${server}/_oauth/github?close`;
|
|
||||||
const scope = 'user:email';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${encodeURIComponent(params)}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressGitlab = () => {
|
|
||||||
logEvent(events.ENTER_WITH_GITLAB);
|
|
||||||
const { services, server, Gitlab_URL } = this.props;
|
|
||||||
const { clientId } = services.gitlab;
|
|
||||||
const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com';
|
|
||||||
const endpoint = `${baseURL}/oauth/authorize`;
|
|
||||||
const redirect_uri = `${server}/_oauth/gitlab?close`;
|
|
||||||
const scope = 'read_user';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressGoogle = () => {
|
|
||||||
logEvent(events.ENTER_WITH_GOOGLE);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.google;
|
|
||||||
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
|
||||||
const redirect_uri = `${server}/_oauth/google?close`;
|
|
||||||
const scope = 'email';
|
|
||||||
const state = this.getOAuthState(LOGIN_STYPE_REDIRECT);
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
Linking.openURL(`${endpoint}${params}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressLinkedin = () => {
|
|
||||||
logEvent(events.ENTER_WITH_LINKEDIN);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.linkedin;
|
|
||||||
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
|
|
||||||
const redirect_uri = `${server}/_oauth/linkedin?close`;
|
|
||||||
const scope = 'r_liteprofile,r_emailaddress';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressMeteor = () => {
|
|
||||||
logEvent(events.ENTER_WITH_METEOR);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services['meteor-developer'];
|
|
||||||
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
|
||||||
const redirect_uri = `${server}/_oauth/meteor-developer`;
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressTwitter = () => {
|
|
||||||
logEvent(events.ENTER_WITH_TWITTER);
|
|
||||||
const { server } = this.props;
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const url = `${server}/_oauth/twitter/?requestTokenAndRedirect=true&state=${state}`;
|
|
||||||
this.openOAuth({ url });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressWordpress = () => {
|
|
||||||
logEvent(events.ENTER_WITH_WORDPRESS);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId, serverURL } = services.wordpress;
|
|
||||||
const endpoint = `${serverURL}/oauth/authorize`;
|
|
||||||
const redirect_uri = `${server}/_oauth/wordpress?close`;
|
|
||||||
const scope = 'openid';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressCustomOAuth = (loginService: IItemService) => {
|
|
||||||
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
|
||||||
const { server } = this.props;
|
|
||||||
const { serverURL, authorizePath, clientId, scope, service } = loginService;
|
|
||||||
const redirectUri = `${server}/_oauth/${service}`;
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&state=${state}&scope=${scope}`;
|
|
||||||
const domain = `${serverURL}`;
|
|
||||||
const absolutePath = `${authorizePath}${params}`;
|
|
||||||
const url = absolutePath.includes(domain) ? absolutePath : domain + absolutePath;
|
|
||||||
this.openOAuth({ url });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressSaml = (loginService: IItemService) => {
|
|
||||||
logEvent(events.ENTER_WITH_SAML);
|
|
||||||
const { server } = this.props;
|
|
||||||
const { clientConfig } = loginService;
|
|
||||||
const { provider } = clientConfig;
|
|
||||||
const ssoToken = random(17);
|
|
||||||
const url = `${server}/_saml/authorize/${provider}/${ssoToken}`;
|
|
||||||
this.openOAuth({ url, ssoToken, authType: 'saml' });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressCas = () => {
|
|
||||||
logEvent(events.ENTER_WITH_CAS);
|
|
||||||
const { server, CAS_login_url } = this.props;
|
|
||||||
const ssoToken = random(17);
|
|
||||||
const url = `${CAS_login_url}?service=${server}/_cas/${ssoToken}`;
|
|
||||||
this.openOAuth({ url, ssoToken, authType: 'cas' });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressAppleLogin = async () => {
|
|
||||||
logEvent(events.ENTER_WITH_APPLE);
|
|
||||||
try {
|
|
||||||
const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
|
|
||||||
requestedScopes: [
|
|
||||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
||||||
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
|
||||||
]
|
|
||||||
});
|
|
||||||
await Services.loginOAuthOrSso({ fullName, email, identityToken });
|
|
||||||
} catch {
|
|
||||||
logEvent(events.ENTER_WITH_APPLE_F);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
|
|
||||||
const credentialToken = random(43);
|
|
||||||
let obj: {
|
|
||||||
loginStyle: string;
|
|
||||||
credentialToken: string;
|
|
||||||
isCordova: boolean;
|
|
||||||
redirectUrl?: string;
|
|
||||||
} = { loginStyle, credentialToken, isCordova: true };
|
|
||||||
if (loginStyle === LOGIN_STYPE_REDIRECT) {
|
|
||||||
obj = {
|
|
||||||
...obj,
|
|
||||||
redirectUrl: 'rocketchat://auth'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return Base64.encodeURI(JSON.stringify(obj));
|
|
||||||
};
|
|
||||||
|
|
||||||
openOAuth = ({ url, ssoToken, authType = 'oauth' }: IOpenOAuth) => {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
|
|
||||||
};
|
|
||||||
|
|
||||||
transitionServicesTo = (height: number) => {
|
|
||||||
const { servicesHeight } = this.state;
|
|
||||||
if (this._animation) {
|
|
||||||
this._animation.stop();
|
|
||||||
}
|
|
||||||
this._animation = Animated.timing(servicesHeight, {
|
|
||||||
toValue: height,
|
|
||||||
duration: 300,
|
|
||||||
easing: Easing.inOut(Easing.quad),
|
|
||||||
useNativeDriver: false
|
|
||||||
}).start();
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleServices = () => {
|
|
||||||
const { collapsed } = this.state;
|
|
||||||
const { services } = this.props;
|
|
||||||
const { length } = Object.values(services);
|
|
||||||
if (collapsed) {
|
|
||||||
this.transitionServicesTo(SERVICE_HEIGHT * length);
|
|
||||||
} else {
|
|
||||||
this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT);
|
|
||||||
}
|
|
||||||
this.setState((prevState: ILoginServicesState) => ({ collapsed: !prevState.collapsed }));
|
|
||||||
};
|
|
||||||
|
|
||||||
getSocialOauthProvider = (name: string) => {
|
|
||||||
const oauthProviders: IOauthProvider = {
|
|
||||||
facebook: this.onPressFacebook,
|
|
||||||
github: this.onPressGithub,
|
|
||||||
gitlab: this.onPressGitlab,
|
|
||||||
google: this.onPressGoogle,
|
|
||||||
linkedin: this.onPressLinkedin,
|
|
||||||
'meteor-developer': this.onPressMeteor,
|
|
||||||
twitter: this.onPressTwitter,
|
|
||||||
wordpress: this.onPressWordpress
|
|
||||||
};
|
|
||||||
return oauthProviders[name];
|
|
||||||
};
|
|
||||||
|
|
||||||
renderServicesSeparator = () => {
|
|
||||||
const { collapsed } = this.state;
|
|
||||||
const { services, separator, theme } = this.props;
|
|
||||||
const { length } = Object.values(services);
|
|
||||||
|
|
||||||
if (length > 3 && separator) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
|
|
||||||
type='secondary'
|
|
||||||
onPress={this.toggleServices}
|
|
||||||
style={styles.options}
|
|
||||||
color={themes[theme].actionTintColor}
|
|
||||||
/>
|
|
||||||
<OrSeparator theme={theme} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (length > 0 && separator) {
|
|
||||||
return <OrSeparator theme={theme} />;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderItem = (service: IItemService) => {
|
|
||||||
const { CAS_enabled, theme } = this.props;
|
|
||||||
let { name } = service;
|
|
||||||
name = name === 'meteor-developer' ? 'meteor' : name;
|
|
||||||
const icon = `${name}-monochromatic` as TIconsName;
|
|
||||||
const isSaml = service.service === 'saml';
|
|
||||||
let onPress = () => {};
|
|
||||||
|
|
||||||
switch (service.authType) {
|
|
||||||
case 'oauth': {
|
|
||||||
onPress = this.getSocialOauthProvider(service.name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'oauth_custom': {
|
|
||||||
onPress = () => this.onPressCustomOAuth(service);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'saml': {
|
|
||||||
onPress = () => this.onPressSaml(service);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'cas': {
|
|
||||||
onPress = () => this.onPressCas();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'apple': {
|
|
||||||
onPress = () => this.onPressAppleLogin();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
||||||
let buttonText;
|
|
||||||
if (isSaml || (service.service === 'cas' && CAS_enabled)) {
|
|
||||||
buttonText = <Text style={[styles.serviceName, isSaml && { color: service.buttonLabelColor }]}>{name}</Text>;
|
|
||||||
} else {
|
|
||||||
buttonText = (
|
|
||||||
<>
|
|
||||||
{I18n.t('Continue_with')} <Text style={styles.serviceName}>{name}</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : themes[theme].chatComponentBackground;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Touch
|
|
||||||
key={service.name}
|
|
||||||
onPress={onPress}
|
|
||||||
style={[styles.serviceButton, { backgroundColor }]}
|
|
||||||
theme={theme}
|
|
||||||
activeOpacity={0.5}
|
|
||||||
underlayColor={themes[theme].buttonText}>
|
|
||||||
<View style={styles.serviceButtonContainer}>
|
|
||||||
{service.authType === 'oauth' || service.authType === 'apple' ? (
|
|
||||||
<CustomIcon name={icon} size={24} color={themes[theme].titleText} style={styles.serviceIcon} />
|
|
||||||
) : null}
|
|
||||||
<Text style={[styles.serviceText, { color: themes[theme].titleText }]}>{buttonText}</Text>
|
|
||||||
</View>
|
|
||||||
</Touch>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { servicesHeight } = this.state;
|
|
||||||
const { services, separator } = this.props;
|
|
||||||
const { length } = Object.values(services);
|
|
||||||
const style: Animated.AnimatedProps<ViewStyle> = {
|
|
||||||
overflow: 'hidden',
|
|
||||||
height: servicesHeight
|
|
||||||
};
|
|
||||||
|
|
||||||
if (length > 3 && separator) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Animated.View style={style}>
|
|
||||||
{Object.values(services).map((service: IItemService) => this.renderItem(service))}
|
|
||||||
</Animated.View>
|
|
||||||
{this.renderServicesSeparator()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{Object.values(services).map((service: IItemService) => this.renderItem(service))}
|
|
||||||
{this.renderServicesSeparator()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
|
||||||
server: state.server.server,
|
|
||||||
Gitlab_URL: state.settings.API_Gitlab_URL as string,
|
|
||||||
CAS_enabled: state.settings.CAS_enabled as boolean,
|
|
||||||
CAS_login_url: state.settings.CAS_login_url as string,
|
|
||||||
services: state.login.services as IServices
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(LoginServices));
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import Touch from '../../lib/methods/helpers/touch';
|
||||||
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
import { IButtonService } from './interfaces';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const ButtonService = ({ name, authType, onPress, backgroundColor, buttonText, icon }: IButtonService) => {
|
||||||
|
const { theme, colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Touch
|
||||||
|
key={name}
|
||||||
|
onPress={onPress}
|
||||||
|
style={[styles.serviceButton, { backgroundColor }]}
|
||||||
|
theme={theme}
|
||||||
|
activeOpacity={0.5}
|
||||||
|
underlayColor={colors.buttonText}>
|
||||||
|
<View style={styles.serviceButtonContainer}>
|
||||||
|
{authType === 'oauth' || authType === 'apple' ? (
|
||||||
|
<CustomIcon name={icon} size={24} color={colors.titleText} style={styles.serviceIcon} />
|
||||||
|
) : null}
|
||||||
|
<Text style={[styles.serviceText, { color: colors.titleText }]}>{buttonText}</Text>
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ButtonService;
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { StyleSheet, Text, ScrollView } from 'react-native';
|
||||||
|
|
||||||
|
import { store } from '../../../storybook/stories';
|
||||||
|
import { ThemeContext } from '../../theme';
|
||||||
|
import { colors } from '../../lib/constants';
|
||||||
|
import i18n from '../../i18n';
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import ServicesSeparator from './ServicesSeparator';
|
||||||
|
import ButtonService from './ButtonService';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
serviceName: {
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const services = {
|
||||||
|
github: {
|
||||||
|
_id: 'github',
|
||||||
|
name: 'github',
|
||||||
|
clientId: 'github-123',
|
||||||
|
buttonLabelText: '',
|
||||||
|
buttonColor: '',
|
||||||
|
buttonLabelColor: '',
|
||||||
|
custom: false,
|
||||||
|
authType: 'oauth'
|
||||||
|
},
|
||||||
|
gitlab: {
|
||||||
|
_id: 'gitlab',
|
||||||
|
name: 'gitlab',
|
||||||
|
clientId: 'gitlab-123',
|
||||||
|
buttonLabelText: '',
|
||||||
|
buttonColor: '',
|
||||||
|
buttonLabelColor: '',
|
||||||
|
custom: false,
|
||||||
|
authType: 'oauth'
|
||||||
|
},
|
||||||
|
google: {
|
||||||
|
_id: 'google',
|
||||||
|
name: 'google',
|
||||||
|
clientId: 'google-123',
|
||||||
|
buttonLabelText: '',
|
||||||
|
buttonColor: '',
|
||||||
|
buttonLabelColor: '',
|
||||||
|
custom: false,
|
||||||
|
authType: 'oauth'
|
||||||
|
},
|
||||||
|
apple: {
|
||||||
|
_id: 'apple',
|
||||||
|
name: 'apple',
|
||||||
|
clientId: 'apple-123',
|
||||||
|
buttonLabelText: 'Sign in with Apple',
|
||||||
|
buttonColor: '#000',
|
||||||
|
buttonLabelColor: '#FFF',
|
||||||
|
custom: false,
|
||||||
|
authType: 'apple'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const theme = 'light';
|
||||||
|
|
||||||
|
const stories = storiesOf('Login Services', module)
|
||||||
|
.addDecorator(story => <Provider store={store}>{story()}</Provider>)
|
||||||
|
.addDecorator(story => <ThemeContext.Provider value={{ theme, colors: colors[theme] }}>{story()}</ThemeContext.Provider>)
|
||||||
|
.addDecorator(story => <ScrollView style={sharedStyles.containerScrollView}>{story()}</ScrollView>);
|
||||||
|
|
||||||
|
stories.add('ServicesSeparator', () => (
|
||||||
|
<>
|
||||||
|
<ServicesSeparator collapsed onPressButtonSeparator={() => {}} separator services={services} />
|
||||||
|
<ServicesSeparator collapsed={false} onPressButtonSeparator={() => {}} separator services={services} />
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
|
||||||
|
stories.add('ServiceList', () => (
|
||||||
|
<>
|
||||||
|
{Object.values(services).map(service => {
|
||||||
|
const icon = `${service.name}-monochromatic`;
|
||||||
|
const buttonText = (
|
||||||
|
<>
|
||||||
|
{i18n.t('Continue_with')} <Text style={styles.serviceName}>{service.name}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ButtonService
|
||||||
|
key={service._id}
|
||||||
|
onPress={() => {}}
|
||||||
|
backgroundColor={colors[theme].chatComponentBackground}
|
||||||
|
buttonText={buttonText}
|
||||||
|
icon={icon}
|
||||||
|
name={service.name}
|
||||||
|
authType={service.authType}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
));
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
import { IItemService, IOauthProvider } from './interfaces';
|
||||||
|
import styles from './styles';
|
||||||
|
import * as ServiceLogin from './serviceLogin';
|
||||||
|
import ButtonService from './ButtonService';
|
||||||
|
|
||||||
|
const Service = React.memo(
|
||||||
|
({
|
||||||
|
CAS_enabled,
|
||||||
|
CAS_login_url,
|
||||||
|
Gitlab_URL,
|
||||||
|
server,
|
||||||
|
service
|
||||||
|
}: {
|
||||||
|
service: IItemService;
|
||||||
|
server: string;
|
||||||
|
Gitlab_URL: string;
|
||||||
|
CAS_enabled: boolean;
|
||||||
|
CAS_login_url: string;
|
||||||
|
storiesTestOnPress?: () => void;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const onPress = useRef<any>();
|
||||||
|
const buttonText = useRef<React.ReactElement>();
|
||||||
|
const modifiedName = useRef<string>();
|
||||||
|
|
||||||
|
const { name } = service;
|
||||||
|
modifiedName.current = name === 'meteor-developer' ? 'meteor' : name;
|
||||||
|
const icon = `${modifiedName.current}-monochromatic` as TIconsName;
|
||||||
|
const isSaml = service.service === 'saml';
|
||||||
|
|
||||||
|
const getSocialOauthProvider = (name: string) => {
|
||||||
|
const oauthProviders: IOauthProvider = {
|
||||||
|
facebook: () => ServiceLogin.onPressFacebook({ service, server }),
|
||||||
|
github: () => ServiceLogin.onPressGithub({ service, server }),
|
||||||
|
gitlab: () => ServiceLogin.onPressGitlab({ service, server, urlOption: Gitlab_URL }),
|
||||||
|
google: () => ServiceLogin.onPressGoogle({ service, server }),
|
||||||
|
linkedin: () => ServiceLogin.onPressLinkedin({ service, server }),
|
||||||
|
'meteor-developer': () => ServiceLogin.onPressMeteor({ service, server }),
|
||||||
|
twitter: () => ServiceLogin.onPressTwitter({ service, server }),
|
||||||
|
wordpress: () => ServiceLogin.onPressWordpress({ service, server })
|
||||||
|
};
|
||||||
|
return oauthProviders[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (service.authType) {
|
||||||
|
case 'oauth': {
|
||||||
|
onPress.current = getSocialOauthProvider(service.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'oauth_custom': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressCustomOAuth({ loginService: service, server });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'saml': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressSaml({ loginService: service, server });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'cas': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressCas({ casLoginUrl: CAS_login_url, server });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'apple': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressAppleLogin();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedName.current = modifiedName.current.charAt(0).toUpperCase() + modifiedName.current.slice(1);
|
||||||
|
if (isSaml || (service.service === 'cas' && CAS_enabled)) {
|
||||||
|
buttonText.current = (
|
||||||
|
<Text style={[styles.serviceName, isSaml && { color: service.buttonLabelColor }]}>{modifiedName.current}</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
buttonText.current = (
|
||||||
|
<>
|
||||||
|
{I18n.t('Continue_with')} <Text style={styles.serviceName}>{modifiedName.current}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : colors.chatComponentBackground;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonService
|
||||||
|
onPress={onPress.current}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
buttonText={buttonText.current}
|
||||||
|
icon={icon}
|
||||||
|
name={service.name}
|
||||||
|
authType={service.authType}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Service;
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import OrSeparator from '../OrSeparator';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import styles from './styles';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { IServicesSeparator } from './interfaces';
|
||||||
|
|
||||||
|
const ServicesSeparator = ({ services, separator, collapsed, onPress }: IServicesSeparator) => {
|
||||||
|
const { colors, theme } = useTheme();
|
||||||
|
|
||||||
|
const { length } = Object.values(services);
|
||||||
|
|
||||||
|
if (length > 3 && separator) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
|
||||||
|
type='secondary'
|
||||||
|
onPress={onPress}
|
||||||
|
style={styles.options}
|
||||||
|
color={colors.actionTintColor}
|
||||||
|
/>
|
||||||
|
<OrSeparator theme={theme} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (length > 0 && separator) {
|
||||||
|
return <OrSeparator theme={theme} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesSeparator;
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Storyshots Login Services ServiceList 1`] = `"{\\"type\\":\\"RCTScrollView\\",\\"props\\":{\\"style\\":{\\"padding\\":15,\\"paddingBottom\\":30}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]}]}"`;
|
||||||
|
|
||||||
|
exports[`Storyshots Login Services ServicesSeparator 1`] = `"{\\"type\\":\\"RCTScrollView\\",\\"props\\":{\\"style\\":{\\"padding\\":15,\\"paddingBottom\\":30}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"focusable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"focusable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]}]}"`;
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { shallowEqual } from 'react-redux';
|
||||||
|
import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { IServices } from '../../selectors/login';
|
||||||
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
import { IItemService, IServiceList } from './interfaces';
|
||||||
|
import { SERVICES_COLLAPSED_HEIGHT, SERVICE_HEIGHT } from './styles';
|
||||||
|
import ServicesSeparator from './ServicesSeparator';
|
||||||
|
import Service from './Service';
|
||||||
|
|
||||||
|
const ServiceList = ({ services, CAS_enabled, CAS_login_url, Gitlab_URL, server }: IServiceList) => (
|
||||||
|
<>
|
||||||
|
{Object.values(services).map((service: IItemService) => (
|
||||||
|
<Service
|
||||||
|
key={service._id}
|
||||||
|
CAS_enabled={CAS_enabled}
|
||||||
|
CAS_login_url={CAS_login_url}
|
||||||
|
Gitlab_URL={Gitlab_URL}
|
||||||
|
server={server}
|
||||||
|
service={service}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const LoginServices = ({ separator }: { separator: boolean }): React.ReactElement => {
|
||||||
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
|
|
||||||
|
const { Gitlab_URL, CAS_enabled, CAS_login_url } = useAppSelector(
|
||||||
|
state => ({
|
||||||
|
Gitlab_URL: state.settings.API_Gitlab_URL as string,
|
||||||
|
CAS_enabled: state.settings.CAS_enabled as boolean,
|
||||||
|
CAS_login_url: state.settings.CAS_login_url as string
|
||||||
|
}),
|
||||||
|
shallowEqual
|
||||||
|
);
|
||||||
|
const server = useAppSelector(state => state.server.server);
|
||||||
|
const services = useAppSelector(state => state.login.services as IServices, shallowEqual);
|
||||||
|
const { length } = Object.values(services);
|
||||||
|
|
||||||
|
const heightButtons = useSharedValue(SERVICES_COLLAPSED_HEIGHT);
|
||||||
|
|
||||||
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: withTiming(heightButtons.value, { duration: 300, easing: Easing.inOut(Easing.quad) })
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onPressButtonSeparator = () => {
|
||||||
|
heightButtons.value = collapsed ? SERVICE_HEIGHT * length : SERVICES_COLLAPSED_HEIGHT;
|
||||||
|
setCollapsed(prevState => !prevState);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (length > 3 && separator) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Animated.View style={animatedStyle}>
|
||||||
|
<ServiceList
|
||||||
|
services={services}
|
||||||
|
CAS_enabled={CAS_enabled}
|
||||||
|
CAS_login_url={CAS_login_url}
|
||||||
|
Gitlab_URL={Gitlab_URL}
|
||||||
|
server={server}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
<ServicesSeparator services={services} separator={separator} collapsed={collapsed} onPress={onPressButtonSeparator} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ServiceList
|
||||||
|
services={services}
|
||||||
|
CAS_enabled={CAS_enabled}
|
||||||
|
CAS_login_url={CAS_login_url}
|
||||||
|
Gitlab_URL={Gitlab_URL}
|
||||||
|
server={server}
|
||||||
|
/>
|
||||||
|
<ServicesSeparator services={services} separator={separator} collapsed={collapsed} onPress={onPressButtonSeparator} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginServices;
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
import { IServices } from '../../selectors/login';
|
||||||
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
|
||||||
|
type TAuthType = 'oauth' | 'oauth_custom' | 'saml' | 'cas' | 'apple';
|
||||||
|
|
||||||
|
type TServiceName = 'facebook' | 'github' | 'gitlab' | 'google' | 'linkedin' | 'meteor-developer' | 'twitter' | 'wordpress';
|
||||||
|
export interface IOpenOAuth {
|
||||||
|
url: string;
|
||||||
|
ssoToken?: string;
|
||||||
|
authType?: TAuthType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IItemService {
|
||||||
|
_id: string;
|
||||||
|
name: TServiceName;
|
||||||
|
service: string;
|
||||||
|
authType: TAuthType;
|
||||||
|
buttonColor: string;
|
||||||
|
buttonLabelColor: string;
|
||||||
|
clientConfig: { provider: string };
|
||||||
|
serverURL: string;
|
||||||
|
authorizePath: string;
|
||||||
|
clientId: string;
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServiceLogin {
|
||||||
|
service: IItemService;
|
||||||
|
server: string;
|
||||||
|
urlOption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOauthProvider {
|
||||||
|
[key: string]: ({ service, server }: IServiceLogin) => void;
|
||||||
|
facebook: ({ service, server }: IServiceLogin) => void;
|
||||||
|
github: ({ service, server }: IServiceLogin) => void;
|
||||||
|
gitlab: ({ service, server }: IServiceLogin) => void;
|
||||||
|
google: ({ service, server }: IServiceLogin) => void;
|
||||||
|
linkedin: ({ service, server }: IServiceLogin) => void;
|
||||||
|
'meteor-developer': ({ service, server }: IServiceLogin) => void;
|
||||||
|
twitter: ({ service, server }: IServiceLogin) => void;
|
||||||
|
wordpress: ({ service, server }: IServiceLogin) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServiceList {
|
||||||
|
services: IServices;
|
||||||
|
CAS_enabled: boolean;
|
||||||
|
CAS_login_url: string;
|
||||||
|
Gitlab_URL: string;
|
||||||
|
server: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServicesSeparator {
|
||||||
|
services: IServices;
|
||||||
|
separator: boolean;
|
||||||
|
collapsed: boolean;
|
||||||
|
onPress(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IButtonService {
|
||||||
|
name: string;
|
||||||
|
authType: TAuthType;
|
||||||
|
onPress: () => void;
|
||||||
|
backgroundColor: string;
|
||||||
|
buttonText: ReactElement;
|
||||||
|
icon: TIconsName;
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||||
|
import { Linking } from 'react-native';
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
|
import { Services } from '../../lib/services';
|
||||||
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
|
import { IItemService, IOpenOAuth, IServiceLogin } from './interfaces';
|
||||||
|
import { random } from '../../lib/methods/helpers';
|
||||||
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
|
|
||||||
|
type TLoginStyle = 'popup' | 'redirect';
|
||||||
|
|
||||||
|
export const onPressFacebook = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_FACEBOOK);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
||||||
|
const redirect_uri = `${server}/_oauth/facebook?close`;
|
||||||
|
const scope = 'email';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&display=touch`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressGithub = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_GITHUB);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = `https://github.com/login?client_id=${clientId}&return_to=${encodeURIComponent('/login/oauth/authorize')}`;
|
||||||
|
const redirect_uri = `${server}/_oauth/github?close`;
|
||||||
|
const scope = 'user:email';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}`;
|
||||||
|
openOAuth({ url: `${endpoint}${encodeURIComponent(params)}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressGitlab = ({ service, server, urlOption }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_GITLAB);
|
||||||
|
const { clientId } = service;
|
||||||
|
const baseURL = urlOption ? urlOption.trim().replace(/\/*$/, '') : 'https://gitlab.com';
|
||||||
|
const endpoint = `${baseURL}/oauth/authorize`;
|
||||||
|
const redirect_uri = `${server}/_oauth/gitlab?close`;
|
||||||
|
const scope = 'read_user';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressGoogle = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_GOOGLE);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
||||||
|
const redirect_uri = `${server}/_oauth/google?close`;
|
||||||
|
const scope = 'email';
|
||||||
|
const state = getOAuthState('redirect');
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
Linking.openURL(`${endpoint}${params}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressLinkedin = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_LINKEDIN);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
|
||||||
|
const redirect_uri = `${server}/_oauth/linkedin?close`;
|
||||||
|
const scope = 'r_liteprofile,r_emailaddress';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressMeteor = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_METEOR);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
||||||
|
const redirect_uri = `${server}/_oauth/meteor-developer`;
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressTwitter = ({ server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_TWITTER);
|
||||||
|
const state = getOAuthState();
|
||||||
|
const url = `${server}/_oauth/twitter/?requestTokenAndRedirect=true&state=${state}`;
|
||||||
|
openOAuth({ url });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressWordpress = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_WORDPRESS);
|
||||||
|
const { clientId, serverURL } = service;
|
||||||
|
const endpoint = `${serverURL}/oauth/authorize`;
|
||||||
|
const redirect_uri = `${server}/_oauth/wordpress?close`;
|
||||||
|
const scope = 'openid';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressCustomOAuth = ({ loginService, server }: { loginService: IItemService; server: string }) => {
|
||||||
|
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
||||||
|
const { serverURL, authorizePath, clientId, scope, service } = loginService;
|
||||||
|
const redirectUri = `${server}/_oauth/${service}`;
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&state=${state}&scope=${scope}`;
|
||||||
|
const domain = `${serverURL}`;
|
||||||
|
const absolutePath = `${authorizePath}${params}`;
|
||||||
|
const url = absolutePath.includes(domain) ? absolutePath : domain + absolutePath;
|
||||||
|
openOAuth({ url });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressSaml = ({ loginService, server }: { loginService: IItemService; server: string }) => {
|
||||||
|
logEvent(events.ENTER_WITH_SAML);
|
||||||
|
const { clientConfig } = loginService;
|
||||||
|
const { provider } = clientConfig;
|
||||||
|
const ssoToken = random(17);
|
||||||
|
const url = `${server}/_saml/authorize/${provider}/${ssoToken}`;
|
||||||
|
openOAuth({ url, ssoToken, authType: 'saml' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressCas = ({ casLoginUrl, server }: { casLoginUrl: string; server: string }) => {
|
||||||
|
logEvent(events.ENTER_WITH_CAS);
|
||||||
|
const ssoToken = random(17);
|
||||||
|
const url = `${casLoginUrl}?service=${server}/_cas/${ssoToken}`;
|
||||||
|
openOAuth({ url, ssoToken, authType: 'cas' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressAppleLogin = async () => {
|
||||||
|
logEvent(events.ENTER_WITH_APPLE);
|
||||||
|
try {
|
||||||
|
const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
|
||||||
|
requestedScopes: [
|
||||||
|
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||||
|
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await Services.loginOAuthOrSso({ fullName, email, identityToken });
|
||||||
|
} catch {
|
||||||
|
logEvent(events.ENTER_WITH_APPLE_F);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOAuthState = (loginStyle: TLoginStyle = 'popup') => {
|
||||||
|
const credentialToken = random(43);
|
||||||
|
let obj: {
|
||||||
|
loginStyle: string;
|
||||||
|
credentialToken: string;
|
||||||
|
isCordova: boolean;
|
||||||
|
redirectUrl?: string;
|
||||||
|
} = { loginStyle, credentialToken, isCordova: true };
|
||||||
|
if (loginStyle === 'redirect') {
|
||||||
|
obj = {
|
||||||
|
...obj,
|
||||||
|
redirectUrl: 'rocketchat://auth'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Base64.encodeURI(JSON.stringify(obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openOAuth = ({ url, ssoToken, authType = 'oauth' }: IOpenOAuth) => {
|
||||||
|
Navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
|
export const BUTTON_HEIGHT = 48;
|
||||||
|
export const SERVICE_HEIGHT = 58;
|
||||||
|
export const BORDER_RADIUS = 2;
|
||||||
|
export const SERVICES_COLLAPSED_HEIGHT = 174;
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
serviceButton: {
|
||||||
|
borderRadius: BORDER_RADIUS,
|
||||||
|
marginBottom: 10
|
||||||
|
},
|
||||||
|
serviceButtonContainer: {
|
||||||
|
borderRadius: BORDER_RADIUS,
|
||||||
|
width: '100%',
|
||||||
|
height: BUTTON_HEIGHT,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 15
|
||||||
|
},
|
||||||
|
serviceIcon: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 15,
|
||||||
|
top: 12,
|
||||||
|
width: 24,
|
||||||
|
height: 24
|
||||||
|
},
|
||||||
|
serviceText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16
|
||||||
|
},
|
||||||
|
serviceName: {
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
marginBottom: 0
|
||||||
|
}
|
||||||
|
});
|
|
@ -230,11 +230,12 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { Accounts_ShowFormLogin, theme, navigation } = this.props;
|
const { Accounts_ShowFormLogin } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormContainer testID='login-view'>
|
<FormContainer testID='login-view'>
|
||||||
<FormContainerInner>
|
<FormContainerInner>
|
||||||
<LoginServices separator={Accounts_ShowFormLogin} navigation={navigation} theme={theme} />
|
<LoginServices separator={Accounts_ShowFormLogin} />
|
||||||
{this.renderUserForm()}
|
{this.renderUserForm()}
|
||||||
</FormContainerInner>
|
</FormContainerInner>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
|
|
|
@ -236,11 +236,11 @@ class RegisterView extends React.Component<IProps, any> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { saving } = this.state;
|
const { saving } = this.state;
|
||||||
const { theme, showLoginButton, navigation } = this.props;
|
const { theme, showLoginButton } = this.props;
|
||||||
return (
|
return (
|
||||||
<FormContainer testID='register-view'>
|
<FormContainer testID='register-view'>
|
||||||
<FormContainerInner>
|
<FormContainerInner>
|
||||||
<LoginServices navigation={navigation} theme={theme} separator />
|
<LoginServices separator />
|
||||||
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>
|
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
label={I18n.t('Name')}
|
label={I18n.t('Name')}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import '../../app/views/CannedResponsesListView/CannedResponseItem.stories';
|
||||||
import '../../app/containers/TextInput/TextInput.stories';
|
import '../../app/containers/TextInput/TextInput.stories';
|
||||||
import '../../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories';
|
import '../../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories';
|
||||||
import '../../app/containers/Button/Button.stories';
|
import '../../app/containers/Button/Button.stories';
|
||||||
|
import '../../app/containers/LoginServices/LoginServices.stories';
|
||||||
import '../../app/containers/SearchBox/SearchBox.stories';
|
import '../../app/containers/SearchBox/SearchBox.stories';
|
||||||
|
|
||||||
// Change here to see themed storybook
|
// Change here to see themed storybook
|
||||||
|
|
Loading…
Reference in New Issue