feat(frontend): remove Pro license gates from admin and settings UI

Strip ProChip badges, ProDialog upsell modals, and pro-conditional
click interception across admin settings, group editing, storage
policy selection, custom props, email templates, audit events, and
navigation. Feature UI now renders and is reachable without a license.

Also fixes a latent upstream ReferenceError: PageNavigation used
undeclared proOpen/setProOpen state, which would crash on click of
any Pro-gated nav item. "Open user files" in the user dialog now
navigates to the admin file list filtered by owner instead of
opening an upsell dialog.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3579/head
Tomas Dvorak 2 weeks ago
parent 88b1910d99
commit ed2dd60263

@ -1,141 +0,0 @@
import {
Alert,
AlertTitle,
Button,
DialogContent,
List,
ListItem,
ListItemIcon,
ListItemText,
Typography,
styled,
} from "@mui/material";
import dayjs from "dayjs";
import { useCallback, useMemo } from "react";
import { Trans, useTranslation } from "react-i18next";
import DraggableDialog, { StyledDialogActions } from "../../Dialogs/DraggableDialog";
import CheckmarkCircleFilled from "../../Icons/CheckmarkCircleFilled";
import Gift from "../../Icons/Gift";
import { Code } from "../../Common/Code.tsx";
export interface ProDialogProps {
open: boolean;
onClose: () => void;
}
const features = [
"shareLinkCollabration",
"filePermission",
"multipleStoragePolicy",
"auditAndActivity",
"vasService",
"sso",
"more",
];
const StyledButton = styled(Button)(({ theme }) => ({
background: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.primary.light} 90%)`,
color: theme.palette.primary.contrastText,
"&:hover": {
background: `linear-gradient(45deg, ${theme.palette.primary.dark} 30%, ${theme.palette.primary.main} 90%)`,
},
transition: "all 300ms cubic-bezier(0.4, 0, 0.2, 1) !important",
}));
// TODO: fetch from cloudreve.org
const currentPromotion = {
code: "LX8YL9QP0",
discount: 15,
start: "2026-03-14T02:00:00Z",
end: "2026-06-14T02:00:00Z",
};
const ProDialog = ({ open, onClose }: ProDialogProps) => {
const { t } = useTranslation("dashboard");
const openMore = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
window.open("https://cloudreve.org/pro", "_blank");
}, []);
const showPromotion = useMemo(() => {
const now = dayjs();
return now >= dayjs(currentPromotion.start) && now <= dayjs(currentPromotion.end);
}, []);
return (
<DraggableDialog
title={t("pro.title")}
dialogProps={{
open,
onClose,
maxWidth: "sm",
fullWidth: true,
}}
>
<DialogContent>
<Typography variant="body1" color="text.secondary">
{t("pro.description")}
</Typography>
<Typography variant="body1" fontWeight={600} sx={{ mt: 2 }}>
{t("pro.proInclude")}
</Typography>
<List dense>
{features.map((feature) => (
<ListItem key={feature}>
<ListItemIcon
sx={{
minWidth: "36px",
}}
>
<CheckmarkCircleFilled color="primary" />
</ListItemIcon>
<ListItemText
slotProps={{
primary: {
sx: {},
variant: "body1",
},
}}
>
{t(`pro.${feature}`)}
</ListItemText>
</ListItem>
))}
</List>
{showPromotion && (
<Alert
iconMapping={{
info: <Gift fontSize="inherit" />,
}}
severity="info"
sx={{ mt: 2 }}
>
<AlertTitle>{t("pro.promotionTitle")}</AlertTitle>
<Trans
i18nKey="dashboard:pro.promotion"
values={{
code: currentPromotion.code,
discount: currentPromotion.discount,
}}
components={[<Code />, <Typography component={"span"} fontWeight={600} />]}
/>
</Alert>
)}
</DialogContent>
<StyledDialogActions
sx={{
justifyContent: "flex-end",
}}
>
<Button variant="outlined" color="primary" onClick={onClose}>
{t("pro.later")}
</Button>
<StyledButton onClick={openMore} variant="contained" color="primary">
{t("pro.learnMore")}
</StyledButton>
</StyledDialogActions>
</DraggableDialog>
);
};
export default ProDialog;

@ -141,7 +141,7 @@ const FileForm = () => {
)}
{fileParentLoading && <Skeleton variant="text" width={100} height={40} />}
</SettingForm>
<SettingForm title={t("file.primaryStoragePolicy")} noContainer lgWidth={3} pro>
<SettingForm title={t("file.primaryStoragePolicy")} noContainer lgWidth={3}>
<SinglePolicySelectionInput simplified value={values.storage_policy_files ?? 0} onChange={() => {}} />
</SettingForm>
<SettingForm title={t("file.metadata")} noContainer lgWidth={6}>

@ -19,8 +19,6 @@ import { CustomProps, CustomPropsType } from "../../../../api/explorer";
import { NoWrapCell, SecondaryButton, StyledTableContainerPaper } from "../../../Common/StyledComponents";
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu";
import Add from "../../../Icons/Add";
import { ProChip } from "../../../Pages/Setting/SettingForm";
import ProDialog from "../../Common/ProDialog";
import { SettingContext } from "../../Settings/SettingWrapper";
import DraggableCustomPropsRow, { FieldTypes } from "./DraggableCustomPropsRow";
import EditPropsDialog from "./EditPropsDialog";
@ -32,7 +30,6 @@ const CustomPropsSetting = () => {
const [open, setOpen] = useState(false);
const [isNew, setIsNew] = useState(false);
const [editProps, setEditProps] = useState<CustomProps | undefined>(undefined);
const [proOpen, setProOpen] = useState(false);
const newPropsPopupState = usePopupState({
variant: "popover",
popupId: "newProp",
@ -117,7 +114,6 @@ const CustomPropsSetting = () => {
return (
<Box component={"form"} ref={formRef} onSubmit={(e) => e.preventDefault()}>
<Stack spacing={1}>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
<SecondaryButton variant="contained" startIcon={<Add />} {...bindTrigger(newPropsPopupState)}>
{t("customProps.add")}
@ -138,12 +134,11 @@ const CustomPropsSetting = () => {
const fieldType = FieldTypes[type];
const Icon = fieldType.icon;
return (
<SquareMenuItem dense key={index} onClick={() => (fieldType.pro ? setProOpen(true) : onNewProp(type))}>
<SquareMenuItem dense key={index} onClick={() => onNewProp(type)}>
<ListItemIcon>
<Icon />
</ListItemIcon>
{t(fieldType.title)}
{fieldType.pro && <ProChip label="Pro" color="primary" size="small" />}
</SquareMenuItem>
);
})}

@ -45,7 +45,6 @@ export interface FieldTypeProps {
maxDes?: string;
maxRequired?: boolean;
showOptions?: boolean;
pro?: boolean;
}
export const FieldTypes: Record<CustomPropsType, FieldTypeProps> = {
@ -82,7 +81,6 @@ export const FieldTypes: Record<CustomPropsType, FieldTypeProps> = {
[CustomPropsType.user]: {
title: "customProps.user",
icon: PersonOutlined,
pro: true,
},
[CustomPropsType.link]: {
title: "customProps.link",

@ -7,7 +7,7 @@ import Boolset from "../../../../util/boolset";
import SizeInput from "../../../Common/SizeInput";
import { DenseFilledTextField } from "../../../Common/StyledComponents";
import InPrivate from "../../../Icons/InPrivate";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm";
import SettingForm from "../../../Pages/Setting/SettingForm";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings";
import { AnonymousGroupID } from "../GroupRow";
import { GroupSettingContext } from "./GroupSettingWrapper";
@ -81,8 +81,7 @@ const BasicInfoSection = () => {
<SettingForm title={t("group.availablePolicies")} lgWidth={5}>
<PolicySelectionInput value={values.edges.storage_policies?.id ?? 0} onChange={onPolicyChange} />
<NoMarginHelperText>{t("group.availablePoliciesDes")}</NoMarginHelperText>
<NoMarginHelperText>
<ProChip size="small" label="Pro" sx={{ ml: 0 }} /> {t("group.availablePolicyDesPro")}
<NoMarginHelperText> {t("group.availablePolicyDesPro")}
</NoMarginHelperText>
</SettingForm>
<SettingForm title={t("group.initialStorageQuota")} lgWidth={5}>

@ -3,7 +3,7 @@ import { useContext } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import SharesInput from "../../Common/SharesInput";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm";
import SettingForm from "../../../Pages/Setting/SettingForm";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings";
import { AnonymousGroupID } from "../GroupRow";
import { GroupSettingContext } from "./GroupSettingWrapper";
@ -19,10 +19,10 @@ const DefaultPinnedSection = () => {
return (
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("group.defaultPinned")} <ProChip label="Pro" color="primary" size="small" />
{t("group.defaultPinned")}
</Typography>
<SettingSectionContent>
<SettingForm lgWidth={5} pro>
<SettingForm lgWidth={5}>
<FormControl fullWidth>
<SharesInput />
<NoMarginHelperText>

@ -18,8 +18,7 @@ import { GroupPermission } from "../../../../api/user";
import Boolset from "../../../../util/boolset";
import SizeInput from "../../../Common/SizeInput";
import { DenseFilledTextField } from "../../../Common/StyledComponents";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm";
import ProDialog from "../../Common/ProDialog";
import SettingForm from "../../../Pages/Setting/SettingForm";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings";
import { AnonymousGroupID } from "../GroupRow";
import { GroupSettingContext } from "./GroupSettingWrapper";
@ -30,7 +29,6 @@ const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor
const FileManagementSection = () => {
const { t } = useTranslation("dashboard");
const { values, setGroup } = useContext(GroupSettingContext);
const [proOpen, setProOpen] = useState(false);
const theme = useTheme();
const [editedConfig, setEditedConfig] = useState("");
@ -154,14 +152,8 @@ const FileManagementSection = () => {
[setGroup],
);
const onProClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
setProOpen(true);
}, []);
return (
<SettingSection>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Typography variant="h6" gutterBottom>
{t("group.fileManagement")}
</Typography>
@ -196,13 +188,12 @@ const FileManagementSection = () => {
</SettingForm>
</Collapse>
<SettingForm lgWidth={5}>
<FormControl fullWidth onClick={onProClick}>
<FormControl fullWidth>
<FormControlLabel
control={<Switch checked={false} />}
label={
<Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.migratePolicy")}
<ProChip size="small" label="Pro" />
</Box>
}
/>
@ -323,20 +314,19 @@ const FileManagementSection = () => {
<NoMarginHelperText>{t("group.advanceDeleteDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("group.allowedNodes")} lgWidth={5} pro>
<SettingForm title={t("group.allowedNodes")} lgWidth={5}>
<FormControl fullWidth>
<MultipleNodeSelectionInput />
<NoMarginHelperText>{t("group.allowedNodesDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm lgWidth={5}>
<FormControl fullWidth onClick={onProClick}>
<FormControl fullWidth>
<FormControlLabel
control={<Switch checked={false} />}
label={
<Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.allowSelectNode")}
<ProChip size="small" label="Pro" />
</Box>
}
/>

@ -1,11 +1,10 @@
import { Box, FormControl, FormControlLabel, Switch, Typography } from "@mui/material";
import { useCallback, useContext, useMemo, useState } from "react";
import { useCallback, useContext, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { GroupEnt } from "../../../../api/dashboard";
import { GroupPermission } from "../../../../api/user";
import Boolset from "../../../../util/boolset";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm";
import ProDialog from "../../Common/ProDialog";
import SettingForm from "../../../Pages/Setting/SettingForm";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings";
import { AnonymousGroupID } from "../GroupRow";
import { GroupSettingContext } from "./GroupSettingWrapper";
@ -13,7 +12,6 @@ import { GroupSettingContext } from "./GroupSettingWrapper";
const ShareSection = () => {
const { t } = useTranslation("dashboard");
const { values, setGroup } = useContext(GroupSettingContext);
const [proOpen, setProOpen] = useState(false);
const permission = useMemo(() => {
return new Boolset(values.permissions ?? "");
@ -39,14 +37,8 @@ const ShareSection = () => {
[setGroup],
);
const onProClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
setProOpen(true);
};
return (
<SettingSection>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Typography variant="h6" gutterBottom>
{t("group.share")}
</Typography>
@ -65,13 +57,12 @@ const ShareSection = () => {
</SettingForm>
)}
<SettingForm lgWidth={5}>
<FormControl fullWidth onClick={onProClick}>
<FormControl fullWidth>
<FormControlLabel
control={<Switch checked={false} />}
label={
<Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.shareFree")}
<ProChip size="small" label="Pro" />
</Box>
}
/>
@ -91,13 +82,12 @@ const ShareSection = () => {
</SettingForm>
{values?.id != AnonymousGroupID && (
<SettingForm lgWidth={5}>
<FormControl fullWidth onClick={onProClick}>
<FormControl fullWidth>
<FormControlLabel
control={<Switch checked={false} />}
label={
<Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.esclateAnonymity")}
<ProChip size="small" label="Pro" />
</Box>
}
/>

@ -6,7 +6,7 @@ import { GroupPermission } from "../../../../api/user";
import Boolset from "../../../../util/boolset";
import SizeInput from "../../../Common/SizeInput";
import { DenseFilledTextField } from "../../../Common/StyledComponents";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm";
import SettingForm from "../../../Pages/Setting/SettingForm";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings";
import { AnonymousGroupID } from "../GroupRow";
import { GroupSettingContext } from "./GroupSettingWrapper";
@ -160,14 +160,13 @@ const UploadDownloadSection = () => {
<NoMarginHelperText>{t("group.reuseDirectLinkDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm lgWidth={5} pro>
<SettingForm lgWidth={5}>
<FormControl fullWidth>
<FormControlLabel
control={<Switch checked={false} />}
label={
<Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.folderDirectLink")}
<ProChip size="small" label="Pro" />
</Box>
}
/>

@ -29,7 +29,7 @@ import { getDashboardSummary } from "../../../api/api.ts";
import { HomepageSummary } from "../../../api/dashboard.ts";
import { useAppDispatch } from "../../../redux/hooks.ts";
import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
import { SecondaryButton, SquareChip } from "../../Common/StyledComponents.tsx";
import { SecondaryButton } from "../../Common/StyledComponents.tsx";
import TimeBadge from "../../Common/TimeBadge.tsx";
import Book from "../../Icons/Book.tsx";
import BoxMultipleFilled from "../../Icons/BoxMultipleFilled.tsx";
@ -39,11 +39,9 @@ import HomeIcon from "../../Icons/Home.tsx";
import OpenFilled from "../../Icons/OpenFilled.tsx";
import PeopleFilled from "../../Icons/PeopleFilled.tsx";
import ShareFilled from "../../Icons/ShareFilled.tsx";
import SparkleFilled from "../../Icons/SparkleFilled.tsx";
import Telegram from "../../Icons/Telegram.tsx";
import PageContainer from "../../Pages/PageContainer.tsx";
import PageHeader from "../../Pages/PageHeader.tsx";
import ProDialog from "../Common/ProDialog.tsx";
import SiteUrlWarning from "./SiteUrlWarning.tsx";
import CommentMultiple from "../../Icons/CommentMultiple.tsx";
@ -64,7 +62,6 @@ const Home = () => {
const [summary, setSummary] = useState<HomepageSummary | undefined>();
const [chartLoading, setChartLoading] = useState(false);
const [siteUrlWarning, setSiteUrlWarning] = useState(false);
const [proDialogOpen, setProDialogOpen] = useState(false);
useEffect(() => {
loadSummary(false);
}, []);
@ -90,7 +87,6 @@ const Home = () => {
return (
<PageContainer>
<ProDialog open={proDialogOpen} onClose={() => setProDialogOpen(false)} />
<SiteUrlWarning
open={siteUrlWarning}
onClose={() => setSiteUrlWarning(false)}
@ -313,9 +309,6 @@ const Home = () => {
<Box sx={{ ml: 2 }}>
<Typography variant={"h5"} fontWeight={600}>
Cloudreve
{summary && summary.version.pro && (
<SquareChip sx={{ ml: 1, height: "initial" }} size={"small"} color={"primary"} label={"Pro"} />
)}
</Typography>
<Typography variant={"subtitle2"} color={"text.secondary"}>
{summary ? summary.version.version : <Skeleton variant={"text"} width={70} />}
@ -383,14 +376,7 @@ const Home = () => {
<OpenFilled />
</StyledListItemIcon>
</ListItemButton>
{summary && !summary.version.pro && (
<ListItemButton onClick={() => setProDialogOpen(true)}>
<ListItemIcon>
<SparkleFilled color={"primary"} />
</ListItemIcon>
<ListItemText primary={t("summary.buyPro")} />
</ListItemButton>
)}
</List>
<Divider />
</StyledPaper>

@ -1,11 +1,10 @@
import { ExpandMoreRounded } from "@mui/icons-material";
import { Box, Typography } from "@mui/material";
import AccordionDetails from "@mui/material/AccordionDetails";
import React, { useContext, useState } from "react";
import React, { useContext } from "react";
import { useTranslation } from "react-i18next";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm.tsx";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { MagicVar } from "../../Common/MagicVarDialog.tsx";
import ProDialog from "../../Common/ProDialog.tsx";
import { SettingContext } from "../SettingWrapper.tsx";
import { SettingSection, SettingSectionContent } from "../Settings.tsx";
import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx";
@ -15,7 +14,6 @@ interface EmailTemplate {
title: string;
description: string;
magicVars: MagicVar[];
pro: boolean;
}
const commonMagicVars: MagicVar[] = [
@ -77,7 +75,6 @@ const userMagicVars: MagicVar[] = [
const EmailTemplates: React.FC = () => {
const { t } = useTranslation("dashboard");
const { setSettings, values } = useContext(SettingContext);
const [proOpen, setProOpen] = useState(false);
// Template setting keys
const templateSettings = [
@ -85,7 +82,6 @@ const EmailTemplates: React.FC = () => {
key: "mail_receipt_template",
title: "receiptEmailTemplate",
description: "receiptEmailTemplateDes",
pro: true,
},
{
key: "mail_activation_template",
@ -105,7 +101,6 @@ const EmailTemplates: React.FC = () => {
key: "mail_exceed_quota_template",
title: "quotaExceededEmailTemplate",
description: "quotaExceededEmailTemplateDes",
pro: true,
},
{
key: "mail_reset_template",
@ -123,14 +118,8 @@ const EmailTemplates: React.FC = () => {
},
];
const handleProClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setProOpen(true);
};
return (
<SettingSection>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Typography variant="h6" gutterBottom>
{t("settings.emailTemplates")}
</Typography>
@ -139,15 +128,12 @@ const EmailTemplates: React.FC = () => {
{templateSettings.map((template) => (
<StyledAccordion
disableGutters
onClick={template.pro ? handleProClick : undefined}
key={template.key}
expanded={template.pro ? false : undefined}
TransitionProps={{ unmountOnExit: true }}
>
<AccordionSummary expandIcon={<ExpandMoreRounded />}>
<Typography>
{t("settings." + template.title)}
{template.pro && <ProChip label="Pro" color="primary" size="small" />}
</Typography>
</AccordionSummary>
<AccordionDetails sx={{ display: "block" }}>

@ -9,11 +9,9 @@ import {
Stack,
Typography,
} from "@mui/material";
import { useContext, useState } from "react";
import { useContext } from "react";
import { useTranslation } from "react-i18next";
import { AuditLogType } from "../../../../api/explorer";
import { ProChip } from "../../../Pages/Setting/SettingForm";
import ProDialog from "../../Common/ProDialog";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings";
import { SettingContext } from "../SettingWrapper";
@ -131,27 +129,20 @@ export const getEventName = (eventType: number): string => {
const Events = () => {
const { t } = useTranslation("dashboard");
const { formRef, setSettings, values } = useContext(SettingContext);
const [proOpen, setProOpen] = useState(false);
const handleProClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setProOpen(true);
};
return (
<Box component={"form"} ref={formRef} onSubmit={(e) => e.preventDefault()}>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Stack spacing={5}>
<SettingSection>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center" }}>
{t("settings.auditLog")} <ProChip label="Pro" color="primary" size="small" />
{t("settings.auditLog")}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{t("settings.auditLogDes")}
</Typography>
{Object.entries(eventCategories).map(([categoryKey, category]) => (
<SettingSection key={categoryKey} onClick={handleProClick}>
<SettingSection key={categoryKey}>
<Box>
<Typography variant="subtitle1">{t(category.title)}</Typography>
<Typography variant="body2" color="text.secondary">

@ -64,7 +64,7 @@ const SiteInformation = () => {
<NoMarginHelperText>{t("settings.customFooterHTMLDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("settings.announcement")} lgWidth={5} pro>
<SettingForm title={t("settings.announcement")} lgWidth={5}>
<FormControl fullWidth>
<DenseFilledTextField inputProps={{ readOnly: true }} fullWidth multiline rows={4} />
<NoMarginHelperText>{t("settings.announcementDes")}</NoMarginHelperText>
@ -229,7 +229,7 @@ const SiteInformation = () => {
<NoMarginHelperText>{t("vas.showAppPromotionDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("vas.appFeedback")} lgWidth={5} pro>
<SettingForm title={t("vas.appFeedback")} lgWidth={5}>
<FormControl fullWidth>
<DenseFilledTextField
fullWidth
@ -242,7 +242,7 @@ const SiteInformation = () => {
<NoMarginHelperText>{t("vas.appLinkDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("vas.appForum")} lgWidth={5} pro>
<SettingForm title={t("vas.appForum")} lgWidth={5}>
<FormControl fullWidth>
<DenseFilledTextField fullWidth slotProps={{ input: { readOnly: true } }} />
<NoMarginHelperText>{t("vas.appLinkDes")}</NoMarginHelperText>

@ -4,7 +4,6 @@ import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/Accord
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { StyledCheckbox } from "../../../Common/StyledComponents.tsx";
import ProDialog from "../../Common/ProDialog.tsx";
export const AccordionSummary = styled((props: AccordionSummaryProps) => <MuiAccordionSummary {...props} />)(
({ theme }) => ({
@ -38,7 +37,6 @@ const SSOSettings = () => {
}, []);
return (
<>
<ProDialog open={open} onClose={() => setOpen(false)} />
<div onClick={onClick}>
<StyledAccordion expanded={false} disableGutters>
<AccordionSummary expandIcon={<ExpandMoreRounded />}>

@ -6,7 +6,7 @@ import { isTrueVal } from "../../../../session/utils.ts";
import SizeInput from "../../../Common/SizeInput.tsx";
import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx";
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm.tsx";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { Code } from "../../../Common/Code.tsx";
import GroupSelectionInput from "../../Common/GroupSelectionInput.tsx";
import SharesInput from "../../Common/SharesInput.tsx";
@ -127,7 +127,7 @@ const UserSession = () => {
<NoMarginHelperText>{t("settings.defaultGroupDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("settings.defaultSymbolics")} lgWidth={5} pro>
<SettingForm title={t("settings.defaultSymbolics")} lgWidth={5}>
<FormControl>
<SharesInput />
<NoMarginHelperText>
@ -139,7 +139,7 @@ const UserSession = () => {
</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("vas.filterEmailProvider")} lgWidth={5} pro>
<SettingForm title={t("vas.filterEmailProvider")} lgWidth={5}>
<FormControl>
<DenseSelect value={0}>
{["filterEmailProviderDisabled", "filterEmailProviderWhitelist", "filterEmailProviderBlacklist"].map(
@ -159,14 +159,13 @@ const UserSession = () => {
<NoMarginHelperText>{t("vas.filterEmailProviderDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm lgWidth={5} pro>
<SettingForm lgWidth={5}>
<FormControl fullWidth>
<FormControlLabel
control={<Switch checked={false} />}
label={
<>
{t("vas.disableSubAddressEmail")}
<ProChip label="Pro" color="primary" size="small" />
</>
}
/>
@ -179,7 +178,7 @@ const UserSession = () => {
</SettingSection>
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("settings.thirdPartySignIn")} <ProChip label="Pro" color="primary" size="small" />
{t("settings.thirdPartySignIn")}
</Typography>
<SettingSectionContent>
<SettingForm lgWidth={5}>

@ -10,11 +10,10 @@ import {
Typography,
} from "@mui/material";
import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
import { useContext, useMemo, useState } from "react";
import { useContext, useMemo } from "react";
import { Trans, useTranslation } from "react-i18next";
import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm.tsx";
import ProDialog from "../../Common/ProDialog.tsx";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
import { SettingContext } from "../SettingWrapper.tsx";
import GiftCodes from "./GiftCodes.tsx";
@ -30,7 +29,6 @@ interface CurrencyOption {
const VAS = () => {
const { t } = useTranslation("dashboard");
const [proOpen, setProOpen] = useState(false);
const { formRef, setSettings, values } = useContext(SettingContext);
const currencyPopupState = usePopupState({
variant: "popover",
@ -40,20 +38,14 @@ const VAS = () => {
const storageProducts = useMemo(() => values.storage_products || "[]", [values.storage_products]);
const groupSellData = useMemo(() => values.group_sell_data || "[]", [values.group_sell_data]);
const onProClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setProOpen(true);
};
return (
<Box component={"form"} ref={formRef}>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Stack spacing={5}>
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("settings.creditAndVAS")} <ProChip label="Pro" color="primary" size="small" />
{t("settings.creditAndVAS")}
</Typography>
<SettingSectionContent onClick={onProClick}>
<SettingSectionContent>
<SettingForm lgWidth={5}>
<FormControl fullWidth>
<FormControlLabel control={<Switch checked={false} />} label={t("settings.enableCredit")} />
@ -134,9 +126,9 @@ const VAS = () => {
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("settings.paymentSettings")} <ProChip label="Pro" color="primary" size="small" />
{t("settings.paymentSettings")}
</Typography>
<SettingSectionContent onClick={onProClick}>
<SettingSectionContent>
<SettingForm title={t("settings.currencyCode")} lgWidth={5}>
<FormControl fullWidth>
<DenseFilledTextField
@ -183,9 +175,9 @@ const VAS = () => {
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("settings.storageProductSettings")} <ProChip label="Pro" color="primary" size="small" />
{t("settings.storageProductSettings")}
</Typography>
<SettingSectionContent onClick={onProClick}>
<SettingSectionContent>
<SettingForm lgWidth={12}>
<FormControl fullWidth>
<StorageProducts />
@ -197,9 +189,9 @@ const VAS = () => {
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("settings.groupProductSettings")} <ProChip label="Pro" color="primary" size="small" />
{t("settings.groupProductSettings")}
</Typography>
<SettingSectionContent onClick={onProClick}>
<SettingSectionContent>
<SettingForm lgWidth={12}>
<FormControl fullWidth>
<GroupProducts />
@ -211,9 +203,9 @@ const VAS = () => {
<SettingSection>
<Typography variant="h6" gutterBottom sx={{ display: "flex", alignItems: "center" }}>
{t("giftCodes.giftCodesSettings")} <ProChip label="Pro" color="primary" size="small" />
{t("giftCodes.giftCodesSettings")}
</Typography>
<SettingSectionContent onClick={onProClick}>
<SettingSectionContent>
<GiftCodes storageProductsConfig={storageProducts} groupProductsConfig={groupSellData} />
</SettingSectionContent>
</SettingSection>

@ -15,9 +15,6 @@ import { SecondaryButton } from "../../Common/StyledComponents";
import DraggableDialog from "../../Dialogs/DraggableDialog";
import Open from "../../Icons/Open";
import { PolicyPropsMap } from "./StoragePolicySetting";
import { useState } from "react";
import ProDialog from "../Common/ProDialog.tsx";
import { ProChip } from "../../Pages/Setting/SettingForm.tsx";
export interface SelectProviderProps {
open: boolean;
@ -33,7 +30,6 @@ const StyledCard = styled(Card)(({ theme }) => ({
const SelectProvider = ({ open, onClose, onSelect }: SelectProviderProps) => {
const { t } = useTranslation("dashboard");
const [proOpen, setProOpen] = useState(false);
return (
<DraggableDialog
title={t("policy.selectAStorageProvider")}
@ -44,7 +40,6 @@ const SelectProvider = ({ open, onClose, onSelect }: SelectProviderProps) => {
maxWidth: "sm",
}}
>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<DialogContent dividers>
<Grid2 container spacing={2} sx={{ mt: 2 }}>
{Object.values(PolicyType).map((type) => (
@ -52,13 +47,12 @@ const SelectProvider = ({ open, onClose, onSelect }: SelectProviderProps) => {
<StyledCard sx={{ display: "flex" }}>
<CardActionArea
sx={{ display: "flex", justifyContent: "flex-start" }}
onClick={() => (PolicyPropsMap[type].pro ? setProOpen(true) : onSelect(type))}
onClick={() => onSelect(type)}
>
<CardMedia component="img" image={PolicyPropsMap[type].img} sx={{ width: 100, height: 60 }} />
<CardContent>
<Typography variant="subtitle1" color="textSecondary">
{t(PolicyPropsMap[type].name)}
{PolicyPropsMap[type].pro && <ProChip size="small" label="Pro" />}
</Typography>
</CardContent>
</CardActionArea>

@ -61,7 +61,6 @@ export interface PolicyProps {
credentialDes?: React.ReactNode;
corsExposedHeaders?: string[];
endpointNotEnforcePrefix?: boolean;
pro?: boolean;
regionCode?: string;
regionCodeDes?: React.ReactNode;
}
@ -78,7 +77,6 @@ export const PolicyPropsMap: Record<PolicyType, PolicyProps> = {
name: "policy.load_balance",
img: "/static/img/lb.svg",
wizardSize: "sm",
pro: true,
},
[PolicyType.remote]: {
name: "policy.remote",

@ -14,8 +14,9 @@ import {
useTheme,
} from "@mui/material";
import { useSnackbar } from "notistack";
import { useCallback, useContext, useState } from "react";
import { useCallback, useContext } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { sendCalibrateUserStorage } from "../../../../api/api";
import { UserStatus } from "../../../../api/dashboard";
import { useAppDispatch } from "../../../../redux/hooks";
@ -24,10 +25,9 @@ import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../../Com
import UserAvatar from "../../../Common/User/UserAvatar";
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu";
import Delete from "../../../Icons/Delete";
import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm";
import SettingForm from "../../../Pages/Setting/SettingForm";
import { CapacityBar } from "../../../Pages/Setting/StorageSetting";
import GroupSelectionInput from "../../Common/GroupSelectionInput";
import ProDialog from "../../Common/ProDialog";
import { NoMarginHelperText } from "../../Settings/Settings";
import { UserDialogContext } from "./UserDialog";
@ -37,8 +37,8 @@ const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loa
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const { t } = useTranslation("dashboard");
const navigate = useNavigate();
const { formRef, values, setUser } = useContext(UserDialogContext);
const [proOpen, setProOpen] = useState(false);
const removeAvatar = useCallback(() => {
setUser((prev) => ({ ...prev, avatar: undefined }));
@ -109,7 +109,6 @@ const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loa
return (
<Box component={"form"} ref={formRef} onSubmit={(e) => e.preventDefault()}>
<ProDialog open={proOpen} onClose={() => setProOpen(false)} />
<Stack spacing={isMobile ? 2 : 3} direction={isMobile ? "column" : "row"}>
<Stack spacing={isMobile ? 2 : 3} direction={"column"} sx={{ minWidth: 200 }}>
<SettingForm title={t("user.avatar")} noContainer lgWidth={12}>
@ -150,11 +149,11 @@ const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loa
<Box>
<SecondaryButton
sx={{ mt: 1 }}
onClick={() => setProOpen(true)}
onClick={() => navigate(`/admin/file?owner=${values.id}`)}
variant="contained"
startIcon={<OpenInNew />}
>
{t("user.openUserFiles")} <ProChip label="Pro" color="primary" size="small" />
{t("user.openUserFiles")}
</SecondaryButton>
</Box>
</Stack>
@ -183,7 +182,7 @@ const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loa
<SettingForm title={t("user.group")} noContainer lgWidth={6}>
<GroupSelectionInput value={values.group_users?.toString() ?? ""} onChange={onGroupChange} fullWidth />
</SettingForm>
<SettingForm title={t("application:vas.points")} noContainer lgWidth={6} pro>
<SettingForm title={t("application:vas.points")} noContainer lgWidth={6}>
<DenseFilledTextField
slotProps={{
htmlInput: {
@ -210,7 +209,7 @@ const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loa
type={"password"}
/>
</SettingForm>
<SettingForm title={t("user.originUserGroup")} noContainer lgWidth={6} pro>
<SettingForm title={t("user.originUserGroup")} noContainer lgWidth={6}>
<GroupSelectionInput
value={" "}
onChange={() => {}}
@ -220,7 +219,7 @@ const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loa
/>
<NoMarginHelperText>{t("user.originUserGroupDes")}</NoMarginHelperText>
</SettingForm>
<SettingForm title={t("user.groupExpired")} noContainer lgWidth={6} pro>
<SettingForm title={t("user.groupExpired")} noContainer lgWidth={6}>
<DenseFilledTextField fullWidth value={""} />
<NoMarginHelperText>{t("user.groupExpiredDes")}</NoMarginHelperText>
</SettingForm>

@ -1,14 +1,13 @@
import { Icon as Iconify } from "@iconify/react";
import { Box, SvgIconProps, useTheme } from "@mui/material";
import SvgIcon from "@mui/material/SvgIcon/SvgIcon";
import { memo, useMemo, useState } from "react";
import { memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router-dom";
import { GroupPermission } from "../../../api/user.ts";
import { useAppSelector } from "../../../redux/hooks.ts";
import SessionManager from "../../../session";
import { GroupBS } from "../../../session/utils.ts";
import ProDialog from "../../Admin/Common/ProDialog.tsx";
import BoxMultiple from "../../Icons/BoxMultiple.tsx";
import BoxMultipleFilled from "../../Icons/BoxMultipleFilled.tsx";
import CloudDownload from "../../Icons/CloudDownload.tsx";
@ -45,7 +44,6 @@ import StorageOutlined from "../../Icons/StorageOutlined.tsx";
import Warning from "../../Icons/Warning.tsx";
import WarningOutlined from "../../Icons/WarningOutlined.tsx";
import WrenchSettings from "../../Icons/WrenchSettings.tsx";
import { ProChip } from "../../Pages/Setting/SettingForm.tsx";
import NavIconTransition from "./NavIconTransition.tsx";
import SideNavItem from "./SideNavItem.tsx";
@ -54,7 +52,6 @@ export interface NavigationItem {
icon?: ((props: SvgIconProps) => JSX.Element)[] | (typeof SvgIcon)[];
iconifyName?: string;
path: string;
pro?: boolean;
}
let NavigationItems: NavigationItem[];
@ -89,36 +86,15 @@ export const SideNavItemComponent = ({ item }: { item: NavigationItem }) => {
const navigate = useNavigate();
const location = useLocation();
const theme = useTheme();
const [proOpen, setProOpen] = useState(false);
const active = useMemo(() => {
return location.pathname == item.path || location.pathname.startsWith(item.path + "/");
}, [location.pathname, item.path]);
return (
<>
{item.pro && <ProDialog open={proOpen} onClose={() => setProOpen(false)} />}
<SideNavItem
key={item.label}
onClick={() =>
item.pro ? setProOpen(true) : item.iconifyName ? window.open(item.path, "_blank") : navigate(item.path)
}
label={
item.pro ? (
<Box sx={{ display: "flex", alignItems: "center" }}>
{t(item.label)}
<ProChip
sx={{
height: "16px",
fontSize: (t) => t.typography.caption.fontSize,
}}
label="Pro"
color="primary"
size="small"
/>
</Box>
) : (
t(item.label)
)
}
onClick={() => (item.iconifyName ? window.open(item.path, "_blank") : navigate(item.path))}
label={t(item.label)}
active={active}
icon={
!item.icon ? (
@ -211,19 +187,16 @@ AdminNavigationItems = [
label: "dashboard:vas.orders",
icon: [PaymentFilled, Payment],
path: "/admin/payment",
pro: true,
},
{
label: "dashboard:nav.events",
icon: [SendLoggingFilled, SendLogging],
path: "/admin/event",
pro: true,
},
{
label: "dashboard:nav.abuseReport",
icon: [Warning, WarningOutlined],
path: "/admin/abuse",
pro: true,
},
{
label: "dashboard:nav.oauthClients",
@ -253,7 +226,6 @@ export const AdminPageNavigation = memo(() => {
});
const PageNavigation = () => {
const shopNavEnabled = useAppSelector((state) => state.siteConfig.basic.config.shop_nav_enabled);
const appPromotionEnabled = useAppSelector((state) => state.siteConfig.basic.config.app_promotion);
const user = SessionManager.currentLoginOrNull();
const isAdmin = useMemo(() => {

@ -1,14 +1,5 @@
import { Chip, Grid2, Typography, styled } from "@mui/material";
import { useCallback, useEffect, useState } from "react";
import ProDialog from "../../Admin/Common/ProDialog";
export const ProChip = styled(Chip)(({ theme }) => ({
marginLeft: 8,
height: "20px",
fontSize: "12px",
background: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.primary.light} 90%)`,
color: theme.palette.primary.contrastText,
}));
import { Grid2, Typography } from "@mui/material";
import { useEffect } from "react";
export interface SettingFormProps {
title?: React.ReactNode;
@ -18,7 +9,6 @@ export interface SettingFormProps {
spacing?: number;
anchorId?: string;
noContainer?: boolean;
pro?: boolean;
}
const SettingForm = ({
@ -29,9 +19,7 @@ const SettingForm = ({
spacing,
noContainer,
anchorId,
pro,
}: SettingFormProps) => {
const [proOpen, setProOpen] = useState(false);
useEffect(() => {
if (anchorId && window.location.hash === `#${anchorId}`) {
const anchor = document.getElementById(`anchor-${anchorId}`);
@ -43,16 +31,6 @@ const SettingForm = ({
}
}, [anchorId]);
const handleProClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (pro) {
e.stopPropagation();
setProOpen(true);
}
},
[pro],
);
const inner = (
<>
<Grid2
@ -72,13 +50,11 @@ const SettingForm = ({
id={anchorId ? `anchor-${anchorId}` : undefined}
>
{title}
{pro && <ProChip label="Pro" color="primary" size="small" />}
</Typography>
)}
<div onClick={handleProClick}>{children}</div>
{children}
</Grid2>
{secondary && secondary}
{pro && <ProDialog open={proOpen} onClose={() => setProOpen(false)} />}
</>
);
if (noContainer) {

Loading…
Cancel
Save