ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: профиль, редиректы и визуальные статусы задач
This commit is contained in:
parent
c4032e3040
commit
4ed63cac4e
|
|
@ -48,9 +48,9 @@ x-live-env: &live-env
|
|||
LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-2FiJk1U2aiVPEQtzLehYGlTSnTnrs7LW}
|
||||
|
||||
x-app-env: &app-env
|
||||
WEB_URL: ${WEB_URL:-http://localhost}
|
||||
WEB_URL: ${WEB_URL:-http://localhost:8090}
|
||||
DEBUG: ${DEBUG:-0}
|
||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS}
|
||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:8090}
|
||||
GUNICORN_WORKERS: 1
|
||||
POSTHOG_API_KEY: ${POSTHOG_API_KEY:-}
|
||||
POSTHOG_HOST: ${POSTHOG_HOST:-}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ urlpatterns = [
|
|||
UserEndpoint.as_view({"patch": "update_email"}),
|
||||
name="user-email-update",
|
||||
),
|
||||
path(
|
||||
"users/me/email/direct/",
|
||||
UserEndpoint.as_view({"patch": "update_email_without_verification"}),
|
||||
name="user-email-direct-update",
|
||||
),
|
||||
# Profile
|
||||
path("users/me/profile/", ProfileEndpoint.as_view(), name="accounts"),
|
||||
# End profile
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# See the LICENSE file for details.
|
||||
|
||||
# Python imports
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import logging
|
||||
|
|
@ -50,6 +51,7 @@ from plane.bgtasks.user_deactivation_email_task import user_deactivation_email
|
|||
from plane.utils.host import base_host
|
||||
from plane.bgtasks.user_email_update_task import send_email_update_magic_code, send_email_update_confirmation
|
||||
from plane.authentication.rate_limit import EmailVerificationThrottle
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
|
||||
|
||||
logger = logging.getLogger("plane")
|
||||
|
|
@ -133,6 +135,10 @@ class UserEndpoint(BaseViewSet):
|
|||
|
||||
return None
|
||||
|
||||
def _is_smtp_configured(self):
|
||||
(email_host,) = get_configuration_value([{"key": "EMAIL_HOST", "default": os.environ.get("EMAIL_HOST", "")}])
|
||||
return bool(email_host)
|
||||
|
||||
def generate_email_verification_code(self, request):
|
||||
"""
|
||||
Generate and send a magic code to the new email address for verification.
|
||||
|
|
@ -248,6 +254,33 @@ class UserEndpoint(BaseViewSet):
|
|||
serialized_data = UserMeSerializer(user).data
|
||||
return Response(serialized_data, status=status.HTTP_200_OK)
|
||||
|
||||
def update_email_without_verification(self, request):
|
||||
"""
|
||||
Update the current user's email when the instance has no SMTP configured.
|
||||
Verified SMTP-backed installations must continue using the magic-code flow.
|
||||
"""
|
||||
if self._is_smtp_configured():
|
||||
return Response(
|
||||
{"error": "Email verification is required when SMTP is configured"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
user = self.get_object()
|
||||
new_email = request.data.get("email", "").strip().lower()
|
||||
|
||||
validation_error = self._validate_new_email(user, new_email)
|
||||
if validation_error:
|
||||
return validation_error
|
||||
|
||||
user.email = new_email
|
||||
user.is_email_verified = False
|
||||
user.save()
|
||||
|
||||
logout(request)
|
||||
|
||||
serialized_data = UserMeSerializer(user).data
|
||||
return Response(serialized_data, status=status.HTTP_200_OK)
|
||||
|
||||
def deactivate(self, request):
|
||||
# Check all workspace user is active
|
||||
user = self.get_object()
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
|||
<div className="group/kanban-block relative mb-2">
|
||||
<div
|
||||
data-active={isActive}
|
||||
data-priority={issue.priority ?? "none"}
|
||||
className="nodedc-external-card relative flex min-h-[220px] w-full cursor-pointer flex-col p-4 transition-all hover:bg-white/5"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
|||
>
|
||||
<div
|
||||
data-active={isActive}
|
||||
data-priority={issue.priority ?? "none"}
|
||||
className={cn(
|
||||
"nodedc-external-card relative flex min-h-[15rem] cursor-pointer flex-col gap-5 px-6 py-5 transition-all hover:bg-white/5",
|
||||
{ "ring-0": isActive }
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { useUser } from "@/hooks/store/user";
|
|||
import { AuthService } from "@/services/auth.service";
|
||||
import userService from "@/services/user.service";
|
||||
|
||||
type Props = { isOpen: boolean; onClose: () => void };
|
||||
type Props = { isOpen: boolean; onClose: () => void; isSMTPConfigured?: boolean };
|
||||
|
||||
type TModalStep = "EMAIL" | "UNIQUE_CODE";
|
||||
type TUniqueCodeValuesForm = { email: string; code: string };
|
||||
|
|
@ -33,7 +33,7 @@ const defaultValues: TUniqueCodeValuesForm = { email: "", code: "" };
|
|||
const authService = new AuthService();
|
||||
|
||||
export const ChangeEmailModal = observer(function ChangeEmailModal(props: Props) {
|
||||
const { isOpen, onClose } = props;
|
||||
const { isOpen, onClose, isSMTPConfigured = true } = props;
|
||||
// states
|
||||
const [currentStep, setCurrentStep] = useState<TModalStep>("EMAIL");
|
||||
// store hooks
|
||||
|
|
@ -107,6 +107,20 @@ export const ChangeEmailModal = observer(function ChangeEmailModal(props: Props)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!isSMTPConfigured) {
|
||||
await userService.updateEmailDirect({ email: formData.email });
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: changeEmailT("toasts.success_title"),
|
||||
message: changeEmailT("toasts.success_message"),
|
||||
});
|
||||
|
||||
await handleSignOut();
|
||||
handleClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate verification code and send to new email
|
||||
await userService.generateEmailCode({ email: formData.email });
|
||||
|
||||
|
|
@ -135,7 +149,9 @@ export const ChangeEmailModal = observer(function ChangeEmailModal(props: Props)
|
|||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.CENTER} width={EModalWidth.XXL}>
|
||||
<div className="space-y-0 px-4 py-4">
|
||||
<h3 className="text-16 leading-6 font-medium text-primary">{changeEmailT("title")}</h3>
|
||||
<p className="my-4 text-13 text-secondary">{changeEmailT("description")}</p>
|
||||
<p className="my-4 text-13 text-secondary">
|
||||
{isSMTPConfigured ? changeEmailT("description") : changeEmailT("direct_description")}
|
||||
</p>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 px-4" noValidate>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ const HomeInternalContourDeckCard = observer(function HomeInternalContourDeckCar
|
|||
>
|
||||
<NodedcWorkItemCard
|
||||
isActive={isActive}
|
||||
priority={issue.priority}
|
||||
surfaceClassName={cn(
|
||||
"nodedc-home-task-card-surface px-0",
|
||||
compact && "!rounded-[24px]",
|
||||
|
|
@ -276,6 +277,7 @@ const HomeExternalContourDeckCard = observer(function HomeExternalContourDeckCar
|
|||
>
|
||||
<div
|
||||
data-active={isActive}
|
||||
data-priority={issue.priority ?? "none"}
|
||||
className={cn(
|
||||
"nodedc-external-card nodedc-home-task-card-surface relative flex w-full flex-col",
|
||||
compact ? "min-h-[168px] rounded-[24px] p-3" : "min-h-[220px] p-4",
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export const InboxIssueListItem = observer(function InboxIssueListItem(props: In
|
|||
>
|
||||
<NodedcWorkItemCard
|
||||
isActive={isActive}
|
||||
priority={issue.priority}
|
||||
surfaceClassName="transition-transform duration-200 hover:-translate-y-0.5"
|
||||
header={
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
|
|||
return (
|
||||
<NodedcWorkItemCard
|
||||
isActive={isActive}
|
||||
priority={issue.priority}
|
||||
surfaceClassName="!rounded-[24px] !p-0"
|
||||
contentClassName="min-h-[220px]"
|
||||
header={header}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import type { TIssuePriorities } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type TNodedcWorkItemCardProps = {
|
||||
isActive: boolean;
|
||||
priority?: TIssuePriorities | null;
|
||||
header: ReactNode;
|
||||
title: ReactNode;
|
||||
footer: ReactNode;
|
||||
|
|
@ -37,6 +39,7 @@ export const getNodedcWorkItemCardAppearance = (isActive: boolean) => ({
|
|||
|
||||
export const NodedcWorkItemCard = ({
|
||||
isActive,
|
||||
priority,
|
||||
header,
|
||||
title,
|
||||
footer,
|
||||
|
|
@ -52,6 +55,8 @@ export const NodedcWorkItemCard = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
data-active={isActive}
|
||||
data-priority={priority ?? "none"}
|
||||
className={cn(
|
||||
"nodedc-work-item-card rounded-[28px] border-0 p-4 shadow-none ring-0 outline-none",
|
||||
appearance.surfaceClassName,
|
||||
|
|
|
|||
|
|
@ -191,7 +191,11 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
return (
|
||||
<>
|
||||
<DeactivateAccountModal isOpen={deactivateAccountModal} onClose={() => setDeactivateAccountModal(false)} />
|
||||
<ChangeEmailModal isOpen={isChangeEmailModalOpen} onClose={() => setIsChangeEmailModalOpen(false)} />
|
||||
<ChangeEmailModal
|
||||
isOpen={isChangeEmailModalOpen}
|
||||
onClose={() => setIsChangeEmailModalOpen(false)}
|
||||
isSMTPConfigured={isSMTPConfigured}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="avatar_url"
|
||||
|
|
@ -371,23 +375,19 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.email)}
|
||||
placeholder={t("profile_general.email_placeholder")}
|
||||
className={`nodedc-settings-input w-full cursor-not-allowed !bg-white/4 ${
|
||||
errors.email ? "border-danger-strong" : ""
|
||||
}`}
|
||||
className={`nodedc-settings-input w-full !bg-white/4 ${errors.email ? "border-danger-strong" : ""}`}
|
||||
autoComplete="on"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isSMTPConfigured && (
|
||||
<button
|
||||
type="button"
|
||||
className="nodedc-settings-chip flex w-fit items-center gap-2 px-3.5 py-1.5 text-12 font-medium text-primary"
|
||||
onClick={() => setIsChangeEmailModalOpen(true)}
|
||||
>
|
||||
{t("account_settings.profile.change_email_modal.title")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="nodedc-settings-chip flex w-fit items-center gap-2 px-3.5 py-1.5 text-12 font-medium text-primary"
|
||||
onClick={() => setIsChangeEmailModalOpen(true)}
|
||||
>
|
||||
{t("account_settings.profile.change_email_modal.title")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,10 @@ export const ProjectAuthWrapper = observer(function ProjectAuthWrapper(props: IP
|
|||
() => fetchProjectDetails(workspaceSlug, projectId)
|
||||
);
|
||||
// fetching user project member information
|
||||
useSWR(PROJECT_ME_INFORMATION(workspaceSlug, projectId), () => fetchUserProjectInfo(workspaceSlug, projectId));
|
||||
const { isLoading: isProjectMemberLoading, error: projectMemberError } = useSWR(
|
||||
PROJECT_ME_INFORMATION(workspaceSlug, projectId),
|
||||
() => fetchUserProjectInfo(workspaceSlug, projectId)
|
||||
);
|
||||
// fetching project member preferences
|
||||
useSWR(
|
||||
currentUserData?.id ? PROJECT_MEMBER_PREFERENCES(projectId, currentProjectRole) : null,
|
||||
|
|
@ -142,14 +145,17 @@ export const ProjectAuthWrapper = observer(function ProjectAuthWrapper(props: IP
|
|||
joinProject(workspaceSlug, projectId).finally(() => setIsJoiningProject(false));
|
||||
};
|
||||
|
||||
const isProjectLoading = (isParentLoading || isProjectDetailsLoading) && !projectDetailsError;
|
||||
const isProjectLoading =
|
||||
(isParentLoading || isProjectDetailsLoading || isProjectMemberLoading) && !projectDetailsError && !projectMemberError;
|
||||
const accessErrorStatus =
|
||||
projectDetailsError?.status ?? (projectMemberError?.status === 404 ? 403 : projectMemberError?.status);
|
||||
|
||||
if (isProjectLoading) return null;
|
||||
|
||||
if (!isProjectLoading && hasPermissionToCurrentProject === false) {
|
||||
return (
|
||||
<ProjectAccessRestriction
|
||||
errorStatusCode={projectDetailsError?.status}
|
||||
errorStatusCode={accessErrorStatus}
|
||||
isWorkspaceAdmin={isWorkspaceAdmin}
|
||||
handleJoinProject={handleJoinProject}
|
||||
isJoinButtonDisabled={isJoiningProject}
|
||||
|
|
|
|||
|
|
@ -297,6 +297,14 @@ export class UserService extends APIService {
|
|||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateEmailDirect(data: { email: string }): Promise<IUser> {
|
||||
return this.patch("/api/users/me/email/direct/", data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const userService = new UserService();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,16 @@
|
|||
--nodedc-on-card-passive-rgb: 245 247 251;
|
||||
--nodedc-card-active-rgb: 195 255 102;
|
||||
--nodedc-on-card-active-rgb: 11 17 23;
|
||||
--nodedc-priority-none-rgb: 124 128 138;
|
||||
--nodedc-priority-none-mix-rgb: 255 255 255;
|
||||
--nodedc-priority-low-rgb: 51 163 255;
|
||||
--nodedc-priority-low-mix-rgb: 255 199 95;
|
||||
--nodedc-priority-medium-rgb: 255 213 79;
|
||||
--nodedc-priority-medium-mix-rgb: 80 126 255;
|
||||
--nodedc-priority-high-rgb: 255 136 48;
|
||||
--nodedc-priority-high-mix-rgb: 58 190 255;
|
||||
--nodedc-priority-urgent-rgb: 255 68 82;
|
||||
--nodedc-priority-urgent-mix-rgb: 48 214 188;
|
||||
--nodedc-bottom-dock-height: 2.75rem;
|
||||
--nodedc-bottom-dock-offset: 2.75rem;
|
||||
--nodedc-bottom-dock-visual-overlap: 0.625rem;
|
||||
|
|
@ -215,6 +225,144 @@
|
|||
animation: nodedcDropFillHighlight 1.6s ease-out;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card,
|
||||
.nodedc-external-card {
|
||||
--nodedc-priority-card-rgb: var(--nodedc-priority-none-rgb);
|
||||
--nodedc-priority-card-mix-rgb: var(--nodedc-priority-none-mix-rgb);
|
||||
--nodedc-priority-card-border-opacity: 0.08;
|
||||
--nodedc-priority-card-fill-opacity: 0;
|
||||
--nodedc-priority-card-glow-opacity: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-border-opacity)),
|
||||
0 12px 28px rgba(0, 0, 0, 0.2),
|
||||
0 0 18px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-glow-opacity)),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.035) !important;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card[data-priority="low"],
|
||||
.nodedc-external-card[data-priority="low"] {
|
||||
--nodedc-priority-card-rgb: var(--nodedc-priority-low-rgb);
|
||||
--nodedc-priority-card-mix-rgb: var(--nodedc-priority-low-mix-rgb);
|
||||
--nodedc-priority-card-border-opacity: 0.42;
|
||||
--nodedc-priority-card-fill-opacity: 0.46;
|
||||
--nodedc-priority-card-glow-opacity: 0.12;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card[data-priority="medium"],
|
||||
.nodedc-external-card[data-priority="medium"] {
|
||||
--nodedc-priority-card-rgb: var(--nodedc-priority-medium-rgb);
|
||||
--nodedc-priority-card-mix-rgb: var(--nodedc-priority-medium-mix-rgb);
|
||||
--nodedc-priority-card-border-opacity: 0.46;
|
||||
--nodedc-priority-card-fill-opacity: 0.5;
|
||||
--nodedc-priority-card-glow-opacity: 0.13;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card[data-priority="high"],
|
||||
.nodedc-external-card[data-priority="high"] {
|
||||
--nodedc-priority-card-rgb: var(--nodedc-priority-high-rgb);
|
||||
--nodedc-priority-card-mix-rgb: var(--nodedc-priority-high-mix-rgb);
|
||||
--nodedc-priority-card-border-opacity: 0.5;
|
||||
--nodedc-priority-card-fill-opacity: 0.54;
|
||||
--nodedc-priority-card-glow-opacity: 0.15;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card[data-priority="urgent"],
|
||||
.nodedc-external-card[data-priority="urgent"] {
|
||||
--nodedc-priority-card-rgb: var(--nodedc-priority-urgent-rgb);
|
||||
--nodedc-priority-card-mix-rgb: var(--nodedc-priority-urgent-mix-rgb);
|
||||
--nodedc-priority-card-border-opacity: 0.62;
|
||||
--nodedc-priority-card-fill-opacity: 0.62;
|
||||
--nodedc-priority-card-glow-opacity: 0.2;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card[data-active="true"],
|
||||
.nodedc-external-card[data-active="true"] {
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-border-opacity)),
|
||||
0 14px 32px rgba(0, 0, 0, 0.22),
|
||||
0 0 20px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-glow-opacity)),
|
||||
inset 0 0 0 1px rgb(var(--nodedc-accent-rgb) / 0.18),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06) !important;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card::before,
|
||||
.nodedc-external-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
background:
|
||||
radial-gradient(
|
||||
120% 90% at 50% 118%,
|
||||
rgb(var(--nodedc-priority-card-rgb) / 0.74) 0%,
|
||||
rgb(var(--nodedc-priority-card-rgb) / 0.28) 30%,
|
||||
transparent 64%
|
||||
),
|
||||
radial-gradient(
|
||||
105% 78% at 50% -26%,
|
||||
rgb(var(--nodedc-priority-card-mix-rgb) / 0.3) 0%,
|
||||
transparent 62%
|
||||
),
|
||||
radial-gradient(
|
||||
62% 70% at -18% 50%,
|
||||
rgb(var(--nodedc-priority-card-rgb) / 0.24) 0%,
|
||||
transparent 68%
|
||||
),
|
||||
radial-gradient(
|
||||
62% 70% at 118% 50%,
|
||||
rgb(var(--nodedc-priority-card-mix-rgb) / 0.22) 0%,
|
||||
transparent 68%
|
||||
);
|
||||
opacity: var(--nodedc-priority-card-fill-opacity);
|
||||
mask: radial-gradient(72% 58% at 50% 50%, transparent 0%, transparent 52%, rgba(0, 0, 0, 0.42) 68%, #000 100%);
|
||||
-webkit-mask: radial-gradient(
|
||||
72% 58% at 50% 50%,
|
||||
transparent 0%,
|
||||
transparent 52%,
|
||||
rgba(0, 0, 0, 0.42) 68%,
|
||||
#000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.nodedc-work-item-card::after,
|
||||
.nodedc-external-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
padding: 1px;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
background:
|
||||
linear-gradient(
|
||||
145deg,
|
||||
rgb(var(--nodedc-priority-card-rgb) / 0.82) 0%,
|
||||
rgb(var(--nodedc-priority-card-mix-rgb) / 0.44) 32%,
|
||||
rgba(255, 255, 255, 0.18) 48%,
|
||||
rgb(var(--nodedc-priority-card-rgb) / 0.34) 100%
|
||||
);
|
||||
opacity: var(--nodedc-priority-card-border-opacity);
|
||||
mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
mask-composite: exclude;
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card > *,
|
||||
.nodedc-external-card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Progress Bar Styles */
|
||||
:root {
|
||||
--bprogress-color: var(--background-color-accent-primary);
|
||||
|
|
@ -1384,6 +1532,25 @@
|
|||
color: rgba(11, 17, 23, 0.72) !important;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card,
|
||||
.nodedc-external-card {
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-border-opacity)),
|
||||
0 12px 28px rgba(0, 0, 0, 0.2),
|
||||
0 0 18px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-glow-opacity)),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.035) !important;
|
||||
}
|
||||
|
||||
.nodedc-work-item-card[data-active="true"],
|
||||
.nodedc-external-card[data-active="true"] {
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-border-opacity)),
|
||||
0 14px 32px rgba(0, 0, 0, 0.22),
|
||||
0 0 20px rgb(var(--nodedc-priority-card-rgb) / var(--nodedc-priority-card-glow-opacity)),
|
||||
inset 0 0 0 1px rgb(var(--nodedc-accent-rgb) / 0.18),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06) !important;
|
||||
}
|
||||
|
||||
.nodedc-external-content-shell {
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
|
|
|
|||
|
|
@ -1569,6 +1569,8 @@ export default {
|
|||
change_email_modal: {
|
||||
title: "Change email",
|
||||
description: "Enter a new email address to receive a verification link.",
|
||||
direct_description:
|
||||
"Enter a new email address. SMTP is not configured, so the address will be changed directly and you will need to sign in again.",
|
||||
toasts: {
|
||||
success_title: "Success!",
|
||||
success_message: "Email updated successfully. Please sign in again.",
|
||||
|
|
|
|||
|
|
@ -1733,6 +1733,8 @@ export default {
|
|||
change_email_modal: {
|
||||
title: "Изменить email",
|
||||
description: "Введите новый адрес электронной почты, чтобы получить ссылку для подтверждения.",
|
||||
direct_description:
|
||||
"Введите новый адрес электронной почты. SMTP не настроен, поэтому адрес будет изменен напрямую, после сохранения потребуется войти снова.",
|
||||
toasts: {
|
||||
success_title: "Успех!",
|
||||
success_message: "Email успешно обновлён. Пожалуйста, войдите снова.",
|
||||
|
|
|
|||
Loading…
Reference in New Issue