From 8d9116c053497278b1bcbe4bc1d00cd3cd4f114a Mon Sep 17 00:00:00 2001 From: sahlamba Date: Sun, 8 Sep 2019 15:05:50 +0530 Subject: [PATCH 1/3] feat: add UserAccount and placeholder Login action --- src/components/Header.tsx | 12 ++++-- src/components/UserAccount.tsx | 40 ++++++++++++++++++ src/css/App.scss | 77 ++++++++++++++++++++++++++++++---- src/css/Palette.scss | 1 + src/css/Vars-Mixins.scss | 3 +- 5 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 src/components/UserAccount.tsx diff --git a/src/components/Header.tsx b/src/components/Header.tsx index bccb656..81cfd23 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -2,6 +2,7 @@ import React from 'react' import { IoIosMoon } from 'react-icons/io' import { useDarkModeContext } from '../context/DarkModeContext' import { WorkspaceSelector } from '../components/WorkspaceSelector' +import { UserAccount } from './UserAccount' export const Header = () => { const { darkMode, toggleDarkMode } = useDarkModeContext() @@ -9,10 +10,13 @@ export const Header = () => { return (
- toggleDarkMode()} - /> +
+ toggleDarkMode()} + /> + +
) } diff --git a/src/components/UserAccount.tsx b/src/components/UserAccount.tsx new file mode 100644 index 0000000..0b040f1 --- /dev/null +++ b/src/components/UserAccount.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react' +import { IoIosLogIn } from 'react-icons/io' + +const LoginAction = ({ darkMode }) => { + const [showOptions, setShowOptions] = useState(false) + + const onClick = () => setShowOptions(!showOptions) + + return ( +
+ + {showOptions && ( +
+
+

Login

+
+
+ )} +
+ ) +} + +export const UserAccount = ({ darkMode }) => { + return ( +
+ +
+ ) +} diff --git a/src/css/App.scss b/src/css/App.scss index 7747c06..4976d5e 100644 --- a/src/css/App.scss +++ b/src/css/App.scss @@ -73,7 +73,7 @@ $header-height: 72px; position: relative; width: 100%; height: $header-height; - padding: 0 20%; + padding: 0 15%; background-color: $white; box-shadow: $generic-box-shadow; @@ -86,17 +86,78 @@ $header-height: 72px; box-shadow: $generic-box-shadow-dark; } - .dark-mode-toggle { + &__actions { + @include va(); position: absolute; - right: 20%; - @include generic-icon(); + right: 15%; @include mq(900px) { right: 16px; } - &.dark-mode { - @include dark-mode-styles(true); + .dark-mode-toggle { + @include generic-icon(4px); + + &.dark-mode { + @include dark-mode-styles(true); + } + } + + /* User Account + ======================================= */ + .user-account { + &__login { + @include va(center); + position: relative; + + &-options { + position: absolute; + width: 150px; + max-height: 300px; + overflow-y: scroll; + top: calc(100% + 14px); + right: 0; + background-color: $white; + box-shadow: rgba(#000000, 0.24) 0px 4px 4px 0px; + border-top: $generic-border; + z-index: 200; + + @include mq(900px) { + width: 100vw; + right: -16px; + } + + &.dark-mode { + @include dark-mode-styles(); + border-color: rgba(#000000, 0.24); + } + + &-item { + padding: 8px 16px; + + &.dark-mode { + border-color: rgba(#000000, 0.24); + } + } + } + + &-toggle { + @include generic-icon(4px); + + &.dark-mode { + @include dark-mode-styles(true); + } + + &.active { + color: $white; + background-color: darken($green, 16); + + &:hover { + background-color: $green; + } + } + } + } } } } @@ -209,10 +270,10 @@ $header-height: 72px; &.delete-action { color: $white; - background-color: $red; + background-color: darken($red, 16); &:hover { - background-color: darken($red, 16); + background-color: $red; } } } diff --git a/src/css/Palette.scss b/src/css/Palette.scss index 28455cf..d559fee 100644 --- a/src/css/Palette.scss +++ b/src/css/Palette.scss @@ -9,3 +9,4 @@ $white: #ffffff; /* Colors ======================================= */ $red: #d96668; +$green: #65da65; diff --git a/src/css/Vars-Mixins.scss b/src/css/Vars-Mixins.scss index 2dd1a23..f8852c9 100644 --- a/src/css/Vars-Mixins.scss +++ b/src/css/Vars-Mixins.scss @@ -35,7 +35,8 @@ $generic-border-dark: 1px solid $light-gray; } } -@mixin generic-icon() { +@mixin generic-icon($margin: 0) { + margin: $margin; padding: 4px; border: 0; border-radius: 4px; From b50a9dc82e83c6062f651af40d859a3a038a67dc Mon Sep 17 00:00:00 2001 From: sahlamba Date: Sun, 8 Sep 2019 17:01:20 +0530 Subject: [PATCH 2/3] feat: add Firebase phone auth, ui login, custom logout with styles --- package.json | 2 + src/App.tsx | 19 ++++--- src/components/UserAccount.tsx | 94 ++++++++++++++++++++++++++++++++-- src/context/AuthContext.js | 30 +++++++++++ src/css/App.scss | 18 +++++-- src/utils/firebase.js | 40 +++++++++++++++ 6 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 src/context/AuthContext.js create mode 100644 src/utils/firebase.js diff --git a/package.json b/package.json index 14d05f6..b61c0d4 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "private": true, "dependencies": { "draft-js": "^0.11.0", + "firebase": "^6.6.0", "react": "^16.9.0", "react-dom": "^16.9.0", + "react-firebaseui": "^4.0.0", "react-ga": "^2.6.0", "react-icons": "^3.7.0", "react-scripts": "3.1.1" diff --git a/src/App.tsx b/src/App.tsx index af90fa5..01cd697 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { Header } from './components/Header' import { NoteEditor } from './components/NoteEditor' import { WorkspaceContextProvider } from './context/WorkspaceContext' import { DarkModeContextProvider } from './context/DarkModeContext' +import { AuthContextProvider } from './context/AuthContext' import { initAnalytics, trackPage } from './utils/tracking.js' export const App = () => { @@ -15,13 +16,15 @@ export const App = () => { }, []) return ( - - -
-
- -
-
-
+ + + +
+
+ +
+
+
+
) } diff --git a/src/components/UserAccount.tsx b/src/components/UserAccount.tsx index 0b040f1..9fe4cc8 100644 --- a/src/components/UserAccount.tsx +++ b/src/components/UserAccount.tsx @@ -1,14 +1,59 @@ import React, { useState } from 'react' -import { IoIosLogIn } from 'react-icons/io' +import { IoIosLogIn, IoIosLogOut, IoIosSettings } from 'react-icons/io' +import { GoCheck, GoX } from 'react-icons/go' +import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth' +import { useAuthContext } from '../context/AuthContext' +import { uiConfig, firebase, logout } from '../utils/firebase' -const LoginAction = ({ darkMode }) => { +const LogoutAction = ({ darkMode }) => { + const [confirmLogout, setConfirmLogout] = useState(false) + + const logoutOnClick = () => { + logout() + } + + const toggleConfirmLogout = () => setConfirmLogout(!confirmLogout) + + return ( +
+

Logout

+
+ {confirmLogout ? ( +
+ + toggleConfirmLogout()} + /> +
+ ) : ( + toggleConfirmLogout()} + /> + )} +
+
+ ) +} + +const UserAccountOptions = ({ darkMode }) => { const [showOptions, setShowOptions] = useState(false) const onClick = () => setShowOptions(!showOptions) return (
- { className={`user-account__login-options-item ${ darkMode ? 'dark-mode' : '' }`}> -

Login

+ +
+ + )} + + ) +} + +const LoginAction = ({ darkMode }) => { + const [showOptions, setShowOptions] = useState(false) + + const onClick = () => setShowOptions(!showOptions) + + return ( +
+ + {showOptions && ( +
+
+
)} @@ -32,9 +110,15 @@ const LoginAction = ({ darkMode }) => { } export const UserAccount = ({ darkMode }) => { + const { isLoggedIn } = useAuthContext() + return (
- + {isLoggedIn ? ( + + ) : ( + + )}
) } diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js new file mode 100644 index 0000000..2954117 --- /dev/null +++ b/src/context/AuthContext.js @@ -0,0 +1,30 @@ +import React, { createContext, useContext, useState, useEffect } from 'react' +import { firebase } from '../utils/firebase' + +const AuthContext = createContext(null) + +export const AuthContextProvider = ({ children }) => { + const [phone, setPhone] = useState('') + const [isLoggedIn, setIsLoggedIn] = useState(false) + + useEffect(() => { + // TODO: use a custom authUser() async/await method here and + // use a loadingAuth state to render loading message before + // render + const unregister = firebase.auth().onAuthStateChanged(user => { + setIsLoggedIn(!!user) + setPhone(!!user ? user.phoneNumber : '') + }) + return () => { + unregister() + } + }, []) + + return ( + + {children} + + ) +} + +export const useAuthContext = () => useContext(AuthContext) diff --git a/src/css/App.scss b/src/css/App.scss index 4976d5e..17ac7ca 100644 --- a/src/css/App.scss +++ b/src/css/App.scss @@ -112,8 +112,8 @@ $header-height: 72px; &-options { position: absolute; - width: 150px; - max-height: 300px; + width: 200px; + max-height: 450px; overflow-y: scroll; top: calc(100% + 14px); right: 0; @@ -122,6 +122,14 @@ $header-height: 72px; border-top: $generic-border; z-index: 200; + &.auto-width { + width: 352px; + + @include mq(900px) { + width: 100vw; + } + } + @include mq(900px) { width: 100vw; right: -16px; @@ -135,6 +143,10 @@ $header-height: 72px; &-item { padding: 8px 16px; + &.zero-padding { + padding: 0; + } + &.dark-mode { border-color: rgba(#000000, 0.24); } @@ -226,7 +238,7 @@ $header-height: 72px; &__list { position: absolute; width: 100%; - max-height: 300px; + max-height: 450px; overflow-y: scroll; top: 100%; left: 50%; diff --git a/src/utils/firebase.js b/src/utils/firebase.js new file mode 100644 index 0000000..e16325a --- /dev/null +++ b/src/utils/firebase.js @@ -0,0 +1,40 @@ +import app from 'firebase/app' +import 'firebase/auth' +import 'firebase/firestore' + +const firebaseConfig = { + apiKey: process.env.REACT_APP_FIREBASE_API_KEY, + authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, + databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL, + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.REACT_APP_FIREBASE_APP_ID, +} + +app.initializeApp(firebaseConfig) + +export const uiConfig = { + signInFlow: 'redirect', + signInOptions: [ + { + provider: app.auth.PhoneAuthProvider.PROVIDER_ID, + }, + ], + callbacks: { + signInSuccessWithAuthResult: () => true, + }, +} + +export const logout = async () => { + try { + const result = await app.auth().signOut() + return result + } catch (error) { + // eslint-disable-next-line + console.error({ error }) + return error + } +} + +export { app as firebase } From f102358c2f8fb1cda5daa8fa5b1f4f847bf0c6ba Mon Sep 17 00:00:00 2001 From: sahlamba Date: Sun, 8 Sep 2019 21:03:06 +0530 Subject: [PATCH 3/3] fix: display loader on app startup while loading persisted auth state --- src/components/UserAccount.tsx | 21 +++++++++++++++------ src/context/AuthContext.js | 8 ++++---- src/css/App.scss | 14 ++++++++++++++ src/css/Vars-Mixins.scss | 7 +++++++ src/utils/firebase.js | 6 ++---- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/components/UserAccount.tsx b/src/components/UserAccount.tsx index 9fe4cc8..e8727cc 100644 --- a/src/components/UserAccount.tsx +++ b/src/components/UserAccount.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import { IoIosLogIn, IoIosLogOut, IoIosSettings } from 'react-icons/io' +import { GiEuropeanFlag } from 'react-icons/gi' import { GoCheck, GoX } from 'react-icons/go' import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth' import { useAuthContext } from '../context/AuthContext' @@ -110,15 +111,23 @@ const LoginAction = ({ darkMode }) => { } export const UserAccount = ({ darkMode }) => { - const { isLoggedIn } = useAuthContext() + const { authenticating, isLoggedIn } = useAuthContext() return ( -
- {isLoggedIn ? ( - + <> + {authenticating ? ( +
+ +
) : ( - +
+ {isLoggedIn ? ( + + ) : ( + + )} +
)} -
+ ) } diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js index 2954117..311f1f9 100644 --- a/src/context/AuthContext.js +++ b/src/context/AuthContext.js @@ -5,13 +5,13 @@ const AuthContext = createContext(null) export const AuthContextProvider = ({ children }) => { const [phone, setPhone] = useState('') + const [authenticating, setAuthenticating] = useState(true) const [isLoggedIn, setIsLoggedIn] = useState(false) useEffect(() => { - // TODO: use a custom authUser() async/await method here and - // use a loadingAuth state to render loading message before - // render const unregister = firebase.auth().onAuthStateChanged(user => { + // Used only on App startup while Firebase checks for persisted Auth state + setAuthenticating(false) setIsLoggedIn(!!user) setPhone(!!user ? user.phoneNumber : '') }) @@ -21,7 +21,7 @@ export const AuthContextProvider = ({ children }) => { }, []) return ( - + {children} ) diff --git a/src/css/App.scss b/src/css/App.scss index 17ac7ca..b4dcd2a 100644 --- a/src/css/App.scss +++ b/src/css/App.scss @@ -64,6 +64,20 @@ p.text-input-error { display: none; } +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.loading-icon { + @include loading-icon(4px); + animation: spin 2.4s linear infinite; +} + /* Header ======================================= */ $header-height: 72px; diff --git a/src/css/Vars-Mixins.scss b/src/css/Vars-Mixins.scss index f8852c9..70b9414 100644 --- a/src/css/Vars-Mixins.scss +++ b/src/css/Vars-Mixins.scss @@ -48,6 +48,13 @@ $generic-border-dark: 1px solid $light-gray; } } +@mixin loading-icon($margin: 0) { + margin: $margin; + padding: 4px; + border: 0; + font-size: 2.5em; +} + @mixin dark-mode-styles($hover-styles: false) { color: $white; background-color: $black; diff --git a/src/utils/firebase.js b/src/utils/firebase.js index e16325a..103b5fb 100644 --- a/src/utils/firebase.js +++ b/src/utils/firebase.js @@ -26,14 +26,12 @@ export const uiConfig = { }, } -export const logout = async () => { +export const logout = () => { try { - const result = await app.auth().signOut() - return result + app.auth().signOut() } catch (error) { // eslint-disable-next-line console.error({ error }) - return error } }