Skip to content

Commit

Permalink
add violation detection
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-torabiv committed Sep 20, 2024
1 parent 10c43d9 commit d96408d
Show file tree
Hide file tree
Showing 2 changed files with 349 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ function TcCommunityPlatforms() {
const [activeTab, setActiveTab] = useState<number>(0);
const [hivemindManageIsLoading, setHivemindManageIsLoading] =
useState<boolean>(false);
const [
violationDetectionManageIsLoading,
setViolationDetectionManageIsLoading,
] = useState<boolean>(false);
const router = useRouter();

const communityId =
Expand Down Expand Up @@ -119,6 +123,31 @@ function TcCommunityPlatforms() {
}
};

const handleViolationDetectionModule = async () => {
try {
setViolationDetectionManageIsLoading(true);
const hivemindModules = await retrieveModules({
community: communityId,
name: 'violationDetection',
});

if (hivemindModules.results.length > 0) {
router.push('/community-settings/violation-detection');
} else {
await createModule({
name: 'violationDetection',
community: communityId,
});
router.push('/community-settings/violation-detection');
}
setViolationDetectionManageIsLoading(false);
} catch (error) {
console.log('error', error);
} finally {
setViolationDetectionManageIsLoading(false);
}
};

const handleUpdateCommunityPlatform = async () => {
await fetchPlatformsByType();
};
Expand Down Expand Up @@ -246,25 +275,50 @@ function TcCommunityPlatforms() {
/>
</div>

<TcCard
className='min-h-[6rem] max-w-[10rem]'
children={
<div className='flex flex-col items-center justify-center space-y-2 py-4'>
<TcText text='Hivemind' variant='subtitle1' fontWeight='bold' />
<TcButton
text={
hivemindManageIsLoading ? (
<CircularProgress size={20} />
) : (
'Manage'
)
}
variant='text'
onClick={() => handleManageHivemindModule()}
/>
</div>
}
/>
<div className='flex flex-col space-y-4 md:flex-row md:space-y-0 md:space-x-4'>
<TcCard
className='max-h-[6rem] min-h-[6rem] min-w-[10rem] max-w-[10rem] flex-grow'
children={
<div className='flex flex-col items-center justify-center space-y-2 py-4'>
<TcText text='Hivemind' variant='subtitle1' fontWeight='bold' />
<TcButton
text={
hivemindManageIsLoading ? (
<CircularProgress size={20} />
) : (
'Manage'
)
}
variant='text'
onClick={() => handleManageHivemindModule()}
/>
</div>
}
/>
<TcCard
className='max-h-[6rem] min-h-[6rem] min-w-[10rem] max-w-[10rem] flex-grow'
children={
<div className='flex flex-col items-center justify-center space-y-2 py-4'>
<TcText
text='Violation Detection'
variant='subtitle1'
fontWeight='bold'
/>
<TcButton
text={
violationDetectionManageIsLoading ? (
<CircularProgress size={20} />
) : (
'Manage'
)
}
variant='text'
onClick={() => handleViolationDetectionModule()}
/>
</div>
}
/>
</div>
</div>
</div>
);
Expand Down
276 changes: 276 additions & 0 deletions src/pages/community-settings/violation-detection/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/* eslint-disable react/jsx-key */
import {
Alert,
AlertTitle,
Autocomplete,
Chip,
CircularProgress,
FormControl,
FormControlLabel,
FormHelperText,
FormLabel,
Switch,
TextField,
Typography,
} from '@mui/material';
import router from 'next/router';
import React, { useEffect, useState } from 'react';

import TcButton from '@/components/shared/TcButton';

import useAppStore from '@/store/useStore';

import { useSnackbar } from '@/context/SnackbarContext';
import { useToken } from '@/context/TokenContext';
import { StorageService } from '@/services/StorageService';
import { IDiscordModifiedCommunity, IPlatformProps } from '@/utils/interfaces';

import SEO from '../../../components/global/SEO';
import TcBoxContainer from '../../../components/shared/TcBox/TcBoxContainer';
import TcBreadcrumbs from '../../../components/shared/TcBreadcrumbs';
import { defaultLayout } from '../../../layouts/defaultLayout';
import { withRoles } from '../../../utils/withRoles';

function Index() {
const { retrieveModules, patchModule } = useAppStore();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [emails, setEmails] = useState<string[]>([]);
const [activePlatform, setActivePlatform] = useState<IPlatformProps | null>(
null
);
const [violationModules, setViolationModules] = useState<any[]>([]);
const [isViolationDetectionOn, setIsViolationDetectionOn] =
useState<boolean>(false);
const [emailError, setEmailError] = useState<string | null>(null);

const { community } = useToken();
const { showMessage } = useSnackbar();

const fetchDiscourseViolation = async () => {
const communityId =
StorageService.readLocalStorage<IDiscordModifiedCommunity>(
'community'
)?.id;

const discoursePlatform = community?.platforms.find(
(platform) =>
platform.name === 'discourse' && platform.disconnectedAt === null
) as unknown as IPlatformProps;

setActivePlatform(discoursePlatform);

if (communityId) {
const { results } = await retrieveModules({
community: communityId,
name: 'violationDetection',
});

setViolationModules(results);

if (results.length > 0) {
setEmails(
results[0].options.platforms[0].metadata.selectedEmails || []
);

const hasActiveModerators =
results[0].options.platforms[0].metadata.selectedEmails.length > 0;
setIsViolationDetectionOn(hasActiveModerators ? true : false);
} else {
setEmails([]);
setIsViolationDetectionOn(false);
}
}
};

useEffect(() => {
fetchDiscourseViolation();
}, []);

const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

const handleEmailChange = (event: any, newValue: string[] | null) => {
if (newValue) {
const invalidEmail = newValue.find((email) => !validateEmail(email));
if (invalidEmail) {
setEmailError(`Invalid email: ${invalidEmail}`);
} else {
setEmailError(null);
setEmails(newValue);
}
}
};

const handleDiscourseViolation = async () => {
if (!activePlatform) return;

const updatedEmails = isViolationDetectionOn ? emails : [];

const payload = {
platforms: [
{
platform: activePlatform.id,
name: activePlatform.name,
metadata: {
selectedEmails: updatedEmails,
fromDate: activePlatform.metadata.period,
toDate: null,
selectedResources: [],
},
},
],
};

try {
setIsLoading(true);
const data = await patchModule({
moduleId: violationModules[0].id,
payload,
});

if (data) {
router.push('/community-settings');
showMessage(
'Violation detection settings updated successfully',
'success'
);
}
} catch (error) {
console.error('Error updating violation module:', error);
} finally {
setIsLoading(false);
}
};

return (
<>
<SEO titleTemplate='Violation detection Settings' />
<div className='container flex flex-col space-y-3 px-4 py-4 md:px-12'>
<TcBreadcrumbs
items={[
{
label: 'Community Settings',
path: '/community-settings',
},
{
label: 'Violation detection Settings',
path: '/community-settings/violation-detection',
},
]}
/>
<TcBoxContainer
contentContainerChildren={
<div className='space-y-4'>
<div className='space-y-4 px-4 pt-4 pb-[1rem] md:px-10'>
<Typography variant='h6' fontWeight='bold'>
Violation Detection Settings
</Typography>
<Typography variant='body2'>
Configure the settings for the violation detection system.
This system will automatically detect and take action on any
violations of the community guidelines. You can configure the
system to automatically take action on violations or to notify
moderators for manual review.
</Typography>
<Alert severity='info' className='my-2 rounded-sm'>
<AlertTitle>Module working for discourse only</AlertTitle>
This module is currently only available for Discourse.
</Alert>

<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={isViolationDetectionOn}
onChange={(event) =>
setIsViolationDetectionOn(event.target.checked)
}
/>
}
label='Violation Detection'
/>
<FormHelperText>
Activate/Deactivate the violation detection module.
</FormHelperText>
</FormControl>

{isViolationDetectionOn && (
<FormControl fullWidth error={!!emailError}>
<FormLabel>Moderator Emails</FormLabel>
<Autocomplete
multiple
freeSolo
value={emails}
onChange={handleEmailChange}
renderInput={(params) => (
<TextField
{...params}
variant='filled'
label='Moderator Emails'
placeholder='Enter Moderator Emails'
error={!!emailError}
helperText={
emailError || 'Enter valid moderator emails.'
}
/>
)}
options={[]}
renderTags={(value, getTagProps) => {
return value.map((option, index) => (
<Chip
label={option}
{...getTagProps({ index })}
variant='outlined'
size='small'
sx={{
borderRadius: '4px',
borderColor: '#D1D1D1',
backgroundColor: 'white',
color: 'black',
}}
/>
));
}}
/>
<FormHelperText>
Enter the email addresses of the moderators who should be
notified when a violation is detected.
</FormHelperText>
</FormControl>
)}

<div className='mt-6 flex flex-col items-center justify-between space-y-3 md:flex-row md:space-y-0'>
<TcButton
text='Cancel'
variant='outlined'
className='md:w-1/4'
onClick={() => router.push('/community-settings')}
/>
<TcButton
text={
isLoading ? (
<CircularProgress size={20} color='inherit' />
) : (
'Save Changes'
)
}
disabled={isLoading || !!emailError}
variant='contained'
className='md:w-1/4'
onClick={handleDiscourseViolation}
/>
</div>
</div>
</div>
}
/>
</div>
</>
);
}

Index.pageLayout = defaultLayout;

export default withRoles(Index, ['admin']);

0 comments on commit d96408d

Please sign in to comment.