mirror of
https://github.com/makeplane/plane.git
synced 2025-12-29 00:24:56 +01:00
Merge pull request #431 from makeplane/sync/ce-ee
sync: community changes
This commit is contained in:
11
packages/ui/src/emoji/helpers.ts
Normal file
11
packages/ui/src/emoji/helpers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const emojiCodeToUnicode = (emoji: string) => {
|
||||
if (!emoji) return "";
|
||||
|
||||
// convert emoji code to unicode
|
||||
const uniCodeEmoji = emoji
|
||||
.split("-")
|
||||
.map((emoji) => parseInt(emoji, 10).toString(16))
|
||||
.join("-");
|
||||
|
||||
return uniCodeEmoji;
|
||||
};
|
||||
@@ -2,3 +2,4 @@ export * from "./emoji-icon-picker-new";
|
||||
export * from "./emoji-icon-picker";
|
||||
export * from "./emoji-icon-helper";
|
||||
export * from "./icons";
|
||||
export * from "./logo";
|
||||
|
||||
102
packages/ui/src/emoji/logo.tsx
Normal file
102
packages/ui/src/emoji/logo.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { Emoji } from "emoji-picker-react";
|
||||
import useFontFaceObserver from "use-font-face-observer";
|
||||
// icons
|
||||
import { LUCIDE_ICONS_LIST } from "./icons";
|
||||
// helpers
|
||||
import { emojiCodeToUnicode } from "./helpers";
|
||||
|
||||
type TLogoProps = {
|
||||
in_use: "emoji" | "icon";
|
||||
emoji?: {
|
||||
value?: string;
|
||||
url?: string;
|
||||
};
|
||||
icon?: {
|
||||
name?: string;
|
||||
color?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {
|
||||
logo: TLogoProps;
|
||||
size?: number;
|
||||
type?: "lucide" | "material";
|
||||
};
|
||||
|
||||
export const Logo: FC<Props> = (props) => {
|
||||
const { logo, size = 16, type = "material" } = props;
|
||||
|
||||
// destructuring the logo object
|
||||
const { in_use, emoji, icon } = logo;
|
||||
|
||||
// derived values
|
||||
const value = in_use === "emoji" ? emoji?.value : icon?.name;
|
||||
const color = icon?.color;
|
||||
const lucideIcon = LUCIDE_ICONS_LIST.find((item) => item.name === value);
|
||||
|
||||
const isMaterialSymbolsFontLoaded = useFontFaceObserver([
|
||||
{
|
||||
family: `Material Symbols Rounded`,
|
||||
style: `normal`,
|
||||
weight: `normal`,
|
||||
stretch: `condensed`,
|
||||
},
|
||||
]);
|
||||
// if no value, return empty fragment
|
||||
if (!value) return <></>;
|
||||
|
||||
if (!isMaterialSymbolsFontLoaded) {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
className="rounded animate-pulse bg-custom-background-80"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// emoji
|
||||
if (in_use === "emoji") {
|
||||
return <Emoji unified={emojiCodeToUnicode(value)} size={size} />;
|
||||
}
|
||||
|
||||
// icon
|
||||
if (in_use === "icon") {
|
||||
return (
|
||||
<>
|
||||
{type === "lucide" ? (
|
||||
<>
|
||||
{lucideIcon && (
|
||||
<lucideIcon.element
|
||||
style={{
|
||||
color: color,
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span
|
||||
className="material-symbols-rounded"
|
||||
style={{
|
||||
fontSize: size,
|
||||
color: color,
|
||||
scale: "115%",
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// if no value, return empty fragment
|
||||
return <></>;
|
||||
};
|
||||
@@ -50,6 +50,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
||||
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
||||
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
||||
const [isExistingEmail, setIsExistingEmail] = useState(false);
|
||||
// hooks
|
||||
const { config } = useInstance();
|
||||
|
||||
@@ -125,6 +126,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
||||
setAuthStep(EAuthSteps.PASSWORD);
|
||||
}
|
||||
}
|
||||
setIsExistingEmail(response.existing);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorhandler = authErrorHandler(error?.error_code?.toString(), data?.email || undefined);
|
||||
@@ -172,6 +174,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
||||
<AuthUniqueCodeForm
|
||||
mode={authMode}
|
||||
email={email}
|
||||
isExistingEmail={isExistingEmail}
|
||||
handleEmailClear={handleEmailClear}
|
||||
generateEmailUniqueCode={generateEmailUniqueCode}
|
||||
nextPath={nextPath || undefined}
|
||||
|
||||
@@ -10,7 +10,12 @@ import { Button, Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/account";
|
||||
// constants
|
||||
import { FORGOT_PASSWORD } from "@/constants/event-tracker";
|
||||
import {
|
||||
FORGOT_PASSWORD,
|
||||
SIGN_IN_WITH_CODE,
|
||||
SIGN_IN_WITH_PASSWORD,
|
||||
SIGN_UP_WITH_PASSWORD,
|
||||
} from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
@@ -70,6 +75,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
|
||||
const redirectToUniqueCodeSignIn = async () => {
|
||||
handleAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||
captureEvent(SIGN_IN_WITH_CODE);
|
||||
};
|
||||
|
||||
const passwordSupport =
|
||||
@@ -116,7 +122,10 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
className="mt-5 space-y-4"
|
||||
method="POST"
|
||||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
|
||||
onSubmit={() => setIsSubmitting(true)}
|
||||
onSubmit={() => {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(mode === EAuthModes.SIGN_IN ? SIGN_IN_WITH_PASSWORD : SIGN_UP_WITH_PASSWORD);
|
||||
}}
|
||||
onError={() => setIsSubmitting(false)}
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CircleCheck, XCircle } from "lucide-react";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { CODE_VERIFIED } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { EAuthModes } from "@/helpers/authentication.helper";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import useTimer from "@/hooks/use-timer";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
@@ -17,6 +20,7 @@ const authService = new AuthService();
|
||||
type TAuthUniqueCodeForm = {
|
||||
mode: EAuthModes;
|
||||
email: string;
|
||||
isExistingEmail: boolean;
|
||||
handleEmailClear: () => void;
|
||||
generateEmailUniqueCode: (email: string) => Promise<{ code: string } | undefined>;
|
||||
nextPath: string | undefined;
|
||||
@@ -33,9 +37,9 @@ const defaultValues: TUniqueCodeFormValues = {
|
||||
};
|
||||
|
||||
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
const { mode, email, handleEmailClear, generateEmailUniqueCode, nextPath } = props;
|
||||
const { mode, email, handleEmailClear, generateEmailUniqueCode, isExistingEmail, nextPath } = props;
|
||||
// hooks
|
||||
// const { captureEvent } = useEventTracker();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// derived values
|
||||
const defaultResetTimerValue = 5;
|
||||
// states
|
||||
@@ -76,7 +80,13 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
className="mt-5 space-y-4"
|
||||
method="POST"
|
||||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "magic-sign-in" : "magic-sign-up"}/`}
|
||||
onSubmit={() => setIsSubmitting(true)}
|
||||
onSubmit={() => {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(CODE_VERIFIED, {
|
||||
state: "SUCCESS",
|
||||
first_time: !isExistingEmail,
|
||||
});
|
||||
}}
|
||||
onError={() => setIsSubmitting(false)}
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
|
||||
@@ -82,8 +82,14 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
||||
|
||||
if (!isRepeated) {
|
||||
if (currentEstimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(currentEstimateType)) {
|
||||
if (estimateInputValue && Number(estimateInputValue) && Number(estimateInputValue) >= 0) {
|
||||
isEstimateValid = true;
|
||||
if (estimateInputValue && !isNaN(Number(estimateInputValue))) {
|
||||
if (Number(estimateInputValue) <= 0) {
|
||||
handleEstimatePointError &&
|
||||
handleEstimatePointError(estimateInputValue, "Estimate point should be greater than 0.");
|
||||
return;
|
||||
} else {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
}
|
||||
} else if (currentEstimateType && currentEstimateType === EEstimateSystem.CATEGORIES) {
|
||||
if (estimateInputValue && estimateInputValue.length > 0 && isNaN(Number(estimateInputValue))) {
|
||||
|
||||
@@ -87,8 +87,14 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
||||
|
||||
if (!isRepeated) {
|
||||
if (currentEstimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(currentEstimateType)) {
|
||||
if (estimateInputValue && Number(estimateInputValue) && Number(estimateInputValue) >= 0) {
|
||||
isEstimateValid = true;
|
||||
if (estimateInputValue && !isNaN(Number(estimateInputValue))) {
|
||||
if (Number(estimateInputValue) <= 0) {
|
||||
handleEstimatePointError &&
|
||||
handleEstimatePointError(estimateInputValue, "Estimate point should be greater than 0.");
|
||||
return;
|
||||
} else {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
}
|
||||
} else if (currentEstimateType && currentEstimateType === EEstimateSystem.CATEGORIES) {
|
||||
if (estimateInputValue && estimateInputValue.length > 0 && isNaN(Number(estimateInputValue))) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
||||
// ui
|
||||
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { WORKSPACE_CREATED } from "@/constants/event-tracker";
|
||||
import { E_ONBOARDING, WORKSPACE_CREATED } from "@/constants/event-tracker";
|
||||
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useEventTracker, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||
@@ -71,7 +71,7 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
first_time: true,
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING,
|
||||
},
|
||||
});
|
||||
await fetchWorkspaces();
|
||||
@@ -83,7 +83,7 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
first_time: true,
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
|
||||
@@ -15,7 +15,7 @@ import { PasswordStrengthMeter } from "@/components/account";
|
||||
import { UserImageUploadModal } from "@/components/core";
|
||||
import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding";
|
||||
// constants
|
||||
import { USER_DETAILS } from "@/constants/event-tracker";
|
||||
import { USER_DETAILS, E_ONBOARDING_STEP_1, E_ONBOARDING_STEP_2 } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||
// hooks
|
||||
@@ -142,8 +142,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||
]);
|
||||
captureEvent(USER_DETAILS, {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING_STEP_1,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -157,7 +159,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
} catch {
|
||||
captureEvent(USER_DETAILS, {
|
||||
state: "FAILED",
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING_STEP_1,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -181,7 +183,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
} catch {
|
||||
captureEvent(USER_DETAILS, {
|
||||
state: "FAILED",
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING_STEP_1,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -202,8 +204,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||
]);
|
||||
captureEvent(USER_DETAILS, {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING_STEP_2,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -217,7 +221,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
} catch {
|
||||
captureEvent(USER_DETAILS, {
|
||||
state: "FAILED",
|
||||
element: "Onboarding",
|
||||
element: E_ONBOARDING_STEP_2,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
||||
@@ -192,6 +192,8 @@ export const SETUP_PASSWORD = "Password setup";
|
||||
export const PASSWORD_CREATE_SELECTED = "Password created";
|
||||
export const PASSWORD_CREATE_SKIPPED = "Skipped to setup";
|
||||
export const SIGN_IN_WITH_PASSWORD = "Sign in with password";
|
||||
export const SIGN_UP_WITH_PASSWORD = "Sign up with password";
|
||||
export const SIGN_IN_WITH_CODE = "Sign in with magic link";
|
||||
export const FORGOT_PASSWORD = "Forgot password clicked";
|
||||
export const FORGOT_PASS_LINK = "Forgot password link generated";
|
||||
export const NEW_PASS_CREATED = "New password created";
|
||||
@@ -222,3 +224,8 @@ export const SNOOZED_NOTIFICATIONS = "Snoozed notifications viewed";
|
||||
export const ARCHIVED_NOTIFICATIONS = "Archived notifications viewed";
|
||||
// Groups
|
||||
export const GROUP_WORKSPACE = "Workspace_metrics";
|
||||
|
||||
//Elements
|
||||
export const E_ONBOARDING = "Onboarding";
|
||||
export const E_ONBOARDING_STEP_1 = "Onboarding step 1";
|
||||
export const E_ONBOARDING_STEP_2 = "Onboarding step 2";
|
||||
Reference in New Issue
Block a user