diff --git a/android/app/src/main/java/com/notes/homocodians/MainActivity.java b/android/app/src/main/java/com/notes/homocodians/MainActivity.java index 5835f00..6c59ce1 100644 --- a/android/app/src/main/java/com/notes/homocodians/MainActivity.java +++ b/android/app/src/main/java/com/notes/homocodians/MainActivity.java @@ -1,5 +1,14 @@ package com.notes.homocodians; +import android.os.Bundle; +import com.codetrixstudio.capacitor.GoogleAuth.GoogleAuth; import com.getcapacitor.BridgeActivity; -public class MainActivity extends BridgeActivity {} +public class MainActivity extends BridgeActivity { + + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + registerPlugin(GoogleAuth.class); + } +} diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index bcb139d..fbf4b7d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -4,4 +4,5 @@ Notes com.notes.homocodians com.notes.homocodians + 754355741739-t7sec02ku3v1k0dt3opc4e2mktphq8m1.apps.googleusercontent.com diff --git a/capacitor.config.ts b/capacitor.config.ts index 441d3fa..be94a35 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -12,7 +12,8 @@ const config: CapacitorConfig = { }, GoogleAuth: { scopes: ["profile", "email"], - serverClientId: "xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com", + serverClientId: + "754355741739-t7sec02ku3v1k0dt3opc4e2mktphq8m1.apps.googleusercontent.com", forceCodeForRefreshToken: true, }, }, diff --git a/index.html b/index.html index 9c31c6a..cd0a1cf 100644 --- a/index.html +++ b/index.html @@ -31,6 +31,12 @@ + + + + + Notes diff --git a/package.json b/package.json index 83ba558..df42075 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "notes", "private": true, - "version": "2.1.1", + "version": "2.1.2", "scripts": { "dev": "vite --port 3000", "build": "tsc && vite build", @@ -27,6 +27,7 @@ "octokit": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-hotkeys-hook": "^4.4.1", "react-router": "^6.15.0", "react-router-dom": "^6.15.0", diff --git a/src/App.tsx b/src/App.tsx index 7104d37..0e9d2b2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useMemo } from "react"; +import { useEffect, useMemo } from "react"; import loadable from "@loadable/component"; import { Routes, Route } from "react-router-dom"; @@ -10,11 +10,12 @@ import Loading from "@/components/Loading"; import NotFound from "@/components/NotFound"; import PrivateRoute from "@/components/PrivateRoute"; import DrawerProvider from "@/context/DrawerContext"; -import { getDesignTokens } from "@/utils/getDesignToken"; +import { getDesignTokens } from "@/utils/get-design-token"; import Connectivity from "@/components/general/Connectivity"; import AccountMenuProvider from "@/context/AccountMenuContext"; import { changeStatusbarColor } from "@/utils/change-statusbar-color"; -import CheckForUpdates from "./components/general/CheckForUpdates"; +import CheckForUpdates from "@/components/general/CheckForUpdates"; +import ThemedToaster from "@/components/ThemedToaster"; const HomePage = loadable(() => import("@/pages/Home")); const ImportantPage = loadable(() => import("@/pages/Important")); @@ -85,14 +86,16 @@ function App() { return createTheme(getDesignTokens(isDarkMode ? "dark" : "light")); }, [isDarkMode]); - changeStatusbarColor(isDarkMode); + useEffect(() => { + changeStatusbarColor(isDarkMode); + }, [isDarkMode]); return ( -
+
{routes.map((route) => ( + diff --git a/src/components/FormDialog.tsx b/src/components/FormDialog.tsx index f8ad180..3e0fe07 100644 --- a/src/components/FormDialog.tsx +++ b/src/components/FormDialog.tsx @@ -1,93 +1,99 @@ -import { HTMLInputTypeAttribute, useState } from "react"; +import { HTMLInputTypeAttribute, useRef, useState } from "react"; import { - Dialog, - Button, - TextField, - DialogTitle, - DialogContent, - DialogActions, - CircularProgress, - DialogContentText, + Dialog, + Button, + TextField, + DialogTitle, + DialogContent, + DialogActions, + CircularProgress, + DialogContentText, } from "@mui/material"; import Box from "@mui/system/Box"; +import toast from "react-hot-toast"; type FormDialogProps = { - isOpen: boolean; - setOpen: (isOpen: boolean) => void; - title: string; - content: string; - textFieldLabel: string; - textFieldType: HTMLInputTypeAttribute; - positiveButtonLabel: string; - TextFieldcolor?: - | "error" - | "primary" - | "secondary" - | "info" - | "success" - | "warning"; - positiveButtonAction: (value: string, cb: () => void) => void; + isOpen: boolean; + setOpen: (isOpen: boolean) => void; + title: string; + content: string; + textFieldLabel: string; + textFieldType: HTMLInputTypeAttribute; + positiveButtonLabel: string; + TextFieldcolor?: + | "error" + | "primary" + | "secondary" + | "info" + | "success" + | "warning"; + positiveButtonAction: (value: string, cb: () => void) => void; }; function FormDialog({ - title, - isOpen, - setOpen, - content, - textFieldLabel, - textFieldType, - positiveButtonLabel, - positiveButtonAction, - TextFieldcolor, + title, + isOpen, + setOpen, + content, + textFieldLabel, + textFieldType, + positiveButtonLabel, + positiveButtonAction, + TextFieldcolor, }: FormDialogProps) { - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const inputRef = useRef(); - const handleClose = () => { - setOpen(false); - }; + const handleClose = () => { + setOpen(false); + }; - const handlePositiveClick = () => { - setIsLoading(true); - const input = document.getElementById("name") as HTMLInputElement; - if (input.value) { - positiveButtonAction(input.value, () => { - setIsLoading(false); - }); - } - }; + const handlePositiveClick = () => { + setIsLoading(true); + const input = inputRef.current; + if (input && input.value) { + positiveButtonAction(input.value, () => { + setIsLoading(false); + }); + } else { + setIsLoading(false); + toast.error("Invalid email"); + } + }; - return ( -
- - {title} - - {content} - - - - - {isLoading ? ( - - - - ) : ( - - )} - - -
- ); + return ( +
+ + {title} + + {content} + + + + + {isLoading ? ( + + + + ) : ( + + )} + + +
+ ); } export default FormDialog; diff --git a/src/components/ThemedToaster.tsx b/src/components/ThemedToaster.tsx new file mode 100644 index 0000000..0f60eaa --- /dev/null +++ b/src/components/ThemedToaster.tsx @@ -0,0 +1,20 @@ +import { Toaster } from "react-hot-toast"; +import { useTernaryDarkMode } from "usehooks-ts"; + +function ThemedToaster() { + const { isDarkMode } = useTernaryDarkMode(); + return ( + + ); +} + +export default ThemedToaster; diff --git a/src/components/general/CheckForUpdates.tsx b/src/components/general/CheckForUpdates.tsx index 55f009b..fa33200 100644 --- a/src/components/general/CheckForUpdates.tsx +++ b/src/components/general/CheckForUpdates.tsx @@ -1,67 +1,34 @@ -import React from "react"; +import { useCallback, useEffect, useState } from "react"; -import { getLatestRelease } from "@/utils/get-latest-release"; -import CustomSnackbar from "../CustomSnackbar"; +import { checkForUpdates } from "@/utils/get-latest-release"; +import CustomSnackbar from "@/components/CustomSnackbar"; import { Capacitor } from "@capacitor/core"; -type Alert = { - type: React.ComponentProps["alertType"]; - message: React.ComponentProps["message"]; - open: React.ComponentProps["open"]; -}; - function CheckForUpdates() { - const [alert, setAlert] = React.useState({ - type: "error", - message: "", - open: false, - }); + const [open, setOpen] = useState(false); + const [message, setMessage] = useState(""); - const checkForUpdate = React.useCallback(async () => { + const checkForUpdate = useCallback(async () => { try { - const data = await getLatestRelease(); - if ( - data.status === 200 && - data.data.target_commitish === "main" && - data.data.draft === false && - Number(data.data?.name?.substring(1)?.split(".")?.join("")) > - Number(import.meta.env.VITE_RELEASE_NUMBER) - ) { - setAlert({ - open: true, - message: `New update available ${data.data.html_url}`, - type: "info", - }); - } - // else { - // setAlert({ - // open: true, - // message: `There are currently no new updates available.`, - // type: "info", - // }); - // } - } catch (error) { - // setAlert({ - // open: true, - // message: "Failed to check for update.", - // type: "error", - // }); - } + const data = await checkForUpdates(); + if (!data) return; + setMessage(`New update available ${data.data.html_url}`); + setOpen(true); + } catch (error) {} }, []); - React.useEffect(() => { - if (Capacitor.getPlatform() === "web") return; - checkForUpdate(); + useEffect(() => { + if (Capacitor.isNativePlatform()) { + checkForUpdate(); + } }, [checkForUpdate]); return ( - setAlert((prev) => ({ ...prev, open: prop, message: "" })) - } - alertType={alert.type} - message={alert.message} + open={open} + setOpen={setOpen} + alertType="info" + message={message} anchorPosition={{ vertical: "bottom", horizontal: "center" }} /> ); diff --git a/src/components/general/SettingsMenu.tsx b/src/components/general/SettingsMenu.tsx index e7d4658..1372a94 100644 --- a/src/components/general/SettingsMenu.tsx +++ b/src/components/general/SettingsMenu.tsx @@ -10,16 +10,13 @@ import IconButton from "@mui/material/IconButton"; import Typography from "@mui/material/Typography"; import LogoutIcon from "@mui/icons-material/Logout"; import ListItemIcon from "@mui/material/ListItemIcon"; -import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import { useHotkeys } from "react-hotkeys-hook"; import { useAuth } from "@/context/AuthContext"; import ThemeMenuItem from "@/components/general/ThemeMenuItem"; import ProfileAvatar from "@/components/general/ProfileAvatar"; -import { writeToClipboard } from "@/utils/clipboard"; import CustomSnackbar from "../CustomSnackbar"; import { Settings } from "@mui/icons-material"; -import { grey } from "@mui/material/colors"; export default function SettingsMenu() { const { logout, user } = useAuth(); diff --git a/src/components/main/AddButton.tsx b/src/components/main/AddButton.tsx index 092e4a7..1941e58 100644 --- a/src/components/main/AddButton.tsx +++ b/src/components/main/AddButton.tsx @@ -2,7 +2,7 @@ import { Fab, useMediaQuery } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; interface IProps { - openAddTodoModal: React.Dispatch>; + openAddTodoModal: (prop: boolean) => void; } function AddButton({ openAddTodoModal }: IProps) { diff --git a/src/components/main/NoteCard.tsx b/src/components/main/NoteCard.tsx index 80ebee1..cc862a2 100644 --- a/src/components/main/NoteCard.tsx +++ b/src/components/main/NoteCard.tsx @@ -9,7 +9,7 @@ import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; import CardActions from "@mui/material/CardActions"; -import formatDate from "@/utils/formatDate"; +import formatDate from "@/utils/format-date"; import NoteMenu from "@/components/main/NoteMenu"; import useTheme from "@mui/material/styles/useTheme"; import { useAuth } from "@/context/AuthContext"; diff --git a/src/components/main/Notes.tsx b/src/components/main/Notes.tsx index 62c7b65..8e48f59 100644 --- a/src/components/main/Notes.tsx +++ b/src/components/main/Notes.tsx @@ -24,7 +24,7 @@ function Notes() { ) : ( - + {notes.map((note) => { const { text, diff --git a/src/components/main/SideDrawer.tsx b/src/components/main/SideDrawer.tsx index d147a40..3f4cf58 100644 --- a/src/components/main/SideDrawer.tsx +++ b/src/components/main/SideDrawer.tsx @@ -21,24 +21,18 @@ import { useDrawer } from "@/context/DrawerContext"; import ConfirmDialog from "@/components/ConfirmDialog"; import CustomSnackbar from "@/components/CustomSnackbar"; import useDeleteAllNotes from "@/hooks/useDeleteAllNotes"; -import { getLatestRelease } from "@/utils/get-latest-release"; +import { checkForUpdates } from "@/utils/get-latest-release"; -type Alert = { - type: ComponentProps["alertType"]; - message: ComponentProps["message"]; - open: ComponentProps["open"]; -}; +type Type = ComponentProps["alertType"]; function SideDrawer() { const navigate = useNavigate(); const [open, setOpen] = useState(false); + const [message, setMessage] = useState(""); const [loading, setLoading] = useState(false); + const [type, setType] = useState("info"); const { isDrawerOpen, setDrawerIsOpen } = useDrawer(); - const [alert, setAlert] = useState({ - type: "error", - message: "", - open: false, - }); + const [isSnackbarOpen, setIsSnackbarOpen] = useState(false); useHotkeys("shift+d", () => { setDrawerIsOpen((prev) => !prev); @@ -61,37 +55,25 @@ function SideDrawer() { const checkForUpdate = useCallback(async () => { setLoading(true); try { - const data = await getLatestRelease(); - if ( - data.status === 200 && - data.data.target_commitish === "main" && - data.data.draft === false && - Number(data.data?.name?.substring(1)?.split(".")?.join("")) > - Number(import.meta.env.VITE_RELEASE_NUMBER) - ) { - setLoading(false); - handleClose(); - setAlert({ - open: true, - message: `New update available ${data.data.html_url}`, - type: "info", - }); - } else { + const data = await checkForUpdates(); + if (!data) { setLoading(false); handleClose(); - setAlert({ - open: true, - message: `There are currently no new updates available.`, - type: "info", - }); + setType("info"); + setIsSnackbarOpen(true); + setMessage("There are currently no new updates available."); + return; } + setLoading(false); + handleClose(); + setType("info"); + setIsSnackbarOpen(true); + setMessage(`New update available ${data.data.html_url}`); } catch (error) { handleClose(); - setAlert({ - open: true, - message: "Failed to check for update.", - type: "error", - }); + setType("error"); + setIsSnackbarOpen(true); + setMessage("Failed to check for update."); } }, []); @@ -147,7 +129,7 @@ function SideDrawer() { gap="0.5rem" padding="1rem 0.5rem" > - {Capacitor.getPlatform() === "web" ? null : ( + {Capacitor.isNativePlatform() ? ( Check update - )} + ) : null} - {Capacitor.getPlatform() === "web" && ( - - )} + + - theme.zIndex.drawer + 1 }} + sx={{ + color: "#fff", + zIndex: (theme) => theme.zIndex.drawer + 1, + }} open={isLoading} > - ); } diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx index 89673f3..4a0ddf8 100644 --- a/src/pages/SignUp.tsx +++ b/src/pages/SignUp.tsx @@ -20,10 +20,11 @@ import LockIcon from "@mui/icons-material/LockOutlined"; import { useNavigate } from "react-router-dom"; import Container from "@mui/material/Container"; import { Capacitor } from "@capacitor/core"; +import toast from "react-hot-toast"; import { useAuth } from "@/context/AuthContext"; -import VerifyFirebaseErrorCode from "@/utils/authError"; -import CustomSnackbar from "@/components/CustomSnackbar"; +import VerifyFirebaseErrorCode from "@/utils/firebase-auth-error"; +import { signInWithGoogleNative } from "@/utils/native-google-login"; interface InputFields { email: string; @@ -104,11 +105,8 @@ export default function SignUp() { password: password === "" ? true : false, confirmPassword: confirm_password === "" ? true : false, }); - setAlert({ - message: "Password does not match!", - isOpen: true, - }); setIsLoading(false); + toast.error("Password does not match."); return; } if (password !== confirm_password) { @@ -117,36 +115,33 @@ export default function SignUp() { password: true, confirmPassword: true, }); - setAlert({ - message: "Password does not match.", - isOpen: true, - }); setIsLoading(false); + toast.error("Password does not match."); return; } try { await signUp(email, password); } catch (error: any) { - setAlert({ - message: VerifyFirebaseErrorCode(error.code), - isOpen: true, - }); + const errorMessage = VerifyFirebaseErrorCode(error?.code); setIsLoading(false); + toast.error(errorMessage); } }; const signInWithPopup = async () => { setIsLoading(true); try { - await signInWithGooglePopup(); + if (Capacitor.isNativePlatform()) { + await signInWithGoogleNative(); + } else { + await signInWithGooglePopup(); + } setIsLoading(false); navigate("/", { replace: true }); } catch (error: any) { - setAlert({ - message: VerifyFirebaseErrorCode(error.code), - isOpen: true, - }); + const errorMessage = VerifyFirebaseErrorCode(error?.code); setIsLoading(false); + toast.error(errorMessage); } }; @@ -262,18 +257,17 @@ export default function SignUp() { > Sign Up - {Capacitor.getPlatform() === "web" && ( - - )} + + @@ -284,18 +278,6 @@ export default function SignUp() { - theme.zIndex.drawer + 1 }} open={isLoading} diff --git a/src/utils/change-statusbar-color.ts b/src/utils/change-statusbar-color.ts index 9062ee3..ffe29cf 100644 --- a/src/utils/change-statusbar-color.ts +++ b/src/utils/change-statusbar-color.ts @@ -4,10 +4,18 @@ import { StatusBar, Style } from "@capacitor/status-bar"; export async function changeStatusbarColor(isDarkMode: boolean) { if (Capacitor.getPlatform() === "android") { if (isDarkMode) { - await StatusBar.setBackgroundColor({ color: "#121212" }); + await StatusBar.setBackgroundColor({ color: "#272727" }); } else { await StatusBar.setBackgroundColor({ color: "#ff5722" }); } await StatusBar.setStyle({ style: Style.Dark }); } } + +export async function setStatusbarColor(color: string, style?: Style) { + await StatusBar.setBackgroundColor({ color }); + + if (style) { + await StatusBar.setStyle({ style }); + } +} diff --git a/src/utils/authError.ts b/src/utils/firebase-auth-error.ts similarity index 98% rename from src/utils/authError.ts rename to src/utils/firebase-auth-error.ts index 92bc7cc..454d121 100644 --- a/src/utils/authError.ts +++ b/src/utils/firebase-auth-error.ts @@ -172,7 +172,9 @@ export default function VerifyFirebaseErrorCode(errorCode: any): string { return "The credential used to initialize the Admin SDK has insufficient permission to access the requested Authentication resource."; case "auth/internal-error": return "The Authentication server encountered an unexpected error while trying to process the request."; + case "12501": + return "Sign-in flow cancelled"; default: - return errorCode; + return "Something went wrong."; } } diff --git a/src/utils/formatDate.ts b/src/utils/format-date.ts similarity index 100% rename from src/utils/formatDate.ts rename to src/utils/format-date.ts diff --git a/src/utils/getDesignToken.ts b/src/utils/get-design-token.ts similarity index 100% rename from src/utils/getDesignToken.ts rename to src/utils/get-design-token.ts diff --git a/src/utils/get-latest-release.ts b/src/utils/get-latest-release.ts index 0960780..62f3919 100644 --- a/src/utils/get-latest-release.ts +++ b/src/utils/get-latest-release.ts @@ -10,3 +10,22 @@ export async function getLatestRelease() { repo: import.meta.env.VITE_GITHUB_REPO, }); } + +export async function checkForUpdates() { + const data = await getLatestRelease(); + + if (!data) { + return null; + } + + if ( + data.status === 200 && + data.data.target_commitish === "main" && + data.data.draft === false && + Number(data.data?.tag_name?.substring(1)?.split(".")?.join("")) > + Number(import.meta.env.VITE_RELEASE_NUMBER) + ) { + return data; + } + return null; +} diff --git a/src/utils/native-google-login.ts b/src/utils/native-google-login.ts new file mode 100644 index 0000000..f0b1168 --- /dev/null +++ b/src/utils/native-google-login.ts @@ -0,0 +1,11 @@ +import { auth } from "@/firebase"; +import { GoogleAuth } from "@codetrix-studio/capacitor-google-auth"; +import { GoogleAuthProvider, signInWithCredential } from "firebase/auth"; + +export async function signInWithGoogleNative() { + const response = await GoogleAuth.signIn(); + const credentials = GoogleAuthProvider.credential( + response?.authentication?.idToken + ); + return signInWithCredential(auth, credentials); +} diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000..507d915 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,3 @@ +export async function sleep(ms = 5000) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/yarn.lock b/yarn.lock index 77126c5..9f8774a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2704,6 +2704,11 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +goober@^2.1.10: + version "2.1.13" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.13.tgz#e3c06d5578486212a76c9eba860cbc3232ff6d7c" + integrity sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -3651,6 +3656,13 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-hot-toast@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994" + integrity sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ== + dependencies: + goober "^2.1.10" + react-hotkeys-hook@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.4.1.tgz#1f7a7a1c9c21d4fa3280bf340fcca8fd77d81994"