diff --git a/package.json b/package.json index c1625404..54f88216 100644 --- a/package.json +++ b/package.json @@ -1,100 +1,102 @@ { - "name": "rustchat-web", - "version": "0.1.0", - "private": true, - "homepage": "http://privoce.rustchat.com", - "dependencies": { - "@babel/core": "^7.17.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", - "@reduxjs/toolkit": "^1.7.2", - "@rtk-query/codegen-openapi": "^1.0.0-alpha.1", - "@svgr/webpack": "^6.2.1", - "animate.css": "^4.1.1", - "axios": "^0.25.0", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-preset-react-app": "^10.0.1", - "bfj": "^7.0.2", - "browserslist": "^4.19.1", - "camelcase": "^6.3.0", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "css-loader": "^6.6.0", - "css-minimizer-webpack-plugin": "^3.4.1", - "dayjs": "^1.10.7", - "dotenv": "^16.0.0", - "dotenv-expand": "^8.0.1", - "emoji-mart": "^3.0.1", - "eslint": "^8.8.0", - "event-source-polyfill": "^1.0.25", - "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "html-webpack-plugin": "^5.5.0", - "mini-css-extract-plugin": "^2.5.3", - "react": "^17.0.2", - "react-dev-utils": "^12.0.0", - "react-dnd": "^15.1.1", - "react-dnd-html5-backend": "^15.1.2", - "react-dom": "^17.0.2", - "react-hot-toast": "^2.2.0", - "react-icons": "^4.3.1", - "react-redux": "^7.2.6", - "react-refresh": "^0.11.0", - "react-router-dom": "6", - "react-textarea-autosize": "^8.3.3", - "redux-persist": "^6.0.0", - "resolve": "^1.22.0", - "rooks": "^5.10.0", - "semver": "^7.3.5", - "source-map-loader": "^3.0.1", - "style-loader": "^3.3.1", - "styled-components": "^5.3.3", - "styled-reset": "^4.3.4", - "terser-webpack-plugin": "^5.3.1", - "webpack": "^5.68.0", - "webpack-dev-server": "^4.7.4", - "webpack-manifest-plugin": "^4.1.1", - "workbox-webpack-plugin": "^6.4.2" - }, - "scripts": { - "start": "node scripts/start.js", - "build": "node scripts/build.js", - "deploy": "yarn build && gh-pages -d build", - "lint": "lint-staged", - "prepare": "husky install" - }, - "browserslist": { - "production": [ - "last 5 chrome version", - "last 5 firefox version", - "last 5 safari version" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "babel": { - "presets": [ - "react-app" - ] - }, - "devDependencies": { - "@commitlint/cli": "^16.1.0", - "@commitlint/config-conventional": "^16.0.0", - "autoprefixer": "^10.4.2", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0", - "gh-pages": "^3.2.3", - "husky": "^7.0.4", - "lint-staged": "^12.3.3", - "postcss": "^8.4.6", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-loader": "^6.2.1", - "postcss-preset-env": "^7.3.1", - "prettier": "^2.5.1" - } + "name": "rustchat-web", + "version": "0.1.0", + "private": true, + "homepage": "http://privoce.rustchat.com", + "dependencies": { + "@babel/core": "^7.17.0", + "@metamask/onboarding": "^1.0.1", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", + "@reduxjs/toolkit": "^1.7.2", + "@rtk-query/codegen-openapi": "^1.0.0-alpha.1", + "@svgr/webpack": "^6.2.1", + "animate.css": "^4.1.1", + "axios": "^0.25.0", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.19.1", + "camelcase": "^6.3.0", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.6.0", + "css-minimizer-webpack-plugin": "^3.4.1", + "dayjs": "^1.10.7", + "dotenv": "^16.0.0", + "dotenv-expand": "^8.0.1", + "emoji-mart": "^3.0.1", + "eslint": "^8.8.0", + "event-source-polyfill": "^1.0.25", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "mini-css-extract-plugin": "^2.5.3", + "react": "^17.0.2", + "react-dev-utils": "^12.0.0", + "react-dnd": "^15.1.1", + "react-dnd-html5-backend": "^15.1.2", + "react-dom": "^17.0.2", + "react-google-login": "^5.2.2", + "react-hot-toast": "^2.2.0", + "react-icons": "^4.3.1", + "react-redux": "^7.2.6", + "react-refresh": "^0.11.0", + "react-router-dom": "6", + "react-textarea-autosize": "^8.3.3", + "redux-persist": "^6.0.0", + "resolve": "^1.22.0", + "rooks": "^5.10.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.1", + "style-loader": "^3.3.1", + "styled-components": "^5.3.3", + "styled-reset": "^4.3.4", + "terser-webpack-plugin": "^5.3.1", + "webpack": "^5.68.0", + "webpack-dev-server": "^4.7.4", + "webpack-manifest-plugin": "^4.1.1", + "workbox-webpack-plugin": "^6.4.2" + }, + "scripts": { + "start": "node scripts/start.js", + "build": "node scripts/build.js", + "deploy": "yarn build && gh-pages -d build", + "lint": "lint-staged", + "prepare": "husky install" + }, + "browserslist": { + "production": [ + "last 5 chrome version", + "last 5 firefox version", + "last 5 safari version" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "babel": { + "presets": [ + "react-app" + ] + }, + "devDependencies": { + "@commitlint/cli": "^16.1.0", + "@commitlint/config-conventional": "^16.0.0", + "autoprefixer": "^10.4.2", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0", + "gh-pages": "^3.2.3", + "husky": "^7.0.4", + "lint-staged": "^12.3.3", + "postcss": "^8.4.6", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-preset-env": "^7.3.1", + "prettier": "^2.5.1" + } } diff --git a/src/routes/login/GoogleLoginButton.js b/src/routes/login/GoogleLoginButton.js new file mode 100644 index 00000000..1a1415c8 --- /dev/null +++ b/src/routes/login/GoogleLoginButton.js @@ -0,0 +1,31 @@ +// import { useState, useEffect } from "react"; +import { useGoogleLogin } from 'react-google-login'; +import { googleClientID } from '../../app/config'; +export default function GoogleLoginButton({ login }) { + const { signIn, loaded } = useGoogleLogin({ + clientId: googleClientID, + onSuccess: ({ tokenId, ...rest }) => { + console.log('success', tokenId, rest); + login({ + id_token: tokenId, + type: 'google' + }); + }, + onFailure: (wtf) => { + console.log('failure', wtf); + } + }); + const handleGoogleLogin = () => { + signIn(); + }; + return ( + + ); +} diff --git a/src/routes/login/MetamaskLoginButton.js b/src/routes/login/MetamaskLoginButton.js new file mode 100644 index 00000000..5e30906d --- /dev/null +++ b/src/routes/login/MetamaskLoginButton.js @@ -0,0 +1,62 @@ +import { useState, useEffect, useRef } from 'react'; +import MetaMaskOnboarding from '@metamask/onboarding'; +const ONBOARD_TEXT = 'Click here to install MetaMask!'; +const CONNECT_TEXT = 'Sign in with MetaMask'; +const CONNECTED_TEXT = 'Connected'; +export default function MetamaskLoginButton() { + const [btnTxt, setBtnTxt] = useState(ONBOARD_TEXT); + const [isDisabled, setIsDisabled] = useState(false); + const [accounts, setAccounts] = useState([]); + const onboarding = useRef(); + useEffect(() => { + if (!onboarding.current) { + onboarding.current = new MetaMaskOnboarding(); + } + }, []); + useEffect(() => { + if (MetaMaskOnboarding.isMetaMaskInstalled()) { + console.log(accounts); + if (accounts.length > 0) { + setBtnTxt(CONNECTED_TEXT); + setIsDisabled(true); + onboarding.current.stopOnboarding(); + } else { + setBtnTxt(CONNECT_TEXT); + setIsDisabled(false); + } + } + }, [accounts]); + + useEffect(() => { + function handleNewAccounts(newAccounts) { + setAccounts(newAccounts); + } + if (MetaMaskOnboarding.isMetaMaskInstalled()) { + window.ethereum.request({ method: 'eth_requestAccounts' }).then(handleNewAccounts); + window.ethereum.on('accountsChanged', handleNewAccounts); + return () => { + window.ethereum.off('accountsChanged', handleNewAccounts); + }; + } + }, []); + + const handleMetamaskLogin = () => { + if (MetaMaskOnboarding.isMetaMaskInstalled()) { + window.ethereum + .request({ method: 'eth_requestAccounts' }) + .then((newAccounts) => setAccounts(newAccounts)); + } else { + onboarding.current.startOnboarding(); + } + }; + return ( + + ); +} diff --git a/src/routes/login/index.js b/src/routes/login/index.js index 7fbcd8c5..78640d83 100644 --- a/src/routes/login/index.js +++ b/src/routes/login/index.js @@ -1,98 +1,115 @@ -import { useState } from "react"; -import StyledWrapper from "./styled"; -import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import toast from "react-hot-toast"; +/* eslint-disable no-undef */ +import { useState, useEffect } from 'react'; +import StyledWrapper from './styled'; +import { useDispatch } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; +import toast from 'react-hot-toast'; -import { useLoginMutation } from "../../app/services/auth"; -import { setAuthData } from "../../app/slices/auth.data"; +// import web3 from "web3"; +import MetamaskLoginButton from './MetamaskLoginButton'; + +import GoogleLoginButton from './GoogleLoginButton'; +import { useLoginMutation } from '../../app/services/auth'; +import { setAuthData } from '../../app/slices/auth.data'; export default function LoginPage() { - // const { token } = useSelector((store) => store.authData); - const navigateTo = useNavigate(); - const dispatch = useDispatch(); - const [login] = useLoginMutation(); - const [input, setInput] = useState({ - email: "", - password: "", + const [login, { data, isSuccess, isLoading, error }] = useLoginMutation(); + + // const { token } = useSelector((store) => store.authData); + const navigateTo = useNavigate(); + const dispatch = useDispatch(); + const [input, setInput] = useState({ + email: '', + password: '' + }); + + useEffect(() => { + if (error) { + console.log(error); + switch (error.status) { + case 'PARSING_ERROR': + toast.error(error.data); + break; + case 401: + toast.error('username or password incorrect'); + break; + case 404: + toast.error('account not exsit'); + break; + + default: + toast.error('something error'); + break; + } + return; + } + }, [error]); + useEffect(() => { + if (isSuccess && data) { + // 更新本地认证信息 + toast.success('login success'); + dispatch(setAuthData(data)); + navigateTo('/'); + } + }, [isSuccess, data]); + + const handleLogin = (evt) => { + evt.preventDefault(); + console.log('wtf', input); + login({ + ...input, + type: 'password' }); + }; - const handleLogin = async (evt) => { - evt.preventDefault(); - console.log("wtf", input); - const { data, error } = await login({ - ...input, - type: "password", - }); - if (error) { - console.log(error); - switch (error.status) { - case 401: - toast.error("username or password incorrect"); - break; - - default: - break; - } - return; - } - if (data) { - // 更新本地认证信息 - toast.success("login success"); - dispatch(setAuthData(data)); - navigateTo("/"); - // location.reload(true); - } - }; - const handleInput = (evt) => { - const { type } = evt.target.dataset; - const { value } = evt.target; - console.log(type, value); - setInput((prev) => { - prev[type] = value; - return { ...prev }; - }); - }; - const { email, password } = input; - return ( - -
-
- logo -

Login to Rustchat

- Please enter your details. -
-
- - - -
-
- - Sign in with Google - -
-
- ); + const handleInput = (evt) => { + const { type } = evt.target.dataset; + const { value } = evt.target; + console.log(type, value); + setInput((prev) => { + prev[type] = value; + return { ...prev }; + }); + }; + const { email, password } = input; + return ( + +
+
+ logo +

Login to Rustchat

+ Please enter your details. +
+
+ + + +
+
+ + +
+
+ ); } diff --git a/src/routes/login/styled.js b/src/routes/login/styled.js index 88bdd788..643f3c6a 100644 --- a/src/routes/login/styled.js +++ b/src/routes/login/styled.js @@ -1,95 +1,103 @@ -import styled from "styled-components"; +import styled from 'styled-components'; const StyledWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - .form { - padding: 36px 40px 32px 40px; - /* border: 1px solid #eee; */ - box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), - 0px 2px 4px -2px rgba(16, 24, 40, 0.06); - border-radius: 12px; - .tips { - display: flex; - flex-direction: column; - align-items: center; - padding-bottom: 24px; - .logo { - width: 56px; - height: 56px; - margin-bottom: 28px; - } - .title { - font-weight: 600; - font-size: 24px; - line-height: 32px; - color: #101828; - margin-bottom: 8px; - } - } - .desc { - font-weight: normal; - font-size: 16px; - line-height: 24px; - color: #667085; - } - form { - display: flex; - flex-direction: column; - gap: 20px; - input { - width: 360px; - background: #ffffff; - border: 1px solid #d0d5dd; - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); - border-radius: 8px; - padding: 10px 14px; - font-weight: normal; - font-size: 16px; - line-height: 24px; - color: #667085; - } - } - .or { - border: none; - position: relative; - height: 1px; - background-color: #e4e7ec; - margin: 26px 0; - &:after { - padding: 4px; - background-color: #fff; - content: "OR"; - position: absolute; - left: 50%; - top: 50%; - transform: translate3d(-50%, -50%, 0); - font-size: 14px; - line-height: 20px; - color: #667085; - } - } - .btn { - display: inline-block; - text-align: center; - width: 100%; - font-weight: 500; - font-size: 16px; - line-height: 24px; - color: #ffffff; - padding: 10px; - background: #1fe1f9; - border: 1px solid #1fe1f9; - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); - border-radius: 8px; - &.google { - color: #344054; - border-color: #d0d5dd; - background: none; - } - } + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + .form { + padding: 36px 40px 32px 40px; + /* border: 1px solid #eee; */ + box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06); + border-radius: 12px; + .tips { + display: flex; + flex-direction: column; + align-items: center; + padding-bottom: 24px; + .logo { + width: 56px; + height: 56px; + margin-bottom: 28px; + } + .title { + font-weight: 600; + font-size: 24px; + line-height: 32px; + color: #101828; + margin-bottom: 8px; + } } + .desc { + font-weight: normal; + font-size: 16px; + line-height: 24px; + color: #667085; + } + form { + display: flex; + flex-direction: column; + gap: 20px; + input { + width: 360px; + background: #ffffff; + border: 1px solid #d0d5dd; + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 8px; + padding: 10px 14px; + font-weight: normal; + font-size: 16px; + line-height: 24px; + color: #667085; + } + } + .or { + border: none; + position: relative; + height: 1px; + background-color: #e4e7ec; + margin: 26px 0; + &:after { + padding: 4px; + background-color: #fff; + content: 'OR'; + position: absolute; + left: 50%; + top: 50%; + transform: translate3d(-50%, -50%, 0); + font-size: 14px; + line-height: 20px; + color: #667085; + } + } + .btn { + display: inline-block; + text-align: center; + width: 100%; + font-weight: 500; + font-size: 16px; + line-height: 24px; + color: #ffffff; + padding: 10px; + background: #1fe1f9; + border: 1px solid #1fe1f9; + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 8px; + &.social { + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + color: #344054; + border-color: #d0d5dd; + background: none; + .icon { + width: 24px; + height: 24px; + } + } + } + } `; export default StyledWrapper; diff --git a/yarn.lock b/yarn.lock index 71a563e3..7106953a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1401,6 +1401,13 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@metamask/onboarding@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-1.0.1.tgz#14a36e1e175e2f69f09598e2008ab6dc1b3297e6" + integrity sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ== + dependencies: + bowser "^2.9.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2426,6 +2433,11 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bowser@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -6284,6 +6296,14 @@ react-error-overlay@^6.0.10: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== +react-google-login@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/react-google-login/-/react-google-login-5.2.2.tgz#a20b46440c6c1610175ef75baf427118ff0e9859" + integrity sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g== + dependencies: + "@types/react" "*" + prop-types "^15.6.0" + react-hot-toast@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.2.0.tgz#ab6f4caed4214b9534f94bb8cfaaf21b051e62b9"