UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: auth screens, project actions и полировка внешних контуров
This commit is contained in:
parent
c6ace8b9cc
commit
8fed697531
|
|
@ -90,7 +90,7 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
|
||||||
hasDirectTargetAccess={hasDirectTargetAccess}
|
hasDirectTargetAccess={hasDirectTargetAccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ContentWrapper className="divide-y-2 divide-subtle-1">
|
<ContentWrapper className="space-y-4 px-4 py-4">
|
||||||
<ExternalContoursIssueMainContent
|
<ExternalContoursIssueMainContent
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
sourceProjectId={projectId}
|
sourceProjectId={projectId}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export function ExternalContourDeclineModal(props: Props) {
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
position={EModalPosition.CENTER}
|
position={EModalPosition.CENTER}
|
||||||
width={EModalWidth.LG}
|
width={EModalWidth.LG}
|
||||||
className="rounded-lg"
|
className="nodedc-glass-modal rounded-[1.75rem]"
|
||||||
>
|
>
|
||||||
<div className="space-y-4 p-6">
|
<div className="space-y-4 p-6">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|
@ -54,13 +54,14 @@ export function ExternalContourDeclineModal(props: Props) {
|
||||||
rows={5}
|
rows={5}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
className="nodedc-modal-input min-h-[8rem] resize-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button variant="secondary" onClick={handleClose} disabled={isSubmitting}>
|
<Button variant="secondary" onClick={handleClose} disabled={isSubmitting} className="nodedc-modal-secondary-button">
|
||||||
{t("cancel")}
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={handleSubmit} disabled={isSubmitting || !comment.trim()}>
|
<Button variant="primary" onClick={handleSubmit} disabled={isSubmitting || !comment.trim()} className="nodedc-modal-primary-button">
|
||||||
{t("external_contours_page.decline_modal.submit")}
|
{t("external_contours_page.decline_modal.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||||
onSubmit={(comment) => handleDecision("decline", comment)}
|
onSubmit={(comment) => handleDecision("decline", comment)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Row className="relative z-15 hidden h-full w-full items-center justify-between gap-2 border-b border-subtle bg-surface-1 lg:flex">
|
<Row className="relative z-15 hidden h-full w-full items-center justify-between gap-2 px-4 lg:flex">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{issue?.project_id && issue.sequence_id && (
|
{issue?.project_id && issue.sequence_id && (
|
||||||
<h3 className="flex-shrink-0 text-14 font-medium text-tertiary">
|
<h3 className="flex-shrink-0 text-14 font-medium text-tertiary">
|
||||||
|
|
@ -157,11 +157,11 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{canReviewClosedRequest && (
|
{canReviewClosedRequest && (
|
||||||
<>
|
<>
|
||||||
<Button variant="secondary" size="lg" onClick={() => handleDecision("accept")}>
|
<Button variant="primary" size="lg" onClick={() => handleDecision("accept")} className="nodedc-external-primary-button">
|
||||||
<CheckCircleFilledIcon className="size-4 shrink-0 text-success-secondary" />
|
<CheckCircleFilledIcon className="size-4 shrink-0 text-success-secondary" />
|
||||||
{t("external_contours_page.actions.accept")}
|
{t("external_contours_page.actions.accept")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" size="lg" onClick={() => setIsDeclineModalOpen(true)}>
|
<Button variant="secondary" size="lg" onClick={() => setIsDeclineModalOpen(true)} className="nodedc-external-action-button">
|
||||||
<CloseCircleFilledIcon className="size-4 shrink-0 text-danger-secondary" />
|
<CloseCircleFilledIcon className="size-4 shrink-0 text-danger-secondary" />
|
||||||
{t("external_contours_page.actions.decline")}
|
{t("external_contours_page.actions.decline")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -172,12 +172,18 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||||
<Badge variant="neutral">{t("external_contours_page.traceability.source_decision_accepted")}</Badge>
|
<Badge variant="neutral">{t("external_contours_page.traceability.source_decision_accepted")}</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button variant="secondary" size="lg" prependIcon={<LinkIcon className="h-2.5 w-2.5" />} onClick={handleCopyLink}>
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
prependIcon={<LinkIcon className="h-2.5 w-2.5" />}
|
||||||
|
onClick={handleCopyLink}
|
||||||
|
className="nodedc-external-action-button"
|
||||||
|
>
|
||||||
{t("external_contours_page.actions.copy")}
|
{t("external_contours_page.actions.copy")}
|
||||||
</Button>
|
</Button>
|
||||||
{hasDirectTargetAccess && (
|
{hasDirectTargetAccess && (
|
||||||
<ControlLink href={workItemLink} onClick={() => router.push(workItemLink)} target="_self">
|
<ControlLink href={workItemLink} onClick={() => router.push(workItemLink)} target="_self">
|
||||||
<Button variant="secondary" size="lg" prependIcon={<NewTabIcon className="h-2.5 w-2.5" />}>
|
<Button variant="secondary" size="lg" prependIcon={<NewTabIcon className="h-2.5 w-2.5" />} className="nodedc-external-action-button">
|
||||||
{t("external_contours_page.actions.open")}
|
{t("external_contours_page.actions.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</ControlLink>
|
</ControlLink>
|
||||||
|
|
@ -196,10 +202,10 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
{canReviewClosedRequest && (
|
{canReviewClosedRequest && (
|
||||||
<>
|
<>
|
||||||
<Button variant="secondary" size="sm" onClick={() => handleDecision("accept")}>
|
<Button variant="primary" size="sm" onClick={() => handleDecision("accept")} className="nodedc-external-primary-button">
|
||||||
{t("external_contours_page.actions.accept")}
|
{t("external_contours_page.actions.accept")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" size="sm" onClick={() => setIsDeclineModalOpen(true)}>
|
<Button variant="secondary" size="sm" onClick={() => setIsDeclineModalOpen(true)} className="nodedc-external-action-button">
|
||||||
{t("external_contours_page.actions.decline")}
|
{t("external_contours_page.actions.decline")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
@ -209,7 +215,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||||
)}
|
)}
|
||||||
{hasDirectTargetAccess && (
|
{hasDirectTargetAccess && (
|
||||||
<ControlLink href={workItemLink} onClick={() => router.push(workItemLink)} target="_self">
|
<ControlLink href={workItemLink} onClick={() => router.push(workItemLink)} target="_self">
|
||||||
<Button variant="secondary" size="sm">
|
<Button variant="secondary" size="sm" className="nodedc-external-action-button">
|
||||||
{t("external_contours_page.actions.open")}
|
{t("external_contours_page.actions.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</ControlLink>
|
</ControlLink>
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||||
if (!hasDirectTargetAccess) {
|
if (!hasDirectTargetAccess) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="nodedc-external-section space-y-4 p-5">
|
<div className="nodedc-external-content-shell space-y-4 p-5">
|
||||||
{isSourceEditable ? (
|
{isSourceEditable ? (
|
||||||
<>
|
<>
|
||||||
<IssueTitleInput
|
<IssueTitleInput
|
||||||
|
|
@ -239,8 +239,8 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="space-y-4">
|
||||||
<div className="space-y-4 pb-4">
|
<div className="nodedc-external-content-shell space-y-4 p-5">
|
||||||
{duplicateIssues.length > 0 && (
|
{duplicateIssues.length > 0 && (
|
||||||
<DeDupeIssuePopoverRoot
|
<DeDupeIssuePopoverRoot
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
@ -313,15 +313,13 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-4">
|
|
||||||
<ExternalContoursRequestTraceability contourRequest={contourRequest} />
|
<ExternalContoursRequestTraceability contourRequest={contourRequest} />
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="py-4">
|
<div className="nodedc-external-section px-4 py-4">
|
||||||
<IssueAttachmentRoot workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} disabled={!isEditable} />
|
<IssueAttachmentRoot workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} disabled={!isEditable} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-4">
|
<div className="nodedc-external-section px-4 py-4">
|
||||||
<ExternalContoursIssueContentProperties
|
<ExternalContoursIssueContentProperties
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
targetProjectId={targetProjectId}
|
targetProjectId={targetProjectId}
|
||||||
|
|
@ -331,9 +329,9 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4">
|
<div className="nodedc-external-section px-4 py-4">
|
||||||
<IssueActivity workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} />
|
<IssueActivity workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,11 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
||||||
if (!request || !issue) return <></>;
|
if (!request || !issue) return <></>;
|
||||||
|
|
||||||
const assigneeDetails = issue.assignee_details?.slice(0, 2) ?? [];
|
const assigneeDetails = issue.assignee_details?.slice(0, 2) ?? [];
|
||||||
const visibleLabels = issue.label_details?.slice(0, 3) ?? [];
|
|
||||||
const lastUpdatedAt = issue.updated_at || request.updated_at;
|
const lastUpdatedAt = issue.updated_at || request.updated_at;
|
||||||
|
const requester = issue.created_by_detail;
|
||||||
|
const requesterName =
|
||||||
|
request.requested_by_name || requester?.display_name || t("external_contours_page.mirror.system_actor");
|
||||||
|
const contourName = request.target_project_name || issue.project_detail?.name || t("common.none");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -55,57 +58,48 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
||||||
<Row
|
<Row
|
||||||
data-active={selectedInboxIssueId === request.id}
|
data-active={selectedInboxIssueId === request.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"nodedc-external-card relative flex cursor-pointer flex-col gap-4 px-4 py-4 transition-all hover:bg-white/5",
|
"nodedc-external-card relative flex min-h-[15rem] cursor-pointer flex-col gap-5 px-5 py-5 transition-all hover:bg-white/5",
|
||||||
{ "ring-1 ring-accent-primary/35": selectedInboxIssueId === request.id }
|
{ "ring-1 ring-accent-primary/35": selectedInboxIssueId === request.id }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="relative flex items-center justify-between gap-2">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
<div className="flex min-w-0 items-center gap-2 text-11 font-medium text-tertiary">
|
<Avatar
|
||||||
<span>
|
src={requester?.avatar_url || ""}
|
||||||
|
name={requesterName}
|
||||||
|
size="lg"
|
||||||
|
showTooltip
|
||||||
|
/>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="truncate text-15 font-semibold text-primary">{requesterName}</div>
|
||||||
|
<div className="truncate text-13 text-secondary">{contourName}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
|
<div className="text-11 font-medium text-tertiary">
|
||||||
{issue.project_detail?.identifier || "REQ"}-{issue.sequence_id}
|
{issue.project_detail?.identifier || "REQ"}-{issue.sequence_id}
|
||||||
</span>
|
</div>
|
||||||
{issue.project_detail?.name && <span className="truncate text-placeholder">{issue.project_detail.name}</span>}
|
<div className="flex items-center gap-2">
|
||||||
{request.has_unread_updates && (
|
{request.has_unread_updates && (
|
||||||
<Tooltip tooltipHeading={t("external_contours_page.list.unread_updates")} isMobile={isMobile}>
|
<Tooltip tooltipHeading={t("external_contours_page.list.unread_updates")} isMobile={isMobile}>
|
||||||
<span className="size-2 rounded-full bg-accent-primary" />
|
<span className="size-2 rounded-full bg-accent-primary" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className="shrink-0">
|
|
||||||
<ExternalContourStatePill request={request} />
|
<ExternalContourStatePill request={request} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="w-full text-15 leading-6 font-semibold text-primary">{issue.name}</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex flex-1 items-center justify-center px-2">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<h3 className="line-clamp-3 w-full max-w-[18rem] text-center text-16 leading-7 font-semibold text-primary">
|
||||||
<Tooltip
|
{issue.name}
|
||||||
tooltipHeading={t("external_contours_page.list.last_updated")}
|
</h3>
|
||||||
tooltipContent={`${renderFormattedDate(lastUpdatedAt ?? "")}`}
|
|
||||||
isMobile={isMobile}
|
|
||||||
>
|
|
||||||
<div className="text-11 text-secondary">{renderFormattedDate(lastUpdatedAt ?? "")}</div>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{issue.priority && issue.priority !== "none" && (
|
|
||||||
<Tooltip tooltipHeading={t("priority")} tooltipContent={`${issue.priority ?? t("none")}`}>
|
|
||||||
<PriorityIcon priority={issue.priority} withContainer className="h-3 w-3" />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{visibleLabels.map((label) => (
|
|
||||||
<div key={label.id} className="relative flex h-6 items-center gap-1 rounded-full bg-white/6 px-2 text-11">
|
|
||||||
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: label.color }} />
|
|
||||||
<span className="max-w-28 truncate normal-case">{label.name}</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
{assigneeDetails.length > 0 ? (
|
{assigneeDetails.length > 0 ? (
|
||||||
<>
|
assigneeDetails.map((assignee) => (
|
||||||
{assigneeDetails.map((assignee) => (
|
|
||||||
<Avatar
|
<Avatar
|
||||||
key={assignee.id}
|
key={assignee.id}
|
||||||
src={assignee.avatar_url || ""}
|
src={assignee.avatar_url || ""}
|
||||||
|
|
@ -113,14 +107,30 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
|
||||||
size="lg"
|
size="lg"
|
||||||
showTooltip
|
showTooltip
|
||||||
/>
|
/>
|
||||||
))}
|
))
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<Tooltip tooltipHeading={t("assignee")} tooltipContent={t("external_contours_page.list.unassigned")} isMobile={isMobile}>
|
<Tooltip tooltipHeading={t("assignee")} tooltipContent={t("external_contours_page.list.unassigned")} isMobile={isMobile}>
|
||||||
<div className="text-11 text-placeholder">{t("external_contours_page.list.unassigned")}</div>
|
<div className="text-11 text-placeholder">{t("external_contours_page.list.unassigned")}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||||
|
<Tooltip
|
||||||
|
tooltipHeading={t("external_contours_page.list.last_updated")}
|
||||||
|
tooltipContent={`${renderFormattedDate(lastUpdatedAt ?? "")}`}
|
||||||
|
isMobile={isMobile}
|
||||||
|
>
|
||||||
|
<div className="rounded-full bg-white/6 px-3 py-1.5 text-12 text-secondary">
|
||||||
|
{renderFormattedDate(lastUpdatedAt ?? "")}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{issue.priority && issue.priority !== "none" && (
|
||||||
|
<Tooltip tooltipHeading={t("priority")} tooltipContent={`${issue.priority ?? t("none")}`}>
|
||||||
|
<PriorityIcon priority={issue.priority} withContainer className="h-3 w-3" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ export const AuthRoot = observer(function AuthRoot(props: TAuthRoot) {
|
||||||
|
|
||||||
function AuthContainer({ children }: { children: React.ReactNode }) {
|
function AuthContainer({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-6 flex w-full flex-grow flex-col items-center justify-center py-8">
|
<div className="mt-8 flex w-full flex-grow flex-col items-center justify-center py-8">
|
||||||
<div className="nodedc-auth-shell relative flex w-full max-w-[28rem] flex-col gap-6">{children}</div>
|
<div className="nodedc-auth-shell relative flex w-full max-w-[28rem] flex-col gap-6">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ type AuthBaseProps = {
|
||||||
|
|
||||||
export function AuthBase({ authType }: AuthBaseProps) {
|
export function AuthBase({ authType }: AuthBaseProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 flex h-screen w-screen flex-col items-center overflow-hidden overflow-y-auto bg-canvas px-8 pt-8 pb-10">
|
<div className="relative z-10 flex h-screen w-screen flex-col items-center overflow-hidden overflow-y-auto bg-canvas px-8 pt-10 pb-12">
|
||||||
<AuthHeader type={authType} />
|
<AuthHeader type={authType} />
|
||||||
<AuthRoot authMode={authType} />
|
<AuthRoot authMode={authType} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,11 @@ export function AuthHeaderBase(props: TAuthHeaderBase) {
|
||||||
<PageHead title={pageTitle + " - NODE.DC"} />
|
<PageHead title={pageTitle + " - NODE.DC"} />
|
||||||
<div className="sticky top-0 flex w-full flex-shrink-0 items-center justify-between gap-6 px-2 py-1">
|
<div className="sticky top-0 flex w-full flex-shrink-0 items-center justify-between gap-6 px-2 py-1">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<PlaneLockup height={40} width={190} className="text-primary transition-opacity hover:opacity-90" />
|
<PlaneLockup
|
||||||
|
height={54}
|
||||||
|
width={258}
|
||||||
|
className="nodedc-auth-logo-lockup text-primary transition-opacity hover:opacity-90"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
{additionalAction}
|
{additionalAction}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { ACCEPTED_COVER_IMAGE_MIME_TYPES_FOR_REACT_DROPZONE, MAX_FILE_SIZE } fro
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
import { useOutsideClickDetector } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Tabs } from "@plane/propel/tabs";
|
import { Tabs } from "@plane/propel/tabs";
|
||||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
import { Button } from "@plane/propel/button";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
import { EFileAssetType } from "@plane/types";
|
import { EFileAssetType } from "@plane/types";
|
||||||
import { Input, Loader } from "@plane/ui";
|
import { Input, Loader } from "@plane/ui";
|
||||||
|
|
@ -204,7 +204,9 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr
|
||||||
return (
|
return (
|
||||||
<Popover className="relative z-19" ref={ref} tabIndex={tabIndex} onKeyDown={handleKeyDown}>
|
<Popover className="relative z-19" ref={ref} tabIndex={tabIndex} onKeyDown={handleKeyDown}>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={cn(getButtonStyling("secondary", "sm"), buttonClassName)}
|
className={cn(
|
||||||
|
buttonClassName || "inline-flex min-h-10 items-center justify-center rounded-[1.25rem] px-4 py-2"
|
||||||
|
)}
|
||||||
onClick={handleOnClick}
|
onClick={handleOnClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -17,39 +17,46 @@ export function InstanceNotReady() {
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<div className="relative z-10 flex h-screen w-screen overflow-hidden">
|
<div className="relative z-10 flex h-screen w-screen overflow-hidden">
|
||||||
{/* Background decorations */}
|
|
||||||
<img
|
|
||||||
src={GradientBgLogo}
|
|
||||||
className="pointer-events-none absolute -top-24 -left-32 h-56 w-96 opacity-15"
|
|
||||||
alt=""
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={GradientBgLogo}
|
|
||||||
className="pointer-events-none absolute -right-20 -bottom-16 h-56 w-96 opacity-15"
|
|
||||||
alt=""
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{/* Main content */}
|
|
||||||
<div className="flex h-full w-full flex-col items-center px-8 pt-6 pb-10">
|
<div className="flex h-full w-full flex-col items-center px-8 pt-6 pb-10">
|
||||||
<div className="sticky top-0 flex w-full shrink-0 items-center justify-between gap-6">
|
<div className="sticky top-0 flex w-full shrink-0 items-center justify-between gap-6">
|
||||||
<PlaneLockup height={20} width={95} className="text-primary" />
|
<PlaneLockup height={40} width={190} className="nodedc-auth-logo-lockup text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-7">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-7">
|
||||||
<div className="flex flex-col items-center gap-11">
|
<div className="nodedc-error-shell flex max-w-3xl flex-col items-center gap-11 text-center">
|
||||||
|
<img
|
||||||
|
src={GradientBgLogo}
|
||||||
|
className="pointer-events-none absolute -top-24 -left-32 h-56 w-96 opacity-12"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={GradientBgLogo}
|
||||||
|
className="pointer-events-none absolute -right-20 -bottom-16 h-56 w-96 opacity-12"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<img src={GradientLogo} className="h-24 w-40 object-contain" alt="NODE.DC Logo" />
|
<img src={GradientLogo} className="h-24 w-40 object-contain" alt="NODE.DC Logo" />
|
||||||
<div className="flex max-w-124 flex-col items-center gap-3">
|
<div className="flex max-w-124 flex-col items-center gap-3">
|
||||||
<h1 className="text-h2-semibold text-primary">Welcome to NODE.DC</h1>
|
<h1 className="text-h2-semibold text-primary">NODE.DC готов к запуску</h1>
|
||||||
<p className="text-center text-body-md-regular text-secondary">
|
<p className="text-center text-body-md-regular text-secondary">
|
||||||
Set up your instance and create your first workspace to begin managing projects and work.
|
Завершите настройку инстанса и создайте первое рабочее пространство, чтобы начать работу с
|
||||||
|
проектами и рабочими элементами.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<a href={GOD_MODE_URL} className="w-80">
|
||||||
<a href={GOD_MODE_URL} className="w-72">
|
<Button variant="primary" className="nodedc-error-primary w-full" size="xl">
|
||||||
<Button variant="primary" className="w-full" size="xl">
|
Перейти к настройке
|
||||||
Get started
|
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://nodedc.dctouch.ru/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="nodedc-error-link text-13 font-medium"
|
||||||
|
>
|
||||||
|
Служба поддержки
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ function ProjectCreateHeader(props: Props) {
|
||||||
}}
|
}}
|
||||||
control={control}
|
control={control}
|
||||||
value={value ?? null}
|
value={value ?? null}
|
||||||
buttonClassName="nodedc-overlay-button"
|
buttonClassName="nodedc-overlay-button min-w-[10.5rem]"
|
||||||
tabIndex={getIndex("cover_image")}
|
tabIndex={getIndex("cover_image")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
||||||
control={control}
|
control={control}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={value ?? null}
|
value={value ?? null}
|
||||||
buttonClassName="nodedc-overlay-button"
|
buttonClassName="nodedc-overlay-button min-w-[10.5rem]"
|
||||||
disabled={!isAdmin}
|
disabled={!isAdmin}
|
||||||
projectId={project.id}
|
projectId={project.id}
|
||||||
/>
|
/>
|
||||||
|
|
@ -436,7 +436,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
disabled={!isAdmin}
|
disabled={!isAdmin}
|
||||||
className="nodedc-settings-save-button min-w-[11.5rem]"
|
className="nodedc-settings-save-button min-w-[11.5rem] !text-[#0b1117] hover:!text-[#0b1117]"
|
||||||
>
|
>
|
||||||
{isLoading ? t("updating") : t("common.update_project")}
|
{isLoading ? t("updating") : t("common.update_project")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export const ProjectFeatureUpdate = observer(function ProjectFeatureUpdate(props
|
||||||
<Link
|
<Link
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/issues`}
|
href={`/${workspaceSlug}/projects/${projectId}/issues`}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="nodedc-modal-primary-button inline-flex min-w-[10.5rem] items-center justify-center"
|
className="nodedc-modal-primary-button inline-flex min-w-[10.5rem] items-center justify-center !text-[#0b1117] hover:!text-[#0b1117]"
|
||||||
tabIndex={2}
|
tabIndex={2}
|
||||||
>
|
>
|
||||||
{t("open_project")}
|
{t("open_project")}
|
||||||
|
|
|
||||||
|
|
@ -494,6 +494,15 @@
|
||||||
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nodedc-modal-primary-button,
|
||||||
|
.nodedc-modal-primary-button *,
|
||||||
|
.nodedc-settings-primary-button,
|
||||||
|
.nodedc-settings-primary-button *,
|
||||||
|
.nodedc-settings-save-button,
|
||||||
|
.nodedc-settings-save-button * {
|
||||||
|
color: #0b1117 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nodedc-modal-danger-button {
|
.nodedc-modal-danger-button {
|
||||||
min-height: 2.75rem;
|
min-height: 2.75rem;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
|
|
@ -650,18 +659,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodedc-overlay-button {
|
.nodedc-overlay-button {
|
||||||
min-height: 2.5rem;
|
min-height: 2.75rem;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border-radius: 1.05rem !important;
|
border-radius: 1.25rem !important;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.018) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.045) 0%, rgba(255, 255, 255, 0.02) 100%),
|
||||||
rgba(8, 8, 10, 0.58) !important;
|
rgba(9, 9, 12, 0.72) !important;
|
||||||
color: #f5f7fb !important;
|
color: #f5f7fb !important;
|
||||||
padding-inline: 1rem !important;
|
padding-inline: 1.05rem !important;
|
||||||
-webkit-backdrop-filter: blur(18px);
|
-webkit-backdrop-filter: blur(22px);
|
||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(22px);
|
||||||
transition:
|
transition:
|
||||||
background 120ms ease,
|
background 120ms ease,
|
||||||
color 120ms ease,
|
color 120ms ease,
|
||||||
|
|
@ -670,8 +679,8 @@
|
||||||
|
|
||||||
.nodedc-overlay-button:hover {
|
.nodedc-overlay-button:hover {
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.03) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.03) 100%),
|
||||||
rgba(8, 8, 10, 0.72) !important;
|
rgba(9, 9, 12, 0.8) !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -798,17 +807,24 @@
|
||||||
|
|
||||||
.nodedc-auth-shell {
|
.nodedc-auth-shell {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 28rem;
|
max-width: 32rem;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow:
|
||||||
border-radius: 1.75rem !important;
|
0 24px 64px rgba(0, 0, 0, 0.34),
|
||||||
padding: 1.75rem !important;
|
0 8px 20px rgba(0, 0, 0, 0.18) !important;
|
||||||
|
border-radius: 1.9rem !important;
|
||||||
|
padding: 2.2rem !important;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.015) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.048) 0%, rgba(255, 255, 255, 0.015) 100%),
|
||||||
rgba(10, 10, 12, 0.82) !important;
|
rgba(9, 9, 12, 0.84) !important;
|
||||||
-webkit-backdrop-filter: blur(34px);
|
-webkit-backdrop-filter: blur(40px);
|
||||||
backdrop-filter: blur(34px);
|
backdrop-filter: blur(40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-auth-logo-lockup {
|
||||||
|
color: var(--text-color-primary) !important;
|
||||||
|
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.2));
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodedc-auth-banner {
|
.nodedc-auth-banner {
|
||||||
|
|
@ -885,14 +901,16 @@
|
||||||
max-width: 36rem;
|
max-width: 36rem;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow:
|
||||||
border-radius: 1.9rem !important;
|
0 24px 64px rgba(0, 0, 0, 0.34),
|
||||||
padding: 2rem !important;
|
0 8px 20px rgba(0, 0, 0, 0.18) !important;
|
||||||
|
border-radius: 1.95rem !important;
|
||||||
|
padding: 2.15rem !important;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.015) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.045) 0%, rgba(255, 255, 255, 0.016) 100%),
|
||||||
rgba(10, 10, 12, 0.82) !important;
|
rgba(9, 9, 12, 0.86) !important;
|
||||||
-webkit-backdrop-filter: blur(36px);
|
-webkit-backdrop-filter: blur(40px);
|
||||||
backdrop-filter: blur(36px);
|
backdrop-filter: blur(40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodedc-error-link {
|
.nodedc-error-link {
|
||||||
|
|
@ -955,10 +973,10 @@
|
||||||
.nodedc-external-sidebar-shell {
|
.nodedc-external-sidebar-shell {
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.035) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||||
rgba(8, 8, 11, 0.82) !important;
|
rgba(8, 8, 11, 0.86) !important;
|
||||||
-webkit-backdrop-filter: blur(26px);
|
-webkit-backdrop-filter: blur(30px);
|
||||||
backdrop-filter: blur(26px);
|
backdrop-filter: blur(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodedc-external-tab {
|
.nodedc-external-tab {
|
||||||
|
|
@ -985,23 +1003,65 @@
|
||||||
.nodedc-external-card {
|
.nodedc-external-card {
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow:
|
||||||
border-radius: 1.9rem !important;
|
0 12px 28px rgba(0, 0, 0, 0.12),
|
||||||
background: rgb(var(--nodedc-card-passive-rgb)) !important;
|
inset 0 1px 0 rgba(255, 255, 255, 0.018) !important;
|
||||||
|
border-radius: 2rem !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.018) 0%, rgba(255, 255, 255, 0.006) 100%),
|
||||||
|
rgb(var(--nodedc-card-passive-rgb)) !important;
|
||||||
color: var(--text-color-primary) !important;
|
color: var(--text-color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodedc-external-card[data-active="true"] {
|
.nodedc-external-card[data-active="true"] {
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.01) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.024) 0%, rgba(255, 255, 255, 0.008) 100%),
|
||||||
rgba(255, 255, 255, 0.04) !important;
|
rgba(255, 255, 255, 0.035) !important;
|
||||||
box-shadow: inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.3);
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.32),
|
||||||
|
0 12px 32px rgba(0, 0, 0, 0.16) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-content-shell {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow:
|
||||||
|
0 18px 44px rgba(0, 0, 0, 0.18),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.02) !important;
|
||||||
|
border-radius: 2rem !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.034) 0%, rgba(255, 255, 255, 0.014) 100%),
|
||||||
|
rgba(255, 255, 255, 0.03) !important;
|
||||||
|
-webkit-backdrop-filter: blur(28px);
|
||||||
|
backdrop-filter: blur(28px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-field {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 1.4rem !important;
|
||||||
|
background: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-field:hover,
|
||||||
|
.nodedc-external-field:focus-within {
|
||||||
|
background: rgba(255, 255, 255, 0.055) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-chip {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 999px !important;
|
||||||
|
background: rgba(255, 255, 255, 0.06) !important;
|
||||||
|
color: var(--text-color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodedc-external-panel {
|
.nodedc-external-panel {
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.018) !important;
|
||||||
border-radius: 1.6rem !important;
|
border-radius: 1.6rem !important;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.012) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||||
|
|
@ -1013,7 +1073,9 @@
|
||||||
.nodedc-external-section {
|
.nodedc-external-section {
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow:
|
||||||
|
0 12px 32px rgba(0, 0, 0, 0.12),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.018) !important;
|
||||||
border-radius: 1.5rem !important;
|
border-radius: 1.5rem !important;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||||
|
|
@ -1030,4 +1092,35 @@
|
||||||
background: rgba(255, 255, 255, 0.035) !important;
|
background: rgba(255, 255, 255, 0.035) !important;
|
||||||
color: var(--text-color-secondary) !important;
|
color: var(--text-color-secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nodedc-external-action-button {
|
||||||
|
min-height: 2.75rem;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 1.25rem !important;
|
||||||
|
background: rgba(255, 255, 255, 0.06) !important;
|
||||||
|
color: var(--text-color-primary) !important;
|
||||||
|
padding-inline: 1.15rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-action-button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-primary-button {
|
||||||
|
min-height: 2.75rem;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 1.25rem !important;
|
||||||
|
background: rgb(var(--nodedc-card-active-rgb)) !important;
|
||||||
|
color: #0b1117 !important;
|
||||||
|
padding-inline: 1.2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-external-primary-button:hover {
|
||||||
|
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
||||||
|
color: #0b1117 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,32 +60,32 @@ export type PasswordCriteria = {
|
||||||
export const getPasswordCriteria = (password: string): PasswordCriteria[] => [
|
export const getPasswordCriteria = (password: string): PasswordCriteria[] => [
|
||||||
{
|
{
|
||||||
key: "length",
|
key: "length",
|
||||||
label: "Min 8 characters",
|
label: "Минимум 8 символов",
|
||||||
isValid: password.length >= 8,
|
isValid: password.length >= 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "uppercase",
|
key: "uppercase",
|
||||||
label: "Min 1 upper-case letter",
|
label: "Минимум 1 заглавная буква",
|
||||||
isValid: /[A-Z]/.test(password),
|
isValid: /[A-Z]/.test(password),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "lowercase",
|
key: "lowercase",
|
||||||
label: "Min 1 lower-case letter",
|
label: "Минимум 1 строчная буква",
|
||||||
isValid: /[a-z]/.test(password),
|
isValid: /[a-z]/.test(password),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "number",
|
key: "number",
|
||||||
label: "Min 1 number",
|
label: "Минимум 1 цифра",
|
||||||
isValid: /[0-9]/.test(password),
|
isValid: /[0-9]/.test(password),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "special",
|
key: "special",
|
||||||
label: "Min 1 special character",
|
label: "Минимум 1 спецсимвол",
|
||||||
isValid: /[!@#$%^&*()\-_+=\[\]{}|;:'",.<>?/]/.test(password),
|
isValid: /[!@#$%^&*()\-_+=\[\]{}|;:'",.<>?/]/.test(password),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "predictable",
|
key: "predictable",
|
||||||
label: "Avoid common or predictable patterns",
|
label: "Избегайте простых и предсказуемых шаблонов",
|
||||||
isValid: password.length >= 8 ? zxcvbn(password).score >= 3 : false,
|
isValid: password.length >= 8 ? zxcvbn(password).score >= 3 : false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -96,218 +96,218 @@ const errorCodeMessages: {
|
||||||
} = {
|
} = {
|
||||||
// global
|
// global
|
||||||
[EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: {
|
[EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: {
|
||||||
title: `Instance not configured`,
|
title: `Инстанс не настроен`,
|
||||||
message: () => `Instance not configured. Please contact your administrator.`,
|
message: () => `Инстанс не настроен. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.SIGNUP_DISABLED]: {
|
[EAuthErrorCodes.SIGNUP_DISABLED]: {
|
||||||
title: `Sign up disabled`,
|
title: `Регистрация отключена`,
|
||||||
message: () => `Sign up disabled. Please contact your administrator.`,
|
message: () => `Регистрация отключена. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_PASSWORD]: {
|
[EAuthErrorCodes.INVALID_PASSWORD]: {
|
||||||
title: `Invalid password`,
|
title: `Неверный пароль`,
|
||||||
message: () => `Invalid password. Please try again.`,
|
message: () => `Неверный пароль. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.PASSWORD_TOO_WEAK]: {
|
[EAuthErrorCodes.PASSWORD_TOO_WEAK]: {
|
||||||
title: `Password too weak`,
|
title: `Слишком простой пароль`,
|
||||||
message: () =>
|
message: () =>
|
||||||
`Password must include upper-case, lower-case, number, special character, and must not be predictable.`,
|
`Пароль должен содержать заглавные и строчные буквы, цифру, спецсимвол и не быть предсказуемым.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.SMTP_NOT_CONFIGURED]: {
|
[EAuthErrorCodes.SMTP_NOT_CONFIGURED]: {
|
||||||
title: `SMTP not configured`,
|
title: `SMTP не настроен`,
|
||||||
message: () => `SMTP not configured. Please contact your administrator.`,
|
message: () => `SMTP не настроен. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
// email check in both sign up and sign in
|
// email check in both sign up and sign in
|
||||||
[EAuthErrorCodes.INVALID_EMAIL]: {
|
[EAuthErrorCodes.INVALID_EMAIL]: {
|
||||||
title: `Invalid email`,
|
title: `Некорректный e-mail`,
|
||||||
message: () => `Invalid email. Please try again.`,
|
message: () => `Некорректный e-mail. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.EMAIL_REQUIRED]: {
|
[EAuthErrorCodes.EMAIL_REQUIRED]: {
|
||||||
title: `Email required`,
|
title: `Нужен e-mail`,
|
||||||
message: () => `Email required. Please try again.`,
|
message: () => `Укажите e-mail и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
// sign up
|
// sign up
|
||||||
[EAuthErrorCodes.USER_ALREADY_EXIST]: {
|
[EAuthErrorCodes.USER_ALREADY_EXIST]: {
|
||||||
title: `User already exists`,
|
title: `Аккаунт уже существует`,
|
||||||
message: () => `Your account is already registered. Sign in now.`,
|
message: () => `Аккаунт уже зарегистрирован. Выполните вход.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: {
|
[EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: {
|
||||||
title: `Email and password required`,
|
title: `Нужны e-mail и пароль`,
|
||||||
message: () => `Email and password required. Please try again.`,
|
message: () => `Укажите e-mail и пароль, затем попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
|
[EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
|
||||||
title: `Authentication failed`,
|
title: `Не удалось выполнить вход`,
|
||||||
message: () => `Authentication failed. Please try again.`,
|
message: () => `Не удалось выполнить вход. Проверьте данные и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_EMAIL_SIGN_UP]: {
|
[EAuthErrorCodes.INVALID_EMAIL_SIGN_UP]: {
|
||||||
title: `Invalid email`,
|
title: `Некорректный e-mail`,
|
||||||
message: () => `Invalid email. Please try again.`,
|
message: () => `Некорректный e-mail. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
|
[EAuthErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
|
||||||
title: `Email and code required`,
|
title: `Нужны e-mail и код`,
|
||||||
message: () => `Email and code required. Please try again.`,
|
message: () => `Укажите e-mail и код подтверждения, затем попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
|
[EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
|
||||||
title: `Invalid email`,
|
title: `Некорректный e-mail`,
|
||||||
message: () => `Invalid email. Please try again.`,
|
message: () => `Некорректный e-mail. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
// sign in
|
// sign in
|
||||||
[EAuthErrorCodes.USER_ACCOUNT_DEACTIVATED]: {
|
[EAuthErrorCodes.USER_ACCOUNT_DEACTIVATED]: {
|
||||||
title: `User account deactivated`,
|
title: `Аккаунт деактивирован`,
|
||||||
message: () => `User account deactivated. Please contact administrator.`,
|
message: () => `Аккаунт деактивирован. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.USER_DOES_NOT_EXIST]: {
|
[EAuthErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||||
title: `User does not exist`,
|
title: `Аккаунт не найден`,
|
||||||
message: () => `No account found. Create one to get started.`,
|
message: () => `Аккаунт не найден. Создайте новый для начала работы.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
|
[EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
|
||||||
title: `Email and password required`,
|
title: `Нужны e-mail и пароль`,
|
||||||
message: () => `Email and password required. Please try again.`,
|
message: () => `Укажите e-mail и пароль, затем попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
|
[EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
|
||||||
title: `Authentication failed`,
|
title: `Не удалось выполнить вход`,
|
||||||
message: () => `Authentication failed. Please try again.`,
|
message: () => `Не удалось выполнить вход. Проверьте данные и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_EMAIL_SIGN_IN]: {
|
[EAuthErrorCodes.INVALID_EMAIL_SIGN_IN]: {
|
||||||
title: `Invalid email`,
|
title: `Некорректный e-mail`,
|
||||||
message: () => `Invalid email. Please try again.`,
|
message: () => `Некорректный e-mail. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
|
[EAuthErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
|
||||||
title: `Email and code required`,
|
title: `Нужны e-mail и код`,
|
||||||
message: () => `Email and code required. Please try again.`,
|
message: () => `Укажите e-mail и код подтверждения, затем попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
|
[EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
|
||||||
title: `Invalid email`,
|
title: `Некорректный e-mail`,
|
||||||
message: () => `Invalid email. Please try again.`,
|
message: () => `Некорректный e-mail. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
// Both Sign in and Sign up
|
// Both Sign in and Sign up
|
||||||
[EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_IN]: {
|
[EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_IN]: {
|
||||||
title: `Authentication failed`,
|
title: `Неверный код подтверждения`,
|
||||||
message: () => `Invalid magic code. Please try again.`,
|
message: () => `Неверный код подтверждения. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_UP]: {
|
[EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_UP]: {
|
||||||
title: `Authentication failed`,
|
title: `Неверный код подтверждения`,
|
||||||
message: () => `Invalid magic code. Please try again.`,
|
message: () => `Неверный код подтверждения. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_IN]: {
|
[EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_IN]: {
|
||||||
title: `Expired magic code`,
|
title: `Код подтверждения истёк`,
|
||||||
message: () => `Expired magic code. Please try again.`,
|
message: () => `Код подтверждения истёк. Запросите новый и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_UP]: {
|
[EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_UP]: {
|
||||||
title: `Expired magic code`,
|
title: `Код подтверждения истёк`,
|
||||||
message: () => `Expired magic code. Please try again.`,
|
message: () => `Код подтверждения истёк. Запросите новый и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN]: {
|
[EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN]: {
|
||||||
title: `Expired magic code`,
|
title: `Лимит попыток исчерпан`,
|
||||||
message: () => `Expired magic code. Please try again.`,
|
message: () => `Лимит попыток ввода кода исчерпан. Запросите новый код.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP]: {
|
[EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP]: {
|
||||||
title: `Expired magic code`,
|
title: `Лимит попыток исчерпан`,
|
||||||
message: () => `Expired magic code. Please try again.`,
|
message: () => `Лимит попыток ввода кода исчерпан. Запросите новый код.`,
|
||||||
},
|
},
|
||||||
// Oauth
|
// Oauth
|
||||||
[EAuthErrorCodes.OAUTH_NOT_CONFIGURED]: {
|
[EAuthErrorCodes.OAUTH_NOT_CONFIGURED]: {
|
||||||
title: `OAuth not configured`,
|
title: `OAuth не настроен`,
|
||||||
message: () => `OAuth not configured. Please contact your administrator.`,
|
message: () => `OAuth не настроен. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.GOOGLE_NOT_CONFIGURED]: {
|
[EAuthErrorCodes.GOOGLE_NOT_CONFIGURED]: {
|
||||||
title: `Google not configured`,
|
title: `Google OAuth не настроен`,
|
||||||
message: () => `Google not configured. Please contact your administrator.`,
|
message: () => `Google OAuth не настроен. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.GITHUB_NOT_CONFIGURED]: {
|
[EAuthErrorCodes.GITHUB_NOT_CONFIGURED]: {
|
||||||
title: `GitHub not configured`,
|
title: `GitHub OAuth не настроен`,
|
||||||
message: () => `GitHub not configured. Please contact your administrator.`,
|
message: () => `GitHub OAuth не настроен. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.GITLAB_NOT_CONFIGURED]: {
|
[EAuthErrorCodes.GITLAB_NOT_CONFIGURED]: {
|
||||||
title: `GitLab not configured`,
|
title: `GitLab OAuth не настроен`,
|
||||||
message: () => `GitLab not configured. Please contact your administrator.`,
|
message: () => `GitLab OAuth не настроен. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
|
[EAuthErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
|
||||||
title: `Google OAuth provider error`,
|
title: `Ошибка Google OAuth`,
|
||||||
message: () => `Google OAuth provider error. Please try again.`,
|
message: () => `Не удалось авторизоваться через Google. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
|
[EAuthErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
|
||||||
title: `GitHub OAuth provider error`,
|
title: `Ошибка GitHub OAuth`,
|
||||||
message: () => `GitHub OAuth provider error. Please try again.`,
|
message: () => `Не удалось авторизоваться через GitHub. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.GITLAB_OAUTH_PROVIDER_ERROR]: {
|
[EAuthErrorCodes.GITLAB_OAUTH_PROVIDER_ERROR]: {
|
||||||
title: `GitLab OAuth provider error`,
|
title: `Ошибка GitLab OAuth`,
|
||||||
message: () => `GitLab OAuth provider error. Please try again.`,
|
message: () => `Не удалось авторизоваться через GitLab. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
// Reset Password
|
// Reset Password
|
||||||
[EAuthErrorCodes.INVALID_PASSWORD_TOKEN]: {
|
[EAuthErrorCodes.INVALID_PASSWORD_TOKEN]: {
|
||||||
title: `Invalid password token`,
|
title: `Некорректный токен`,
|
||||||
message: () => `Invalid password token. Please try again.`,
|
message: () => `Некорректный токен восстановления. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
|
[EAuthErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
|
||||||
title: `Expired password token`,
|
title: `Токен истёк`,
|
||||||
message: () => `Expired password token. Please try again.`,
|
message: () => `Токен восстановления истёк. Запросите новый.`,
|
||||||
},
|
},
|
||||||
// Change password
|
// Change password
|
||||||
[EAuthErrorCodes.MISSING_PASSWORD]: {
|
[EAuthErrorCodes.MISSING_PASSWORD]: {
|
||||||
title: `Password required`,
|
title: `Нужен пароль`,
|
||||||
message: () => `Password required. Please try again.`,
|
message: () => `Укажите пароль и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INCORRECT_OLD_PASSWORD]: {
|
[EAuthErrorCodes.INCORRECT_OLD_PASSWORD]: {
|
||||||
title: `Incorrect old password`,
|
title: `Неверный старый пароль`,
|
||||||
message: () => `Incorrect old password. Please try again.`,
|
message: () => `Старый пароль указан неверно. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_NEW_PASSWORD]: {
|
[EAuthErrorCodes.INVALID_NEW_PASSWORD]: {
|
||||||
title: `Invalid new password`,
|
title: `Некорректный новый пароль`,
|
||||||
message: () => `Invalid new password. Please try again.`,
|
message: () => `Новый пароль не соответствует требованиям.`,
|
||||||
},
|
},
|
||||||
// set password
|
// set password
|
||||||
[EAuthErrorCodes.PASSWORD_ALREADY_SET]: {
|
[EAuthErrorCodes.PASSWORD_ALREADY_SET]: {
|
||||||
title: `Password already set`,
|
title: `Пароль уже установлен`,
|
||||||
message: () => `Password already set. Please try again.`,
|
message: () => `Пароль уже установлен. Выполните вход.`,
|
||||||
},
|
},
|
||||||
// admin
|
// admin
|
||||||
[EAuthErrorCodes.ADMIN_ALREADY_EXIST]: {
|
[EAuthErrorCodes.ADMIN_ALREADY_EXIST]: {
|
||||||
title: `Admin already exists`,
|
title: `Администратор уже существует`,
|
||||||
message: () => `Admin already exists. Please try again.`,
|
message: () => `Администратор уже существует. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
[EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
||||||
title: `Email, password and first name required`,
|
title: `Нужны e-mail, пароль и имя`,
|
||||||
message: () => `Email, password and first name required. Please try again.`,
|
message: () => `Укажите e-mail, пароль и имя, затем попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_ADMIN_EMAIL]: {
|
[EAuthErrorCodes.INVALID_ADMIN_EMAIL]: {
|
||||||
title: `Invalid admin email`,
|
title: `Некорректный e-mail администратора`,
|
||||||
message: () => `Invalid admin email. Please try again.`,
|
message: () => `Некорректный e-mail администратора. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
[EAuthErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
||||||
title: `Invalid admin password`,
|
title: `Некорректный пароль администратора`,
|
||||||
message: () => `Invalid admin password. Please try again.`,
|
message: () => `Некорректный пароль администратора. Попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
[EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
||||||
title: `Email and password required`,
|
title: `Нужны e-mail и пароль`,
|
||||||
message: () => `Email and password required. Please try again.`,
|
message: () => `Укажите e-mail и пароль, затем попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
[EAuthErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
||||||
title: `Authentication failed`,
|
title: `Не удалось выполнить вход`,
|
||||||
message: () => `Authentication failed. Please try again.`,
|
message: () => `Не удалось выполнить вход. Проверьте данные и попробуйте снова.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
[EAuthErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
||||||
title: `Admin user already exists`,
|
title: `Администратор уже существует`,
|
||||||
message: () => `Admin user already exists. Sign in now.`,
|
message: () => `Администратор уже существует. Выполните вход.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
[EAuthErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
||||||
title: `Admin user does not exist`,
|
title: `Администратор не найден`,
|
||||||
message: () => `Admin user does not exist. Sign in now.`,
|
message: () => `Администратор не найден. Выполните вход.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.MAGIC_LINK_LOGIN_DISABLED]: {
|
[EAuthErrorCodes.MAGIC_LINK_LOGIN_DISABLED]: {
|
||||||
title: `Magic link login disabled`,
|
title: `Вход по magic link отключён`,
|
||||||
message: () => `Magic link login is disabled. Please use password to login.`,
|
message: () => `Вход по magic link отключён. Используйте пароль.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.PASSWORD_LOGIN_DISABLED]: {
|
[EAuthErrorCodes.PASSWORD_LOGIN_DISABLED]: {
|
||||||
title: `Password login disabled`,
|
title: `Вход по паролю отключён`,
|
||||||
message: () => `Password login is disabled. Please use magic link to login.`,
|
message: () => `Вход по паролю отключён. Используйте magic link.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.ADMIN_USER_DEACTIVATED]: {
|
[EAuthErrorCodes.ADMIN_USER_DEACTIVATED]: {
|
||||||
title: `Admin user deactivated`,
|
title: `Администратор деактивирован`,
|
||||||
message: () => `Admin user account has been deactivated. Please contact administrator.`,
|
message: () => `Аккаунт администратора деактивирован. Обратитесь к администратору.`,
|
||||||
},
|
},
|
||||||
[EAuthErrorCodes.RATE_LIMIT_EXCEEDED]: {
|
[EAuthErrorCodes.RATE_LIMIT_EXCEEDED]: {
|
||||||
title: `Rate limit exceeded`,
|
title: `Слишком много запросов`,
|
||||||
message: () => `Too many requests. Please try again later.`,
|
message: () => `Слишком много запросов. Повторите попытку позже.`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -365,8 +365,8 @@ export const authErrorHandler = (errorCode: EAuthErrorCodes, email?: string): TA
|
||||||
return {
|
return {
|
||||||
type: EErrorAlertType.BANNER_ALERT,
|
type: EErrorAlertType.BANNER_ALERT,
|
||||||
code: errorCode,
|
code: errorCode,
|
||||||
title: errorCodeMessages[errorCode]?.title || "Error",
|
title: errorCodeMessages[errorCode]?.title || "Ошибка",
|
||||||
message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.",
|
message: errorCodeMessages[errorCode]?.message(email) || "Что-то пошло не так. Попробуйте снова.",
|
||||||
};
|
};
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue