mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 03:20:46 +02:00
merge: pull latest main into SaaS
This commit is contained in:
@@ -39,13 +39,15 @@ export function OverviewHeader() {
|
||||
</Text>
|
||||
{user?.email && (
|
||||
<Text size="xs" c="dimmed" mt="0.25rem">
|
||||
Signed in as: {user.email}
|
||||
{t("account.overview.signedInAs", "Signed in as: {{email}}", {
|
||||
email: user.email,
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
{user && (
|
||||
<Button color="red" variant="filled" onClick={handleLogout}>
|
||||
Log out
|
||||
{t("account.overview.logOut", "Log out")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+12
-3
@@ -809,7 +809,10 @@ export default function AdminAdvancedSection() {
|
||||
},
|
||||
})
|
||||
}
|
||||
placeholder="Default: java.io.tmpdir/stirling-pdf"
|
||||
placeholder={t(
|
||||
"admin.settings.advanced.tempFileManagement.baseTmpDir.placeholder",
|
||||
"Default: java.io.tmpdir/stirling-pdf",
|
||||
)}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -834,7 +837,10 @@ export default function AdminAdvancedSection() {
|
||||
},
|
||||
})
|
||||
}
|
||||
placeholder="Default: baseTmpDir/libreoffice"
|
||||
placeholder={t(
|
||||
"admin.settings.advanced.tempFileManagement.libreofficeDir.placeholder",
|
||||
"Default: baseTmpDir/libreoffice",
|
||||
)}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -859,7 +865,10 @@ export default function AdminAdvancedSection() {
|
||||
},
|
||||
})
|
||||
}
|
||||
placeholder="System temp directory path"
|
||||
placeholder={t(
|
||||
"admin.settings.advanced.tempFileManagement.systemTempDir.placeholder",
|
||||
"System temp directory path",
|
||||
)}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+16
-2
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { isAxiosError } from "axios";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
NumberInput,
|
||||
@@ -52,6 +53,7 @@ interface DatabaseSettingsData {
|
||||
|
||||
export default function AdminDatabaseSection() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { loginEnabled, validateLoginEnabled, getDisabledStyles } =
|
||||
useLoginRequired();
|
||||
const {
|
||||
@@ -462,7 +464,16 @@ export default function AdminDatabaseSection() {
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge color="grape" size="lg">
|
||||
<Badge
|
||||
color="grape"
|
||||
size="lg"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => navigate("/settings/adminPlan")}
|
||||
title={t(
|
||||
"admin.settings.badge.clickToUpgrade",
|
||||
"Click to view plan details",
|
||||
)}
|
||||
>
|
||||
ENTERPRISE
|
||||
</Badge>
|
||||
</Group>
|
||||
@@ -698,7 +709,10 @@ export default function AdminDatabaseSection() {
|
||||
onChange={(value) =>
|
||||
setSettings({ ...settings, password: value })
|
||||
}
|
||||
placeholder="Enter database password"
|
||||
placeholder={t(
|
||||
"admin.settings.database.password.placeholder",
|
||||
"Enter database password",
|
||||
)}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+8
-2
@@ -296,7 +296,10 @@ export default function AdminEndpointsSection() {
|
||||
}))}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select endpoints to disable"
|
||||
placeholder={t(
|
||||
"admin.settings.endpoints.toRemove.placeholder",
|
||||
"Select endpoints to disable",
|
||||
)}
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
@@ -330,7 +333,10 @@ export default function AdminEndpointsSection() {
|
||||
}))}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select groups to disable"
|
||||
placeholder={t(
|
||||
"admin.settings.endpoints.groupsToRemove.placeholder",
|
||||
"Select groups to disable",
|
||||
)}
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
|
||||
+4
-1
@@ -286,7 +286,10 @@ export default function AdminMailSection() {
|
||||
onChange={(value) =>
|
||||
setSettings({ ...settings, password: value })
|
||||
}
|
||||
placeholder="Enter SMTP password"
|
||||
placeholder={t(
|
||||
"admin.settings.mail.password.placeholder",
|
||||
"Enter SMTP password",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
+8
-2
@@ -183,8 +183,14 @@ const AdminPlanSection: React.FC = () => {
|
||||
|
||||
if (!plans || plans.length === 0) {
|
||||
return (
|
||||
<Alert color="yellow" title="No data available">
|
||||
Plans data is not available at the moment.
|
||||
<Alert
|
||||
color="yellow"
|
||||
title={t("admin.settings.plan.noData.title", "No data available")}
|
||||
>
|
||||
{t(
|
||||
"admin.settings.plan.noData.message",
|
||||
"Plans data is not available at the moment.",
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
+16
-4
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import {
|
||||
TextInput,
|
||||
Switch,
|
||||
@@ -132,17 +132,29 @@ export default function AdminPremiumSection() {
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Text size="sm" component="span">
|
||||
<strong>Custom Metadata</strong> (PRO) - General
|
||||
<Trans
|
||||
i18nKey="admin.settings.premium.movedFeatures.customMetadata"
|
||||
defaults="<0>Custom Metadata</0> (PRO) - General"
|
||||
components={[<strong />]}
|
||||
/>
|
||||
</Text>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Text size="sm" component="span">
|
||||
<strong>Audit Logging</strong> (ENTERPRISE) - Security
|
||||
<Trans
|
||||
i18nKey="admin.settings.premium.movedFeatures.auditLogging"
|
||||
defaults="<0>Audit Logging</0> (ENTERPRISE) - Security"
|
||||
components={[<strong />]}
|
||||
/>
|
||||
</Text>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Text size="sm" component="span">
|
||||
<strong>Database Configuration</strong> (ENTERPRISE) - Database
|
||||
<Trans
|
||||
i18nKey="admin.settings.premium.movedFeatures.databaseConfiguration"
|
||||
defaults="<0>Database Configuration</0> (ENTERPRISE) - Database"
|
||||
components={[<strong />]}
|
||||
/>
|
||||
</Text>
|
||||
</List.Item>
|
||||
</List>
|
||||
|
||||
+12
-1
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
NumberInput,
|
||||
@@ -67,6 +68,7 @@ interface SecuritySettingsData {
|
||||
|
||||
export default function AdminSecuritySection() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { loginEnabled, validateLoginEnabled } = useLoginRequired();
|
||||
const {
|
||||
restartModalOpened,
|
||||
@@ -817,7 +819,16 @@ export default function AdminSecuritySection() {
|
||||
<Text fw={600} size="sm">
|
||||
{t("admin.settings.security.audit.label", "Audit Logging")}
|
||||
</Text>
|
||||
<Badge color="grape" size="sm">
|
||||
<Badge
|
||||
color="grape"
|
||||
size="sm"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => navigate("/settings/adminPlan")}
|
||||
title={t(
|
||||
"admin.settings.badge.clickToUpgrade",
|
||||
"Click to view plan details",
|
||||
)}
|
||||
>
|
||||
ENTERPRISE
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
+12
-7
@@ -11,7 +11,10 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alert } from "@app/components/toast";
|
||||
import { LicenseInfo, mapLicenseToTier } from "@app/services/licenseService";
|
||||
import { PLAN_FEATURES, PLAN_HIGHLIGHTS } from "@app/constants/planConstants";
|
||||
import {
|
||||
usePlanFeatures,
|
||||
usePlanHighlights,
|
||||
} from "@app/constants/planConstants";
|
||||
import FeatureComparisonTable from "@app/components/shared/config/configSections/plan/FeatureComparisonTable";
|
||||
import StaticCheckoutModal from "@app/components/shared/config/configSections/plan/StaticCheckoutModal";
|
||||
import LicenseKeySection from "@app/components/shared/config/configSections/plan/LicenseKeySection";
|
||||
@@ -32,6 +35,8 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({
|
||||
currentLicenseInfo,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const planFeatures = usePlanFeatures();
|
||||
const planHighlights = usePlanHighlights();
|
||||
const [showComparison, setShowComparison] = useState(false);
|
||||
|
||||
// Static checkout modal state
|
||||
@@ -88,8 +93,8 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({
|
||||
price: 0,
|
||||
currency: "£",
|
||||
period: "",
|
||||
highlights: PLAN_HIGHLIGHTS.FREE,
|
||||
features: PLAN_FEATURES.FREE,
|
||||
highlights: planHighlights.FREE,
|
||||
features: planFeatures.FREE,
|
||||
maxUsers: 5,
|
||||
},
|
||||
{
|
||||
@@ -99,8 +104,8 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({
|
||||
currency: "",
|
||||
period: "",
|
||||
popular: false,
|
||||
highlights: PLAN_HIGHLIGHTS.SERVER_MONTHLY,
|
||||
features: PLAN_FEATURES.SERVER,
|
||||
highlights: planHighlights.SERVER_MONTHLY,
|
||||
features: planFeatures.SERVER,
|
||||
maxUsers: "Unlimited users",
|
||||
},
|
||||
{
|
||||
@@ -109,8 +114,8 @@ const StaticPlanSection: React.FC<StaticPlanSectionProps> = ({
|
||||
price: 0,
|
||||
currency: "",
|
||||
period: "",
|
||||
highlights: PLAN_HIGHLIGHTS.ENTERPRISE_MONTHLY,
|
||||
features: PLAN_FEATURES.ENTERPRISE,
|
||||
highlights: planHighlights.ENTERPRISE_MONTHLY,
|
||||
features: planFeatures.ENTERPRISE,
|
||||
maxUsers: "Custom",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -185,14 +185,20 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
return (
|
||||
<Stack align="center" justify="center" p="xl">
|
||||
<Loader size="lg" />
|
||||
<Text c="dimmed">Loading session...</Text>
|
||||
<Text c="dimmed">
|
||||
{t("workflow.participant.loadingSession", "Loading session...")}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert icon={<InfoIcon fontSize="small" />} color="red" title="Error">
|
||||
<Alert
|
||||
icon={<InfoIcon fontSize="small" />}
|
||||
color="red"
|
||||
title={t("workflow.participant.errorTitle", "Error")}
|
||||
>
|
||||
{error}
|
||||
</Alert>
|
||||
);
|
||||
@@ -201,7 +207,10 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
if (!session || !participant) {
|
||||
return (
|
||||
<Alert icon={<InfoIcon fontSize="small" />} color="orange">
|
||||
Session not found or access denied.
|
||||
{t(
|
||||
"workflow.participant.sessionNotFound",
|
||||
"Session not found or access denied.",
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -209,15 +218,35 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case "SIGNED":
|
||||
return <Badge color="green">Signed</Badge>;
|
||||
return (
|
||||
<Badge color="green">
|
||||
{t("workflow.participant.statusSigned", "Signed")}
|
||||
</Badge>
|
||||
);
|
||||
case "DECLINED":
|
||||
return <Badge color="red">Declined</Badge>;
|
||||
return (
|
||||
<Badge color="red">
|
||||
{t("workflow.participant.statusDeclined", "Declined")}
|
||||
</Badge>
|
||||
);
|
||||
case "VIEWED":
|
||||
return <Badge color="blue">Viewed</Badge>;
|
||||
return (
|
||||
<Badge color="blue">
|
||||
{t("workflow.participant.statusViewed", "Viewed")}
|
||||
</Badge>
|
||||
);
|
||||
case "NOTIFIED":
|
||||
return <Badge color="yellow">Notified</Badge>;
|
||||
return (
|
||||
<Badge color="yellow">
|
||||
{t("workflow.participant.statusNotified", "Notified")}
|
||||
</Badge>
|
||||
);
|
||||
case "PENDING":
|
||||
return <Badge color="gray">Pending</Badge>;
|
||||
return (
|
||||
<Badge color="gray">
|
||||
{t("workflow.participant.statusPending", "Pending")}
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return <Badge>{status}</Badge>;
|
||||
}
|
||||
@@ -254,7 +283,9 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
{session.documentName}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
From: {session.ownerUsername}
|
||||
{t("workflow.participant.from", "From: {{name}}", {
|
||||
name: session.ownerUsername,
|
||||
})}
|
||||
</Text>
|
||||
</div>
|
||||
{getStatusBadge(participant.status)}
|
||||
@@ -272,7 +303,9 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
|
||||
{session.dueDate && (
|
||||
<Text size="sm" c="dimmed">
|
||||
Due Date: {session.dueDate}
|
||||
{t("workflow.participant.dueDate", "Due Date: {{date}}", {
|
||||
date: session.dueDate,
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -283,7 +316,7 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
onClick={() => downloadDocument(token)}
|
||||
variant="light"
|
||||
>
|
||||
Download Document
|
||||
{t("workflow.participant.downloadDocument", "Download Document")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -293,17 +326,35 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
<Card shadow="sm" padding="md" radius="md" withBorder>
|
||||
<Stack gap="md">
|
||||
<Text fw={500} size="lg">
|
||||
Sign Document
|
||||
{t("workflow.participant.signDocument", "Sign Document")}
|
||||
</Text>
|
||||
|
||||
<Select
|
||||
label="Certificate Type"
|
||||
label={t(
|
||||
"workflow.participant.certificateType",
|
||||
"Certificate Type",
|
||||
)}
|
||||
value={certType}
|
||||
onChange={(value) => setCertType(value || "P12")}
|
||||
data={[
|
||||
{ value: "P12", label: "P12/PKCS12 Certificate" },
|
||||
{ value: "JKS", label: "JKS Keystore" },
|
||||
{ value: "SERVER", label: "Server Certificate (if available)" },
|
||||
{
|
||||
value: "P12",
|
||||
label: t(
|
||||
"workflow.participant.certTypeP12",
|
||||
"P12/PKCS12 Certificate",
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "JKS",
|
||||
label: t("workflow.participant.certTypeJks", "JKS Keystore"),
|
||||
},
|
||||
{
|
||||
value: "SERVER",
|
||||
label: t(
|
||||
"workflow.participant.certTypeServer",
|
||||
"Server Certificate (if available)",
|
||||
),
|
||||
},
|
||||
]}
|
||||
size="sm"
|
||||
data-testid="cert-type-select"
|
||||
@@ -312,8 +363,14 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
{certType !== "SERVER" && (
|
||||
<>
|
||||
<FileInput
|
||||
label="Certificate File"
|
||||
placeholder="Select certificate file"
|
||||
label={t(
|
||||
"workflow.participant.certificateFile",
|
||||
"Certificate File",
|
||||
)}
|
||||
placeholder={t(
|
||||
"workflow.participant.certificateFilePlaceholder",
|
||||
"Select certificate file",
|
||||
)}
|
||||
value={certFile}
|
||||
onChange={setCertFile}
|
||||
accept=".p12,.pfx,.jks"
|
||||
@@ -322,7 +379,10 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Certificate Password"
|
||||
label={t(
|
||||
"workflow.participant.certificatePassword",
|
||||
"Certificate Password",
|
||||
)}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
@@ -386,23 +446,32 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
)}
|
||||
|
||||
<TextInput
|
||||
label="Location"
|
||||
placeholder="e.g., San Francisco, CA"
|
||||
label={t("workflow.participant.location", "Location")}
|
||||
placeholder={t(
|
||||
"workflow.participant.locationPlaceholder",
|
||||
"e.g., San Francisco, CA",
|
||||
)}
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.currentTarget.value)}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Reason"
|
||||
placeholder="e.g., Document approval"
|
||||
label={t("workflow.participant.reason", "Reason")}
|
||||
placeholder={t(
|
||||
"workflow.participant.reasonPlaceholder",
|
||||
"e.g., Document approval",
|
||||
)}
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.currentTarget.value)}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Page Number (optional)"
|
||||
label={t(
|
||||
"workflow.participant.pageNumber",
|
||||
"Page Number (optional)",
|
||||
)}
|
||||
type="number"
|
||||
value={pageNumber}
|
||||
onChange={(e) =>
|
||||
@@ -423,7 +492,7 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
color="green"
|
||||
data-testid="submit-signature-button"
|
||||
>
|
||||
Submit Signature
|
||||
{t("workflow.participant.submitSignature", "Submit Signature")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -433,7 +502,7 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
variant="light"
|
||||
data-testid="decline-button"
|
||||
>
|
||||
Decline
|
||||
{t("workflow.participant.decline", "Decline")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -442,14 +511,24 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
||||
|
||||
{participant.hasCompleted && (
|
||||
<Alert icon={<CheckCircleIcon fontSize="small" />} color="green">
|
||||
You have {participant.status === "SIGNED" ? "signed" : "declined"}{" "}
|
||||
this document.
|
||||
{participant.status === "SIGNED"
|
||||
? t(
|
||||
"workflow.participant.completedSigned",
|
||||
"You have signed this document.",
|
||||
)
|
||||
: t(
|
||||
"workflow.participant.completedDeclined",
|
||||
"You have declined this document.",
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{participant.isExpired && (
|
||||
<Alert icon={<InfoIcon fontSize="small" />} color="orange">
|
||||
Your access to this document has expired.
|
||||
{t(
|
||||
"workflow.participant.accessExpired",
|
||||
"Your access to this document has expired.",
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user