Skip to content

Commit

Permalink
Merge pull request #134 from ASM-Studios/mra/feat-user-page
Browse files Browse the repository at this point in the history
feat(user): add user page
  • Loading branch information
Mael-RABOT authored Dec 9, 2024
2 parents 47cd1b8 + dc4980b commit 08a7504
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 62 deletions.
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

0 comments on commit 08a7504

Please sign in to comment.