diff --git a/core/auth/components/OAuthButton.tsx b/core/auth/components/OAuthButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f23bed71f4aefa0378c42356f16eae7cf0b77bf1 --- /dev/null +++ b/core/auth/components/OAuthButton.tsx @@ -0,0 +1,94 @@ +import { accessibilityProps, type AccessibilityProps } from 'holi-bricks/accessibility' +import { useStyles } from 'holi-bricks/hooks' +import { Spacing } from 'holi-bricks/tokens' +import { createStyleSheet } from 'holi-bricks/utils' +import { Text } from 'holi-bricks/components/text' +import type React from 'react' +import { useState } from 'react' +import { type GestureResponderEvent, Pressable, View } from 'react-native' + +import { HoliIcon } from '@holi/icons/src/HoliIcon' +import { LogoGoogle, LogoFacebook, LogoApple } from '@holi/icons/src/generated' +import { + buttonBaseStyle, + buttonContentStyle, + getButtonTextVarianStyle, + getButtonVariantStyle, +} from 'holi-bricks/components/button/helpers' + +type OAuthProvider = 'google' | 'facebook' | 'apple' + +export type OAuthButtonProps = AccessibilityProps & { + provider: OAuthProvider + label: string + onPress?: (event: GestureResponderEvent) => void + disabled?: boolean + loading?: boolean + loadingLabel?: string + selected?: boolean + testID?: string +} + +export const OAuthButton = ({ provider, label, onPress, disabled = false, testID, ...rest }: OAuthButtonProps) => { + const [isHovered, setIsHovered] = useState(false) + const { styles } = useStyles(stylesheet) + + return ( + <Pressable + accessibilityRole="button" + accessibilityLabel={label} + accessibilityState={{ + disabled: disabled, + }} + aria-disabled={disabled} + onPress={onPress} + onHoverIn={() => setIsHovered(true)} + onHoverOut={() => setIsHovered(false)} + style={({ pressed }) => [styles.button(!disabled && isHovered, !disabled && !!pressed)]} + disabled={disabled} + testID={testID} + {...accessibilityProps(rest)} + > + <View style={styles.content}> + <View style={styles.iconContainer}> + <HoliIcon + icon={provider === 'google' ? LogoGoogle : provider === 'facebook' ? LogoFacebook : LogoApple} + size={24} + color={styles.text(disabled, false).color} + /> + </View> + <View style={styles.textContainer}> + <Text size="lg" colorOveride={styles.text(disabled, false).color}> + {label} + </Text> + </View> + </View> + </Pressable> + ) +} + +const stylesheet = createStyleSheet((theme) => ({ + button: (hovered: boolean, pressed: boolean) => ({ + ...buttonBaseStyle, + width: '100%', + ...getButtonVariantStyle({ variant: 'secondary', theme, pressed, hovered }), + ...(pressed && { + transform: [{ scale: 0.98 }], + }), + }), + content: { + ...buttonContentStyle, + padding: Spacing['4xs'], + }, + textContainer: { + height: Spacing.xl, + justifyContent: 'center', + }, + iconContainer: { + marginRight: Spacing['4xs'], + }, + text: (disabled: boolean, selected: boolean) => + getButtonTextVarianStyle({ variant: 'secondary', theme, disabled, selected }), +})) + +export default OAuthButton diff --git a/core/auth/components/OAuthSection.tsx b/core/auth/components/OAuthSection.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c6e1cf805537fd9eca42fbbbe99efbf0ce676dcc --- /dev/null +++ b/core/auth/components/OAuthSection.tsx @@ -0,0 +1,113 @@ +import React from 'react' +import { StyleSheet, View } from 'react-native' +import { useTranslation } from 'react-i18next' +import { Text } from 'holi-bricks/components/text' +import { Spacing } from 'holi-bricks/tokens' +import { type HoliTheme, useTheme } from '@holi/ui/styles/theme' +import { OAuthButton } from '@holi/core/auth/components/OAuthButton' +import { TrackingEvent } from '@holi/core/tracking' +import useTracking from '@holi/core/tracking/hooks/useTracking' + +export type OAuthSectionProps = { + onGooglePress?: () => void + onFacebookPress?: () => void + onApplePress?: () => void + withDivider?: boolean + testID?: string +} + +export const OAuthSection = ({ + onGooglePress, + onFacebookPress, + onApplePress, + withDivider, + testID = 'oauth-section', +}: OAuthSectionProps) => { + const { t } = useTranslation() + const { theme } = useTheme() + const styles = getStyles(theme) + const { track } = useTracking() + + const handleGooglePress = () => { + track(TrackingEvent.Auth.OAuthWithGoogleButtonPressed) + onGooglePress?.() + } + + const handleFacebookPress = () => { + track(TrackingEvent.Auth.OAuthWithFacebookButtonPressed) + onFacebookPress?.() + } + + const hasAnyProvider = Boolean(onGooglePress) || Boolean(onFacebookPress) || Boolean(onApplePress) + + if (!hasAnyProvider) { + return null + } + + return ( + <View testID={testID}> + <View style={styles.socialButtons}> + {onApplePress && ( + <OAuthButton + provider="apple" + label={t('registration.signUp.socialButtons.apple')} + onPress={handleFacebookPress} + testID={`${testID}-facebook`} + /> + )} + {onGooglePress && ( + <OAuthButton + provider="google" + label={t('registration.signUp.socialButtons.google')} + onPress={handleGooglePress} + testID={`${testID}-google`} + /> + )} + {onFacebookPress && ( + <OAuthButton + provider="facebook" + label={t('registration.signUp.socialButtons.facebook')} + onPress={handleFacebookPress} + testID={`${testID}-facebook`} + /> + )} + </View> + {withDivider && hasAnyProvider && ( + <View style={styles.dividerContainer}> + <View style={styles.dividerLineLeft} /> + <Text size="sm" color="support"> + {t('registration.signUp.divider')} + </Text> + <View style={styles.dividerLineRight} /> + </View> + )} + </View> + ) +} + +const getStyles = (theme: HoliTheme) => + StyleSheet.create({ + socialButtons: { + marginTop: Spacing.md, + gap: Spacing.xs, + }, + dividerContainer: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: Spacing.xs, + }, + dividerLineLeft: { + flex: 1, + height: 1, + backgroundColor: theme.colors.gray100, + alignSelf: 'center', + marginRight: Spacing.xs, + }, + dividerLineRight: { + flex: 1, + height: 1, + backgroundColor: theme.colors.gray100, + alignSelf: 'center', + marginLeft: Spacing.xs, + }, + }) diff --git a/core/i18n/locales/de.json b/core/i18n/locales/de.json index b108fc7fcc9d581d91467fb33dfc5bdddfd2397b..0b1c044d4b85cecf8291fae9489078ae3e0f6058 100644 --- a/core/i18n/locales/de.json +++ b/core/i18n/locales/de.json @@ -766,7 +766,11 @@ "registration.emailVerification.message.invalidCode": "Der Aktivierungscode ist ungültig oder wurde bereits verwendet. Bitte versuche es noch einmal oder sende die E-Mail erneut.", "registration.emailVerification.resendEmail": "E-Mail erneut senden", "registration.signUp.description": "Erstelle ein Konto, um eine personalisierte Erfahrung zu erhalten und auf alle sozialen Funktionen zuzugreifen.", + "registration.signUp.divider": "oder verwende deine E-Mail-Adresse", "registration.signUp.heading": "Hier geht die Action los", + "registration.signUp.socialButtons.apple": "Mit Apple fortfahren", + "registration.signUp.socialButtons.facebook": "Mit Facebook fortfahren", + "registration.signUp.socialButtons.google": "Mit Google fortfahren", "report.FEED_POST.title": "Post melden", "report.POST.title": "Post melden", "report.POST_COMMENT.title": "Kommentar melden", diff --git a/core/i18n/locales/en.json b/core/i18n/locales/en.json index d294d1c86c6cb0a027f076501227294f3e0aa202..eb04817a5653fa35c783964fbfe3f5d4eca52096 100644 --- a/core/i18n/locales/en.json +++ b/core/i18n/locales/en.json @@ -765,7 +765,11 @@ "registration.emailVerification.message.invalidCode": "The verification code is invalid or has already been used. Please try again or resend the email.", "registration.emailVerification.resendEmail": "Resend email", "registration.signUp.description": "Save your personalization details and get access to social features by creating an account.", + "registration.signUp.divider": "or use your email address", "registration.signUp.heading": "Action starts here", + "registration.signUp.socialButtons.apple": "Continue with Apple", + "registration.signUp.socialButtons.facebook": "Continue with Facebook", + "registration.signUp.socialButtons.google": "Continue with Google", "report.FEED_POST.title": "Report post", "report.POST.title": "Report post", "report.POST_COMMENT.title": "Report comment", diff --git a/core/tracking/events.ts b/core/tracking/events.ts index 93cbb18c40a80d9d55df0f74f007efa937312749..8f2a4c10b555278b9f2e586759ae81595a6ec6b9 100644 --- a/core/tracking/events.ts +++ b/core/tracking/events.ts @@ -265,6 +265,14 @@ export namespace TrackingEvent { name: 'resendEmailButtonPressed', ...versionOne, } + export const OAuthWithGoogleButtonPressed: TrackingEvent = { + name: 'oauthWithGoogleButtonPressed', + ...versionOne, + } + export const OAuthWithFacebookButtonPressed: TrackingEvent = { + name: 'oauthWithFacebookButtonPressed', + ...versionOne, + } } export const ContentTranslated = ( diff --git a/packages/icons/assets/logo-apple.svg b/packages/icons/assets/logo-apple.svg new file mode 100644 index 0000000000000000000000000000000000000000..7388a2dbd1723b85380f4bf6e46ad08f045138a2 --- /dev/null +++ b/packages/icons/assets/logo-apple.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M15.6133 6.77881C15.7923 6.77881 16.0975 6.80729 16.5288 6.86426C16.9601 6.92122 17.4321 7.08398 17.9448 7.35254C18.4657 7.61296 18.9377 8.04834 19.3608 8.65869C19.3364 8.68311 19.2184 8.77262 19.0068 8.92725C18.7952 9.07373 18.5592 9.29346 18.2988 9.58643C18.0384 9.87126 17.8105 10.2375 17.6152 10.6851C17.4199 11.1245 17.3223 11.6494 17.3223 12.2598C17.3223 12.9596 17.4443 13.5537 17.6885 14.042C17.9408 14.5303 18.2297 14.925 18.5552 15.2261C18.8888 15.519 19.1818 15.7347 19.4341 15.873C19.6945 16.0114 19.8328 16.0846 19.8491 16.0928C19.841 16.1253 19.7352 16.4142 19.5317 16.9595C19.3364 17.5047 19.0109 18.111 18.5552 18.7783C18.1564 19.3561 17.7251 19.8892 17.2612 20.3774C16.8055 20.8657 16.2562 21.1099 15.6133 21.1099C15.182 21.1099 14.828 21.0488 14.5513 20.9268C14.2746 20.7965 13.9897 20.6704 13.6968 20.5483C13.4038 20.4181 13.0091 20.353 12.5127 20.353C12.0326 20.353 11.6297 20.4181 11.3042 20.5483C10.9868 20.6785 10.6816 20.8088 10.3887 20.939C10.1038 21.0692 9.76611 21.1343 9.37549 21.1343C8.78141 21.1343 8.26058 20.8983 7.81299 20.4263C7.3654 19.9543 6.9056 19.3887 6.43359 18.7295C5.88835 17.9482 5.42041 16.9961 5.02979 15.873C4.6473 14.7419 4.45605 13.6025 4.45605 12.4551C4.45605 11.2262 4.68799 10.1968 5.15186 9.3667C5.61572 8.52848 6.2098 7.89779 6.93408 7.47461C7.6665 7.04329 8.42334 6.82764 9.20459 6.82764C9.61963 6.82764 10.0103 6.89681 10.3765 7.03516C10.7427 7.16536 11.0845 7.29964 11.4019 7.43799C11.7274 7.57633 12.0203 7.64551 12.2808 7.64551C12.533 7.64551 12.826 7.57227 13.1597 7.42578C13.4933 7.2793 13.8677 7.13688 14.2827 6.99854C14.6978 6.85205 15.1413 6.77881 15.6133 6.77881ZM14.9419 5.22852C14.6245 5.611 14.2257 5.93245 13.7456 6.19287C13.2655 6.44515 12.8097 6.57129 12.3784 6.57129C12.2889 6.57129 12.2035 6.56315 12.1221 6.54688C12.1139 6.52246 12.1058 6.4777 12.0977 6.4126C12.0895 6.34749 12.0854 6.27832 12.0854 6.20508C12.0854 5.7168 12.1912 5.24479 12.4028 4.78906C12.6144 4.3252 12.8545 3.94271 13.123 3.6416C13.4648 3.2347 13.8962 2.89697 14.417 2.62842C14.9378 2.35986 15.4342 2.21745 15.9062 2.20117C15.9307 2.30697 15.9429 2.43311 15.9429 2.57959C15.9429 3.06787 15.8493 3.54395 15.6621 4.00781C15.4749 4.46354 15.2349 4.87044 14.9419 5.22852Z" fill="#262424"/> +</svg> diff --git a/packages/icons/assets/logo-facebook.svg b/packages/icons/assets/logo-facebook.svg new file mode 100644 index 0000000000000000000000000000000000000000..e9177c0be0b30db57a079d40f323dcda537c35e6 --- /dev/null +++ b/packages/icons/assets/logo-facebook.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M22 11.9755C22 6.46625 17.5228 2 12 2C6.4772 2 2 6.46625 2 11.9755C2 16.6537 5.2288 20.5792 9.5844 21.6574V15.0241H7.5224V11.9755H9.5844V10.662C9.5844 7.26668 11.1248 5.69294 14.4664 5.69294C15.1 5.69294 16.1932 5.81704 16.6404 5.94073V8.70396C16.4044 8.67922 15.9944 8.66685 15.4852 8.66685C13.8456 8.66685 13.212 9.28653 13.212 10.8974V11.9755H16.4784L15.9172 15.0241H13.212V21.878C18.1628 21.2815 22 17.0758 22 11.9755Z" fill="#0966FF"/> +</svg> diff --git a/packages/icons/assets/logo-google.svg b/packages/icons/assets/logo-google.svg new file mode 100644 index 0000000000000000000000000000000000000000..593ddc7fe5209e0bd508c5ce60db2c54127e38be --- /dev/null +++ b/packages/icons/assets/logo-google.svg @@ -0,0 +1,11 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<mask id="mask0_17916_57838" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="2" y="2" width="20" height="20"> +<path d="M22 2H2V22H22V2Z" fill="white"/> +</mask> +<g mask="url(#mask0_17916_57838)"> +<path d="M21.6 12.2273C21.6 11.5182 21.5364 10.8364 21.4182 10.1818H12V14.05H17.3818C17.15 15.3 16.4455 16.3591 15.3864 17.0682V19.5773H18.6182C20.5091 17.8364 21.6 15.2727 21.6 12.2273Z" fill="#4285F4"/> +<path d="M11.9999 22C14.6999 22 16.9635 21.1045 18.618 19.5773L15.3862 17.0682C14.4908 17.6682 13.3453 18.0227 11.9999 18.0227C9.39528 18.0227 7.19078 16.2636 6.40438 13.9H3.06348V16.4909C4.70898 19.7591 8.09078 22 11.9999 22Z" fill="#34A853"/> +<path d="M6.4045 13.9C6.2045 13.3 6.0909 12.6591 6.0909 12C6.0909 11.3409 6.2045 10.7 6.4045 10.1V7.50909H3.0636C2.3864 8.85909 2 10.3864 2 12C2 13.6136 2.3864 15.1409 3.0636 16.4909L6.4045 13.9Z" fill="#FBBC04"/> +<path d="M11.9999 5.9773C13.468 5.9773 14.7862 6.4818 15.8226 7.4727L18.6908 4.6045C16.959 2.9909 14.6953 2 11.9999 2C8.09078 2 4.70898 4.2409 3.06348 7.5091L6.40438 10.1C7.19078 7.7364 9.39528 5.9773 11.9999 5.9773Z" fill="#E94235"/> +</g> +</svg> diff --git a/packages/icons/src/generated/LogoApple.js b/packages/icons/src/generated/LogoApple.js new file mode 100644 index 0000000000000000000000000000000000000000..df1f2854d4c003b38c667f38551c2b6d6559048f --- /dev/null +++ b/packages/icons/src/generated/LogoApple.js @@ -0,0 +1,11 @@ +import * as React from 'react' +import Svg, { Path } from 'react-native-svg' +const SvgLogoApple = (props) => ( + <Svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} fill="none" viewBox="0 0 24 24" {...props}> + <Path + fill="currentColor" + d="M15.613 6.779q.27 0 .916.085t1.416.489q.78.39 1.416 1.306a6 6 0 0 1-.354.268 4.2 4.2 0 0 0-.708.66q-.39.426-.684 1.098-.293.66-.293 1.575 0 1.05.367 1.782.377.732.866 1.184.5.44.88.647l.414.22q-.012.048-.317.866-.294.819-.977 1.82-.598.866-1.294 1.598-.684.733-1.648.733-.646 0-1.062-.183a21 21 0 0 0-.854-.379q-.44-.195-1.184-.195-.72 0-1.209.195-.476.195-.915.391-.428.195-1.014.195-.89 0-1.562-.708a16 16 0 0 1-1.38-1.696q-.818-1.173-1.403-2.857a10.6 10.6 0 0 1-.574-3.418q0-1.843.696-3.088.696-1.259 1.782-1.892 1.099-.648 2.27-.647.624 0 1.172.207.55.196 1.026.403.487.207.879.208.378 0 .879-.22.5-.221 1.123-.427a4 4 0 0 1 1.33-.22m-.671-1.55a4 4 0 0 1-1.196.964q-.72.378-1.368.378-.135 0-.256-.024a1.663 1.663 0 0 1-.037-.342q0-.732.318-1.416a4.6 4.6 0 0 1 .72-1.147 4.1 4.1 0 0 1 1.294-1.014q.78-.402 1.49-.427.036.159.036.379 0 .732-.28 1.428a4.8 4.8 0 0 1-.721 1.22" + /> + </Svg> +) +export default SvgLogoApple diff --git a/packages/icons/src/generated/LogoFacebook.js b/packages/icons/src/generated/LogoFacebook.js new file mode 100644 index 0000000000000000000000000000000000000000..9d4aef6cdf4a80790516958f61d44e3bfd615e0e --- /dev/null +++ b/packages/icons/src/generated/LogoFacebook.js @@ -0,0 +1,11 @@ +import * as React from 'react' +import Svg, { Path } from 'react-native-svg' +const SvgLogoFacebook = (props) => ( + <Svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} fill="none" viewBox="0 0 24 24" {...props}> + <Path + fill="#0966FF" + d="M22 11.976C22 6.466 17.523 2 12 2S2 6.466 2 11.976c0 4.678 3.229 8.603 7.584 9.681v-6.633H7.522v-3.048h2.062v-1.314c0-3.395 1.54-4.97 4.882-4.97.634 0 1.727.125 2.174.249v2.763a13 13 0 0 0-1.155-.037c-1.64 0-2.273.62-2.273 2.23v1.079h3.266l-.56 3.048h-2.706v6.854c4.95-.596 8.788-4.802 8.788-9.902" + /> + </Svg> +) +export default SvgLogoFacebook diff --git a/packages/icons/src/generated/LogoGoogle.js b/packages/icons/src/generated/LogoGoogle.js new file mode 100644 index 0000000000000000000000000000000000000000..c79a1252b478a4462ecce5a72152a2745c041c6c --- /dev/null +++ b/packages/icons/src/generated/LogoGoogle.js @@ -0,0 +1,38 @@ +import * as React from 'react' +import Svg, { Mask, Path, G } from 'react-native-svg' +const SvgLogoGoogle = (props) => ( + <Svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} fill="none" viewBox="0 0 24 24" {...props}> + <Mask + id="logo-google_svg__a" + width={20} + height={20} + x={2} + y={2} + maskUnits="userSpaceOnUse" + style={{ + maskType: 'luminance', + }} + > + <Path fill="#fff" d="M22 2H2v20h20z" /> + </Mask> + <G mask="url(#logo-google_svg__a)"> + <Path + fill="#4285F4" + d="M21.6 12.227c0-.709-.064-1.39-.182-2.045H12v3.868h5.382a4.6 4.6 0 0 1-1.996 3.018v2.51h3.232c1.891-1.742 2.982-4.305 2.982-7.35" + /> + <Path + fill="#34A853" + d="M12 22c2.7 0 4.964-.895 6.618-2.423l-3.232-2.509c-.895.6-2.04.955-3.386.955-2.605 0-4.81-1.76-5.596-4.123h-3.34v2.59A10 10 0 0 0 12 22" + /> + <Path + fill="#FBBC04" + d="M6.405 13.9c-.2-.6-.314-1.24-.314-1.9s.114-1.3.313-1.9V7.51h-3.34A10 10 0 0 0 2 12c0 1.614.386 3.14 1.064 4.49z" + /> + <Path + fill="#E94235" + d="M12 5.977c1.468 0 2.786.505 3.823 1.496l2.868-2.868C16.959 2.99 14.695 2 12 2 8.09 2 4.709 4.24 3.063 7.51l3.341 2.59C7.191 7.736 9.395 5.977 12 5.977" + /> + </G> + </Svg> +) +export default SvgLogoGoogle diff --git a/packages/icons/src/generated/index.js b/packages/icons/src/generated/index.js index e36db4245f81cf29fbdbaa2812fbbb0f3f7cc51a..5cae95258243d2b4f8d40deb955cd9c2ac45766d 100644 --- a/packages/icons/src/generated/index.js +++ b/packages/icons/src/generated/index.js @@ -170,6 +170,9 @@ export { default as LoaderFilled } from './LoaderFilled' export { default as Loader } from './Loader' export { default as LockFilled } from './LockFilled' export { default as Lock } from './Lock' +export { default as LogoApple } from './LogoApple' +export { default as LogoFacebook } from './LogoFacebook' +export { default as LogoGoogle } from './LogoGoogle' export { default as LogoutBoxFilled } from './LogoutBoxFilled' export { default as LogoutBox } from './LogoutBox' export { default as MailFilled } from './MailFilled'