mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-02 20:21:19 +02:00
feat: improve login experience & add manual syncing
This commit is contained in:
@@ -21,6 +21,14 @@ import { useStore as useUserStore } from "./stores/user-store";
|
||||
import Animated from "./components/animated";
|
||||
|
||||
const NavMenuItem = props => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isSyncing = useUserStore(store => store.isSyncing);
|
||||
useEffect(() => {
|
||||
if (props.item.animatable) {
|
||||
if (props.item.key === "sync") setIsLoading(isSyncing);
|
||||
}
|
||||
}, [isSyncing, setIsLoading, props.item]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={() => props.onSelected()}
|
||||
@@ -48,6 +56,7 @@ const NavMenuItem = props => {
|
||||
size={18}
|
||||
sx={{ marginRight: "2px" }}
|
||||
color={props.selected ? "primary" : props.item.color || "icon"}
|
||||
rotate={isLoading}
|
||||
/>
|
||||
<Text
|
||||
sx={{
|
||||
@@ -189,7 +198,7 @@ function App() {
|
||||
<NavMenuItem
|
||||
onSelected={async () => {
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
await item.onClick();
|
||||
if (item.component) setSelectedKey(item.key);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Flex, Box, Text, Button as RebassButton } from "rebass";
|
||||
import { ThemeProvider } from "../../utils/theme";
|
||||
import * as Icon from "../icons";
|
||||
import Modal from "react-modal";
|
||||
|
||||
export default class Dialog extends React.Component {
|
||||
@@ -72,9 +73,16 @@ export default class Dialog extends React.Component {
|
||||
mx={1}
|
||||
width={"50%"}
|
||||
disabled={props.positiveButton.disabled || false}
|
||||
onClick={props.positiveButton.onClick}
|
||||
onClick={
|
||||
!props.positiveButton.disabled &&
|
||||
props.positiveButton.onClick
|
||||
}
|
||||
>
|
||||
{props.positiveButton.text || "OK"}
|
||||
{props.positiveButton.loading ? (
|
||||
<Icon.Loading rotate={true} color="static" />
|
||||
) : (
|
||||
props.positiveButton.text || "OK"
|
||||
)}
|
||||
</RebassButton>
|
||||
)}
|
||||
|
||||
|
||||
@@ -4,13 +4,15 @@ import { Input } from "@rebass/forms";
|
||||
import * as Icon from "../icons";
|
||||
import Dialog, { showDialog } from "./dialog";
|
||||
import { showSignUpDialog } from "./signupdialog";
|
||||
import { useStore } from "../../stores/user-store";
|
||||
import { db } from "../../common";
|
||||
|
||||
const LoginDialog = props => {
|
||||
const [username, setUsername] = useState();
|
||||
const [password, setPassword] = useState();
|
||||
const [errorMessage, setErrorMessage] = useState();
|
||||
|
||||
const isLoggingIn = useStore(store => store.isLoggingIn);
|
||||
const login = useStore(store => store.login);
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
@@ -20,6 +22,8 @@ const LoginDialog = props => {
|
||||
negativeButton={{ onClick: props.onClose }}
|
||||
positiveButton={{
|
||||
text: "Login",
|
||||
loading: isLoggingIn,
|
||||
disabled: isLoggingIn,
|
||||
onClick: () => {
|
||||
setErrorMessage();
|
||||
if (username === "" || username === undefined) {
|
||||
@@ -32,19 +36,19 @@ const LoginDialog = props => {
|
||||
return;
|
||||
}
|
||||
|
||||
db.user
|
||||
.login(username, password)
|
||||
login(username, password)
|
||||
.then(() => {
|
||||
props.onClose();
|
||||
})
|
||||
.catch(() => {
|
||||
setErrorMessage("Failed to login. Please try again.");
|
||||
.catch(e => {
|
||||
setErrorMessage(e.message);
|
||||
});
|
||||
}
|
||||
}}
|
||||
content={
|
||||
<Box my={1}>
|
||||
<Input
|
||||
autoFocus
|
||||
variant="default"
|
||||
placeholder="Username"
|
||||
onChange={e => {
|
||||
|
||||
@@ -5,13 +5,14 @@ import { motion } from "framer-motion";
|
||||
import { useTheme } from "emotion-theming";
|
||||
import Animated from "../animated";
|
||||
|
||||
const Icon = ({ name, size = 24, color = "text" }) => {
|
||||
const Icon = ({ name, size = 24, color = "text", rotate }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<MDIIcon
|
||||
path={name}
|
||||
size={size + "px"}
|
||||
color={theme.colors[color] || color}
|
||||
spin={rotate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -23,6 +24,7 @@ const createIcon = name => {
|
||||
height={(props.size || 24) + "px"}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
animate={props.animation}
|
||||
>
|
||||
<Icon name={name} {...props} />
|
||||
</Animated.Box>
|
||||
@@ -54,6 +56,8 @@ export const NormalMode = createIcon(Icons.mdiFullscreenExit);
|
||||
export const Settings = createIcon(Icons.mdiCogOutline);
|
||||
export const Home = createIcon(Icons.mdiHomeOutline);
|
||||
export const Restore = createIcon(Icons.mdiRecycle);
|
||||
export const Sync = createIcon(Icons.mdiSync);
|
||||
export const Loading = createIcon(Icons.mdiLoading);
|
||||
|
||||
/** Properties Icons */
|
||||
export const ChevronLeft = createIcon(Icons.mdiChevronLeft);
|
||||
|
||||
@@ -18,7 +18,7 @@ function read(key) {
|
||||
}
|
||||
|
||||
function readMulti(keys) {
|
||||
if (keys.length <= 0) return {};
|
||||
if (keys.length <= 0) return [];
|
||||
return localforage.getItems(sort(keys).asc());
|
||||
}
|
||||
|
||||
|
||||
@@ -16,19 +16,22 @@ import Search from "../../views/Search";
|
||||
import { store as noteStore } from "../../stores/note-store";
|
||||
import { store as userStore } from "../../stores/user-store";
|
||||
import { showLogInDialog } from "../../components/dialogs/logindialog";
|
||||
import { db } from "../../common";
|
||||
|
||||
export const bottomRoutes = {
|
||||
...createDeadRoute("nightmode", Icon.Theme, {
|
||||
onClick: () => changeTheme(),
|
||||
isToggled: () => isDarkTheme()
|
||||
onClick: () => changeTheme()
|
||||
}),
|
||||
...createDeadRoute("sync", Icon.Sync, {
|
||||
onClick: async () => userStore.getState().sync(),
|
||||
animatable: true
|
||||
}),
|
||||
...createNormalRoute("account", Account, Icon.User, {
|
||||
onClick: async () => {
|
||||
if (userStore.getState().isLoggedIn) {
|
||||
RootNavigator.navigate("account");
|
||||
} else {
|
||||
if (!userStore.getState().isLoggedIn) {
|
||||
await showLogInDialog();
|
||||
}
|
||||
RootNavigator.navigate("account");
|
||||
}
|
||||
}),
|
||||
...createRoute("settings", SettingsContainer, {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import createStore from "../common/store";
|
||||
import { db } from "../common";
|
||||
import { showPasswordDialog } from "../components/dialogs/passworddialog";
|
||||
import { store as noteStore } from "./note-store";
|
||||
import { store as notebookStore } from "./notebook-store";
|
||||
|
||||
function appStore(set, get) {
|
||||
return {
|
||||
@@ -12,6 +14,11 @@ function appStore(set, get) {
|
||||
selectedItems: [],
|
||||
theme: {},
|
||||
colors: [],
|
||||
refreshApp: function() {
|
||||
noteStore.getState().refresh();
|
||||
notebookStore.getState().refresh();
|
||||
noteStore.getState().refreshSelectedContext();
|
||||
},
|
||||
closeSideMenu: function() {
|
||||
set(state => {
|
||||
state.isSideMenuOpen = false;
|
||||
|
||||
@@ -25,6 +25,11 @@ function noteStore(set, get) {
|
||||
state.notes = db.notes.group(undefined, true);
|
||||
});
|
||||
},
|
||||
refreshSelectedContext: function() {
|
||||
const { setSelectedContext, selectedContext } = get();
|
||||
if (!selectedContext.type) return;
|
||||
setSelectedContext(selectedContext);
|
||||
},
|
||||
setSelectedContext: function(context) {
|
||||
let notes = [];
|
||||
switch (context.type) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import createStore from "../common/store";
|
||||
import { db } from "../common";
|
||||
import { store as appStore } from "./app-store";
|
||||
|
||||
function userStore(set, get) {
|
||||
return {
|
||||
isLoggedIn: false,
|
||||
isLoggingIn: false,
|
||||
isSyncing: false,
|
||||
user: undefined,
|
||||
init: () => {
|
||||
return db.user.get().then(user => {
|
||||
@@ -12,17 +15,40 @@ function userStore(set, get) {
|
||||
state.user = user;
|
||||
state.isLoggedIn = true;
|
||||
});
|
||||
get().sync();
|
||||
return true;
|
||||
});
|
||||
},
|
||||
login: (username, password) => {
|
||||
set(state => {
|
||||
state.isLoggingIn = true;
|
||||
});
|
||||
return db.user
|
||||
.login(username, password)
|
||||
.then(() => {
|
||||
return get().init();
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
.finally(() => {
|
||||
set(state => {
|
||||
state.isLoggingIn = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
sync: () => {
|
||||
set(state => {
|
||||
state.isSyncing = true;
|
||||
});
|
||||
db.sync()
|
||||
.then(() => {
|
||||
appStore.getState().refreshApp();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
set(state => {
|
||||
state.isSyncing = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
logout: () => {
|
||||
|
||||
Reference in New Issue
Block a user