diff --git a/.devops/migrator/src/scripts/api-server-pg/2026.02.12T17.20.25.add_sepia.sql b/.devops/migrator/src/scripts/api-server-pg/2026.02.12T17.20.25.add_sepia.sql new file mode 100644 index 00000000..51c83597 --- /dev/null +++ b/.devops/migrator/src/scripts/api-server-pg/2026.02.12T17.20.25.add_sepia.sql @@ -0,0 +1,5 @@ +ALTER TABLE user_management_service.user_interface_settings +ADD COLUMN moderator_safety_sepia boolean; + +ALTER TABLE user_management_service.org_default_user_interface_settings +ADD COLUMN moderator_safety_sepia boolean NOT NULL DEFAULT false; diff --git a/client/src/graphql/generated.ts b/client/src/graphql/generated.ts index 99eaa93d..b70b712a 100644 --- a/client/src/graphql/generated.ts +++ b/client/src/graphql/generated.ts @@ -2095,6 +2095,7 @@ export type GQLModeratorSafetySettingsInput = { readonly moderatorSafetyBlurLevel: Scalars['Int']; readonly moderatorSafetyGrayscale: Scalars['Boolean']; readonly moderatorSafetyMuteVideo: Scalars['Boolean']; + readonly moderatorSafetySepia: Scalars['Boolean']; }; export type GQLMrtJobEnqueueSourceInfo = { @@ -4473,6 +4474,7 @@ export type GQLUserInterfacePreferences = { readonly moderatorSafetyBlurLevel: Scalars['Int']; readonly moderatorSafetyGrayscale: Scalars['Boolean']; readonly moderatorSafetyMuteVideo: Scalars['Boolean']; + readonly moderatorSafetySepia: Scalars['Boolean']; readonly mrtChartConfigurations: ReadonlyArray; }; @@ -11237,6 +11239,7 @@ export type GQLManualReviewSafetySettingsQuery = { readonly moderatorSafetyMuteVideo: boolean; readonly moderatorSafetyGrayscale: boolean; readonly moderatorSafetyBlurLevel: number; + readonly moderatorSafetySepia: boolean; }; } | null; }; @@ -23426,6 +23429,7 @@ export type GQLPersonalSafetySettingsQuery = { readonly moderatorSafetyMuteVideo: boolean; readonly moderatorSafetyGrayscale: boolean; readonly moderatorSafetyBlurLevel: number; + readonly moderatorSafetySepia: boolean; }; } | null; }; @@ -23617,6 +23621,7 @@ export type GQLOrgDefaultSafetySettingsQuery = { readonly moderatorSafetyMuteVideo: boolean; readonly moderatorSafetyGrayscale: boolean; readonly moderatorSafetyBlurLevel: number; + readonly moderatorSafetySepia: boolean; }; } | null; }; @@ -30034,6 +30039,7 @@ export const GQLManualReviewSafetySettingsDocument = gql` moderatorSafetyMuteVideo moderatorSafetyGrayscale moderatorSafetyBlurLevel + moderatorSafetySepia } } } @@ -35859,6 +35865,7 @@ export const GQLPersonalSafetySettingsDocument = gql` moderatorSafetyMuteVideo moderatorSafetyGrayscale moderatorSafetyBlurLevel + moderatorSafetySepia } } } @@ -36620,6 +36627,7 @@ export const GQLOrgDefaultSafetySettingsDocument = gql` moderatorSafetyMuteVideo moderatorSafetyGrayscale moderatorSafetyBlurLevel + moderatorSafetySepia } } } diff --git a/client/src/webpages/dashboard/mrt/ManualReviewSafetySettings.tsx b/client/src/webpages/dashboard/mrt/ManualReviewSafetySettings.tsx index 4044e57b..1673b97c 100644 --- a/client/src/webpages/dashboard/mrt/ManualReviewSafetySettings.tsx +++ b/client/src/webpages/dashboard/mrt/ManualReviewSafetySettings.tsx @@ -27,6 +27,7 @@ gql` moderatorSafetyMuteVideo moderatorSafetyGrayscale moderatorSafetyBlurLevel + moderatorSafetySepia } } } @@ -47,10 +48,12 @@ export default function ManualReviewSafetySettings() { moderatorSafetyBlurLevel: BlurStrength; moderatorSafetyGrayscale: boolean; moderatorSafetyMuteVideo: boolean; + moderatorSafetySepia: boolean; }>({ moderatorSafetyBlurLevel: 2, moderatorSafetyGrayscale: true, moderatorSafetyMuteVideo: true, + moderatorSafetySepia: true, }); const [notificationApi, notificationContextHolder] = notification.useNotification(); @@ -76,11 +79,13 @@ export default function ManualReviewSafetySettings() { moderatorSafetyMuteVideo, moderatorSafetyGrayscale, moderatorSafetyBlurLevel, + moderatorSafetySepia, } = data.me.interfacePreferences; setSettings({ moderatorSafetyMuteVideo, moderatorSafetyGrayscale, moderatorSafetyBlurLevel: moderatorSafetyBlurLevel as BlurStrength, + moderatorSafetySepia }); }, [data?.me?.interfacePreferences]); @@ -139,6 +144,23 @@ export default function ManualReviewSafetySettings() { +
+
+ + setSettings({ + ...settings, + moderatorSafetySepia: value, + }) + } + checked={settings.moderatorSafetySepia} + /> + +
+
+
diff --git a/client/src/webpages/dashboard/mrt/manual_review_job/IframeContentDisplayComponent.tsx b/client/src/webpages/dashboard/mrt/manual_review_job/IframeContentDisplayComponent.tsx index 917e226f..7e20e520 100644 --- a/client/src/webpages/dashboard/mrt/manual_review_job/IframeContentDisplayComponent.tsx +++ b/client/src/webpages/dashboard/mrt/manual_review_job/IframeContentDisplayComponent.tsx @@ -28,18 +28,21 @@ export default function IframeContentDisplayComponent(props: { blur: boolean; grayscale: boolean; shouldTranslate: boolean; + sepia: boolean; }>({ blur: true, grayscale: false, shouldTranslate: false, + sepia: false, }); - const { blur, grayscale, shouldTranslate } = state; + const { blur, grayscale, shouldTranslate, sepia } = state; const { loading, data } = useGQLPersonalSafetySettingsQuery(); const { moderatorSafetyBlurLevel = 2 as BlurStrength, moderatorSafetyGrayscale = true, + moderatorSafetySepia = true, } = data?.me?.interfacePreferences ?? {}; useEffect(() => { @@ -47,8 +50,9 @@ export default function IframeContentDisplayComponent(props: { blur: moderatorSafetyBlurLevel !== 0, grayscale: moderatorSafetyGrayscale, shouldTranslate: false, + sepia: moderatorSafetySepia, }); - }, [moderatorSafetyBlurLevel, moderatorSafetyGrayscale]); + }, [moderatorSafetyBlurLevel, moderatorSafetyGrayscale, moderatorSafetySepia]); useEffect(() => { const handleMessage = (event: MessageEvent) => { @@ -75,6 +79,7 @@ export default function IframeContentDisplayComponent(props: { blur: blur ? moderatorSafetyBlurLevel : 0, grayscale, shouldTranslate, + sepia, }, contentProxyUrl, ); @@ -91,6 +96,7 @@ export default function IframeContentDisplayComponent(props: { blur: blur ? moderatorSafetyBlurLevel : 0, grayscale, shouldTranslate, + sepia, }, contentProxyUrl, ); @@ -114,6 +120,7 @@ export default function IframeContentDisplayComponent(props: { shouldTranslate, contentProxyUrl, isIframeLoading, + sepia, ], ); diff --git a/client/src/webpages/dashboard/mrt/manual_review_job/ManualReviewJobContentBlurableImage.tsx b/client/src/webpages/dashboard/mrt/manual_review_job/ManualReviewJobContentBlurableImage.tsx index 973a9db1..b1f3893a 100644 --- a/client/src/webpages/dashboard/mrt/manual_review_job/ManualReviewJobContentBlurableImage.tsx +++ b/client/src/webpages/dashboard/mrt/manual_review_job/ManualReviewJobContentBlurableImage.tsx @@ -14,6 +14,7 @@ export default function ManualReviewJobContentBlurableImage(props: { blurStrength?: BlurStrength; grayscale?: boolean; disableZoom?: boolean; + sepia?: boolean }; onError?: () => void; }) { @@ -25,6 +26,7 @@ export default function ManualReviewJobContentBlurableImage(props: { blurStrength = 0, grayscale = false, disableZoom = false, + sepia = false, } = options ?? {}; const [clicked, setClicked] = useState(false); @@ -49,7 +51,7 @@ export default function ManualReviewJobContentBlurableImage(props: { setClicked(true)} diff --git a/client/src/webpages/dashboard/mrt/manual_review_job/v2/ManualReviewJobFieldsComponent.tsx b/client/src/webpages/dashboard/mrt/manual_review_job/v2/ManualReviewJobFieldsComponent.tsx index 68340468..bbe0fe06 100644 --- a/client/src/webpages/dashboard/mrt/manual_review_job/v2/ManualReviewJobFieldsComponent.tsx +++ b/client/src/webpages/dashboard/mrt/manual_review_job/v2/ManualReviewJobFieldsComponent.tsx @@ -197,6 +197,7 @@ function TableRowComponent(props: { ? (safetySettings.moderatorSafetyBlurLevel as BlurStrength) : (2 as const), grayscale: safetySettings?.moderatorSafetyGrayscale ?? false, + sepia: safetySettings?.moderatorSafetySepia ?? false, }} /> {label ?
{label}
: null} diff --git a/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECMediaViewer.tsx b/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECMediaViewer.tsx index 592a2bf5..90cae1c6 100644 --- a/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECMediaViewer.tsx +++ b/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECMediaViewer.tsx @@ -84,10 +84,12 @@ export default function NCMECMediaViewer(props: { moderatorSafetyBlurLevel: BlurStrength; moderatorSafetyGrayscale: boolean; moderatorSafetyMuteVideo: boolean; + moderatorSafetySepia: boolean; }>({ moderatorSafetyBlurLevel: 2, moderatorSafetyGrayscale: true, moderatorSafetyMuteVideo: true, + moderatorSafetySepia: true, }); const { loading, error, data } = useGQLPersonalSafetySettingsQuery(); @@ -170,7 +172,8 @@ export default function NCMECMediaViewer(props: { shouldBlur ? BLUR_LEVELS[safetySettings.moderatorSafetyBlurLevel] : 0 - } ${safetySettings.moderatorSafetyGrayscale ? 'grayscale' : ''}`} + } ${safetySettings.moderatorSafetyGrayscale ? 'grayscale' : ''} + ${safetySettings.moderatorSafetySepia ? 'sepia' : ''} `} alt="" src={mediaId.urlInfo.url} onError={(img) => { @@ -190,7 +193,9 @@ export default function NCMECMediaViewer(props: { isInInspectedView ? 'w-auto' : 'object-scale-down grow max-w-64 max-h-48' - } ${safetySettings.moderatorSafetyGrayscale ? 'grayscale' : ''}`} + } ${safetySettings.moderatorSafetyGrayscale ? 'grayscale' : ''} ${ + safetySettings.moderatorSafetySepia ? 'sepia' : '' + } `} url={mediaId.urlInfo.url} options={{ shouldBlur: @@ -267,6 +272,20 @@ export default function NCMECMediaViewer(props: { />
+
+ + setSafetySettings({ + ...safetySettings, + moderatorSafetySepia: isSepia, + }) + } + checked={safetySettings.moderatorSafetySepia} + /> + +
)} {grayOutThumbnail || isInInspectedView ? null : ( diff --git a/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx b/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx index 13f11a8c..276aee7f 100644 --- a/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx +++ b/client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx @@ -161,7 +161,9 @@ export function getMatchedBanksForMediaUrl( | { value: { url?: string; matchedBanks?: string[] }; type: string }[] | undefined; if (valueOrValues === undefined) continue; - const values = Array.isArray(valueOrValues) ? valueOrValues : [valueOrValues]; + const values = Array.isArray(valueOrValues) + ? valueOrValues + : [valueOrValues]; for (const tagged of values) { const v = tagged.value; if (v?.url === mediaUrl) { @@ -397,6 +399,7 @@ export default function NCMECReviewUser( moderatorSafetyBlurLevel, moderatorSafetyGrayscale, moderatorSafetyMuteVideo, + moderatorSafetySepia, } = data?.me?.interfacePreferences ?? {}; // Compares two pieces of media to determine whether they're the same, based @@ -582,14 +585,17 @@ export default function NCMECReviewUser(
{!loading && moderatorSafetyBlurLevel != null && - moderatorSafetyGrayscale != null ? ( + moderatorSafetyGrayscale != null && + moderatorSafetySepia != null ? ( media.urlInfo.mediaType === 'IMAGE' ? ( @@ -598,7 +604,8 @@ export default function NCMECReviewUser( url={media.urlInfo.url} className={`object-scale-down w-64 h-48 rounded-2xl ${ moderatorSafetyGrayscale ? 'grayscale' : '' - }`} + } + ${moderatorSafetySepia ? 'sepia' : ''} `} options={{ shouldBlur: !unblurAllMediaInConfirmation && diff --git a/client/src/webpages/settings/AccountSettings.tsx b/client/src/webpages/settings/AccountSettings.tsx index 08c77a82..2ef1a34d 100644 --- a/client/src/webpages/settings/AccountSettings.tsx +++ b/client/src/webpages/settings/AccountSettings.tsx @@ -60,6 +60,7 @@ gql` moderatorSafetyMuteVideo moderatorSafetyGrayscale moderatorSafetyBlurLevel + moderatorSafetySepia } } } @@ -141,6 +142,7 @@ type SafetySettings = { moderatorSafetyBlurLevel: BlurStrength; moderatorSafetyGrayscale: boolean; moderatorSafetyMuteVideo: boolean; + moderatorSafetySepia: boolean; }; export default function AccountSettings() { @@ -149,6 +151,7 @@ export default function AccountSettings() { moderatorSafetyBlurLevel: 2, moderatorSafetyGrayscale: true, moderatorSafetyMuteVideo: true, + moderatorSafetySepia: true, }); const [dialogConfig, setDialogConfig] = useState({ @@ -179,7 +182,9 @@ export default function AccountSettings() { }); }, onCompleted: (data) => { - if (data.changePassword.__typename === 'ChangePasswordSuccessResponse') { + if ( + data.changePassword.__typename === 'ChangePasswordSuccessResponse' + ) { toast.success('Password Changed', { description: 'Your password has been successfully updated.', }); @@ -226,12 +231,14 @@ export default function AccountSettings() { moderatorSafetyMuteVideo, moderatorSafetyGrayscale, moderatorSafetyBlurLevel, + moderatorSafetySepia, } = safetySettingsData.me.interfacePreferences; setSafetySettings({ moderatorSafetyMuteVideo, moderatorSafetyGrayscale, moderatorSafetyBlurLevel: moderatorSafetyBlurLevel as BlurStrength, + moderatorSafetySepia, }); }, [safetySettingsData?.me?.interfacePreferences]); @@ -305,6 +312,15 @@ export default function AccountSettings() { [], ); + const setSepiaPreference = useCallback( + (moderatorSafetySepia: boolean): void => + setSafetySettings((prevSettings) => ({ + ...prevSettings, + moderatorSafetySepia, + })), + [], + ); + const handleLastNameChange = useCallback< React.ChangeEventHandler >((e) => setLastName(e.target.value), []); @@ -496,7 +512,9 @@ export default function AccountSettings() {
+
+ + +
+
diff --git a/client/src/webpages/settings/OrgSafetySettings.tsx b/client/src/webpages/settings/OrgSafetySettings.tsx index 3b9a032f..f8435c20 100644 --- a/client/src/webpages/settings/OrgSafetySettings.tsx +++ b/client/src/webpages/settings/OrgSafetySettings.tsx @@ -27,6 +27,7 @@ gql` moderatorSafetyMuteVideo moderatorSafetyGrayscale moderatorSafetyBlurLevel + moderatorSafetySepia } } } @@ -46,6 +47,7 @@ type SafetySettings = { moderatorSafetyBlurLevel: BlurStrength; moderatorSafetyGrayscale: boolean; moderatorSafetyMuteVideo: boolean; + moderatorSafetySepia: boolean; }; export default function ManualReviewSafetySettings() { @@ -53,6 +55,7 @@ export default function ManualReviewSafetySettings() { moderatorSafetyBlurLevel: 2, moderatorSafetyGrayscale: true, moderatorSafetyMuteVideo: true, + moderatorSafetySepia: true, }); const { loading, error, data } = useGQLOrgDefaultSafetySettingsQuery(); @@ -77,11 +80,13 @@ export default function ManualReviewSafetySettings() { moderatorSafetyMuteVideo, moderatorSafetyGrayscale, moderatorSafetyBlurLevel, + moderatorSafetySepia, } = data.myOrg.defaultInterfacePreferences; setSafetySettings({ moderatorSafetyMuteVideo, moderatorSafetyGrayscale, moderatorSafetyBlurLevel: moderatorSafetyBlurLevel as BlurStrength, + moderatorSafetySepia, }); }, [data?.myOrg?.defaultInterfacePreferences]); @@ -104,11 +109,11 @@ export default function ManualReviewSafetySettings() { Standardize Your Organization's Safety Settings - Configure your organization's default safety settings. When a new - employee joins your team and needs to use Coop, these settings - will be applied by default to maintain their safety and well-being. - If an employee wants to override these settings, they can do so in - their personal Safety Settings. + Configure your organization's default safety settings. When a new + employee joins your team and needs to use Coop, these settings will be + applied by default to maintain their safety and well-being. If an + employee wants to override these settings, they can do so in their + personal Safety Settings.
@@ -144,6 +149,19 @@ export default function ManualReviewSafetySettings() { />
+
+ + + setSafetySettings({ + ...safetySettings, + moderatorSafetySepia: value, + }) + } + /> +
+