📩 feat: invite user (#3012)

* feat: basic invite-user script

* feat: add invite user functionality and registration validation middleware

* fix: invite user fixes

* refactor: consolidate direct model access to a central place of functions

* style(Registration): add spinner to continue button

* refactor: import ordrer

* feat: improve invite user script and error handling

* fix: merge conflict

* refactor: remove `console.log` and use `logger`

* fix: token operation and checkinvite issues

* bring back comment and remove console log

* fix: return invalid token when token is not found

* fix: getInvite fix

* refactor: Update Token.js to use async/await syntax for update and delete operations

* feat: Refactor Token.js to use async/await syntax for createToken and findToken functions

* refactor(inviteUser): define functions outside of module.exports

* Update AuthService.js

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2024-08-18 06:23:38 +02:00 committed by GitHub
parent a45b384bbc
commit bbb9324447
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 695 additions and 61 deletions

View file

@ -1,10 +1,11 @@
import { useForm } from 'react-hook-form';
import React, { useState, useEffect } from 'react';
import { useNavigate, useOutletContext } from 'react-router-dom';
import React, { useState } from 'react';
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
import type { TRegisterUser, TError } from 'librechat-data-provider';
import type { TLoginLayoutContext } from '~/common';
import { ErrorMessage } from './ErrorMessage';
import { Spinner } from '~/components/svg';
import { useLocalize } from '~/hooks';
const Registration: React.FC = () => {
@ -21,10 +22,19 @@ const Registration: React.FC = () => {
const password = watch('password');
const [errorMessage, setErrorMessage] = useState<string>('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [countdown, setCountdown] = useState<number>(3);
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const token = queryParams.get('token');
const registerUser = useRegisterUserMutation({
onMutate: () => {
setIsSubmitting(true);
},
onSuccess: () => {
setIsSubmitting(false);
setCountdown(3);
const timer = setInterval(() => {
setCountdown((prevCountdown) => {
@ -39,18 +49,13 @@ const Registration: React.FC = () => {
}, 1000);
},
onError: (error: unknown) => {
setIsSubmitting(false);
if ((error as TError).response?.data?.message) {
setErrorMessage((error as TError).response?.data?.message ?? '');
}
},
});
useEffect(() => {
if (startupConfig?.registrationEnabled === false) {
navigate('/login');
}
}, [startupConfig, navigate]);
const renderInput = (id: string, label: string, type: string, validation: object) => (
<div className="mb-2">
<div className="relative">
@ -110,7 +115,9 @@ const Registration: React.FC = () => {
className="mt-6"
aria-label="Registration form"
method="POST"
onSubmit={handleSubmit((data: TRegisterUser) => registerUser.mutate(data))}
onSubmit={handleSubmit((data: TRegisterUser) =>
registerUser.mutate({ ...data, token: token ?? undefined }),
)}
>
{renderInput('name', 'com_auth_full_name', 'text', {
required: localize('com_auth_name_required'),
@ -170,7 +177,7 @@ const Registration: React.FC = () => {
aria-label="Submit registration"
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-550 focus:bg-green-550 focus:outline-none disabled:cursor-not-allowed disabled:hover:bg-green-500"
>
{localize('com_auth_continue')}
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
</button>
</div>
</form>

View file

@ -33,12 +33,15 @@ const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) =>
// Define Tailwind CSS classes based on state
const baseStyles = 'border border-solid border-gray-300 dark:border-gray-600 transition-colors';
const pressedStyles = 'bg-blue-200 border-blue-200 dark:bg-blue-900 dark:border-blue-600';
const hoverStyles = 'bg-gray-100 dark:bg-gray-700';
let dynamicStyles = '';
return `${baseStyles} ${
isPressed && activeButton === id ? pressedStyles : isHovered ? hoverStyles : ''
}`;
if (isPressed && activeButton === id) {
dynamicStyles = 'bg-blue-200 border-blue-200 dark:bg-blue-900 dark:border-blue-600';
} else if (isHovered) {
dynamicStyles = 'bg-gray-100 dark:bg-gray-700';
}
return `${baseStyles} ${dynamicStyles}`;
};
return (