mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 01:40:15 +01:00
📩 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:
parent
a45b384bbc
commit
bbb9324447
16 changed files with 695 additions and 61 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue