mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
major changes
state management now handled with redux responsiveness uses custom hook (hooks.ts)
This commit is contained in:
@@ -1,62 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import Navigation from './components/Navigation/Navigation';
|
||||
import DocsGPT from './components/DocsGPT/DocsGPT';
|
||||
import Navigation from './components/Navigation';
|
||||
import Conversation from './components/Conversation/Conversation';
|
||||
import APIKeyModal from './components/APIKeyModal';
|
||||
import About from './components/About';
|
||||
|
||||
export default function App() {
|
||||
//Currently using primitive state management. Will most likely be replaced with Redux.
|
||||
const [isMobile, setIsMobile] = useState(true);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||
const [isApiModalOpen, setIsApiModalOpen] = useState(false);
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth > 768 && isMobile) {
|
||||
setIsMobile(false);
|
||||
} else {
|
||||
setIsMobile(true);
|
||||
setIsMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
handleResize();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
isMobile ? 'flex-col' : 'flex-row'
|
||||
} relative flex transition-all`}
|
||||
>
|
||||
<APIKeyModal
|
||||
apiKey={apiKey}
|
||||
setApiKey={setApiKey}
|
||||
isApiModalOpen={isApiModalOpen}
|
||||
setIsApiModalOpen={setIsApiModalOpen}
|
||||
/>
|
||||
<Navigation
|
||||
isMobile={isMobile}
|
||||
isMenuOpen={isMenuOpen}
|
||||
setIsMenuOpen={setIsMenuOpen}
|
||||
setIsApiModalOpen={setIsApiModalOpen}
|
||||
/>
|
||||
<div className="relative flex flex-col transition-all md:flex-row">
|
||||
<APIKeyModal />
|
||||
<Navigation />
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={<DocsGPT isMenuOpen={isMenuOpen} isMobile={isMobile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/about"
|
||||
element={<About isMenuOpen={isMenuOpen} isMobile={isMobile} />}
|
||||
/>
|
||||
<Route path="/" element={<Conversation />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function APIKeyModal({
|
||||
isApiModalOpen,
|
||||
setIsApiModalOpen,
|
||||
apiKey,
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
setApiKey,
|
||||
}: {
|
||||
isApiModalOpen: boolean;
|
||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
apiKey: string;
|
||||
setApiKey: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) {
|
||||
//TODO - Add form validation
|
||||
toggleApiKeyModal,
|
||||
selectIsApiKeyModalOpen,
|
||||
} from '../store';
|
||||
|
||||
export default function APIKeyModal({}) {
|
||||
//TODO - Add form validation?
|
||||
//TODO - Connect to backend
|
||||
//TODO - Add link to OpenAI API Key page
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const isApiModalOpen = useSelector(selectIsApiKeyModalOpen);
|
||||
const [key, setKey] = useState('');
|
||||
const [formError, setFormError] = useState(false);
|
||||
|
||||
const handleResetKey = () => {
|
||||
if (!apiKey) {
|
||||
function handleSubmit() {
|
||||
if (key.length < 1) {
|
||||
setFormError(true);
|
||||
} else {
|
||||
setFormError(false);
|
||||
setIsApiModalOpen(false);
|
||||
return;
|
||||
}
|
||||
};
|
||||
dispatch(setApiKey(key));
|
||||
dispatch(toggleApiKeyModal());
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -42,17 +41,17 @@ export default function APIKeyModal({
|
||||
<input
|
||||
type="text"
|
||||
className="h-10 w-full border-b-2 border-jet focus:outline-none"
|
||||
value={apiKey}
|
||||
value={key}
|
||||
maxLength={100}
|
||||
placeholder="API Key"
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
{formError && (
|
||||
<p className="text-sm text-red-500">Please enter a valid API key</p>
|
||||
)}
|
||||
<button
|
||||
onClick={handleResetKey}
|
||||
onClick={() => handleSubmit()}
|
||||
className="ml-auto h-10 w-20 rounded-lg bg-violet-800 text-white transition-all hover:bg-violet-700"
|
||||
>
|
||||
Save
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
export default function About({
|
||||
isMenuOpen,
|
||||
isMobile,
|
||||
}: {
|
||||
isMenuOpen: boolean;
|
||||
isMobile: boolean;
|
||||
}) {
|
||||
//TODO - Add hyperlinks to text
|
||||
//TODO - Styling
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useMediaQuery } from '../hooks';
|
||||
import { selectIsMenuOpen } from '../store';
|
||||
|
||||
//TODO - Add hyperlinks to text
|
||||
//TODO - Styling
|
||||
|
||||
export default function About() {
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
const isMenuOpen = useSelector(selectIsMenuOpen);
|
||||
|
||||
return (
|
||||
//Parent div for all content shown through App.tsx routing needs to have this styling. Might change when state management is updated.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
export default function DocsGPT({
|
||||
isMenuOpen,
|
||||
isMobile,
|
||||
}: {
|
||||
isMenuOpen: boolean;
|
||||
isMobile: boolean;
|
||||
}) {
|
||||
import { useMediaQuery } from '../../hooks';
|
||||
import { selectIsMenuOpen } from '../../store';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
export default function Conversation() {
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
const isMenuOpen = useSelector(selectIsMenuOpen);
|
||||
|
||||
return (
|
||||
//Parent div for all content shown through App.tsx routing needs to have this styling. Might change when state management is updated.
|
||||
//Parent div for all content shown through App.tsx routing needs to have this styling.
|
||||
<div
|
||||
className={`${
|
||||
isMobile
|
||||
@@ -1,23 +1,25 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Arrow1 from './imgs/arrow.svg';
|
||||
import Hamburger from './imgs/hamburger.svg';
|
||||
import Key from './imgs/key.svg';
|
||||
import Info from './imgs/info.svg';
|
||||
import Link from './imgs/link.svg';
|
||||
import Exit from './imgs/exit.svg';
|
||||
import { useMediaQuery } from '../hooks';
|
||||
import {
|
||||
toggleApiKeyModal,
|
||||
selectIsMenuOpen,
|
||||
toggleIsMenuOpen,
|
||||
} from '../store';
|
||||
import Arrow1 from '../imgs/arrow.svg';
|
||||
import Hamburger from '../imgs/hamburger.svg';
|
||||
import Key from '../imgs/key.svg';
|
||||
import Info from '../imgs/info.svg';
|
||||
import Link from '../imgs/link.svg';
|
||||
import Exit from '../imgs/exit.svg';
|
||||
|
||||
//TODO - Need to replace Chat button to open secondary nav with scrollable past chats option and new chat at top
|
||||
//TODO - Need to add Discord and Github links
|
||||
|
||||
function MobileNavigation({}) {
|
||||
const dispatch = useDispatch();
|
||||
const isMenuOpen = useSelector(selectIsMenuOpen);
|
||||
|
||||
function MobileNavigation({
|
||||
isMenuOpen,
|
||||
setIsMenuOpen,
|
||||
setIsApiModalOpen,
|
||||
}: {
|
||||
isMenuOpen: boolean;
|
||||
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
//TODO - Need to replace Chat button to open secondary nav with scrollable past chats option and new chat at top
|
||||
//TODO - Need to add Discord and Github links
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
@@ -29,7 +31,7 @@ function MobileNavigation({
|
||||
<>
|
||||
<button
|
||||
className="mt-5 ml-6 h-6 w-6"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
onClick={() => dispatch(toggleIsMenuOpen())}
|
||||
>
|
||||
<img src={Exit} alt="menu toggle" className="w-5" />
|
||||
</button>
|
||||
@@ -38,7 +40,7 @@ function MobileNavigation({
|
||||
<>
|
||||
<button
|
||||
className="mt-5 ml-6 h-6 w-6"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
onClick={() => dispatch(toggleIsMenuOpen())}
|
||||
>
|
||||
<img src={Hamburger} alt="menu toggle" className="w-7" />
|
||||
</button>
|
||||
@@ -71,7 +73,7 @@ function MobileNavigation({
|
||||
</div>
|
||||
<div
|
||||
className="flex h-12 cursor-pointer gap-4 rounded-md px-6 hover:bg-gray-100"
|
||||
onClick={() => setIsApiModalOpen(true)}
|
||||
onClick={() => dispatch(toggleApiKeyModal())}
|
||||
>
|
||||
<img src={Key} alt="info" className="ml-2 w-5" />
|
||||
<p className="my-auto text-eerie-black">Reset Key</p>
|
||||
@@ -82,15 +84,10 @@ function MobileNavigation({
|
||||
);
|
||||
}
|
||||
|
||||
function DesktopNavigation({
|
||||
isMenuOpen,
|
||||
setIsMenuOpen,
|
||||
setIsApiModalOpen,
|
||||
}: {
|
||||
isMenuOpen: boolean;
|
||||
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
function DesktopNavigation() {
|
||||
const dispatch = useDispatch();
|
||||
const isMenuOpen = useSelector(selectIsMenuOpen);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
@@ -104,7 +101,7 @@ function DesktopNavigation({
|
||||
>
|
||||
<button
|
||||
className="float-right mr-5 mt-5 h-5 w-5"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
onClick={() => dispatch(toggleIsMenuOpen())}
|
||||
>
|
||||
<img
|
||||
src={Arrow1}
|
||||
@@ -123,7 +120,7 @@ function DesktopNavigation({
|
||||
<div className="flex h-16 flex-col border-b-2 border-gray-100">
|
||||
<div
|
||||
className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100"
|
||||
onClick={() => setIsApiModalOpen(true)}
|
||||
onClick={() => dispatch(toggleApiKeyModal())}
|
||||
>
|
||||
<img src={Key} alt="key" className="ml-2 w-6" />
|
||||
<p className="my-auto text-eerie-black">Reset Key</p>
|
||||
@@ -155,32 +152,12 @@ function DesktopNavigation({
|
||||
);
|
||||
}
|
||||
|
||||
export default function Navigation({
|
||||
isMobile,
|
||||
isMenuOpen,
|
||||
setIsMenuOpen,
|
||||
setIsApiModalOpen,
|
||||
}: {
|
||||
isMobile: boolean;
|
||||
isMenuOpen: boolean;
|
||||
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
export default function Navigation() {
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<MobileNavigation
|
||||
isMenuOpen={isMenuOpen}
|
||||
setIsMenuOpen={setIsMenuOpen}
|
||||
setIsApiModalOpen={setIsApiModalOpen}
|
||||
/>
|
||||
);
|
||||
return <MobileNavigation />;
|
||||
} else {
|
||||
return (
|
||||
<DesktopNavigation
|
||||
isMenuOpen={isMenuOpen}
|
||||
setIsMenuOpen={setIsMenuOpen}
|
||||
setIsApiModalOpen={setIsApiModalOpen}
|
||||
/>
|
||||
);
|
||||
return <DesktopNavigation />;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export default function PastChat() {}
|
||||
22
frontend/src/hooks.ts
Normal file
22
frontend/src/hooks.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia(query);
|
||||
|
||||
if (media.matches !== matches) {
|
||||
setMatches(media.matches);
|
||||
}
|
||||
|
||||
const listener = () => {
|
||||
setMatches(media.matches);
|
||||
};
|
||||
|
||||
media.addEventListener('resize', listener);
|
||||
return () => media.removeEventListener('resize', listener);
|
||||
}, [matches, query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
@@ -30,6 +30,7 @@ body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,16 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
48
frontend/src/store.ts
Normal file
48
frontend/src/store.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
interface State {
|
||||
isApiKeyModalOpen: boolean;
|
||||
apiKey: string;
|
||||
isMenuOpen: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
isApiKeyModalOpen: true,
|
||||
apiKey: '',
|
||||
isMenuOpen: true,
|
||||
};
|
||||
|
||||
export const slice = createSlice({
|
||||
name: 'app',
|
||||
initialState,
|
||||
reducers: {
|
||||
toggleApiKeyModal: (state) => {
|
||||
state.isApiKeyModalOpen = !state.isApiKeyModalOpen;
|
||||
console.log('showApiKeyModal', state.isApiKeyModalOpen);
|
||||
},
|
||||
setApiKey: (state, action: PayloadAction<string>) => {
|
||||
state.apiKey = action.payload;
|
||||
console.log('setApiKey', action.payload);
|
||||
},
|
||||
toggleIsMenuOpen: (state) => {
|
||||
state.isMenuOpen = !state.isMenuOpen;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { toggleApiKeyModal, setApiKey, toggleIsMenuOpen } = slice.actions;
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
app: slice.reducer,
|
||||
},
|
||||
});
|
||||
|
||||
type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
export const selectIsApiKeyModalOpen = (state: RootState) =>
|
||||
state.app.isApiKeyModalOpen;
|
||||
export const selectApiKey = (state: RootState) => state.app.apiKey;
|
||||
export const selectIsMenuOpen = (state: RootState) => state.app.isMenuOpen;
|
||||
|
||||
export default store;
|
||||
Reference in New Issue
Block a user