Skip to content
Snippets Groups Projects
Commit b2a4678c authored by Embruch, Gerd's avatar Embruch, Gerd
Browse files

added user profile

parent aa6f0e85
No related branches found
No related tags found
No related merge requests found
@tailwind base;@tailwind components;@tailwind utilities;@layer base{.conceal{@apply opacity-0 h-0 w-0 p-0 m-0 overflow-hidden}label,details{@apply block w-full pb-1 relative cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-60 lg:min-w-xs lg:w-[calc(1/2*100%-(1*1rem/2))] xl:w-[calc((1/4*100%)-(3*1rem/4))]}img,svg,video,canvas,audio,iframe,embed,object{display:inline;vertical-align:middle}:root{--background: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 204 98% 37%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 0% 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 353 100% 44%;--destructive-foreground: 0 0% 98%;--border: 0 0% 89.8%;--input: 0 0% 89.8%;--ring: 0 0% 3.9%;--radius: 0.5rem}.dark{--background: 0 0% 3.9%;--foreground: 0 0% 98%;--card: 0 0% 3.9%;--card-foreground: 0 0% 98%;--popover: 0 0% 3.9%;--popover-foreground: 0 0% 98%;--primary: 0 0% 98%;--primary-foreground: 0 0% 9%;--secondary: 0 0% 14.9%;--secondary-foreground: 0 0% 98%;--muted: 0 0% 14.9%;--muted-foreground: 0 0% 63.9%;--accent: 0 0% 14.9%;--accent-foreground: 0 0% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 0 0% 98%;--border: 0 0% 14.9%;--input: 0 0% 14.9%;--ring: 0 0% 83.1%}*{@apply border-border}body{@apply bg-background text-foreground}}/*# sourceMappingURL=tailwind.presets.min.css.map */
\ No newline at end of file
@tailwind base;@tailwind components;@tailwind utilities;@layer base{.conceal{@apply opacity-0 h-0 w-0 p-0 m-0 overflow-hidden}label,details{@apply block w-full pb-1 relative cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-60 lg:min-w-xs lg:w-[calc(1/2*100%-(1*1rem/2))] xl:w-[calc((1/4*100%)-(3*1rem/4))]}fieldset{@apply pb-4 flex flex-wrap lg:gap-x-8 gap-x-4}img,svg,video,canvas,audio,iframe,embed,object{display:inline;vertical-align:middle}:root{--background: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 204 98% 37%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 0% 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 353 100% 44%;--destructive-foreground: 0 0% 98%;--border: 0 0% 89.8%;--input: 0 0% 89.8%;--ring: 0 0% 3.9%;--radius: 0.5rem}.dark{--background: 0 0% 3.9%;--foreground: 0 0% 98%;--card: 0 0% 3.9%;--card-foreground: 0 0% 98%;--popover: 0 0% 3.9%;--popover-foreground: 0 0% 98%;--primary: 0 0% 98%;--primary-foreground: 0 0% 9%;--secondary: 0 0% 14.9%;--secondary-foreground: 0 0% 98%;--muted: 0 0% 14.9%;--muted-foreground: 0 0% 63.9%;--accent: 0 0% 14.9%;--accent-foreground: 0 0% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 0 0% 98%;--border: 0 0% 14.9%;--input: 0 0% 14.9%;--ring: 0 0% 83.1%}*{@apply border-border}body{@apply bg-background text-foreground}}/*# sourceMappingURL=tailwind.presets.min.css.map */
\ No newline at end of file
{"version":3,"sources":["../sass/tailwind.presets.scss"],"names":[],"mappings":"AAAA,cAAA,CACA,oBAAA,CACA,mBAAA,CACA,YACE,SACE,gDAAA,CAEF,cAEE,6MAAA,CAEF,+CAQE,cAAA,CACA,qBAAA,CAEF,MACE,uBAAA,CACA,uBAAA,CAEA,iBAAA,CACA,4BAAA,CAEA,oBAAA,CACA,+BAAA,CAGA,sBAAA,CACA,8BAAA,CAEA,uBAAA,CACA,+BAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,4BAAA,CAEA,2BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,iBAAA,CAEA,gBAAA,CAGF,MACE,uBAAA,CACA,sBAAA,CAEA,iBAAA,CACA,2BAAA,CAEA,oBAAA,CACA,8BAAA,CAEA,mBAAA,CACA,6BAAA,CAEA,uBAAA,CACA,gCAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,6BAAA,CAEA,4BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,kBAAA,CAGF,EACE,oBAAA,CAEF,KACE,oCAAA,CAAA","file":"tailwind.presets.min.css"}
\ No newline at end of file
{"version":3,"sources":["../sass/tailwind.presets.scss"],"names":[],"mappings":"AAAA,cAAA,CACA,oBAAA,CACA,mBAAA,CACA,YACE,SACE,gDAAA,CAEF,cAEE,6MAAA,CAEF,SACE,6CAAA,CAEF,+CAQE,cAAA,CACA,qBAAA,CAEF,MACE,uBAAA,CACA,uBAAA,CAEA,iBAAA,CACA,4BAAA,CAEA,oBAAA,CACA,+BAAA,CAGA,sBAAA,CACA,8BAAA,CAEA,uBAAA,CACA,+BAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,4BAAA,CAEA,2BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,iBAAA,CAEA,gBAAA,CAGF,MACE,uBAAA,CACA,sBAAA,CAEA,iBAAA,CACA,2BAAA,CAEA,oBAAA,CACA,8BAAA,CAEA,mBAAA,CACA,6BAAA,CAEA,uBAAA,CACA,gCAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,6BAAA,CAEA,4BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,kBAAA,CAGF,EACE,oBAAA,CAEF,KACE,oCAAA,CAAA","file":"tailwind.presets.min.css"}
\ No newline at end of file
......@@ -9,6 +9,9 @@
details {
@apply block w-full pb-1 relative cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-60 lg:min-w-xs lg:w-[calc(1/2*100%-(1*1rem/2))] xl:w-[calc((1/4*100%)-(3*1rem/4))];
}
fieldset {
@apply pb-4 flex flex-wrap lg:gap-x-8 gap-x-4;
}
img,
svg,
video,
......
import React, { useEffect, useState } from 'react';
import Chat from './Chat';
import Heading from '../font/Heading';
import { RiAddCircleLine, RiArrowLeftCircleLine, RiArrowRightCircleLine } from 'react-icons/ri';
import { useChat } from '../../contexts/Chat/ChatState';
import { useNavigate, useParams } from 'react-router-dom';
import { useChat } from '../../contexts/Chat/ChatState';
import { mergeBackendValidation } from '../../utils/ErrorHandling';
import Heading from '../font/Heading';
import Chat from './Chat';
const Chats = () => {
// #################################
......
import React, { useEffect, useRef } from 'react';
import Message from './Message';
import { useChat } from '../../contexts/Chat/ChatState';
import { Link } from 'react-router-dom';
import { RxBookmark } from 'react-icons/rx';
import { Link } from 'react-router-dom';
import { useChat } from '../../contexts/Chat/ChatState';
import Message from './Message';
const Messages = () => {
......
import React, { useContext, useReducer, useState } from 'react';
import api from '../../utils/AxiosConfig';
import AuthContext from './AuthContext';
import authReducer from './AuthReducer';
import { USER_ACTIONS } from './AuthTypes';
import api from '../../utils/AxiosConfig';
// ### EXPORT useContext TO REDUCE NEEDED CODE IN CLIENT FILES
export function useAuth() {
......@@ -59,6 +59,16 @@ function AuthState({ children }) {
return api.post('/auth/password-reset', { email });
}
// ### UPDATE USER_ACTIONS
async function update(id, user) {
// remove password keys if not set to avoid emtying password field
if (user && !user.password) delete user.password;
if (user && !user.confirmPassword) delete user.confirmPassword;
// send data to backend
return api.patch(`/users/${id}`,
user
);
}
// ### RETURN
return (
......@@ -66,6 +76,7 @@ function AuthState({ children }) {
value={{
login,
currentUser,
update,
logout,
requestVerificationToken,
requestPasswordReset,
......
......@@ -49,18 +49,18 @@ function NewModel({ data, setData }) {
// OUTPUT
// #################################
return (
<>
<div>
{(currentUser?.role >= 2) ?
<FormProvider {...methods} >
<Heading level="4">install new model</Heading>
<form onSubmit={methods.handleSubmit(handleInstall)} className='md:w-1/3'>
<form onSubmit={methods.handleSubmit(handleInstall)} className=''>
<Input name='model' type='text' title='Model Name' className='h-16' required={true} tooltip={<Link to='https://ollama.com/library' target='_blank' rel='noopener noreferrer'>Ollama Library</Link>} />
<Submit size='sm' value={methods.formState.isSubmitting ? 'installing...' : 'install model'} />
</form>
</FormProvider>
: null}
</>
</div>
);
}
......
import React, { useEffect, useState } from 'react';
import api from '../../utils/AxiosConfig';
import { mergeBackendValidation } from '../../utils/ErrorHandling';
import NewModel from './AI/NewModel';
import Models from './AI/Models';
import NewModel from './AI/NewModel';
import AIStatus from './AI/Status';
function AIModels() {
......@@ -62,17 +62,15 @@ function AIModels() {
<div className="absolute w-[50vw] right-[50%] h-1 bg-UhhRed"></div>
</div>
<div className='max-h-full flex flex-col'>
<div>
<fieldset>
{/* ai status */}
<AIStatus />
</div>
<div>
{/* new model */}
<NewModel data={data} setData={setData} />
</div>
<div className='overflow-y-auto'>
{/* model list */}
<Models data={data} setData={setData} />
</div>
</div>
</fieldset>
......
......@@ -55,20 +55,15 @@ function Embeddings() {
<div className="absolute w-[50vw] right-[50%] h-1 bg-UhhRed"></div>
</div>
<div className='max-h-full flex flex-col'>
<div>
<fieldset>
{/* rag status */}
<Status status={status} />
</div>
<div>
{/* update embeddings */}
<Update setStatus={setStatus} />
</div>
<div>
{/* delete embeddings */}
<Delete setStatus={setStatus} />
</div>
</div>
</fieldset>
</section>
);
}
......
......@@ -46,7 +46,7 @@ function Update({ setStatus }) {
// OUTPUT
// #################################
return (
<>
<div>
{(currentUser?.role >= 2) ?
<FormProvider {...methods}>
<Heading level="4">Update Embeddings
......@@ -61,7 +61,7 @@ function Update({ setStatus }) {
</details>
</FormProvider>
: null}
</>
</div>
);
}
......
import { zodResolver } from '@hookform/resolvers/zod';
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet-async';
import { FormProvider, useForm } from 'react-hook-form';
import { isStrongPassword } from 'validator';
import { z } from "zod";
import Input from '../..//components/form/Input';
import Heading from '../../components/font/Heading';
import Submit from '../../components/form/Submit';
import { useAuth } from '../../contexts/Auth/AuthState';
import { mergeBackendValidation, setFlashMsg } from '../../utils/ErrorHandling';
function Profile() {
// #################################
// VALIDATION SCHEMA
// #################################
// TODO limit file size via .env
// TODO check for file types
const schema = z.object({
name: z.string().min(1),
username: z.string().min(1),
email: z.string().email(),
password: z.string().refine((val) => val && isStrongPassword(val), {
message: 'This field must be min 6 characters long and contain uppercase, lowercase, number, specialchar.',
}).nullish().or(z.literal('')),
confirmPassword: z.string().nullish().or(z.literal('')),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
// #################################
// HOOKS
// #################################
// ### CONNECT AUTH CONTEXT
const { currentUser, dispatchCurrentUser, USER_ACTIONS, update } = useAuth();
// ### PREPARE FORM
const methods = useForm({
resolver: zodResolver(schema),
mode: 'onSubmit',
defaultValues: currentUser
});
// ### RESET FORM AFTER SUCCESSFUL SUBMIT
useEffect(() => {
if (!methods.formState.isSubmitSuccessful) return;
methods.reset(currentUser);
methods.setValue('confirmPassword', '');
methods.setValue('password', '');
}, [methods.formState]);
// #################################
// FUNCTIONS
// #################################
// ### HANDLE SUBMITTING FORM
async function handleSendForm(inputs) {
// TRY UPDATE
try {
// send data to update function
const result = await update(
currentUser._id,
inputs
);
// update currentUser
await dispatchCurrentUser({ type: USER_ACTIONS.SET, payload: result.data.document });
setFlashMsg(result.data?.message);
} catch (error) {
// catch the error
mergeBackendValidation(error.response.status, error.response.data, methods.setError);
}
}
// #################################
// OUTPUT
// #################################
return (
<>
{/* render page title */}
<Helmet><title>[{import.meta.env.VITE_APP_NAME}] Profile</title></Helmet>
<Heading level="1" className="col-span-2">Profile</Heading>
<FormProvider {...methods} >
<form onSubmit={methods.handleSubmit(handleSendForm)}>
<fieldset>
<Input name='name' type='text' title='Name' required={true} autoFocus={true} />
<Input name='username' type='text' title='Username' required={true} />
<Input name='email' type='email' title='eMail' required={true} />
</fieldset>
<fieldset>
<Input name='password' type='password' title='password' placeholder='Leave blank to keep the same' />
<Input name='confirmPassword' type='password' title='confirm password' placeholder='Leave blank to keep the same' />
</fieldset>
<Submit value='update' />
</form>
</FormProvider>
</>
);
}
export default React.memo(Profile);;
\ No newline at end of file
......@@ -38,13 +38,21 @@ export const sitemap = [{
]
},
// LOGOUT
// PROFILE
{
title: 'Logout',
path: '/logout',
element: loadComponent('User/Logout', true, true),
handle: { crumb: () => <Link to="/profile/logout">Logout</Link> }
}
title: 'Profile', path: '/profile', handle: { crumb: () => <Link to="/profile">Profile</Link> },
children: [
{ index: true, element: loadComponent('User/Profile', true, true) },
{ title: 'Logout', path: 'logout', element: loadComponent('User/Logout', true, true), handle: { crumb: () => <Link to="/profile/logout">Logout</Link> } }
]
},
// // LOGOUT
// {
// title: 'Logout',
// path: '/logout',
// element: loadComponent('User/Logout', true, true),
// handle: { crumb: () => <Link to="/profile/logout">Logout</Link> }
// }
]
}]
}, {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment