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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,14 +1,5 @@
import { Chip, Grid2, Typography, styled } from "@mui/material"; import { Grid2, Typography } from "@mui/material";
import { useCallback, useEffect, useState } from "react"; import { useEffect } 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,
}));
export interface SettingFormProps { export interface SettingFormProps {
title?: React.ReactNode; title?: React.ReactNode;
@ -18,7 +9,6 @@ export interface SettingFormProps {
spacing?: number; spacing?: number;
anchorId?: string; anchorId?: string;
noContainer?: boolean; noContainer?: boolean;
pro?: boolean;
} }
const SettingForm = ({ const SettingForm = ({
@ -29,9 +19,7 @@ const SettingForm = ({
spacing, spacing,
noContainer, noContainer,
anchorId, anchorId,
pro,
}: SettingFormProps) => { }: SettingFormProps) => {
const [proOpen, setProOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (anchorId && window.location.hash === `#${anchorId}`) { if (anchorId && window.location.hash === `#${anchorId}`) {
const anchor = document.getElementById(`anchor-${anchorId}`); const anchor = document.getElementById(`anchor-${anchorId}`);
@ -43,16 +31,6 @@ const SettingForm = ({
} }
}, [anchorId]); }, [anchorId]);
const handleProClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (pro) {
e.stopPropagation();
setProOpen(true);
}
},
[pro],
);
const inner = ( const inner = (
<> <>
<Grid2 <Grid2
@ -72,13 +50,11 @@ const SettingForm = ({
id={anchorId ? `anchor-${anchorId}` : undefined} id={anchorId ? `anchor-${anchorId}` : undefined}
> >
{title} {title}
{pro && <ProChip label="Pro" color="primary" size="small" />}
</Typography> </Typography>
)} )}
<div onClick={handleProClick}>{children}</div> {children}
</Grid2> </Grid2>
{secondary && secondary} {secondary && secondary}
{pro && <ProDialog open={proOpen} onClose={() => setProOpen(false)} />}
</> </>
); );
if (noContainer) { if (noContainer) {

Loading…
Cancel
Save