Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(user): add user page #134

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion client_web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import NotFound from './Pages/Errors/NotFound';
import ApiNotConnected from "@/Pages/Errors/ApiNotConnected";
import CustomError from "@/Pages/Errors/CustomError";

import UserPage from "@/Pages/Account/UserPage";

import Layout from '@/Components/Layout/Layout';
import Login from './Pages/Auth/Forms/Login';
import Register from './Pages/Auth/Forms/Register';
Expand Down Expand Up @@ -148,7 +150,7 @@ const App = () => {
<Router>
<Layout>
<Routes>
<Route path="/" element={<Home backgroundColor={backgroundColor} setBackgroundColor={setBackgroundColor} />} />
<Route path="/" element={<Home backgroundColor={backgroundColor} />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/dashboard" element={<Dashboard />} />
Expand All @@ -162,6 +164,8 @@ const App = () => {
<Route path="/workflows" element={<WorkflowsTable />} />
<Route path="/workflow/:id" element={<NotFound />} />

<Route path="/account/me" element={<UserPage backgroundColor={backgroundColor} setBackgroundColor={setBackgroundColor} />} />

<Route path="/error/connection" element={<ApiNotConnected />} />
<Route path="/error/:error" element={<CustomError />} />
<Route path="*" element={<NotFound />} />
Expand Down
30 changes: 23 additions & 7 deletions client_web/src/Components/Auth/OAuthButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import DiscordAuth from './Buttons/DiscordAuth';
import { Divider } from 'antd';

interface OAuthButtonsProps {
mode: 'signin' | 'signup';
mode: 'signin' | 'signup' | 'connect';
onGoogleSuccess: (response: unknown) => void;
onGoogleError: () => void;
onMicrosoftSuccess: (response: unknown) => void;
Expand All @@ -24,34 +24,50 @@ const OAuthButtons = ({
onLinkedinSuccess,
onLinkedinError
}: OAuthButtonsProps) => {
let withText = "";
switch (mode) {
case 'signin':
withText = "Sign in with";
break;
case 'signup':
withText = "Sign up with";
break;
case 'connect':
withText = "Connect with";
break;
default:
withText = "Use";
break;
}

return (
<>
<Divider>Or</Divider>
{mode !== 'connect' && <Divider>Or</Divider>}

<GoogleAuth
onSuccess={onGoogleSuccess}
onError={onGoogleError}
buttonText={`Sign ${mode === 'signin' ? 'in' : 'up'} with Google`}
buttonText={`${withText} Google`}
/>

<MicrosoftAuth
onSuccess={onMicrosoftSuccess}
onError={onMicrosoftError}
buttonText={`Sign ${mode === 'signin' ? 'in' : 'up'} with Microsoft`}
buttonText={`${withText} Microsoft`}
/>

<LinkedinAuth
onSuccess={onLinkedinSuccess}
onError={onLinkedinError}
buttonText={`Sign ${mode === 'signin' ? 'in' : 'up'} with LinkedIn`}
buttonText={`${withText} LinkedIn`}
/>

<SpotifyAuth
buttonText={`Sign ${mode === 'signin' ? 'in' : 'up'} with Spotify`}
buttonText={`${withText} Spotify`}
/>

<DiscordAuth
buttonText={`Sign ${mode === 'signin' ? 'in' : 'up'} with Discord`}
buttonText={`${withText} Discord`}
/>
</>
);
Expand Down
75 changes: 59 additions & 16 deletions client_web/src/Components/Layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { Layout, Menu, Button } from 'antd';
import { Layout, Menu, Button, Dropdown, Avatar } from 'antd';
import { Link, useLocation } from 'react-router-dom';
import { useAuth, useTheme } from '@/Context/ContextHooks';
import React, { useEffect, useState } from "react";
import { UserOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';

const { Header: AntHeader } = Layout;

interface MenuItems {
key: string;
label: React.ReactNode;
auth: boolean;
}

const menuItems: MenuItems[] = [
{ key: '/', label: <Link to="/">Home</Link> },
{ key: '/dashboard', label: <Link to="/dashboard">Dashboard</Link> },
{ key: '/', label: <Link to="/">Home</Link>, auth: false },
{ key: '/login', label: <Link to="/login">Login</Link>, auth: false },
{ key: '/register', label: <Link to="/register">Register</Link>, auth: false },
{ key: '/dashboard', label: <Link to="/dashboard">Dashboard</Link>, auth: true },
{ key: '/workflow/create', label: <Link to="/workflow/create">Create Workflow</Link>, auth: true },
];

const Header: React.FC = () => {
const { theme } = useTheme();
const navigate = useNavigate();
const location = useLocation();
const [selectedKey, setSelectedKey] = useState<string>(location.pathname);

const { setIsAuthenticated, setJsonWebToken } = useAuth();
const { isAuthenticated, setIsAuthenticated, setJsonWebToken } = useAuth();

useEffect(() => {
setSelectedKey(location.pathname);
Expand All @@ -33,29 +40,65 @@ const Header: React.FC = () => {
window.location.href = '/';
}

const profileMenuItems = [
{
key: 'profile',
label: 'Profile Settings'
},
{
key: 'logout',
label: 'Logout',
danger: true
}
];

const handleMenuClick = ({ key }: { key: string }) => {
if (key === 'logout') {
handleLogout();
}
if (key === 'profile') {
navigate('/account/me');
}
};

return (
<div style={{ padding: 24, position: 'relative', zIndex: 1 }}>
<AntHeader style={{ backgroundColor: theme === "dark" ? '#001529' : 'white', display: 'flex', alignItems: 'center', zIndex: 1, borderRadius: '8px' }}>
<Menu
theme={theme}
mode="horizontal"
style={{ flex: 1 }}
items={menuItems}
items={menuItems.filter(item => !item.auth || isAuthenticated)}
selectedKeys={[selectedKey]}
/>
<Button //TODO: Replace by account management
type="primary"
danger
onClick={handleLogout}
style={{
marginLeft: 'auto',
height: '32px',
borderRadius: '6px',
fontWeight: 500,
<Dropdown
menu={{
items: profileMenuItems,
onClick: handleMenuClick
}}
placement="bottomRight"
arrow
>
Logout
</Button>
<Button
type="text"
style={{
marginLeft: 'auto',
height: '40px',
borderRadius: '6px',
display: 'flex',
alignItems: 'center',
gap: '8px'
}}
>
<Avatar
icon={<UserOutlined />}
style={{
backgroundColor: theme === "dark" ? '#1890ff' : '#001529'
}}
/>
<span>Profile</span>
</Button>
</Dropdown>
</AntHeader>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion client_web/src/Components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface LayoutProps {

const Layout: React.FC<LayoutProps> = ({ children }) => {
const location = useLocation();
const headerIncludedPaths: (string | RegExp)[] = ['/', '/dashboard', '/workflow/create', '/workflows'];
const headerIncludedPaths: (string | RegExp)[] = ['/', '/dashboard', '/account/me', '/workflow/create', '/workflows'];
const footerIncludedPaths: (string | RegExp)[] = ['/', '/dashboard'];

const isHeaderIncluded = headerIncludedPaths.some(path =>
Expand Down
2 changes: 1 addition & 1 deletion client_web/src/Context/Scopes/UserContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext, useState, ReactNode } from 'react';

interface User {
id: string;
name: string;
username: string;
}

export interface UserContextType {
Expand Down
115 changes: 115 additions & 0 deletions client_web/src/Pages/Account/UserPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { useState } from "react";
import { Layout, Button, Typography, Space, Card, Row, Col } from "antd";
import { useNavigate } from "react-router-dom";
// @ts-expect-error
import { BlockPicker } from "react-color";
import { useAuth, useUser } from "@/Context/ContextHooks";
import OAuthButtons from "@/Components/Auth/OAuthButtons";
import {toast} from "react-toastify";
import Security from "@/Components/Security";

const { Header, Content } = Layout;
const { Title, Text } = Typography;

interface UserPageProps {
backgroundColor: string;
setBackgroundColor: (color: string) => void;
}

const UserPage: React.FC<UserPageProps> = ({ backgroundColor, setBackgroundColor }) => {
const navigate = useNavigate();
const [tempColor, setTempColor] = useState(backgroundColor);

const { setJsonWebToken, setIsAuthenticated } = useAuth();
const { user } = useUser();

const handleLogout = () => {
localStorage.removeItem("JsonWebToken");
setJsonWebToken("");
setIsAuthenticated(false);
navigate("/");
};

const handleColorChange = (color: any) => {
setTempColor(color.hex);
sessionStorage.setItem('backgroundColor', color.hex);
setBackgroundColor(color.hex);
};

const handleDefaultColor = () => {
const defaultColor = "#FFA500";
setTempColor(defaultColor);
sessionStorage.setItem('backgroundColor', defaultColor);
setBackgroundColor(defaultColor);
};

const handleNotImplemented = () => {
toast.error("This feature is not implemented yet");
};

return (
<Security>
<div style={{padding: '16px 24px', position: 'relative', zIndex: 1, height: '100%'}} role="main">
<Title level={3} style={{marginBottom: 16}}>
Create Workflow
</Title>
<Content style={{padding: "24px"}}>
<Space direction="vertical" size="large" style={{width: "100%"}}>
<Row gutter={[16, 16]}>
<Col xs={24} md={12}>
<Card title="Profile Information">
<Space direction="vertical">
<Text strong>Welcome, {user?.username || 'User'}!</Text>
<Text>Manage your account settings and connected services here.</Text>
</Space>
</Card>
</Col>

<Col xs={24} md={12}>
<Card title="Theme Settings">
<Space direction="vertical" align="center" style={{width: "100%"}}>
<Text>Customize your background color</Text>
<div style={{ border: '1px solid #d9d9d9', padding: '8px', borderRadius: '4px' }}>
<BlockPicker color={tempColor} onChangeComplete={handleColorChange}/>
</div>
<Button onClick={handleDefaultColor}>
Reset to Default Color
</Button>
</Space>
</Card>
</Col>

<Col xs={24}>
<Card title="Connected Services">
<Space direction="vertical" style={{width: "100%"}}>
<Text>Connect your accounts to enhance your experience</Text>
<OAuthButtons
mode={"connect"}
onGoogleError={handleNotImplemented}
onGoogleSuccess={handleNotImplemented}
onLinkedinError={handleNotImplemented}
onLinkedinSuccess={handleNotImplemented}
onMicrosoftError={handleNotImplemented}
onMicrosoftSuccess={handleNotImplemented}
/>
</Space>
</Card>
</Col>
</Row>

<Card>
<Space direction="vertical" align="center" style={{width: "100%"}}>
<Text type="secondary">Ready to leave?</Text>
<Button type="primary" danger onClick={handleLogout}>
Logout
</Button>
</Space>
</Card>
</Space>
</Content>
</div>
</Security>
);
};

export default UserPage;
Loading
Loading