diff --git a/package-lock.json b/package-lock.json index 56d165d..15f38e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,9 @@ "socket.io-client": "^4.7.5", "styled-components": "^6.1.8", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "tailwindcss": "^3.4.4" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -18722,9 +18725,9 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", diff --git a/package.json b/package.json index 5c4b2b6..7c7162b 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "i18next": "^23.11.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.1.2", "react-i18next": "^14.1.1", + "react-redux": "^9.1.2", "react-router-dom": "^6.23.0", "react-scripts": "5.0.1", "socket.io-client": "^4.7.5", @@ -46,5 +46,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "tailwindcss": "^3.4.4" } } diff --git a/src/App.js b/src/App.js index d103282..cfc55c8 100644 --- a/src/App.js +++ b/src/App.js @@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import Main from "./pages/Main.js"; import Register from "./pages/Register.js"; import Login from "./pages/Login.js"; +import Payment from "./pages/Payment.js"; function App() { return ( // already in index.js @@ -10,6 +11,7 @@ function App() { } /> } /> } /> + } /> // ); diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index 34ed2f5..3d73003 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -9,8 +9,19 @@ import { HistoryOutlined, GoogleOutlined, LogoutOutlined, + SendOutlined, } from "@ant-design/icons"; -import { Avatar, Button, Layout, List, Menu, Skeleton, Input, Space, ConfigProvider } from "antd"; +import { + Avatar, + Button, + Layout, + List, + Menu, + Skeleton, + Input, + Space, + ConfigProvider, +} from "antd"; import { onAuthStateChanged, signOut } from "firebase/auth"; import { auth } from "../firebase-config.js"; import { useNavigate } from "react-router-dom"; @@ -18,11 +29,12 @@ import { useCollapsed } from "./Contexts.js"; import GoogleSignIn from "./GoogleSignIn.js"; import { useTranslation } from "react-i18next"; import i18n from "../translations/i18n.js"; -import { useSelector, useDispatch } from 'react-redux' -import { setUser, delUser } from './userSlice.js' -import { addChat, setChat, logoutChat } from './chatSlice.js' -import { changeChat, logoutMsg } from './messageSlice.js' +import { useSelector, useDispatch } from "react-redux"; +import { setUser, delUser } from "./userSlice.js"; +import { addChat, setChat, logoutChat } from "./chatSlice.js"; +import { changeChat, logoutMsg } from "./messageSlice.js"; import axios from "axios"; +import Payment from "../pages/Payment.js"; const { Sider } = Layout; function getItem(label, key, icon, children) { @@ -58,43 +70,43 @@ const Sidebar = () => { return () => unsubscribe(); }, []); - const updateChat = async ()=>{ - try{ + const updateChat = async () => { + try { const resp = await axios.get( - process.env.REACT_APP_DB_URL + "chats/" + user - ) + process.env.REACT_APP_DB_URL + "chats/" + user, + ); dispatch(setChat(resp.data.data)); return resp.data.data; - }catch(e){ + } catch (e) { console.log("Load chat history from server failed"); - console.log(e) + console.log(e); } - } - useEffect(()=>{ + }; + useEffect(() => { // fetch chat list from database when user login - if (!user) return + if (!user) return; - updateChat().then(()=>{}); - - }, [user]) + updateChat().then(() => {}); + }, [user]); const [chatNameIpt, setChatNameIpt] = useState(""); - const handleAddChat = (chatName) => { - if (!chatName) return - if (!user) return - axios.post(process.env.REACT_APP_DB_URL + 'chats', { - 'userId': user, - 'chatName': chatName, - }).then(()=>{ - updateChat().then((resp)=>{ - const chatIds = resp.map((x)=>x.chatId); - const newId = chatIds.reduce((x,y)=>Math.max(x,y), -Infinity); - dispatch(changeChat(newId)); - setChatNameIpt(""); + const handleAddChat = (chatName) => { + if (!chatName) return; + if (!user) return; + axios + .post(process.env.REACT_APP_DB_URL + "chats", { + userId: user, + chatName: chatName, + }) + .then(() => { + updateChat().then((resp) => { + const chatIds = resp.map((x) => x.chatId); + const newId = chatIds.reduce((x, y) => Math.max(x, y), -Infinity); + dispatch(changeChat(newId)); + setChatNameIpt(""); + }); }); - - }) - } + }; const items = [ getItem(t("History"), "1", ), @@ -126,10 +138,13 @@ const Sidebar = () => { } }; - const handleSignOut = () => { signOut(auth).catch((error) => console.error("Error signing out: ", error)); }; + const handleSubscribe = () => { + window.open("/payment", "_blank"); + }; + return ( { items={items} onClick={handleMenuClick} /> */} - {handleAddChat(val)}} value={chatNameIpt} onChange={(e)=>{setChatNameIpt(e.target.value)}} disabled={user == null} style={{display: user==null?'none':''}} /> - + { + handleAddChat(val); + }} + value={chatNameIpt} + onChange={(e) => { + setChatNameIpt(e.target.value); + }} + disabled={user == null} + style={{ display: user == null ? "none" : "" }} + /> + - } - dataSource={chats} - renderItem={(item, idx) => ( - - - - )} - /> + }, + }, + }} + > + } + dataSource={chats} + renderItem={(item, idx) => ( + + + + )} + /> {/* Sidebar content*/} {/**/} {/* */} + ); }; diff --git a/src/index.css b/src/index.css index ec2585e..17df0e7 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', diff --git a/src/pages/Payment.js b/src/pages/Payment.js new file mode 100644 index 0000000..2d7df1b --- /dev/null +++ b/src/pages/Payment.js @@ -0,0 +1,185 @@ +import React, { useState, useEffect } from "react"; + +const Payment = () => { + const [selectedPlan, setSelectedPlan] = useState("yearly"); + const [cardNumber, setCardNumber] = useState(""); + const [expiry, setExpiry] = useState(""); + const [cvv, setCvv] = useState(""); + const [postalCode, setPostalCode] = useState(""); + const [errors, setErrors] = useState({}); + + const handlePlanChange = (event) => { + setSelectedPlan(event.target.id); + }; + + const validateInputs = () => { + const newErrors = {}; + if (!/^\d{16}$/.test(cardNumber.replace(/\s+/g, ""))) { + newErrors.cardNumber = "Card number must be 16 digits"; + } + if (!/^(0[1-9]|1[0-2])\/([0-9]{2})$/.test(expiry)) { + newErrors.expiry = "Expiry date must be in MM/YY format"; + } + if (!/^\d{3,4}$/.test(cvv)) { + newErrors.cvv = "CVV must be 3 or 4 digits"; + } + if (!/^\d{5}$/.test(postalCode)) { + newErrors.postalCode = "Postal code must be 5 digits"; + } + return newErrors; + }; + + const handleSubmit = (event) => { + event.preventDefault(); + const newErrors = validateInputs(); + if (Object.keys(newErrors).length === 0) { + alert("Payment processed successfully!"); + // Process payment here + } else { + setErrors(newErrors); + } + }; + + useEffect(() => { + setErrors(validateInputs()); + }, [cardNumber, expiry, cvv, postalCode]); + + const formatCardNumber = (value) => { + const cleaned = value.replace(/\D/g, ""); + const match = cleaned.match(/.{1,4}/g); + return match ? match.join(" ") : value; + }; + + return ( +
+
+

Order Summary

+
+
PolyglotBot Pro
+
+
+
+ + +
+
+
+
+ +
+ setCardNumber(e.target.value)} + maxLength={19} + /> +
+ Visa + MasterCard +
+
+ {errors.cardNumber && ( +

{errors.cardNumber}

+ )} +
+ setExpiry(e.target.value)} + maxLength={5} + /> + setCvv(e.target.value)} + maxLength={4} + /> +
+ {errors.expiry && ( +

{errors.expiry}

+ )} + {errors.cvv &&

{errors.cvv}

} +
+ + setPostalCode(e.target.value)} + maxLength={5} + /> +
+ {errors.postalCode && ( +

{errors.postalCode}

+ )} +
+ +
+
+
+ ); +}; + +export default Payment; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..fdb91b2 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], + darkMode: false, + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], +};