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/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..e8727cc
--- /dev/null
+++ b/src/components/UserAccount.tsx
@@ -0,0 +1,133 @@
+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'
+import { uiConfig, firebase, logout } from '../utils/firebase'
+
+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 (
+
+
+ {showOptions && (
+
+ )}
+
+ )
+}
+
+const LoginAction = ({ darkMode }) => {
+ const [showOptions, setShowOptions] = useState(false)
+
+ const onClick = () => setShowOptions(!showOptions)
+
+ return (
+
+
+ {showOptions && (
+
+ )}
+
+ )
+}
+
+export const UserAccount = ({ darkMode }) => {
+ const { authenticating, isLoggedIn } = useAuthContext()
+
+ return (
+ <>
+ {authenticating ? (
+
+
+
+ ) : (
+
+ {isLoggedIn ? (
+
+ ) : (
+
+ )}
+
+ )}
+ >
+ )
+}
diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js
new file mode 100644
index 0000000..311f1f9
--- /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 [authenticating, setAuthenticating] = useState(true)
+ const [isLoggedIn, setIsLoggedIn] = useState(false)
+
+ useEffect(() => {
+ 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 : '')
+ })
+ return () => {
+ unregister()
+ }
+ }, [])
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useAuthContext = () => useContext(AuthContext)
diff --git a/src/css/App.scss b/src/css/App.scss
index 7747c06..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;
@@ -73,7 +87,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 +100,90 @@ $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: 200px;
+ max-height: 450px;
+ 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;
+
+ &.auto-width {
+ width: 352px;
+
+ @include mq(900px) {
+ width: 100vw;
+ }
+ }
+
+ @include mq(900px) {
+ width: 100vw;
+ right: -16px;
+ }
+
+ &.dark-mode {
+ @include dark-mode-styles();
+ border-color: rgba(#000000, 0.24);
+ }
+
+ &-item {
+ padding: 8px 16px;
+
+ &.zero-padding {
+ padding: 0;
+ }
+
+ &.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;
+ }
+ }
+ }
+ }
}
}
}
@@ -165,7 +252,7 @@ $header-height: 72px;
&__list {
position: absolute;
width: 100%;
- max-height: 300px;
+ max-height: 450px;
overflow-y: scroll;
top: 100%;
left: 50%;
@@ -209,10 +296,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..70b9414 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;
@@ -47,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
new file mode 100644
index 0000000..103b5fb
--- /dev/null
+++ b/src/utils/firebase.js
@@ -0,0 +1,38 @@
+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 = () => {
+ try {
+ app.auth().signOut()
+ } catch (error) {
+ // eslint-disable-next-line
+ console.error({ error })
+ }
+}
+
+export { app as firebase }