Skip to content

Commit

Permalink
Login on website
Browse files Browse the repository at this point in the history
  • Loading branch information
javipacheco committed Sep 12, 2023
1 parent e7f4aa3 commit eb6cbfe
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.xebia.functional.xef.server.http.routes

import com.aallam.openai.api.BetaOpenAI
import com.xebia.functional.xef.server.models.LoginRequest
import com.xebia.functional.xef.server.models.LoginResponse
import com.xebia.functional.xef.server.models.RegisterRequest
import com.xebia.functional.xef.server.services.PersistenceService
import io.ktor.client.*
import io.ktor.client.call.*
Expand All @@ -12,7 +15,6 @@ import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.jvm.javaio.*
import kotlinx.serialization.json.Json
Expand All @@ -31,14 +33,25 @@ fun String.toProvider(): Provider? = when (this) {
else -> Provider.OPENAI
}


@OptIn(BetaOpenAI::class)
fun Routing.routes(
client: HttpClient,
persistenceService: PersistenceService
) {
val openAiUrl = "https://api.openai.com/v1"

post("/register") {
// fake implementation for testing
val request = Json.decodeFromString<RegisterRequest>(call.receive<String>())
call.respond(LoginResponse("token: ${request.password}"))
}

post("/login") {
// fake implementation for testing
val request = Json.decodeFromString<LoginRequest>(call.receive<String>())
call.respond(LoginResponse("token: ${request.password}"))
}

authenticate("auth-bearer") {
post("/chat/completions") {
val token = call.getToken()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.xebia.functional.xef.server.models

import kotlinx.serialization.Serializable

@Serializable
data class LoginResponse(val authToken: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.xebia.functional.xef.server.models

import kotlinx.serialization.Serializable

@Serializable
data class RegisterRequest(
val name: String,
val email: String,
val password: String
)

@Serializable
data class LoginRequest(
val email: String,
val password: String
)
6 changes: 6 additions & 0 deletions server/web/src/components/Header/Header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
height: 2rem;
width: auto;
}

.panel-right {
display: block;
text-align: right;
margin-left: auto;
}
23 changes: 19 additions & 4 deletions server/web/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material';
import { AppBar, Box, Button, IconButton, Toolbar, Typography } from '@mui/material';
import { Menu } from '@mui/icons-material';

import logo from '@/assets/xef-brand-name.svg';

import styles from './Header.module.css';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/state/Auth';

export type HeaderProps = {
action: () => void;
};

export function Header({ action }: HeaderProps) {
const navigate = useNavigate();
const auth = useAuth();

const handleSubmit = () => {
auth.signout(() => {
navigate("/login", { replace: true });
});
};

return (
<Box className={styles.container} sx={{ flexGrow: 1 }}>
<AppBar position="fixed">
Expand All @@ -24,9 +35,13 @@ export function Header({ action }: HeaderProps) {
<Menu />
</IconButton>
<img className={styles.logo} src={logo} alt="Logo" />
<Typography variant="h5" component="div" sx={{ flexGrow: 1 }}>
Dashboard
</Typography>
<Button
className={styles.panelRight}
onClick={handleSubmit}
variant="text"
disableElevation>
<Typography variant="button">Logout</Typography>
</Button>
</Toolbar>
</AppBar>
</Box>
Expand Down
6 changes: 6 additions & 0 deletions server/web/src/components/Login/Login.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.center {
position: absolute;
left: 50%;
top: 20%;
transform: translate(-50%, -50%);
}
108 changes: 108 additions & 0 deletions server/web/src/components/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useAuth } from '@/state/Auth';
import { Box, Button, TextField, Typography } from '@mui/material';
import { ChangeEvent, useState } from 'react';
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
import styles from './Login.module.css';

export function RequireAuth({ children }: { children: JSX.Element }) {
let auth = useAuth();
let location = useLocation();

if (!auth.authToken) {
return <Navigate to="/login" state={{ from: location }} replace />;
}

return children;
}

export function Login() {
const navigate = useNavigate();
const location = useLocation();
const auth = useAuth();

const from = location.state?.from?.pathname || '/';

const [emailInput, setEmailInput] = useState<string>('');
const [passwordInput, setPasswordInput] = useState<string>('');

const handleSubmit = () => {
auth.signin(emailInput, () => {
navigate(from, { replace: true });
});
};

const emailHandleChange = (event: ChangeEvent<HTMLInputElement>) => {
setEmailInput(event.target.value);
};

const passwordHandleChange = (event: ChangeEvent<HTMLInputElement>) => {
setPasswordInput(event.target.value);
};

const disabledButton = passwordInput?.trim() == "" || emailInput?.trim() == "";

return (
<Box
className={styles.center}
>
<Box
sx={{
my: 1,
}}>
<Typography variant="h4" gutterBottom>
Xef Server
</Typography>
</Box>
<Box
sx={{
my: 3,
}}>
<TextField
id="email"
label="Email"
value={emailInput}
onChange={emailHandleChange}
size="small"
sx={{
width: { xs: '100%', sm: 550 },
}}
/>
</Box>
<Box
sx={{
my: 3,
}}>
<TextField
id="password"
label="Password"
value={passwordInput}
onChange={passwordHandleChange}
size="small"
sx={{
width: { xs: '100%', sm: 550 },
}}
/>
</Box>
<Button
onClick={handleSubmit}
variant="contained"
disableElevation
disabled={disabledButton}>
<Typography variant="button">Login</Typography>
</Button>
</Box>
);

// return (
// <div>
// <p>You must log in to view the page at {from}</p>

// <form onSubmit={handleSubmit}>
// <label>
// Username: <input name="username" type="text" />
// </label>{' '}
// <button type="submit">Login</button>
// </form>
// </div>
// );
}
1 change: 1 addition & 0 deletions server/web/src/components/Login/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Login';
2 changes: 1 addition & 1 deletion server/web/src/components/Pages/Root/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function Root() {
return <>Root</>;
return <>Dashboard</>;
}
56 changes: 45 additions & 11 deletions server/web/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { createBrowserRouter, Navigate, RouterProvider, useLocation, useNavigate } from 'react-router-dom';
import { CssBaseline, StyledEngineProvider } from '@mui/material';
import { ThemeProvider } from '@emotion/react';

Expand All @@ -18,32 +18,64 @@ import { SettingsProvider } from '@/state/Settings';
import { theme } from '@/styles/theme';

import './main.css';
import { AuthProvider } from './state/Auth';
import { Login, RequireAuth } from './components/Login';

const router = createBrowserRouter([
{
path: '/login',
errorElement: <ErrorPage />,
children: [
{
path: '/login',
element: <Login />,
},
]
},
{
path: '/',
element: <App />,
errorElement: <ErrorPage />,
children: [
{
path: '/',
element: <Root />,
element: (
<RequireAuth>
<Root />
</RequireAuth>
),
},
{
path: '1',
element: <FeatureOne />,
element: (
<RequireAuth>
<FeatureOne />
</RequireAuth>
),
},
{
path: '2',
element: <Chat />,
element: (
<RequireAuth>
<Chat />
</RequireAuth>
),
},
{
path: 'generic-question',
element: <GenericQuestion />,
element: (
<RequireAuth>
<GenericQuestion />
</RequireAuth>
),
},
{
path: 'settings',
element: <SettingsPage />,
element: (
<RequireAuth>
<SettingsPage />
</RequireAuth>
),
},
],
},
Expand All @@ -54,11 +86,13 @@ createRoot(document.getElementById('root') as HTMLElement).render(
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<CssBaseline enableColorScheme />
<LoadingProvider>
<SettingsProvider>
<RouterProvider router={router} />
</SettingsProvider>
</LoadingProvider>
<AuthProvider>
<LoadingProvider>
<SettingsProvider>
<RouterProvider router={router} />
</SettingsProvider>
</LoadingProvider>
</AuthProvider>
</ThemeProvider>
</StyledEngineProvider>
</StrictMode>,
Expand Down
36 changes: 36 additions & 0 deletions server/web/src/state/Auth/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { createContext, useState } from 'react';

export function useAuth() {
return React.useContext(AuthContext);
}

interface AuthContextType {
authToken: string;
signin: (authToken: string, callback: VoidFunction) => void;
signout: (callback: VoidFunction) => void;
}

const AuthContext = createContext<AuthContextType>(null!);

function AuthProvider({ children }: { children: React.ReactNode }) {
const [authToken, setAuthToken] = useState<string>(sessionStorage.getItem('authToken') || '');

const signin = (newAuthToken: string, callback: VoidFunction) => {
setAuthToken(newAuthToken);
sessionStorage.setItem('authToken', newAuthToken);
callback();
};

const signout = (callback: VoidFunction) => {
setAuthToken('');
sessionStorage.setItem('authToken', '');
callback();
};

const value = { authToken, signin, signout };

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export { AuthContext, AuthProvider };
1 change: 1 addition & 0 deletions server/web/src/state/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AuthContext';

0 comments on commit eb6cbfe

Please sign in to comment.