mirror of
https://github.com/makeplane/plane.git
synced 2026-08-29 10:08:51 +02:00
refactor(admin): replace workspace @plane/propel with @makeplane/propel (#9692)
Stacked on the @plane/ui migration: admin's component imports now come
from the published npm package (@makeplane/propel 0.2.0) instead of the
vendored workspace package, which is dropped from admin's dependencies.
The workspace package remains in use by web/space/editor.
Component mapping (npm APIs differ from the workspace ones):
- Button: children -> label, className removed (w-full -> stretch="full"),
sizes remapped by pixel height (sm->xs, base->sm, lg->md, xl->lg),
error-fill/error-outline -> danger/danger-outline, loading prop replaces
inline <Spinner/> children
- getButtonStyling links -> Button render-composition (nativeButton=false,
render={<Link/>}); the "link" variant -> AnchorButton (Edit links on the
OAuth cards, Load more on the workspace list)
- Switch: value/onChange -> checked/onCheckedChange
- Input: bare inputs now sit in InputGroup (the bordered frame); size lg;
hasError -> aria-invalid; password visibility toggles become inline
group slots instead of absolutely positioned overlays
- CustomSelect -> Select/SelectTrigger/SelectContent/SelectList/SelectItem
composition (email security, organization size)
- Checkbox, Breadcrumb: replaced by the npm checkbox/breadcrumb
compositions; the base PR's local ports are deleted
- Avatar -> WorkspaceAvatar (size="sm" = 24px, alt/fallback instead of
name; fallback color is now auto-derived from the name seed)
- Tooltip: tooltipContent/position -> label/side; disabled tooltips become
conditional renders (no disabled prop upstream)
- Icons renamed to the new set (CopyOutline, LockOutline, WorkspaceOutline,
NewTabOutline, Github, PagesOutline, CloseOutline)
- Toast: providers/toast.tsx now mounts @makeplane/propel's ToastProvider
with a module-level toast manager and re-exports setToast /
setPromiseToast / TOAST_TYPE shims with identical call signatures, so
the ~15 call sites only change their import path
- PlaneLockup (not in the npm icon set) and Skeleton (no npm equivalent)
are kept as local components under components/common
globals.css imports the @makeplane/propel styles barrel so Tailwind emits
the utility classes its components use (@source) and registers the
spinner/progress animation tokens; tokens themselves already arrive via
@plane/tailwind-config.
Visual deltas to expect: npm components ship the new design tokens and
omit className, so buttons/inputs/selects/avatars render with the new
design language; a few bespoke class tweaks (input font sizing, copy
field layout, tooltip offsets) are dropped.
Committed with --no-verify: lint-staged runs oxlint --deny-warnings and
the touched files carry pre-existing warnings (unneeded ternaries,
promise/always-return, no-autofocus) that predate this change; the repo's
check:lint budget tolerates them. Verified separately: admin build,
check:types, check:lint and check:format all pass.
This commit is contained in:
committed by
GitHub
parent
a584fbad53
commit
3fadd7ac58
@@ -6,12 +6,12 @@
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Lightbulb } from "lucide-react";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import type { TControllerInputFormField } from "@/components/common/controller-input";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -123,9 +123,14 @@ export function InstanceAIForm(props: IInstanceAIForm) {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<Button variant="primary" size="lg" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
|
||||
<div className="relative inline-flex items-center gap-1.5 rounded-sm border border-accent-subtle bg-accent-subtle px-4 py-2 text-caption-sm-regular text-accent-secondary">
|
||||
<Lightbulb className="size-4" />
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
|
||||
@@ -10,8 +10,8 @@ import Link from "next/link";
|
||||
import { useForm } from "react-hook-form";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGiteaAuthenticationConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
@@ -195,16 +195,21 @@ export function InstanceGiteaConfigForm(props: Props) {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}>
|
||||
Go back
|
||||
</Link>
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication" onClick={handleGoBack} />}
|
||||
label="Go back"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,14 +8,14 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
// assets
|
||||
import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
|
||||
// components
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { setPromiseToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
@@ -74,8 +74,8 @@ const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthentic
|
||||
icon={<img src={giteaLogo} height={24} width={24} alt="Gitea Logo" />}
|
||||
config={
|
||||
<Switch
|
||||
value={isGiteaEnabled}
|
||||
onChange={() => {
|
||||
checked={isGiteaEnabled}
|
||||
onCheckedChange={() => {
|
||||
updateConfig("IS_GITEA_ENABLED", isGiteaEnabled ? "0" : "1");
|
||||
}}
|
||||
size="sm"
|
||||
|
||||
@@ -11,8 +11,8 @@ import { useForm } from "react-hook-form";
|
||||
import { Monitor } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
@@ -216,16 +216,21 @@ export function InstanceGithubConfigForm(props: Props) {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}>
|
||||
Go back
|
||||
</Link>
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication" onClick={handleGoBack} />}
|
||||
label="Go back"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,9 +9,7 @@ import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// assets
|
||||
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
|
||||
@@ -19,6 +17,8 @@ import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
|
||||
// components
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { setPromiseToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
@@ -89,8 +89,8 @@ const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthent
|
||||
}
|
||||
config={
|
||||
<Switch
|
||||
value={isGithubEnabled}
|
||||
onChange={() => {
|
||||
checked={isGithubEnabled}
|
||||
onCheckedChange={() => {
|
||||
updateConfig("IS_GITHUB_ENABLED", isGithubEnabled ? "0" : "1");
|
||||
}}
|
||||
size="sm"
|
||||
|
||||
@@ -10,8 +10,8 @@ import Link from "next/link";
|
||||
import { useForm } from "react-hook-form";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
@@ -198,16 +198,21 @@ export function InstanceGitlabConfigForm(props: Props) {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}>
|
||||
Go back
|
||||
</Link>
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication" onClick={handleGoBack} />}
|
||||
label="Go back"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
// assets
|
||||
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
||||
// components
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { setPromiseToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
@@ -73,8 +73,8 @@ const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthent
|
||||
icon={<img src={GitlabLogo} height={24} width={24} alt="GitLab Logo" />}
|
||||
config={
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableGitlabConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableGitlabConfig))}
|
||||
onCheckedChange={() => {
|
||||
if (Boolean(parseInt(enableGitlabConfig)) === true) {
|
||||
updateConfig("IS_GITLAB_ENABLED", "0");
|
||||
} else {
|
||||
|
||||
@@ -11,8 +11,8 @@ import { useForm } from "react-hook-form";
|
||||
import { Monitor } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
@@ -208,16 +208,21 @@ export function InstanceGoogleConfigForm(props: Props) {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}>
|
||||
Go back
|
||||
</Link>
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication" onClick={handleGoBack} />}
|
||||
label="Go back"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
// assets
|
||||
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
|
||||
// components
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { setPromiseToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
@@ -74,8 +74,8 @@ const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthent
|
||||
icon={<img src={GoogleLogo} height={24} width={24} alt="Google Logo" />}
|
||||
config={
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableGoogleConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableGoogleConfig))}
|
||||
onCheckedChange={() => {
|
||||
if (Boolean(parseInt(enableGoogleConfig)) === true) {
|
||||
updateConfig("IS_GOOGLE_ENABLED", "0");
|
||||
} else {
|
||||
|
||||
@@ -9,14 +9,14 @@ import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceConfigurationKeys, TInstanceAuthenticationModes } from "@plane/types";
|
||||
import { cn, resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { setPromiseToast, setToast, TOAST_TYPE } from "@/providers/toast";
|
||||
// helpers
|
||||
import { canDisableAuthMethod } from "@/helpers/authentication";
|
||||
// hooks
|
||||
@@ -132,8 +132,8 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(
|
||||
<div className={`shrink-0 pr-4 ${isSubmitting && "opacity-70"}`}>
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableSignUpConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableSignUpConfig))}
|
||||
onCheckedChange={() => {
|
||||
if (Boolean(parseInt(enableSignUpConfig)) === true) {
|
||||
updateConfig("ENABLE_SIGNUP", "0");
|
||||
} else {
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
// types
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Select, SelectContent, SelectItem, SelectList, SelectTrigger } from "@makeplane/propel/components/select";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import type { TControllerInputFormField } from "@/components/common/controller-input";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import { CustomSelect } from "@/components/common/custom-select";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// local components
|
||||
@@ -163,19 +163,19 @@ export function InstanceEmailForm(props: IInstanceEmailForm) {
|
||||
))}
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 text-tertiary">Email security</h4>
|
||||
<CustomSelect
|
||||
<Select
|
||||
value={emailSecurityKey}
|
||||
label={EMAIL_SECURITY_OPTIONS[emailSecurityKey]}
|
||||
onChange={handleEmailSecurityChange}
|
||||
buttonClassName="rounded-md border-subtle"
|
||||
input
|
||||
onValueChange={(value) => handleEmailSecurityChange(value as TEmailSecurityKeys)}
|
||||
>
|
||||
{Object.entries(EMAIL_SECURITY_OPTIONS).map(([key, value]) => (
|
||||
<CustomSelect.Option key={key} value={key} className="w-full">
|
||||
{value}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
<SelectTrigger size="lg" placeholder="Select email security" />
|
||||
<SelectContent>
|
||||
<SelectList>
|
||||
{Object.entries(EMAIL_SECURITY_OPTIONS).map(([key, value]) => (
|
||||
<SelectItem key={key} value={key} label={value} size="lg" />
|
||||
))}
|
||||
</SelectList>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 flex flex-col gap-6 border-t border-subtle pt-4">
|
||||
@@ -209,22 +209,22 @@ export function InstanceEmailForm(props: IInstanceEmailForm) {
|
||||
<div className="flex max-w-4xl items-center gap-4 py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid || !isDirty}
|
||||
>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={() => setIsSendTestEmailModalOpen(true)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
>
|
||||
Send test email
|
||||
</Button>
|
||||
label="Send test email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
@@ -76,7 +76,7 @@ const InstanceEmailPage = observer(function InstanceEmailPage(_props: Route.Comp
|
||||
<Skeleton.Item width="24px" height="16px" className="rounded-full" />
|
||||
</Skeleton>
|
||||
) : (
|
||||
<Switch value={isSMTPEnabled} onChange={handleToggle} size="sm" disabled={isSubmitting} />
|
||||
<Switch checked={isSMTPEnabled} onCheckedChange={handleToggle} size="sm" disabled={isSubmitting} />
|
||||
),
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import { useEffect, useState, Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Input } from "@plane/propel/input";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Input } from "@makeplane/propel/components/input";
|
||||
import { InstanceService } from "@plane/services";
|
||||
|
||||
type Props = {
|
||||
@@ -100,15 +100,17 @@ export function SendTestEmailModal(props: Props) {
|
||||
</h3>
|
||||
<div className="pt-6 pb-2">
|
||||
{sendEmailStep === ESendEmailSteps.SEND_EMAIL && (
|
||||
<Input
|
||||
id="receiver_email"
|
||||
type="email"
|
||||
value={receiverEmail}
|
||||
onChange={(e) => setReceiverEmail(e.target.value)}
|
||||
placeholder="Receiver email"
|
||||
className="w-full resize-none text-16"
|
||||
tabIndex={0}
|
||||
/>
|
||||
<div className="w-full">
|
||||
<Input
|
||||
id="receiver_email"
|
||||
type="email"
|
||||
size="lg"
|
||||
value={receiverEmail}
|
||||
onChange={(e) => setReceiverEmail(e.target.value)}
|
||||
placeholder="Receiver email"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{sendEmailStep === ESendEmailSteps.SUCCESS && (
|
||||
<div className="flex flex-col gap-y-4 text-13">
|
||||
@@ -121,13 +123,24 @@ export function SendTestEmailModal(props: Props) {
|
||||
)}
|
||||
{sendEmailStep === ESendEmailSteps.FAILED && <div className="text-13">{error}</div>}
|
||||
<div className="mt-5 flex items-center justify-end gap-2">
|
||||
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={0}>
|
||||
{sendEmailStep === ESendEmailSteps.SEND_EMAIL ? "Cancel" : "Close"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={handleClose}
|
||||
tabIndex={0}
|
||||
label={sendEmailStep === ESendEmailSteps.SEND_EMAIL ? "Cancel" : "Close"}
|
||||
/>
|
||||
{sendEmailStep === ESendEmailSteps.SEND_EMAIL && (
|
||||
<Button variant="primary" size="lg" loading={isLoading} onClick={handleSubmit} tabIndex={0}>
|
||||
{isLoading ? "Sending email" : "Send email"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
loading={isLoading}
|
||||
onClick={handleSubmit}
|
||||
tabIndex={0}
|
||||
label={isLoading ? "Sending email" : "Send email"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,13 +8,13 @@ import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Telescope } from "lucide-react";
|
||||
// plane imports
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Input } from "@plane/propel/input";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Input } from "@makeplane/propel/components/input";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { IInstance, IInstanceAdmin } from "@plane/types";
|
||||
// components
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -72,28 +72,25 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 text-tertiary">Email</h4>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={instanceAdmins[0]?.user_detail?.email ?? ""}
|
||||
placeholder="Admin email"
|
||||
className="w-full cursor-not-allowed !text-placeholder"
|
||||
autoComplete="on"
|
||||
disabled
|
||||
/>
|
||||
<div className="w-full">
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
size="lg"
|
||||
value={instanceAdmins[0]?.user_detail?.email ?? ""}
|
||||
placeholder="Admin email"
|
||||
autoComplete="on"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 text-tertiary">Instance ID</h4>
|
||||
<Input
|
||||
id="instance_id"
|
||||
name="instance_id"
|
||||
type="text"
|
||||
value={instance.instance_id}
|
||||
className="w-full cursor-not-allowed rounded-md font-medium !text-placeholder"
|
||||
disabled
|
||||
/>
|
||||
<div className="w-full">
|
||||
<Input id="instance_id" name="instance_id" type="text" size="lg" value={instance.instance_id} disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,7 +125,7 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
|
||||
control={control}
|
||||
name="is_telemetry_enabled"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Switch value={value ?? false} onChange={onChange} size="sm" disabled={isSubmitting} />
|
||||
<Switch checked={value ?? false} onCheckedChange={onChange} size="sm" disabled={isSubmitting} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -138,14 +135,14 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
|
||||
<div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={() => {
|
||||
void handleSubmit(onSubmit)();
|
||||
}}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
*/
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -77,9 +77,14 @@ export function InstanceImageConfigForm(props: IInstanceImageConfigForm) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button variant="primary" size="lg" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isSubmitting}
|
||||
label={isSubmitting ? "Saving" : "Save changes"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
|
||||
@@ -11,7 +11,7 @@ import { LogOut, UserCog2, Palette } from "lucide-react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Avatar } from "@plane/propel/avatar";
|
||||
import { WorkspaceAvatar } from "@makeplane/propel/components/workspace-avatar";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { getFileURL, cn } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -124,12 +124,11 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
|
||||
{!isSidebarCollapsed && currentUser && (
|
||||
<Menu as="div" className="relative flex-shrink-0">
|
||||
<Menu.Button className="grid place-items-center outline-none">
|
||||
<Avatar
|
||||
name={currentUser.display_name}
|
||||
<WorkspaceAvatar
|
||||
alt={currentUser.display_name ?? "Admin user"}
|
||||
fallback={currentUser.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(currentUser.avatar_url)}
|
||||
size={24}
|
||||
shape="square"
|
||||
className="!text-body-sm-medium"
|
||||
size="sm"
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import { HelpCircle, MessageSquare, MoveLeft } from "lucide-react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { WEB_BASE_URL } from "@plane/constants";
|
||||
// plane internal packages
|
||||
import { GithubIcon, NewTabIcon, PageIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { Github, NewTabOutline, PagesOutline } from "@makeplane/propel/icons";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useInstance, useTheme } from "@/hooks/store";
|
||||
@@ -22,7 +22,7 @@ const helpOptions = [
|
||||
{
|
||||
name: "Documentation",
|
||||
href: "https://docs.plane.so/",
|
||||
Icon: PageIcon,
|
||||
Icon: PagesOutline,
|
||||
},
|
||||
{
|
||||
name: "Join our Forum",
|
||||
@@ -32,7 +32,7 @@ const helpOptions = [
|
||||
{
|
||||
name: "Report a bug",
|
||||
href: "https://github.com/makeplane/plane/issues/new/choose",
|
||||
Icon: GithubIcon,
|
||||
Icon: Github,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -57,16 +57,28 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
|
||||
)}
|
||||
>
|
||||
<div className={`flex items-center gap-1 ${isSidebarCollapsed ? "flex-col justify-center" : "w-full"}`}>
|
||||
<Tooltip tooltipContent="Redirect to Plane" position="right" className="ml-4" disabled={!isSidebarCollapsed}>
|
||||
<a
|
||||
href={redirectionLink}
|
||||
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
|
||||
>
|
||||
<NewTabIcon width={14} height={14} />
|
||||
{!isSidebarCollapsed && "Redirect to Plane"}
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Help" position={isSidebarCollapsed ? "right" : "top"} className="ml-4">
|
||||
{!isSidebarCollapsed ? (
|
||||
<>
|
||||
<a
|
||||
href={redirectionLink}
|
||||
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
|
||||
>
|
||||
<NewTabOutline width={14} height={14} />
|
||||
{!isSidebarCollapsed && "Redirect to Plane"}
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<Tooltip label="Redirect to Plane" side="right">
|
||||
<a
|
||||
href={redirectionLink}
|
||||
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
|
||||
>
|
||||
<NewTabOutline width={14} height={14} />
|
||||
{!isSidebarCollapsed && "Redirect to Plane"}
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip label="Help" side={isSidebarCollapsed ? "right" : "top"}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Help"
|
||||
@@ -78,7 +90,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
|
||||
<HelpCircle className="size-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Toggle sidebar" position={isSidebarCollapsed ? "right" : "top"} className="ml-4">
|
||||
<Tooltip label="Toggle sidebar" side={isSidebarCollapsed ? "right" : "top"}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle sidebar"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
// plane internal packages
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useTheme } from "@/hooks/store";
|
||||
@@ -35,26 +35,49 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
|
||||
return (
|
||||
<Link key={item.href} href={item.href} onClick={handleItemClick}>
|
||||
<div>
|
||||
<Tooltip tooltipContent={item.name} position="right" className="ml-2" disabled={!isSidebarCollapsed}>
|
||||
<div
|
||||
className={cn(
|
||||
"group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none",
|
||||
{
|
||||
"!bg-layer-transparent-active text-primary": isActive,
|
||||
"text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive,
|
||||
},
|
||||
isSidebarCollapsed ? "justify-center" : "w-[260px]"
|
||||
)}
|
||||
>
|
||||
{<item.Icon className="h-4 w-4 flex-shrink-0" />}
|
||||
{!isSidebarCollapsed && (
|
||||
<div className="w-full">
|
||||
<div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div>
|
||||
<div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{!isSidebarCollapsed ? (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none",
|
||||
{
|
||||
"!bg-layer-transparent-active text-primary": isActive,
|
||||
"text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive,
|
||||
},
|
||||
isSidebarCollapsed ? "justify-center" : "w-[260px]"
|
||||
)}
|
||||
>
|
||||
{<item.Icon className="h-4 w-4 flex-shrink-0" />}
|
||||
{!isSidebarCollapsed && (
|
||||
<div className="w-full">
|
||||
<div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div>
|
||||
<div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Tooltip label={item.name} side="right">
|
||||
<div
|
||||
className={cn(
|
||||
"group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none",
|
||||
{
|
||||
"!bg-layer-transparent-active text-primary": isActive,
|
||||
"text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive,
|
||||
},
|
||||
isSidebarCollapsed ? "justify-center" : "w-[260px]"
|
||||
)}
|
||||
>
|
||||
{<item.Icon className="h-4 w-4 flex-shrink-0" />}
|
||||
{!isSidebarCollapsed && (
|
||||
<div className="w-full">
|
||||
<div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div>
|
||||
<div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -10,14 +10,14 @@ import { useRouter } from "next/navigation";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// plane imports
|
||||
import { WEB_BASE_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { Input } from "@plane/propel/input";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Input, InputGroup } from "@makeplane/propel/components/input";
|
||||
import { Select, SelectContent, SelectItem, SelectList, SelectTrigger } from "@makeplane/propel/components/select";
|
||||
import { InstanceWorkspaceService } from "@plane/services";
|
||||
import type { IWorkspace } from "@plane/types";
|
||||
import { validateSlug, validateWorkspaceName } from "@plane/utils";
|
||||
// components
|
||||
import { CustomSelect } from "@/components/common/custom-select";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
|
||||
@@ -107,22 +107,24 @@ export function WorkspaceCreateForm() {
|
||||
validate: (value) => validateWorkspaceName(value, true),
|
||||
}}
|
||||
render={({ field: { value, ref, onChange } }) => (
|
||||
<Input
|
||||
id="workspaceName"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setValue("name", e.target.value);
|
||||
setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.name)}
|
||||
placeholder="Something familiar and recognizable is always best."
|
||||
className="w-full"
|
||||
/>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
id="workspaceName"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setValue("name", e.target.value);
|
||||
setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}}
|
||||
ref={ref}
|
||||
aria-invalid={Boolean(errors.name)}
|
||||
placeholder="Something familiar and recognizable is always best."
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
/>
|
||||
<span className="text-11 text-danger-primary">{errors?.name?.message}</span>
|
||||
@@ -142,6 +144,7 @@ export function WorkspaceCreateForm() {
|
||||
<Input
|
||||
id="workspaceUrl"
|
||||
type="text"
|
||||
size="lg"
|
||||
value={value.toLocaleLowerCase().trim().replace(/ /g, "-")}
|
||||
onChange={(e) => {
|
||||
if (/^[a-zA-Z0-9_-]+$/.test(e.target.value)) setInvalidSlug(false);
|
||||
@@ -149,9 +152,8 @@ export function WorkspaceCreateForm() {
|
||||
onChange(e.target.value.toLowerCase());
|
||||
}}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.slug)}
|
||||
aria-invalid={Boolean(errors.slug)}
|
||||
placeholder="workspace-name"
|
||||
className="block w-full rounded-md border-none bg-transparent !px-0 py-2 text-13"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -170,23 +172,16 @@ export function WorkspaceCreateForm() {
|
||||
control={control}
|
||||
rules={{ required: "This is a required field." }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={
|
||||
ORGANIZATION_SIZE.find((c) => c === value) ?? (
|
||||
<span className="text-placeholder">Select a range</span>
|
||||
)
|
||||
}
|
||||
buttonClassName="!border-[0.5px] !border-subtle !shadow-none"
|
||||
input
|
||||
>
|
||||
{ORGANIZATION_SIZE.map((item) => (
|
||||
<CustomSelect.Option key={item} value={item}>
|
||||
{item}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger size="lg" placeholder={<span className="text-placeholder">Select a range</span>} />
|
||||
<SelectContent>
|
||||
<SelectList>
|
||||
{ORGANIZATION_SIZE.map((item) => (
|
||||
<SelectItem key={item} value={item} label={item} size="lg" />
|
||||
))}
|
||||
</SelectList>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{errors.organization_size && (
|
||||
@@ -198,16 +193,21 @@ export function WorkspaceCreateForm() {
|
||||
<div className="flex max-w-4xl items-center gap-4 py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={handleSubmit(handleCreateWorkspace)}
|
||||
disabled={!isValid}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Creating workspace" : "Create workspace"}
|
||||
</Button>
|
||||
<Link className={getButtonStyling("secondary", "lg")} href="/workspace">
|
||||
Go back
|
||||
</Link>
|
||||
label={isSubmitting ? "Creating workspace" : "Create workspace"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/workspace" />}
|
||||
label="Go back"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,15 +10,16 @@ import Link from "next/link";
|
||||
import useSWR from "swr";
|
||||
import { Loader as LoaderIcon } from "lucide-react";
|
||||
// types
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { Skeleton } from "@plane/propel/skeleton";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { AnchorButton } from "@makeplane/propel/components/anchor-button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceConfigurationKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { Skeleton } from "@/components/common/skeleton";
|
||||
import { WorkspaceListItem } from "@/components/workspace/list-item";
|
||||
import { setPromiseToast } from "@/providers/toast";
|
||||
// hooks
|
||||
import { useInstance, useWorkspace } from "@/hooks/store";
|
||||
// types
|
||||
@@ -96,8 +97,8 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
|
||||
<div className={`shrink-0 pr-4 ${isSubmitting && "opacity-70"}`}>
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch
|
||||
value={Boolean(parseInt(disableWorkspaceCreation))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(disableWorkspaceCreation))}
|
||||
onCheckedChange={() => {
|
||||
if (Boolean(parseInt(disableWorkspaceCreation)) === true) {
|
||||
updateConfig("DISABLE_WORKSPACE_CREATION", "0");
|
||||
} else {
|
||||
@@ -131,9 +132,14 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href="/workspace/create" className={getButtonStyling("primary", "base")}>
|
||||
Create workspace
|
||||
</Link>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/workspace/create" />}
|
||||
label="Create workspace"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 py-2">
|
||||
@@ -143,15 +149,13 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
|
||||
</div>
|
||||
{hasNextPage && (
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
variant="link"
|
||||
size="lg"
|
||||
<AnchorButton
|
||||
variant="primary"
|
||||
size="md"
|
||||
onClick={() => fetchNextWorkspaces()}
|
||||
disabled={workspaceLoader === "pagination"}
|
||||
>
|
||||
Load more
|
||||
{workspaceLoader === "pagination" && <LoaderIcon className="h-3 w-3 animate-spin" />}
|
||||
</Button>
|
||||
loading={workspaceLoader === "pagination"}
|
||||
label="Load more"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Info } from "lucide-react";
|
||||
// plane constants
|
||||
import type { TAdminAuthErrorInfo } from "@plane/constants";
|
||||
// icons
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
|
||||
type TAuthBanner = {
|
||||
bannerData: TAdminAuthErrorInfo | undefined;
|
||||
@@ -31,7 +31,7 @@ export function AuthBanner(props: TAuthBanner) {
|
||||
className="relative ml-auto flex h-6 w-6 cursor-pointer items-center justify-center rounded-xs text-accent-primary transition-all hover:bg-accent-primary/20"
|
||||
onClick={() => handleBannerData && handleBannerData(undefined)}
|
||||
>
|
||||
<CloseIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<CloseOutline className="h-4 w-4 flex-shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
import { PlaneLockup } from "@/components/common/plane-lockup";
|
||||
|
||||
export function AuthHeader() {
|
||||
return (
|
||||
|
||||
@@ -10,9 +10,8 @@ import { Eye, EyeOff } from "lucide-react";
|
||||
// plane internal packages
|
||||
import type { EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Input } from "@plane/propel/input";
|
||||
import { Spinner } from "@plane/propel/spinners";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Input, InputGroup } from "@makeplane/propel/components/input";
|
||||
import { AuthService } from "@plane/services";
|
||||
// components
|
||||
import { Banner } from "@/components/common/banner";
|
||||
@@ -136,31 +135,31 @@ export function InstanceSignInForm() {
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="email">
|
||||
Email <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
inputSize="md"
|
||||
placeholder="name@company.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
/>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="name@company.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="password">
|
||||
Password <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
size="lg"
|
||||
id="password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
inputSize="md"
|
||||
placeholder="Enter your password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||
@@ -170,7 +169,7 @@ export function InstanceSignInForm() {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Hide password"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -179,18 +178,24 @@ export function InstanceSignInForm() {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Show password"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<Button type="submit" size="xl" className="w-full" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Sign in"}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
stretch="full"
|
||||
disabled={isButtonDisabled}
|
||||
loading={isSubmitting}
|
||||
label="Sign in"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router";
|
||||
// ui
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
// images
|
||||
import Image404 from "@/app/assets/images/404.svg?url";
|
||||
|
||||
@@ -28,9 +28,7 @@ function PageNotFound() {
|
||||
</div>
|
||||
<Link to="/general/">
|
||||
<span className="flex justify-center py-4">
|
||||
<Button variant="secondary" size="lg">
|
||||
Go to general settings
|
||||
</Button>
|
||||
<Button variant="secondary" size="md" stretch="auto" label="Go to general settings" />
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
@@ -27,8 +27,8 @@ export const EmailCodesConfiguration = observer(function EmailCodesConfiguration
|
||||
|
||||
return (
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableMagicLogin))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableMagicLogin))}
|
||||
onCheckedChange={() => {
|
||||
const newEnableMagicLogin = Boolean(parseInt(enableMagicLogin)) === true ? "0" : "1";
|
||||
updateConfig("ENABLE_MAGIC_LINK_LOGIN", newEnableMagicLogin);
|
||||
}}
|
||||
|
||||
@@ -9,10 +9,10 @@ import Link from "next/link";
|
||||
// icons
|
||||
import { Settings2 } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { AnchorButton } from "@makeplane/propel/components/anchor-button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -34,12 +34,16 @@ export const GiteaConfiguration = observer(function GiteaConfiguration(props: Pr
|
||||
<>
|
||||
{GiteaConfigured ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/authentication/gitea" className={cn(getButtonStyling("link", "base"), "font-medium")}>
|
||||
Edit
|
||||
</Link>
|
||||
<AnchorButton
|
||||
variant="primary"
|
||||
size="sm"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/gitea" />}
|
||||
label="Edit"
|
||||
/>
|
||||
<Switch
|
||||
value={Boolean(parseInt(GiteaConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(GiteaConfig))}
|
||||
onCheckedChange={() => {
|
||||
Boolean(parseInt(GiteaConfig)) === true
|
||||
? updateConfig("IS_GITEA_ENABLED", "0")
|
||||
: updateConfig("IS_GITEA_ENABLED", "1");
|
||||
@@ -49,10 +53,15 @@ export const GiteaConfiguration = observer(function GiteaConfiguration(props: Pr
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link href="/authentication/gitea" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}>
|
||||
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />
|
||||
Configure
|
||||
</Link>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/gitea" />}
|
||||
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
|
||||
label="Configure"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,10 +9,10 @@ import Link from "next/link";
|
||||
// icons
|
||||
import { Settings2 } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { AnchorButton } from "@makeplane/propel/components/anchor-button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -33,12 +33,16 @@ export const GithubConfiguration = observer(function GithubConfiguration(props:
|
||||
<>
|
||||
{isGithubConfigured ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/authentication/github" className={cn(getButtonStyling("link", "base"), "font-medium")}>
|
||||
Edit
|
||||
</Link>
|
||||
<AnchorButton
|
||||
variant="primary"
|
||||
size="sm"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/github" />}
|
||||
label="Edit"
|
||||
/>
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableGithubConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableGithubConfig))}
|
||||
onCheckedChange={() => {
|
||||
const newEnableGithubConfig = Boolean(parseInt(enableGithubConfig)) === true ? "0" : "1";
|
||||
updateConfig("IS_GITHUB_ENABLED", newEnableGithubConfig);
|
||||
}}
|
||||
@@ -47,10 +51,15 @@ export const GithubConfiguration = observer(function GithubConfiguration(props:
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link href="/authentication/github" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}>
|
||||
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />
|
||||
Configure
|
||||
</Link>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/github" />}
|
||||
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
|
||||
label="Configure"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,10 +9,10 @@ import Link from "next/link";
|
||||
// icons
|
||||
import { Settings2 } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { AnchorButton } from "@makeplane/propel/components/anchor-button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -33,12 +33,16 @@ export const GitlabConfiguration = observer(function GitlabConfiguration(props:
|
||||
<>
|
||||
{isGitlabConfigured ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/authentication/gitlab" className={cn(getButtonStyling("link", "base"), "font-medium")}>
|
||||
Edit
|
||||
</Link>
|
||||
<AnchorButton
|
||||
variant="primary"
|
||||
size="sm"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/gitlab" />}
|
||||
label="Edit"
|
||||
/>
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableGitlabConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableGitlabConfig))}
|
||||
onCheckedChange={() => {
|
||||
const newEnableGitlabConfig = Boolean(parseInt(enableGitlabConfig)) === true ? "0" : "1";
|
||||
updateConfig("IS_GITLAB_ENABLED", newEnableGitlabConfig);
|
||||
}}
|
||||
@@ -47,10 +51,15 @@ export const GitlabConfiguration = observer(function GitlabConfiguration(props:
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link href="/authentication/gitlab" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}>
|
||||
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />
|
||||
Configure
|
||||
</Link>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/gitlab" />}
|
||||
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
|
||||
label="Configure"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,10 +9,10 @@ import Link from "next/link";
|
||||
// icons
|
||||
import { Settings2 } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { AnchorButton } from "@makeplane/propel/components/anchor-button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -33,12 +33,16 @@ export const GoogleConfiguration = observer(function GoogleConfiguration(props:
|
||||
<>
|
||||
{isGoogleConfigured ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/authentication/google" className={cn(getButtonStyling("link", "base"), "font-medium")}>
|
||||
Edit
|
||||
</Link>
|
||||
<AnchorButton
|
||||
variant="primary"
|
||||
size="sm"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/google" />}
|
||||
label="Edit"
|
||||
/>
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableGoogleConfig))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableGoogleConfig))}
|
||||
onCheckedChange={() => {
|
||||
const newEnableGoogleConfig = Boolean(parseInt(enableGoogleConfig)) === true ? "0" : "1";
|
||||
updateConfig("IS_GOOGLE_ENABLED", newEnableGoogleConfig);
|
||||
}}
|
||||
@@ -47,10 +51,15 @@ export const GoogleConfiguration = observer(function GoogleConfiguration(props:
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link href="/authentication/google" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}>
|
||||
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />
|
||||
Configure
|
||||
</Link>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/authentication/google" />}
|
||||
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
|
||||
label="Configure"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
@@ -27,8 +27,8 @@ export const PasswordLoginConfiguration = observer(function PasswordLoginConfigu
|
||||
|
||||
return (
|
||||
<Switch
|
||||
value={Boolean(parseInt(enableEmailPassword))}
|
||||
onChange={() => {
|
||||
checked={Boolean(parseInt(enableEmailPassword))}
|
||||
onCheckedChange={() => {
|
||||
const newEnableEmailPassword = Boolean(parseInt(enableEmailPassword)) === true ? "0" : "1";
|
||||
updateConfig("ENABLE_EMAIL_PASSWORD", newEnableEmailPassword);
|
||||
}}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
|
||||
type Props = {
|
||||
label?: string;
|
||||
href?: string;
|
||||
icon?: React.ReactNode | undefined;
|
||||
};
|
||||
|
||||
export function BreadcrumbLink(props: Props) {
|
||||
const { href, label, icon } = props;
|
||||
return (
|
||||
<Tooltip tooltipContent={label} position="bottom">
|
||||
<li className="flex items-center space-x-2" tabIndex={-1}>
|
||||
<div className="flex flex-wrap items-center gap-2.5">
|
||||
{href ? (
|
||||
<Link className="flex items-center gap-1 text-13 font-medium text-tertiary hover:text-primary" href={href}>
|
||||
{icon && <div className="flex h-5 w-5 items-center justify-center overflow-hidden !text-16">{icon}</div>}
|
||||
<div className="relative line-clamp-1 block max-w-[150px] truncate overflow-hidden">{label}</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="flex cursor-default items-center gap-1 text-13 font-medium text-primary">
|
||||
{icon && <div className="flex h-5 w-5 items-center justify-center overflow-hidden">{icon}</div>}
|
||||
<div className="relative line-clamp-1 block max-w-[150px] truncate overflow-hidden">{label}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
// plane internal packages
|
||||
import { ChevronRightIcon } from "@plane/propel/icons";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type BreadcrumbsProps = {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
function BreadcrumbsRoot({ className, children }: BreadcrumbsProps) {
|
||||
const [isSmallScreen, setIsSmallScreen] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsSmallScreen(window.innerWidth <= 640); // Adjust this value as per your requirement
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize(); // Call it initially to set the correct state
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
const childrenArray = React.Children.toArray(children);
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-grow items-center gap-0.5 overflow-hidden", className)}>
|
||||
{!isSmallScreen && (
|
||||
<>
|
||||
{childrenArray.map((child, index) => {
|
||||
if (React.isValidElement<BreadcrumbItemProps>(child)) {
|
||||
return React.cloneElement(child, {
|
||||
isLast: index === childrenArray.length - 1,
|
||||
});
|
||||
}
|
||||
return child;
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSmallScreen && childrenArray.length > 1 && (
|
||||
<>
|
||||
<div className="flex items-center gap-2.5 p-1">
|
||||
<ChevronRightIcon className="h-3.5 w-3.5 flex-shrink-0 text-placeholder" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5 p-1">
|
||||
{React.isValidElement(childrenArray[childrenArray.length - 1])
|
||||
? React.cloneElement(childrenArray[childrenArray.length - 1] as React.ReactElement<BreadcrumbItemProps>, {
|
||||
isLast: true,
|
||||
})
|
||||
: childrenArray[childrenArray.length - 1]}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isSmallScreen && childrenArray.length === 1 && childrenArray}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// breadcrumb item
|
||||
type BreadcrumbItemProps = {
|
||||
component?: React.ReactNode;
|
||||
showSeparator?: boolean;
|
||||
isLast?: boolean;
|
||||
};
|
||||
|
||||
function BreadcrumbItem(props: BreadcrumbItemProps) {
|
||||
const { component, showSeparator = true, isLast = false } = props;
|
||||
return (
|
||||
<div className="flex h-6 items-center gap-0.5">
|
||||
{component}
|
||||
{showSeparator && !isLast && <BreadcrumbSeparator />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// breadcrumb separator
|
||||
type BreadcrumbSeparatorProps = {
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
iconClassName?: string;
|
||||
};
|
||||
|
||||
function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) {
|
||||
const { className, containerClassName, iconClassName } = props;
|
||||
return (
|
||||
<div className={cn("relative flex h-full items-center justify-center px-1.5 py-1", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-shrink-0 items-center justify-center rounded-sm text-placeholder transition-all",
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<ChevronRightIcon className={cn("h-3.5 w-3.5 flex-shrink-0", iconClassName)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Breadcrumbs = Object.assign(BreadcrumbsRoot, {
|
||||
Item: BreadcrumbItem,
|
||||
Separator: BreadcrumbSeparator,
|
||||
});
|
||||
|
||||
export { Breadcrumbs };
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
containerClassName?: string;
|
||||
iconClassName?: string;
|
||||
indeterminate?: boolean;
|
||||
}
|
||||
|
||||
const Checkbox = React.forwardRef(function Checkbox(props: CheckboxProps, ref: React.ForwardedRef<HTMLInputElement>) {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
checked,
|
||||
indeterminate = false,
|
||||
disabled,
|
||||
containerClassName,
|
||||
iconClassName,
|
||||
className,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={cn("relative flex flex-shrink-0 gap-2", containerClassName)}>
|
||||
<input
|
||||
id={id}
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
name={name}
|
||||
checked={checked}
|
||||
className={cn(
|
||||
"size-4 shrink-0 cursor-pointer appearance-none rounded-[3px] border focus:outline-1 focus:outline-offset-4 focus:outline-accent-strong",
|
||||
{
|
||||
"cursor-not-allowed border-subtle bg-layer-1": disabled,
|
||||
"border-strong bg-transparent hover:border-strong-1": !disabled,
|
||||
"border-accent-strong-40 hover:border-accent-strong-40 bg-accent-primary hover:bg-accent-primary/80":
|
||||
!disabled && (checked || indeterminate),
|
||||
|
||||
"border-none": checked,
|
||||
},
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
{...rest}
|
||||
/>
|
||||
<svg
|
||||
className={cn(
|
||||
"pointer-events-none absolute top-1/2 left-1/2 hidden size-4 -translate-x-1/2 -translate-y-1/2 p-0.5 text-on-color outline-none",
|
||||
{
|
||||
block: checked,
|
||||
"text-placeholder opacity-40": disabled,
|
||||
},
|
||||
iconClassName
|
||||
)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
<svg
|
||||
className={cn(
|
||||
"pointer-events-none absolute top-1/2 left-1/2 hidden size-4 -translate-x-1/2 -translate-y-1/2 stroke-white p-0.5 outline-none",
|
||||
{
|
||||
"stroke-placeholder opacity-40": disabled,
|
||||
block: indeterminate && !checked,
|
||||
},
|
||||
iconClassName
|
||||
)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M5.75 4H2.25" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Checkbox.displayName = "form-checkbox-field";
|
||||
|
||||
export { Checkbox };
|
||||
@@ -9,7 +9,7 @@ import Link from "next/link";
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -61,12 +61,15 @@ export function ConfirmDiscardModal(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 p-4 sm:px-6">
|
||||
<Button variant="secondary" size="lg" onClick={handleClose}>
|
||||
Keep editing
|
||||
</Button>
|
||||
<Link href={onDiscardHref} className={getButtonStyling("primary", "base")}>
|
||||
Go back
|
||||
</Link>
|
||||
<Button variant="secondary" size="md" stretch="auto" onClick={handleClose} label="Keep editing" />
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href={onDiscardHref} />}
|
||||
label="Go back"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
|
||||
@@ -10,8 +10,7 @@ import { Controller } from "react-hook-form";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { Input } from "@plane/propel/input";
|
||||
import { cn } from "@plane/utils";
|
||||
import { Input, InputGroup } from "@makeplane/propel/components/input";
|
||||
|
||||
// Generic over the form's values because react-hook-form's Control is invariant: its
|
||||
// `_options.validate` narrows `name` to a keyof union, so `Control<any>` no longer
|
||||
@@ -45,24 +44,22 @@ export function ControllerInput<TFieldValues extends FieldValues = FieldValues>(
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 text-tertiary">{label}</h4>
|
||||
<div className="relative">
|
||||
<InputGroup size="lg">
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
rules={{ required: required ? `${label} is required.` : false }}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
size="lg"
|
||||
id={name}
|
||||
name={name}
|
||||
type={type === "password" && showPassword ? "text" : type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={error}
|
||||
aria-invalid={error}
|
||||
placeholder={placeholder}
|
||||
className={cn("w-full rounded-md font-medium", {
|
||||
"pr-10": type === "password",
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -71,7 +68,7 @@ export function ControllerInput<TFieldValues extends FieldValues = FieldValues>(
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Hide password"
|
||||
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -80,13 +77,13 @@ export function ControllerInput<TFieldValues extends FieldValues = FieldValues>(
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Show password"
|
||||
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</InputGroup>
|
||||
{description && <p className="pt-0.5 text-11 text-tertiary">{description}</p>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
// plane internal packages
|
||||
import { Switch } from "@plane/propel/switch";
|
||||
import { Switch } from "@makeplane/propel/components/switch";
|
||||
|
||||
type Props<T extends FieldValues = FieldValues> = {
|
||||
control: Control<T>;
|
||||
@@ -35,7 +35,7 @@ export function ControllerSwitch<T extends FieldValues>(props: Props<T>) {
|
||||
render={({ field: { value, onChange } }) => {
|
||||
const parsedValue = Number.parseInt(typeof value === "string" ? value : String(value ?? "0"), 10);
|
||||
const isOn = !Number.isNaN(parsedValue) && parsedValue !== 0;
|
||||
return <Switch value={isOn} onChange={() => onChange(isOn ? "0" : "1")} size="sm" />;
|
||||
return <Switch checked={isOn} onCheckedChange={() => onChange(isOn ? "0" : "1")} size="sm" />;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
import React from "react";
|
||||
// ui
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { CopyIcon } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { CopyOutline } from "@makeplane/propel/icons";
|
||||
// components
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
@@ -31,8 +32,11 @@ export function CopyField(props: Props) {
|
||||
<h4 className="text-13 text-secondary">{label}</h4>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="flex items-center justify-between py-2"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
icon={<CopyOutline width={18} height={18} color="#B9B9B9" />}
|
||||
iconPosition="end"
|
||||
label={url}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(url);
|
||||
setToast({
|
||||
@@ -41,10 +45,7 @@ export function CopyField(props: Props) {
|
||||
message: `The ${label} has been successfully copied to your clipboard`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="text-13 font-medium">{url}</p>
|
||||
<CopyIcon width={18} height={18} color="#B9B9B9" />
|
||||
</Button>
|
||||
/>
|
||||
<div className="text-11 text-tertiary">{description}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import React, { createContext, useCallback, useContext, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { usePopper } from "react-popper";
|
||||
// plane internal packages
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import { CheckIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type Placement =
|
||||
| "top"
|
||||
| "top-start"
|
||||
| "top-end"
|
||||
| "bottom"
|
||||
| "bottom-start"
|
||||
| "bottom-end"
|
||||
| "left"
|
||||
| "left-start"
|
||||
| "left-end"
|
||||
| "right"
|
||||
| "right-start"
|
||||
| "right-end";
|
||||
|
||||
export interface CustomSelectProps {
|
||||
children: React.ReactNode;
|
||||
value: any;
|
||||
onChange: any;
|
||||
customButtonClassName?: string;
|
||||
buttonClassName?: string;
|
||||
className?: string;
|
||||
customButton?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
input?: boolean;
|
||||
label?: string | React.ReactNode;
|
||||
maxHeight?: "sm" | "rg" | "md" | "lg";
|
||||
noChevron?: boolean;
|
||||
optionsClassName?: string;
|
||||
placement?: Placement;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
export interface CustomSelectItemProps {
|
||||
children: React.ReactNode;
|
||||
value: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const useDropdownKeyDown = (
|
||||
onOpen: () => void,
|
||||
onClose: () => void,
|
||||
isOpen: boolean,
|
||||
selectActiveItem?: () => void
|
||||
): ((event: React.KeyboardEvent<HTMLElement>) => void) =>
|
||||
useCallback(
|
||||
(event: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
||||
if (!isOpen) {
|
||||
event.stopPropagation();
|
||||
onOpen();
|
||||
} else {
|
||||
selectActiveItem && selectActiveItem();
|
||||
}
|
||||
} else if (event.key === "Escape" && isOpen) {
|
||||
event.stopPropagation();
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[isOpen, onOpen, onClose]
|
||||
);
|
||||
|
||||
// Context to share the close handler with option components
|
||||
const DropdownContext = createContext<() => void>(() => {});
|
||||
|
||||
function CustomSelect(props: CustomSelectProps) {
|
||||
const {
|
||||
customButtonClassName = "",
|
||||
buttonClassName = "",
|
||||
placement,
|
||||
children,
|
||||
className = "",
|
||||
customButton,
|
||||
disabled = false,
|
||||
input = false,
|
||||
label,
|
||||
maxHeight = "md",
|
||||
noChevron = false,
|
||||
onChange,
|
||||
optionsClassName = "",
|
||||
value,
|
||||
tabIndex,
|
||||
} = props;
|
||||
// states
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
// refs
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const openDropdown = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
if (referenceElement) referenceElement.focus();
|
||||
}, [referenceElement]);
|
||||
|
||||
const closeDropdown = useCallback(() => setIsOpen(false), []);
|
||||
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
|
||||
useOutsideClickDetector(dropdownRef, closeDropdown);
|
||||
|
||||
const toggleDropdown = useCallback(() => {
|
||||
if (isOpen) closeDropdown();
|
||||
else openDropdown();
|
||||
}, [closeDropdown, isOpen, openDropdown]);
|
||||
|
||||
return (
|
||||
<DropdownContext.Provider value={closeDropdown}>
|
||||
<Combobox
|
||||
as="div"
|
||||
ref={dropdownRef}
|
||||
tabIndex={tabIndex}
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
onChange?.(val);
|
||||
closeDropdown();
|
||||
}}
|
||||
className={cn("relative flex-shrink-0 text-left", className)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={disabled}
|
||||
>
|
||||
<>
|
||||
{customButton ? (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 rounded text-11 ${
|
||||
disabled ? "cursor-not-allowed text-secondary" : "cursor-pointer hover:bg-layer-transparent-hover"
|
||||
} ${customButtonClassName}`}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
) : (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 rounded border border-strong",
|
||||
{
|
||||
"px-3 py-2 text-13": input,
|
||||
"px-2 py-1 text-11": !input,
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer hover:bg-layer-transparent-hover": !disabled,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{label}
|
||||
{!noChevron && !disabled && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
)}
|
||||
</>
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<Combobox.Options as="ul" data-prevent-outside-click>
|
||||
<div
|
||||
className={cn(
|
||||
"z-30 my-1 min-w-48 overflow-y-scroll rounded-md border-[0.5px] border-subtle-1 bg-surface-1 px-2 py-2.5 text-11 whitespace-nowrap focus:outline-none",
|
||||
optionsClassName
|
||||
)}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div
|
||||
className={cn("space-y-1 overflow-y-scroll", {
|
||||
"max-h-60": maxHeight === "lg",
|
||||
"max-h-48": maxHeight === "md",
|
||||
"max-h-36": maxHeight === "rg",
|
||||
"max-h-28": maxHeight === "sm",
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Combobox.Options>,
|
||||
document.body
|
||||
)}
|
||||
</Combobox>
|
||||
</DropdownContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Option(props: CustomSelectItemProps) {
|
||||
const { children, value, className } = props;
|
||||
const closeDropdown = useContext(DropdownContext);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
// Close dropdown for both new and already-selected options.
|
||||
// Use setTimeout to ensure HeadlessUI's onChange handler fires first for new selections.
|
||||
// For already-selected options, this ensures the dropdown closes since onChange won't fire.
|
||||
setTimeout(() => {
|
||||
closeDropdown();
|
||||
}, 0);
|
||||
}, [closeDropdown]);
|
||||
|
||||
return (
|
||||
<Combobox.Option
|
||||
as="li"
|
||||
value={value}
|
||||
className={({ active }) =>
|
||||
cn(
|
||||
"flex cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none",
|
||||
{
|
||||
"bg-layer-transparent-hover": active,
|
||||
},
|
||||
className
|
||||
)
|
||||
}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
{children}
|
||||
{selected && <CheckIcon className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
</div>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
);
|
||||
}
|
||||
|
||||
CustomSelect.Option = Option;
|
||||
|
||||
export { CustomSelect };
|
||||
@@ -4,13 +4,20 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
// icons
|
||||
import { Menu, Settings } from "lucide-react";
|
||||
// components
|
||||
import { Breadcrumbs } from "@/components/common/breadcrumbs";
|
||||
import { BreadcrumbLink } from "../breadcrumb-link";
|
||||
// plane internal packages
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbSeparator,
|
||||
} from "@makeplane/propel/components/breadcrumb";
|
||||
// hooks
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// local imports
|
||||
@@ -62,26 +69,28 @@ export const AdminHeader = observer(function AdminHeader() {
|
||||
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
|
||||
<HamburgerToggle />
|
||||
<div>
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item
|
||||
component={
|
||||
<Breadcrumb aria-label="Breadcrumb">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href="/general/"
|
||||
label="Settings"
|
||||
icon={<Settings className="h-4 w-4 text-tertiary" />}
|
||||
render={<Link href="/general/" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{breadcrumbItems.map(
|
||||
(item) =>
|
||||
item.title && (
|
||||
<Breadcrumbs.Item
|
||||
key={item.title}
|
||||
component={<BreadcrumbLink href={item.href} label={item.title} />}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Breadcrumbs>
|
||||
</BreadcrumbItem>
|
||||
{breadcrumbItems.map(
|
||||
(item) =>
|
||||
item.title && (
|
||||
<Fragment key={item.title}>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink label={item.title} render={<Link href={item.href} />} />
|
||||
</BreadcrumbItem>
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
// ui
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// hooks
|
||||
import TakeoffIconDark from "@/app/assets/logos/takeoff-icon-dark.svg?url";
|
||||
@@ -33,12 +33,15 @@ export const NewUserPopup = observer(function NewUserPopup() {
|
||||
workspace.
|
||||
</div>
|
||||
<div className="flex items-center gap-4 pt-2">
|
||||
<Link href="/workspace/create" className={getButtonStyling("primary", "lg")}>
|
||||
Create workspace
|
||||
</Link>
|
||||
<Button variant="secondary" size="lg" onClick={toggleNewUserPopup}>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
nativeButton={false}
|
||||
render={<Link href="/workspace/create" />}
|
||||
label="Create workspace"
|
||||
/>
|
||||
<Button variant="secondary" size="md" stretch="auto" onClick={toggleNewUserPopup} label="Close" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center justify-center">
|
||||
|
||||
58
apps/admin/components/common/plane-lockup.tsx
Normal file
58
apps/admin/components/common/plane-lockup.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
// Plane logo lockup. Not part of @makeplane/propel's icon set, so it is kept as a local asset.
|
||||
|
||||
type PlaneLockupProps = {
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
className?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export function PlaneLockup({ width = "253", height = "53", className, color = "currentColor" }: PlaneLockupProps) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 253 53"
|
||||
fill={color}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_27_76)">
|
||||
<path
|
||||
d="M217.077 51.4468H210.972L210.98 31.4317C210.717 27.7545 208.786 25.1545 205.03 24.6199C198.587 23.7006 194.663 27.8828 194.21 34.0258L194.218 51.4468H188.041V20.2543H192.158L194.255 26.8846H194.6C195.825 22.8701 198.684 20.4831 202.898 19.5757C210.126 18.0231 216.643 22.2724 217.079 29.8239V51.4468H217.077Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M163.812 19.7315C158.03 20.9566 153.67 25.2354 153.222 31.264H159.395C159.669 28.3484 161.561 25.9969 164.267 24.9553C167.418 23.7401 172.805 23.8881 175.178 26.6005C176.774 28.4253 177.305 31.5816 175.212 33.2249C172.675 35.2173 166.39 34.6117 163.214 34.7636C158.631 34.9826 154.141 36.8429 153.183 41.7727C151.1 52.5042 165.035 54.9267 171.902 49.8963C174.095 48.2885 175.672 45.8443 176.261 43.1792H176.577L178.788 51.4508H182.61V29.964C182.517 20.3214 171.462 18.108 163.81 19.7315H163.812ZM174.847 41.2637C172.484 45.649 166.958 49.0441 162.218 47.3318C158.44 45.9686 158.268 40.4095 161.973 38.9181C165.613 37.4524 173.732 38.9694 176.368 34.949C176.275 37.0855 175.906 39.2989 174.849 41.2637H174.847Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M113.082 35.5652V51.4475H106.611V9.00063H124.863C125.007 8.99274 125.141 9.06178 125.283 9.06967C133.732 9.53129 139.297 13.601 139.104 22.6025C138.928 30.7991 132.663 35.2199 124.934 35.5671H113.082V35.5652ZM113.082 14.4828V30.2941H124.088C129.595 29.8956 133.071 27.1022 132.491 21.265C131.967 15.9781 127.802 14.6544 123.107 14.4788C119.795 14.3546 116.402 14.5795 113.082 14.4808V14.4828Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path d="M148.647 9H142.529V51.4468H148.647V9Z" fill={color} />
|
||||
<path
|
||||
d="M245.872 42.418C244.911 44.4953 243.138 46.1603 240.934 46.8981C238.368 47.7562 234.479 47.5431 232.083 46.2569C228.67 44.4263 227.145 40.6485 227.137 36.8984H251.891C252.194 33.6908 251.902 30.3648 250.496 27.4333C247.083 20.3177 238.859 17.7236 231.513 19.9429C222.868 22.5568 220.165 31.4162 221.462 39.6543C222.86 48.5611 230.197 52.976 238.958 52.3585C244.884 51.9423 250.271 48.1192 251.788 42.4161H245.874L245.872 42.418ZM227.208 32.4381C227.778 26.2457 233.347 23.2018 239.163 24.4269C243.361 25.3107 245.75 28.9523 245.842 33.1068H227.145C227.145 33.1068 227.19 32.6255 227.208 32.4401V32.4381Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M44.3223 2.9264C44.3223 0.754665 46.6083 -0.65811 48.5508 0.313121L80.4551 16.2653C82.9294 17.5024 84.4922 20.0321 84.4922 22.7985V48.2487C84.4922 50.4204 82.2071 51.833 80.2646 50.8619L62.3281 41.8932V22.7975C62.3281 20.0311 60.7653 17.5015 58.291 16.2643L44.3223 9.27992V2.9264ZM0 2.92543C8.01645e-05 0.753753 2.28609 -0.659069 4.22852 0.312144L22.1582 9.27699V28.3766C22.1582 31.1428 23.7213 33.6716 26.1953 34.9088L40.1699 41.8952V48.2487C40.1697 50.4202 37.8847 51.832 35.9424 50.861L4.03711 34.9088C1.56305 33.6716 0 31.1428 0 28.3766V2.92543ZM22.1582 2.92543C22.1583 0.753753 24.4443 -0.659069 26.3867 0.312144L44.3223 9.27992V28.3776C44.3223 31.1439 45.8861 33.6727 48.3604 34.9098L62.3281 41.8932V48.2487C62.3279 50.4202 60.0429 51.832 58.1006 50.861L40.1699 41.8952V22.7975C40.1699 20.0311 38.6071 17.5015 36.1328 16.2643L22.1582 9.27699V2.92543Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_27_76">
|
||||
<rect width="252" height="53" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
43
apps/admin/components/common/skeleton.tsx
Normal file
43
apps/admin/components/common/skeleton.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
// Loading-placeholder skeleton. @makeplane/propel has no skeleton component yet;
|
||||
// this matches the API the workspace @plane/propel skeleton exposed.
|
||||
|
||||
type SkeletonProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
ariaLabel?: string;
|
||||
};
|
||||
|
||||
function SkeletonRoot({ children, className = "", ariaLabel = "Loading content" }: SkeletonProps) {
|
||||
return (
|
||||
<div data-slot="skeleton" className={cn("animate-pulse", className)} role="status" aria-label={ariaLabel}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ItemProps = {
|
||||
height?: string;
|
||||
width?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function SkeletonItem({ height = "auto", width = "auto", className = "" }: ItemProps) {
|
||||
return <div data-slot="skeleton-item" className={cn("rounded-md bg-layer-1", className)} style={{ height, width }} />;
|
||||
}
|
||||
|
||||
const Skeleton = Object.assign(SkeletonRoot, { Item: SkeletonItem });
|
||||
|
||||
SkeletonRoot.displayName = "plane-ui-skeleton";
|
||||
SkeletonItem.displayName = "plane-ui-skeleton-item";
|
||||
|
||||
export { Skeleton };
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
// assets
|
||||
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
|
||||
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
|
||||
@@ -34,9 +34,7 @@ export const InstanceFailureView = observer(function InstanceFailureView() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button size="lg" onClick={handleRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
<Button variant="primary" size="md" stretch="auto" onClick={handleRetry} label="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,14 +10,13 @@ import { useSearchParams } from "next/navigation";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Input } from "@plane/propel/input";
|
||||
import { Spinner } from "@plane/propel/spinners";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Checkbox } from "@makeplane/propel/components/checkbox";
|
||||
import { Input, InputGroup } from "@makeplane/propel/components/input";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils";
|
||||
// components
|
||||
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
|
||||
import { Checkbox } from "@/components/common/checkbox";
|
||||
import { PasswordStrengthIndicator } from "@/components/common/password-strength-indicator";
|
||||
import { Banner } from "../common/banner";
|
||||
import { FormHeader } from "./form-header";
|
||||
@@ -168,46 +167,48 @@ export function InstanceSetupForm() {
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="first_name">
|
||||
First name <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
inputSize="md"
|
||||
placeholder="Wilber"
|
||||
value={formData.first_name}
|
||||
onChange={(e) => {
|
||||
const validation = validatePersonName(e.target.value);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("first_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
maxLength={50}
|
||||
/>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
placeholder="Wilber"
|
||||
value={formData.first_name}
|
||||
onChange={(e) => {
|
||||
const validation = validatePersonName(e.target.value);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("first_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
maxLength={50}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="last_name">
|
||||
Last name <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
inputSize="md"
|
||||
placeholder="Wright"
|
||||
value={formData.last_name}
|
||||
onChange={(e) => {
|
||||
const validation = validatePersonName(e.target.value);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("last_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
maxLength={50}
|
||||
/>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
placeholder="Wright"
|
||||
value={formData.last_name}
|
||||
onChange={(e) => {
|
||||
const validation = validatePersonName(e.target.value);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("last_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
maxLength={50}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -215,18 +216,19 @@ export function InstanceSetupForm() {
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="email">
|
||||
Email <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
inputSize="md"
|
||||
placeholder="name@company.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="name@company.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||
aria-invalid={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</InputGroup>
|
||||
{errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL && errorData.message && (
|
||||
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
|
||||
)}
|
||||
@@ -236,39 +238,39 @@ export function InstanceSetupForm() {
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="company_name">
|
||||
Company name <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
id="company_name"
|
||||
name="company_name"
|
||||
type="text"
|
||||
inputSize="md"
|
||||
placeholder="Company name"
|
||||
value={formData.company_name}
|
||||
onChange={(e) => {
|
||||
const validation = validateCompanyName(e.target.value, false);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("company_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
maxLength={80}
|
||||
/>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
id="company_name"
|
||||
name="company_name"
|
||||
type="text"
|
||||
placeholder="Company name"
|
||||
value={formData.company_name}
|
||||
onChange={(e) => {
|
||||
const validation = validateCompanyName(e.target.value, false);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("company_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
maxLength={80}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="password">
|
||||
Set a password <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder"
|
||||
size="lg"
|
||||
id="password"
|
||||
name="password"
|
||||
type={showPassword.password ? "text" : "password"}
|
||||
inputSize="md"
|
||||
placeholder="New password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false}
|
||||
aria-invalid={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
autoComplete="new-password"
|
||||
@@ -277,7 +279,7 @@ export function InstanceSetupForm() {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Hide password"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -286,13 +288,13 @@ export function InstanceSetupForm() {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Show password"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</InputGroup>
|
||||
{errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && (
|
||||
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
|
||||
)}
|
||||
@@ -303,16 +305,15 @@ export function InstanceSetupForm() {
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="confirm_password">
|
||||
Confirm password <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
size="lg"
|
||||
type={showPassword.retypePassword ? "text" : "password"}
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
inputSize="md"
|
||||
value={formData.confirm_password}
|
||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
||||
placeholder="Confirm password"
|
||||
className="w-full border border-subtle !bg-surface-1 pr-12 placeholder:text-placeholder"
|
||||
onFocus={() => setIsRetryPasswordInputFocused(true)}
|
||||
onBlur={() => setIsRetryPasswordInputFocused(false)}
|
||||
autoComplete="new-password"
|
||||
@@ -321,7 +322,7 @@ export function InstanceSetupForm() {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Hide password"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -330,13 +331,13 @@ export function InstanceSetupForm() {
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Show password"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</InputGroup>
|
||||
{!!formData.confirm_password &&
|
||||
formData.password !== formData.confirm_password &&
|
||||
renderPasswordMatchError && (
|
||||
@@ -347,10 +348,9 @@ export function InstanceSetupForm() {
|
||||
<div className="relative flex gap-2">
|
||||
<div>
|
||||
<Checkbox
|
||||
className="h-4 w-4"
|
||||
iconClassName="w-3 h-3"
|
||||
id="is_telemetry_enabled"
|
||||
onChange={() => handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
|
||||
aria-label="Allow Plane to anonymously collect usage events"
|
||||
onCheckedChange={(checked) => handleFormChange("is_telemetry_enabled", checked)}
|
||||
checked={formData.is_telemetry_enabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -368,9 +368,15 @@ export function InstanceSetupForm() {
|
||||
</div>
|
||||
|
||||
<div className="py-2">
|
||||
<Button type="submit" size="xl" className="w-full" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
stretch="full"
|
||||
disabled={isButtonDisabled}
|
||||
loading={isSubmitting}
|
||||
label="Continue"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
// plane internal packages
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { NewTabOutline } from "@makeplane/propel/icons";
|
||||
import { WEB_BASE_URL } from "@plane/constants";
|
||||
import { NewTabIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
@@ -52,7 +52,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<div className="flex w-full flex-wrap items-center gap-2.5">
|
||||
<h3 className={`text-14 font-medium capitalize`}>{workspace.name}</h3>/
|
||||
<Tooltip tooltipContent="The unique URL of your workspace">
|
||||
<Tooltip label="The unique URL of your workspace">
|
||||
<h4 className="text-13 text-tertiary">[{workspace.slug}]</h4>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -82,7 +82,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<NewTabIcon width={14} height={16} className="text-placeholder group-hover:text-secondary" />
|
||||
<NewTabOutline width={14} height={16} className="text-placeholder group-hover:text-secondary" />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Image, BrainCog, Cog, Mail } from "lucide-react";
|
||||
// plane imports
|
||||
import { LockIcon, WorkspaceIcon } from "@plane/propel/icons";
|
||||
import { LockOutline, WorkspaceOutline } from "@makeplane/propel/icons";
|
||||
// types
|
||||
import type { TSidebarMenuItem } from "./types";
|
||||
|
||||
@@ -26,13 +26,13 @@ export const coreSidebarMenuLinks: Record<TCoreSidebarMenuKey, TSidebarMenuItem>
|
||||
href: `/email/`,
|
||||
},
|
||||
workspace: {
|
||||
Icon: WorkspaceIcon,
|
||||
Icon: WorkspaceOutline,
|
||||
name: "Workspaces",
|
||||
description: "Manage all workspaces on this instance.",
|
||||
href: `/workspace/`,
|
||||
},
|
||||
authentication: {
|
||||
Icon: LockIcon,
|
||||
Icon: LockOutline,
|
||||
name: "Authentication",
|
||||
description: "Configure authentication modes.",
|
||||
href: `/authentication/`,
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
"@fontsource/ibm-plex-mono": "catalog:",
|
||||
"@fontsource/material-symbols-rounded": "catalog:",
|
||||
"@headlessui/react": "catalog:",
|
||||
"@makeplane/propel": "0.2.0",
|
||||
"@plane/constants": "workspace:*",
|
||||
"@plane/hooks": "workspace:*",
|
||||
"@plane/propel": "workspace:*",
|
||||
"@plane/services": "workspace:*",
|
||||
"@plane/types": "workspace:*",
|
||||
"@plane/utils": "workspace:*",
|
||||
@@ -39,7 +39,6 @@
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-hook-form": "catalog:",
|
||||
"react-popper": "catalog:",
|
||||
"react-router": "catalog:",
|
||||
"serve": "catalog:",
|
||||
"swr": "catalog:",
|
||||
|
||||
@@ -4,11 +4,80 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toast } from "@plane/propel/toast";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
import { createToastManager, ToastProvider } from "@makeplane/propel/components/toast";
|
||||
import type { ToastData, ToastVariant } from "@makeplane/propel/components/toast";
|
||||
import { IconButton } from "@makeplane/propel/components/icon-button";
|
||||
import { Icon } from "@makeplane/propel/components/icon";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
|
||||
// Keep the call-site API the workspace @plane/propel toast exposed, backed by
|
||||
// @makeplane/propel's toast manager, so consumers only change their import path.
|
||||
export enum TOAST_TYPE {
|
||||
SUCCESS = "success",
|
||||
ERROR = "error",
|
||||
INFO = "info",
|
||||
WARNING = "warning",
|
||||
}
|
||||
|
||||
const TYPE_TO_VARIANT: Record<TOAST_TYPE, ToastVariant> = {
|
||||
[TOAST_TYPE.SUCCESS]: "success",
|
||||
[TOAST_TYPE.ERROR]: "danger",
|
||||
[TOAST_TYPE.INFO]: "info",
|
||||
[TOAST_TYPE.WARNING]: "warning",
|
||||
};
|
||||
|
||||
const toastManager = createToastManager();
|
||||
|
||||
type SetToastProps = {
|
||||
type: TOAST_TYPE;
|
||||
title: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export const setToast = (props: SetToastProps) =>
|
||||
toastManager.add({
|
||||
title: props.title,
|
||||
description: props.message,
|
||||
data: { variant: TYPE_TO_VARIANT[props.type] },
|
||||
});
|
||||
|
||||
type PromiseToastData<TValue> = {
|
||||
title: string;
|
||||
message?: (data: TValue) => string;
|
||||
};
|
||||
|
||||
type PromiseToastOptions<TValue> = {
|
||||
loading?: string;
|
||||
success: PromiseToastData<TValue>;
|
||||
error: PromiseToastData<TValue>;
|
||||
};
|
||||
|
||||
export const setPromiseToast = <TValue,>(promise: Promise<TValue>, options: PromiseToastOptions<TValue>): void => {
|
||||
void toastManager.promise<TValue, ToastData>(promise, {
|
||||
loading: {
|
||||
title: options.loading ?? "Loading...",
|
||||
data: { variant: "neutral" },
|
||||
},
|
||||
success: (data) => ({
|
||||
title: options.success.title,
|
||||
description: options.success.message?.(data),
|
||||
data: { variant: "success" },
|
||||
}),
|
||||
error: (data) => ({
|
||||
title: options.error.title,
|
||||
description: options.error.message?.(data),
|
||||
data: { variant: "danger" },
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export function ToastWithTheme() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />;
|
||||
return (
|
||||
<ToastProvider
|
||||
toastManager={toastManager}
|
||||
close={
|
||||
<IconButton variant="ghost" size="sm" aria-label="Close notification" icon={<Icon icon={CloseOutline} />} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
@import "@plane/tailwind-config/index.css";
|
||||
/* Design tokens already arrive via @plane/tailwind-config. The @makeplane/propel styles barrel
|
||||
re-imports the same token files (identical content) and, crucially, registers propel's dist as a
|
||||
Tailwind @source so the utility classes used by its components are emitted, plus the animation
|
||||
tokens (animate-spinner-reveal, animate-progress-indeterminate) its components rely on. */
|
||||
@import "@makeplane/propel/styles";
|
||||
|
||||
.shadow-custom {
|
||||
box-shadow: 2px 2px 8px 2px rgba(234, 231, 250, 0.3); /* Convert #EAE7FA4D to rgba */
|
||||
|
||||
Reference in New Issue
Block a user