mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-31 19:59:31 -05:00
Merge pull request #136 from NicolaiVdS/email-validation-and-requirement
feat(userprofile): email requirement and validation + import user button overhaul
This commit is contained in:
@@ -14,6 +14,7 @@ import useClickOutside from '../../../hooks/useClickOutside';
|
||||
import { Permission, useUser } from '../../../hooks/useUser';
|
||||
import Transition from '../../Transition';
|
||||
import VersionStatus from '../VersionStatus';
|
||||
import UserWarnings from '../UserWarnings';
|
||||
|
||||
const messages = defineMessages({
|
||||
dashboard: 'Discover',
|
||||
@@ -177,6 +178,10 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="px-2">
|
||||
<UserWarnings onClick={() => setClosed()} />
|
||||
</div>
|
||||
|
||||
{hasPermission(Permission.ADMIN) && (
|
||||
<div className="px-2">
|
||||
<VersionStatus onClick={() => setClosed()} />
|
||||
@@ -236,6 +241,9 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="px-2">
|
||||
<UserWarnings />
|
||||
</div>
|
||||
{hasPermission(Permission.ADMIN) && (
|
||||
<div className="px-2">
|
||||
<VersionStatus />
|
||||
|
||||
66
src/components/Layout/UserWarnings/index.tsx
Normal file
66
src/components/Layout/UserWarnings/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ExclamationIcon } from '@heroicons/react/outline';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useUser } from '../../../hooks/useUser';
|
||||
|
||||
const messages = defineMessages({
|
||||
emailRequired: 'An email address is required.',
|
||||
emailInvalid: 'Email address is invalid.',
|
||||
passwordRequired: 'A password is required.',
|
||||
});
|
||||
|
||||
interface UserWarningsProps {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const UserWarnings: React.FC<UserWarningsProps> = ({ onClick }) => {
|
||||
const intl = useIntl();
|
||||
const { user } = useUser();
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let res = null;
|
||||
|
||||
//check if a user has warnings
|
||||
if (user.warnings.length > 0) {
|
||||
user.warnings.forEach((warning) => {
|
||||
let link = '';
|
||||
let warningText = '';
|
||||
let warningTitle = '';
|
||||
switch (warning) {
|
||||
case 'userEmailRequired':
|
||||
link = '/profile/settings/';
|
||||
warningTitle = 'Profile is incomplete';
|
||||
warningText = intl.formatMessage(messages.emailRequired);
|
||||
}
|
||||
|
||||
res = (
|
||||
<Link href={link}>
|
||||
<a
|
||||
onClick={onClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && onClick) {
|
||||
onClick();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="mx-2 mb-2 flex items-center rounded-lg bg-yellow-500 p-2 text-xs text-white ring-1 ring-gray-700 transition duration-300 hover:bg-yellow-400"
|
||||
>
|
||||
<ExclamationIcon className="h-6 w-6" />
|
||||
<div className="flex min-w-0 flex-1 flex-col truncate px-2 last:pr-0">
|
||||
<span className="font-bold">{warningTitle}</span>
|
||||
<span className="truncate">{warningText}</span>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export default UserWarnings;
|
||||
@@ -50,6 +50,7 @@ const Layout: React.FC = ({ children }) => {
|
||||
<div className="absolute top-0 h-64 w-full bg-gradient-to-bl from-gray-800 to-gray-900">
|
||||
<div className="relative inset-0 h-full w-full bg-gradient-to-t from-gray-900 to-transparent" />
|
||||
</div>
|
||||
|
||||
<Sidebar open={isSidebarOpen} setClosed={() => setSidebarOpen(false)} />
|
||||
|
||||
<div className="relative mb-16 flex w-0 min-w-0 flex-1 flex-col lg:ml-64">
|
||||
|
||||
@@ -16,6 +16,7 @@ const messages = defineMessages({
|
||||
validationSmtpHostRequired: 'You must provide a valid hostname or IP address',
|
||||
validationSmtpPortRequired: 'You must provide a valid port number',
|
||||
agentenabled: 'Enable Agent',
|
||||
userEmailRequired: 'Require user email',
|
||||
emailsender: 'Sender Address',
|
||||
smtpHost: 'SMTP Host',
|
||||
smtpPort: 'SMTP Port',
|
||||
@@ -125,6 +126,7 @@ const NotificationsEmail: React.FC = () => {
|
||||
<Formik
|
||||
initialValues={{
|
||||
enabled: data.enabled,
|
||||
userEmailRequired: data.options.userEmailRequired,
|
||||
emailFrom: data.options.emailFrom,
|
||||
smtpHost: data.options.smtpHost,
|
||||
smtpPort: data.options.smtpPort ?? 587,
|
||||
@@ -148,6 +150,7 @@ const NotificationsEmail: React.FC = () => {
|
||||
await axios.post('/api/v1/settings/notifications/email', {
|
||||
enabled: values.enabled,
|
||||
options: {
|
||||
userEmailRequired: values.userEmailRequired,
|
||||
emailFrom: values.emailFrom,
|
||||
smtpHost: values.smtpHost,
|
||||
smtpPort: Number(values.smtpPort),
|
||||
@@ -241,6 +244,18 @@ const NotificationsEmail: React.FC = () => {
|
||||
<Field type="checkbox" id="enabled" name="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="userEmailRequired" className="checkbox-label">
|
||||
{intl.formatMessage(messages.userEmailRequired)}
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="userEmailRequired"
|
||||
name="userEmailRequired"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="senderName" className="text-label">
|
||||
{intl.formatMessage(messages.senderName)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useUser } from '../../hooks/useUser';
|
||||
import PlexLoginButton from '../PlexLoginButton';
|
||||
|
||||
const messages = defineMessages({
|
||||
welcome: 'Welcome to Overseerr',
|
||||
welcome: 'Welcome to Jellyseerr',
|
||||
signinMessage: 'Get started by signing in with your Plex account',
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MediaServerType } from '../../../server/constants/server';
|
||||
import getConfig from 'next/config';
|
||||
|
||||
const messages = defineMessages({
|
||||
welcome: 'Welcome to Overseerr',
|
||||
welcome: 'Welcome to Jellyseerr',
|
||||
signinMessage: 'Get started by signing in',
|
||||
signinWithJellyfin: 'Use your {mediaServerName} account',
|
||||
signinWithPlex: 'Use your Plex account',
|
||||
|
||||
@@ -9,6 +9,7 @@ import globalMessages from '../../i18n/globalMessages';
|
||||
import Alert from '../Common/Alert';
|
||||
import Modal from '../Common/Modal';
|
||||
import getConfig from 'next/config';
|
||||
import { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
||||
|
||||
interface JellyfinImportProps {
|
||||
onCancel?: () => void;
|
||||
@@ -30,6 +31,7 @@ const messages = defineMessages({
|
||||
const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
onCancel,
|
||||
onComplete,
|
||||
children,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
@@ -49,6 +51,18 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
revalidateOnMount: true,
|
||||
});
|
||||
|
||||
const { data: existingUsers } = useSWR<UserResultsResponse>(
|
||||
`/api/v1/user?take=${children}`
|
||||
);
|
||||
|
||||
data?.forEach((user, pos) => {
|
||||
if (
|
||||
existingUsers?.results.some((data) => data.jellyfinUserId === user.id)
|
||||
) {
|
||||
data?.splice(pos, 1);
|
||||
}
|
||||
});
|
||||
|
||||
const importUsers = async () => {
|
||||
setImporting(true);
|
||||
|
||||
|
||||
@@ -482,7 +482,9 @@ const UserList: React.FC = () => {
|
||||
setShowImportModal(false);
|
||||
revalidate();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{data.pageInfo.results}
|
||||
</JellyfinImportModal>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
|
||||
@@ -121,9 +121,9 @@ const UserGeneralSettings: React.FC = () => {
|
||||
</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
displayName: data?.username,
|
||||
email: data?.email,
|
||||
discordId: data?.discordId,
|
||||
displayName: data?.username ?? '',
|
||||
email: data?.email ?? '',
|
||||
discordId: data?.discordId ?? '',
|
||||
locale: data?.locale,
|
||||
region: data?.region,
|
||||
originalLanguage: data?.originalLanguage,
|
||||
@@ -251,6 +251,9 @@ const UserGeneralSettings: React.FC = () => {
|
||||
<div className="form-row">
|
||||
<label htmlFor="email" className="text-label">
|
||||
{intl.formatMessage(messages.email)}
|
||||
{user?.warnings.find((w) => w === 'userEmailRequired') && (
|
||||
<span className="label-required">*</span>
|
||||
)}
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
@@ -258,7 +261,12 @@ const UserGeneralSettings: React.FC = () => {
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder={user?.email}
|
||||
placeholder="example@domain.com"
|
||||
className={
|
||||
user?.warnings.find((w) => w === 'userEmailRequired')
|
||||
? 'border-2 border-red-400 focus:border-blue-600'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{errors.email && touched.email && (
|
||||
|
||||
@@ -13,6 +13,7 @@ export type { PermissionCheckOptions };
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
warnings: string[];
|
||||
plexUsername?: string;
|
||||
username?: string;
|
||||
displayName: string;
|
||||
|
||||
Reference in New Issue
Block a user