diff --git a/packages/desktop2/package.json b/packages/desktop2/package.json index e054479..3f61c9d 100644 --- a/packages/desktop2/package.json +++ b/packages/desktop2/package.json @@ -90,6 +90,8 @@ "typescript": "~4.5.4" }, "dependencies": { + "@getstation/electron-google-oauth2": "^2.1.0", + "@mui/icons-material": "^5.5.1", "axios": "^0.26.1", "electron-squirrel-startup": "^1.0.0", "electron-store": "^8.0.1", diff --git a/packages/desktop2/src/app.tsx b/packages/desktop2/src/app.tsx index 5a8ef9b..1139c81 100644 --- a/packages/desktop2/src/app.tsx +++ b/packages/desktop2/src/app.tsx @@ -3,11 +3,10 @@ import "./index.css"; import * as React from "react"; import * as ReactDOM from "react-dom"; +import NirvanaApp from "./nirvanaApp"; + function render() { - ReactDOM.render( -

Hello from React!

, - document.getElementById("root") - ); + ReactDOM.render(, document.getElementById("root")); } render(); diff --git a/packages/desktop2/src/components/Logo/index.tsx b/packages/desktop2/src/components/Logo/index.tsx new file mode 100644 index 0000000..0f615a5 --- /dev/null +++ b/packages/desktop2/src/components/Logo/index.tsx @@ -0,0 +1,93 @@ +export enum LogoType { + primary = "primary", + small = "small", +} + +export default function Logo({ + className, + type, +}: { + className: string; + type?: LogoType; +}) { + if (type === LogoType.small) { + return ( + + + + + + ); + } + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/desktop2/src/components/ProtectedRoute/index.tsx b/packages/desktop2/src/components/ProtectedRoute/index.tsx new file mode 100644 index 0000000..cdddf77 --- /dev/null +++ b/packages/desktop2/src/components/ProtectedRoute/index.tsx @@ -0,0 +1,22 @@ +import { Route, useNavigate } from "react-router-dom"; + +import { useGetUserDetails } from "../../controller/index"; + +export default function ProtectedRoute({ ...children }) { + const { isLoading, isError } = useGetUserDetails(); + + const navigate = useNavigate(); + + if (isLoading) return please wait while we authenticate you; + + if (isError) { + // toast.error("Please sign in first!"); + navigate("/login"); + + return you are not allowed here!; + } + + // if we can successfully get user details, we are good to continue + + return <>{children}; +} diff --git a/packages/desktop2/src/controller/index.tsx b/packages/desktop2/src/controller/index.tsx new file mode 100644 index 0000000..adccbbb --- /dev/null +++ b/packages/desktop2/src/controller/index.tsx @@ -0,0 +1,12 @@ +import { nirvanaApi } from "./nirvanaApi"; +import { useQuery } from "react-query"; + +export enum Querytypes { + GET_USER_DETAILS = "GET_USER_DETAILS", +} + +export function useGetUserDetails() { + return useQuery(Querytypes.GET_USER_DETAILS, nirvanaApi.getUserDetails, { + retry: true, + }); +} diff --git a/packages/desktop2/src/controller/nirvanaApi.ts b/packages/desktop2/src/controller/nirvanaApi.ts new file mode 100644 index 0000000..f7a200c --- /dev/null +++ b/packages/desktop2/src/controller/nirvanaApi.ts @@ -0,0 +1,40 @@ +import axios, { AxiosRequestConfig } from "axios"; + +// export const localHost = process.env.REACT_APP_API_DOMAIN; + +export const localHost = "http://localhost:5000"; + +class NirvanaApi { + // auth token from google that our backend will use + private _authToken?: string; + + setPrivateToken(_googleIdToken: string) { + this._authToken = _googleIdToken; + } + + async fetch(options: AxiosRequestConfig, privateRoute = false) { + // use the auth token if it's a private route + // error if no auth token and it's a private route + // throw error and show message on anything that is an error from the backend + if (privateRoute && this._authToken) { + return await axios({ + ...options, + headers: { Authorization: this._authToken }, + }); + } + + return axios(options); + } + + async getUserDetails() { + return await this.fetch( + { + method: "GET", + url: localHost + `/users`, + }, + true + ); + } +} + +export const nirvanaApi = new NirvanaApi(); diff --git a/packages/desktop2/src/electron/constants.ts b/packages/desktop2/src/electron/constants.ts new file mode 100644 index 0000000..ae527f1 --- /dev/null +++ b/packages/desktop2/src/electron/constants.ts @@ -0,0 +1,6 @@ +enum Channels { + ACTIVATE_LOG_IN = "ACTIVATE_LOG_IN", + AUTH_TOKENS = "AUTH_TOKENS", +} + +export default Channels; diff --git a/packages/desktop2/src/electron/handleLogin.ts b/packages/desktop2/src/electron/handleLogin.ts new file mode 100644 index 0000000..6cd0ed6 --- /dev/null +++ b/packages/desktop2/src/electron/handleLogin.ts @@ -0,0 +1,41 @@ +import Channels from "./constants"; +import ElectronGoogleOAuth2 from "@getstation/electron-google-oauth2"; +import { browserWindow } from "../index"; +import { ipcMain } from "electron"; +import store from "./store"; + +const myApiOauth = new ElectronGoogleOAuth2( + "423533244953-banligobgbof8hg89i6cr1l7u0p7c2pk.apps.googleusercontent.com", + "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", + [""], + { successRedirectURL: "https://usenirvana.com" } +); + +export async function handleLogin() { + // read saved refresh token if any + // todo: fix this...should be working + const refreshToken = await store.get("tokens"); + + // todo: remove this when I have way of getting access token from a refresh token + // if (refreshToken) { + // console.log("have a refresh token from user auth previously", refreshToken); + // // myApiOauth.setTokens({ refresh_token: refreshToken }); + + // // send token to client + // browserWindow.webContents.send(Channels.AUTH_TOKENS, refreshToken); + // } else { + // const token = await myApiOauth.openAuthWindowAndGetTokens(); + + // // store the refresh token in cookies for app reopen + // store.set("tokens", token); + + // // todo: send the access token to the renderer + // // send token to client + // browserWindow.webContents.send(Channels.AUTH_TOKENS, token); + // } + + const tokens = await myApiOauth.openAuthWindowAndGetTokens(); + store.set("tokens", tokens); + + browserWindow.webContents.send(Channels.AUTH_TOKENS, tokens); +} diff --git a/packages/desktop2/src/electron/store.ts b/packages/desktop2/src/electron/store.ts new file mode 100644 index 0000000..69dbb96 --- /dev/null +++ b/packages/desktop2/src/electron/store.ts @@ -0,0 +1,4 @@ +import Store from "electron-store"; +const store = new Store(); + +export default store; diff --git a/packages/desktop2/src/index.css b/packages/desktop2/src/index.css index e757dd6..5216226 100644 --- a/packages/desktop2/src/index.css +++ b/packages/desktop2/src/index.css @@ -1,9 +1,6 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - margin: auto; - max-width: 38rem; - padding: 2rem; } @tailwind base; diff --git a/packages/desktop2/src/index.ts b/packages/desktop2/src/index.ts index 99fa399..b4d152e 100644 --- a/packages/desktop2/src/index.ts +++ b/packages/desktop2/src/index.ts @@ -1,44 +1,65 @@ -import { app, BrowserWindow } from 'electron'; +import { BrowserWindow, app, ipcMain } from "electron"; + +import Channels from "./electron/constants"; +import { handleLogin } from "./electron/handleLogin"; + // This allows TypeScript to pick up the magic constant that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on // whether you're running in development or production). declare const MAIN_WINDOW_WEBPACK_ENTRY: string; // Handle creating/removing shortcuts on Windows when installing/uninstalling. -if (require('electron-squirrel-startup')) { +if (require("electron-squirrel-startup")) { // eslint-disable-line global-require app.quit(); } +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +export let browserWindow: BrowserWindow; + const createWindow = (): void => { // Create the browser window. - const mainWindow = new BrowserWindow({ + browserWindow = new BrowserWindow({ height: 600, width: 800, + webPreferences: { + nodeIntegration: true, + }, }); // and load the index.html of the app. - mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); + browserWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); // Open the DevTools. - mainWindow.webContents.openDevTools(); + browserWindow.webContents.openDevTools(); }; // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.on('ready', createWindow); +app + .whenReady() + .then(createWindow) + .then(() => { + console.log("hahha"); + + // End of the file + ipcMain.on(Channels.ACTIVATE_LOG_IN, async (event, arg) => { + await handleLogin(); + }); + }); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { app.quit(); } }); -app.on('activate', () => { +app.on("activate", () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { diff --git a/packages/desktop2/src/nirvanaApp.tsx b/packages/desktop2/src/nirvanaApp.tsx new file mode 100644 index 0000000..330d355 --- /dev/null +++ b/packages/desktop2/src/nirvanaApp.tsx @@ -0,0 +1,40 @@ +import { HashRouter, Route, Routes } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "react-query"; + +import Home from "./pages/Home"; +import Login from "./pages/Login"; +import ProtectedRoute from "./components/ProtectedRoute"; +import testConnection from "@nirvana/core"; + +testConnection(); + +// Create a client +const queryClient = new QueryClient(); + +function NirvanaApp() { + return ( + <> + + +
+ + } /> + + + + } + /> + {/* + */} + +
+
+
+ + ); +} + +export default NirvanaApp; diff --git a/packages/desktop2/src/pages/Home/index.tsx b/packages/desktop2/src/pages/Home/index.tsx new file mode 100644 index 0000000..ed80486 --- /dev/null +++ b/packages/desktop2/src/pages/Home/index.tsx @@ -0,0 +1,64 @@ +import { + Add, + CardGiftcardRounded, + ContactsRounded, + LinkRounded, + PushPinRounded, +} from "@mui/icons-material"; +import Logo, { LogoType } from "../../components/Logo"; + +export default function Home() { + return ( +
+ {/* header */} +
+ + + + + + + cannot find + + +
+ + {/* actions navbar */} +
+ + + + + + + + + + + + + + +
+ + {/* pinned conversations */} +
+ + + + Pinned + + +
+
+ ); +} diff --git a/packages/desktop2/src/pages/Login/index.tsx b/packages/desktop2/src/pages/Login/index.tsx new file mode 100644 index 0000000..db83f9d --- /dev/null +++ b/packages/desktop2/src/pages/Login/index.tsx @@ -0,0 +1,65 @@ +import { Button, CircularProgress } from "@mui/material"; +import { useEffect, useState } from "react"; + +import Channels from "../../electron/constants"; +import Logo from "../../components/Logo"; +import { UserInfo } from "@nirvana/core/models"; +import axios from "axios"; +import { useGetUserDetails } from "../../controller"; +import { useNavigate } from "react-router-dom"; + +export default function Login() { + const navigate = useNavigate(); + + const logIn = () => { + // send to main process + // ipcRenderer.send(Channels.ACTIVATE_LOG_IN); + }; + + // useEffect(() => { + // window.API.receive(channels.AUTH_TOKENS, async (tokens: any) => { + // console.log(tokens); + + // const { access_token, id_token, refresh_token } = tokens; + + // // get user info from access token + // const userInfo: UserInfo = await ( + // await fetch( + // `https://www.googleapis.com/oauth2/v1/userinfo?access_token=${access_token}` + // ) + // ).json(); + + // console.log("user data: "); + // console.log(userInfo); + + // // if user is an existing user, then continue to next route + // axios({ + // method: "GET", + // url: localHost + `/user`, + // headers: { + // Authorization: + // "eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ2M2RiZTczYWFkODhjODU0ZGUwZDhkNmMwMTRjMzZkYzI1YzQyOTIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MjM1MzMyNDQ5NTMtYmFubGlnb2JnYm9mOGhnODlpNmNyMWw3dTBwN2MycGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MjM1MzMyNDQ5NTMtYmFubGlnb2JnYm9mOGhnODlpNmNyMWw3dTBwN2MycGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM0NzA3ODY2OTAzNTMxMDkwODYiLCJlbWFpbCI6InBhdGVsLmFyanVuNTBAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJKRlBBOC15TGgxeGlGOW5acUZQU0FBIiwibmFtZSI6IkFyanVuIFBhdGVsIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdqbTI5U2QxV25tOE52WmJYa29zdmY2U29JRDZrQlA1T0hSTFZJT0JRPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IkFyanVuIiwiZmFtaWx5X25hbWUiOiJQYXRlbCIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNjQ3Mzc4MzgzLCJleHAiOjE2NDczODE5ODN9.llIYMEjq3Ye1UFxiKIsgtjnRho2Ad7dQjCSFVDDlsiH3AVq_MssoKTXMS0tuHpQAZAfyqbi1kRpp4Ax3mFJb44z2OIyzOTDzhcNB_L_3C8U3gRuSmUaVfBa7EdkobkxByee3ddvjekD7Y9dobtXs3IllldjewD9Eg9FZmEkuSN6yYVxxRLYNYTjk_BAA17QKKkPPpyanAHHXLwxpMFhQc34tuAYCYmmDQQijV8YVvU4r9ruPpysumVXlUmX3CuNg9P415Dq1lUHQxaf5hWf_6RxIsw9PNg1wZ8CuJGvmLFnGBRQACKgnAkI9E7-tu2tWe3glRhORZlQmgRCKqG5xoA", + // }, + // }).then((res) => console.log(res)); + + // navigate("/home"); + + // // if user is not an existing user, then show create account page + // }); + + // // return () => { + // // ipcRenderer.removeAllListeners(channels.AUTH_TOKEN); + // // }; + // }, []); + + return ( +
+ + + + +
+ ); +} diff --git a/packages/desktop2/src/pages/Profile/index.tsx b/packages/desktop2/src/pages/Profile/index.tsx new file mode 100644 index 0000000..3f0ea80 --- /dev/null +++ b/packages/desktop2/src/pages/Profile/index.tsx @@ -0,0 +1,3 @@ +export default function Profile() { + return this is the profile page; +} diff --git a/packages/desktop2/webpack.renderer.config.js b/packages/desktop2/webpack.renderer.config.js index 753e39d..7338e9b 100644 --- a/packages/desktop2/webpack.renderer.config.js +++ b/packages/desktop2/webpack.renderer.config.js @@ -1,8 +1,6 @@ const rules = require("./webpack.rules"); const plugins = require("./webpack.plugins"); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); - const path = require("path"); rules.push({ diff --git a/yarn.lock b/yarn.lock index d8efe34..c3490e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1579,7 +1579,7 @@ "@getstation/electron-google-oauth2@^2.1.0": version "2.1.0" - resolved "https://registry.npmjs.org/@getstation/electron-google-oauth2/-/electron-google-oauth2-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/@getstation/electron-google-oauth2/-/electron-google-oauth2-2.1.0.tgz#ed0c489c7f97bf9e4542b599c45e6f7a6ece8b54" integrity sha512-lWoyxkeqFP1eHkka918eq9649vtiKjQaaQlPH22f6DTMGXg+K3HmzgaE3cPGUIavQFHoDpPJE138V/FiJnPVYw== dependencies: google-auth-library "^5.9.2" @@ -1847,6 +1847,13 @@ dependencies: "@babel/runtime" "^7.17.2" +"@mui/icons-material@^5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.5.1.tgz#848a57972617411370775980cbc6990588d4aafb" + integrity sha512-40f68p5+Yhq3dCn3QYHqQt5RETPyR3AkDw+fma8PtcjqvZ+d+jF84kFmT6NqwA3he7TlwluEtkyAmPzUE4uPdA== + dependencies: + "@babel/runtime" "^7.17.2" + "@mui/material@^5.5.0": version "5.5.0" resolved "https://registry.npmjs.org/@mui/material/-/material-5.5.0.tgz" @@ -8405,14 +8412,7 @@ min-indent@^1.0.0: resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-css-extract-plugin@^2.4.5: - version "2.6.0" - resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz" - integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== - dependencies: - schema-utils "^4.0.0" - -mini-css-extract-plugin@^2.6.0: +mini-css-extract-plugin@^2.4.5, mini-css-extract-plugin@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w==