Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: empty state refactor #6404

Open
wants to merge 29 commits into
base: preview
Choose a base branch
from

Conversation

anmolsinghbhatia
Copy link
Collaborator

@anmolsinghbhatia anmolsinghbhatia commented Jan 15, 2025

Description

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

References

Summary by CodeRabbit

Release Notes

Internationalization and Localization Enhancements

  • Added comprehensive translations for multiple languages (English, Spanish, French, Japanese)
  • Expanded translation support across various components and pages
  • Updated translation keys for improved clarity and consistency

Empty State Improvements

  • Replaced generic EmptyState component with more detailed DetailedEmptyState and SimpleEmptyState
  • Enhanced empty state rendering with localized titles, descriptions, and dynamic asset paths
  • Improved user guidance for scenarios with no data

User Permissions Management

  • Integrated advanced permission checks across components
  • Added dynamic permission-based rendering for buttons and actions
  • Introduced more granular role-based access control

Asset Management

  • Added useResolvedAssetPath hook for dynamic asset resolution
  • Improved asset path handling for empty state illustrations

Key Changes

  • Removed deprecated EmptyState component and related constants
  • Updated import statements and component structures
  • Enhanced internationalization support using translation hooks

Copy link
Contributor

coderabbitai bot commented Jan 15, 2025

Walkthrough

This pull request introduces comprehensive localization and internationalization updates across the Plane web application. The changes primarily focus on enhancing empty state components, translation management, and user experience by integrating dynamic translations and resolved asset paths. Multiple files across the frontend codebase have been modified to support more robust and flexible localization, with a particular emphasis on replacing static empty states with dynamic, translatable components.

Changes

File/Path Change Summary
packages/i18n/src/locales/*/translations.json Added multiple new translation keys for workspace, project, and feature-specific contexts
web/core/components/empty-state/* Removed EmptyState component, introduced SimpleEmptyState and DetailedEmptyState
web/**/page.tsx Updated empty state rendering with new localization and permission-aware components
web/core/constants/empty-state.tsx Removed EmptyStateDetails and EmptyStateType interfaces

Poem

🐰 A Rabbit's Localization Ballad 🌍

In empty states, where silence dwells,
Translations weave their magic spells
With dynamic paths and words so bright
Our interfaces now shine with light!

Permissions dance, translations sing 🎶

Suggested labels

🌐frontend, 🧹chore, 🌟enhancement

Suggested reviewers

  • sriramveeraghanta
  • rahulramesha
  • gurusainath

Possibly related PRs

Finishing Touches

  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

makeplane bot commented Jan 16, 2025

Pull Request Linked with Plane Issues

Comment Automatically Generated by Plane

@prateekshourya29 prateekshourya29 marked this pull request as ready for review January 17, 2025 17:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🔭 Outside diff range comments (3)
web/core/constants/profile.ts (1)

Line range hint 66-71: Add missing 'key' property for consistency.

In the PROFILE_ADMINS_TAB array, the "Created" tab object is missing the key property, while all other items have it. This breaks the pattern and could cause issues with React key props.

  {
+   key: "created",
    route: "created",
    label: "Created",
    selected: "/created/",
  },
web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx (1)

Line range hint 65-92: Consider reducing code duplication in the conditional rendering.

The UI structure is duplicated between the two conditions. Consider extracting common elements to reduce duplication.

  return (
    <>
      <PageHead title={pageTitle} />
      <div className="w-full overflow-y-auto">
        <CreateWebhookModal
          createWebhook={createWebhook}
          clearSecretKey={clearSecretKey}
          currentWorkspace={currentWorkspace}
          isOpen={showCreateWebhookModal}
          onClose={() => {
            setShowCreateWebhookModal(false);
          }}
        />
+       <div className="flex h-full w-full flex-col">
+         <div className="flex items-center justify-between gap-4 border-b border-custom-border-200 pb-3.5">
+           <div className="text-xl font-medium">Webhooks</div>
+           <Button variant="primary" size="sm" onClick={() => setShowCreateWebhookModal(true)}>
+             Add webhook
+           </Button>
+         </div>
          {Object.keys(webhooks).length > 0 ? (
-           <div className="flex h-full w-full flex-col">
-             <div className="flex items-center justify-between gap-4 border-b border-custom-border-200 pb-3.5">
-               <div className="text-xl font-medium">Webhooks</div>
-               <Button variant="primary" size="sm" onClick={() => setShowCreateWebhookModal(true)}>
-                 Add webhook
-               </Button>
-             </div>
              <WebhooksList />
-           </div>
          ) : (
-           <div className="flex h-full w-full flex-col">
-             <div className="flex items-center justify-between gap-4 border-b border-custom-border-200 pb-3.5">
-               <div className="text-xl font-medium">Webhooks</div>
-               <Button variant="primary" size="sm" onClick={() => setShowCreateWebhookModal(true)}>
-                 Add webhook
-               </Button>
-             </div>
              <div className="h-full w-full flex items-center justify-center">
                <DetailedEmptyState
                  title={t("workspace_settings.empty_state.webhooks.title")}
                  description={t("workspace_settings.empty_state.webhooks.description")}
                  assetPath={resolvedPath}
                />
              </div>
-           </div>
          )}
+       </div>
      </div>
    </>
  );
web/core/components/integration/guide.tsx (1)

Translation key mismatch detected in locale files

Inconsistent translation keys found for empty state messages:

  • English: issue_relation.empty_state.no_issues
  • French/Japanese: issue_relation.empty_state.general

These keys should be synchronized across all locale files to prevent missing translations.

🔗 Analysis chain

Line range hint 317-780: Verify translation key consistency across locale files

Let's ensure all locale files have the same structure and no missing translations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistency of translation keys across locale files

# Function to extract keys from a JSON file
extract_keys() {
  jq -r 'path(..)|map(tostring)|join(".")' "$1" | sort
}

# Compare keys between locale files
echo "Comparing translation keys..."
for locale in en fr ja; do
  if [ -f "packages/i18n/src/locales/${locale}/translations.json" ]; then
    extract_keys "packages/i18n/src/locales/${locale}/translations.json" > "/tmp/${locale}_keys.txt"
  fi
done

# Find differences
echo "Checking for missing keys..."
for locale1 in en fr ja; do
  for locale2 in en fr ja; do
    if [ "$locale1" != "$locale2" ]; then
      diff "/tmp/${locale1}_keys.txt" "/tmp/${locale2}_keys.txt" || true
    fi
  done
done

Length of output: 2378

🧹 Nitpick comments (29)
web/core/components/cycles/active-cycle/cycle-stats.tsx (2)

241-243: Consider extracting empty state configurations for better maintainability.

While the empty state implementations are consistent, consider extracting the configurations into a constant to reduce repetition and improve maintainability.

Example implementation:

const EMPTY_STATES = {
  priority: {
    translationKey: "active_cycle.empty_state.priority_issue.title",
    assetBasePath: "/empty-state/active-cycle/priority"
  },
  assignee: {
    translationKey: "active_cycle.empty_state.assignee.title",
    assetBasePath: "/empty-state/active-cycle/assignee"
  },
  label: {
    translationKey: "active_cycle.empty_state.label.title",
    assetBasePath: "/empty-state/active-cycle/label"
  }
} as const;

Also applies to: 302-304, 344-344


44-46: LGTM! Consider enhancing the local storage documentation.

The local storage implementation is clear, but consider adding a brief JSDoc comment explaining the purpose and possible values of the "activeCycleTab" key.

Example:

/**
 * Persists the active tab selection.
 * Possible values: "Priority-Issues" | "Assignees" | "Labels"
 */
const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees");
web/core/components/home/home-dashboard-widgets.tsx (1)

86-92: Consider improving the empty state wrapper structure.

While the empty state implementation is functional, the wrapper structure could be improved for better semantics and accessibility.

Consider this improvement:

-        <div className="h-full w-full grid place-items-center">
+        <section 
+          className="h-full w-full grid place-items-center" 
+          aria-label={t("home_widgets.empty_state.general.title")}
+        >
           <SimpleEmptyState
             title={t("home_widgets.empty_state.general.title")}
             description={t("home_widgets.empty_state.general.description")}
             assetPath={noWidgetsResolvedPath}
           />
-        </div>
+        </section>

This change:

  • Uses semantic HTML with section
  • Adds ARIA label for better accessibility
  • Maintains the same visual layout
web/core/components/issues/issue-layouts/empty-states/cycle.tsx (2)

36-40: Consider simplifying the filter count logic.

The current implementation could be more concise using Object.values().

-  const issueFilterCount = size(
-    Object.fromEntries(
-      Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
-    )
-  );
+  const issueFilterCount = Object.values(userFilters ?? {})
+    .filter((value) => value && Array.isArray(value) && value.length > 0)
+    .length;

49-59: Consider extracting asset paths to constants.

The base paths for assets could be moved to a constants file to improve maintainability and reusability.

// suggested constants file
export const EMPTY_STATE_ASSET_PATHS = {
  EMPTY_FILTERS: '/empty-state/empty-filters/',
  CYCLE_ISSUES: '/empty-state/cycle-issues/',
  COMPLETED_NO_ISSUES: '/empty-state/cycle/completed-no-issues',
} as const;
web/core/components/inbox/sidebar/root.tsx (1)

141-155: Consider centralizing translation keys

The translation keys are currently hardcoded strings. Consider creating a constant object to centralize these keys, making them easier to maintain and reuse.

Example:

// constants/translations/inbox.ts
export const INBOX_TRANSLATIONS = {
  EMPTY_STATE: {
    SIDEBAR_FILTER: {
      TITLE: "inbox.empty_state.sidebar_filter.title",
      DESCRIPTION: "inbox.empty_state.sidebar_filter.description"
    },
    SIDEBAR_OPEN_TAB: {
      TITLE: "inbox.empty_state.sidebar_open_tab.title",
      DESCRIPTION: "inbox.empty_state.sidebar_open_tab.description"
    },
    SIDEBAR_CLOSED_TAB: {
      TITLE: "inbox.empty_state.sidebar_closed_tab.title",
      DESCRIPTION: "inbox.empty_state.sidebar_closed_tab.description"
    }
  }
};

This would make the code more maintainable and less prone to typos:

<SimpleEmptyState
  title={t(INBOX_TRANSLATIONS.EMPTY_STATE.SIDEBAR_FILTER.TITLE)}
  description={t(INBOX_TRANSLATIONS.EMPTY_STATE.SIDEBAR_FILTER.DESCRIPTION)}
  assetPath={sidebarFilterAssetPath}
/>
web/core/components/stickies/layout/stickies-list.tsx (1)

119-122: Add error handling for sticky creation.

The sticky creation operation should include error handling to gracefully handle failures and provide user feedback.

 onClick: () => {
+  try {
     toggleShowNewSticky(true);
-    stickyOperations.create();
+    await stickyOperations.create();
+  } catch (error) {
+    // Handle error appropriately (e.g., show toast notification)
+    console.error("Failed to create sticky:", error);
+    toggleShowNewSticky(false);
+  }
 },
web/core/components/core/modals/issue-search-modal-empty-state.tsx (1)

28-30: Consider extracting EmptyStateContainer to a shared component.

The EmptyStateContainer component could be reused across other empty states. Consider moving it to a shared location.

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx (1)

38-48: Consider adding secondary action.

While the implementation is correct, consider adding a secondary button for non-admin users to contact admins.

 <DetailedEmptyState
   title={t("disabled_project.empty_state.inbox.title")}
   description={t("disabled_project.empty_state.inbox.description")}
   assetPath={resolvedPath}
   primaryButton={{
     text: t("disabled_project.empty_state.inbox.primary_button.text"),
     onClick: () => {
       router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
     },
     disabled: !canPerformEmptyStateActions,
   }}
+  secondaryButton={{
+    text: t("disabled_project.empty_state.inbox.secondary_button.text"),
+    onClick: () => {
+      // Add contact admin action
+    }
+  }}
 />
web/core/components/cycles/archived-cycles/root.tsx (1)

70-74: Consider adding refresh action.

While the empty state implementation is correct, consider adding a refresh action for users to check for new archived cycles.

 <DetailedEmptyState
   title={t("project_cycles.empty_state.archived.title")}
   description={t("project_cycles.empty_state.archived.description")}
   assetPath={resolvedPath}
+  primaryButton={{
+    text: t("project_cycles.empty_state.archived.refresh"),
+    onClick: () => {
+      fetchArchivedCycles(workspaceSlug.toString(), projectId.toString());
+    }
+  }}
 />
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx (1)

37-38: Consider adding a comment explaining the permission check.

The permission check is correct, but adding a brief comment would help explain why these specific roles are required for empty state actions.

+ // Only project admins can access feature settings
 const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx (1)

35-36: Consider adding a comment explaining the permission check.

Similar to the views page, adding a brief comment would help explain the permission requirements.

+ // Only project admins can access feature settings
 const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx (1)

84-88: Consider adding a primary button to the empty state.

The empty state could benefit from a primary button that opens the create token modal, similar to how the header button works. This would provide a more consistent user experience.

 <DetailedEmptyState
   title={t("workspace_settings.empty_state.api_tokens.title")}
   description={t("workspace_settings.empty_state.api_tokens.description")}
   assetPath={resolvedPath}
+  primaryButton={{
+    text: t("workspace_settings.empty_state.api_tokens.primary_button.text"),
+    onClick: () => setIsCreateTokenModalOpen(true)
+  }}
 />
web/core/components/inbox/root.tsx (2)

30-31: Consider adding error boundaries for better error handling.

While the component handles loading and error states, wrapping it with an error boundary would provide a more robust error handling mechanism.

Also applies to: 34-35


104-104: Consider enhancing the empty state with more context.

The empty state could be more informative by providing guidance on how to create new inbox items or explaining when items appear here.

-            <SimpleEmptyState title={t("inbox.empty_state.detail.title")} assetPath={resolvedPath} />
+            <SimpleEmptyState 
+              title={t("inbox.empty_state.detail.title")} 
+              description={t("inbox.empty_state.detail.description")}
+              assetPath={resolvedPath}
+            />
web/ce/components/cycles/active-cycle/root.tsx (1)

39-39: Consider memoizing the resolved path.

The activeCycleResolvedPath could be memoized to prevent unnecessary recalculations.

-  const activeCycleResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/cycle/active" });
+  const activeCycleResolvedPath = useMemo(
+    () => useResolvedAssetPath({ basePath: "/empty-state/cycle/active" }),
+    []
+  );

Also applies to: 86-86

web/core/components/issues/workspace-draft/root.tsx (1)

66-81: Consider adding a loading state for the asset.

The empty state implementation looks good, but consider adding a loading state for the asset to handle cases where the asset might take time to load.

 <DetailedEmptyState
   size="sm"
   title={t("workspace_projects.empty_state.no_projects.title")}
   description={t("workspace_projects.empty_state.no_projects.description")}
   assetPath={noProjectResolvedPath}
+  showLoader={!noProjectResolvedPath}
   customPrimaryButton={
     <ComicBoxButton
       label={t("workspace_projects.empty_state.no_projects.primary_button.text")}
web/core/components/page-views/workspace-dashboard.tsx (1)

102-117: Consider adding error handling for asset loading.

While the empty state implementation is good, it would be beneficial to handle potential asset loading failures.

 <DetailedEmptyState
   title={t("workspace_dashboard.empty_state.general.title")}
   description={t("workspace_dashboard.empty_state.general.description")}
   assetPath={resolvedPath}
+  onAssetLoadError={() => {
+    // Handle asset loading error
+    console.error("Failed to load empty state asset");
+  }}
   customPrimaryButton={
web/app/[workspaceSlug]/(projects)/notifications/page.tsx (1)

89-89: Consider adding a fallback for missing translations.

While the empty state implementation is clean, it would be beneficial to add a fallback text in case the translation key is missing.

-<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
+<SimpleEmptyState 
+  title={t("notification.empty_state.detail.title", "No notification selected")} 
+  assetPath={resolvedPath} 
+/>
web/app/[workspaceSlug]/(projects)/analytics/page.tsx (1)

83-98: Consider adding aria-labels for better accessibility.

The empty state implementation looks good, but could benefit from additional accessibility attributes.

 <DetailedEmptyState
   title={t("workspace_analytics.empty_state.general.title")}
   description={t("workspace_analytics.empty_state.general.description")}
   assetPath={resolvedPath}
+  aria-label={t("workspace_analytics.empty_state.general.aria_label", "Analytics empty state")}
   customPrimaryButton={
     <ComicBoxButton
       label={t("workspace_analytics.empty_state.general.primary_button.text")}
       title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
       description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
+      aria-label={t("workspace_analytics.empty_state.general.primary_button.aria_label", "Create new project")}
       onClick={() => {
         setTrackElement("Analytics empty state");
         toggleCreateProjectModal(true);
       }}
       disabled={!canPerformEmptyStateActions}
     />
   }
 />
web/core/components/pages/pages-list-main-content.tsx (1)

39-50: Consider consolidating asset path resolution.

Instead of resolving multiple asset paths individually, consider using a single hook call with a base path and combining it with specific paths.

-const generalPageResolvedPath = useResolvedAssetPath({
-  basePath: "/empty-state/onboarding/pages",
-});
-const publicPageResolvedPath = useResolvedAssetPath({
-  basePath: "/empty-state/pages/public",
-});
-const privatePageResolvedPath = useResolvedAssetPath({
-  basePath: "/empty-state/pages/private",
-});
-const archivedPageResolvedPath = useResolvedAssetPath({
-  basePath: "/empty-state/pages/archived",
-});
+const resolvedBasePath = useResolvedAssetPath({
+  basePath: "/empty-state",
+});
+const paths = {
+  general: `${resolvedBasePath}/onboarding/pages`,
+  public: `${resolvedBasePath}/pages/public`,
+  private: `${resolvedBasePath}/pages/private`,
+  archived: `${resolvedBasePath}/pages/archived`,
+};
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx (1)

104-105: Consider adding more context to event tracking.

The event tracking could benefit from additional context about the user's journey.

-setTrackElement("Cycle empty state");
+setTrackElement("Cycle empty state", {
+  source: "Empty state",
+  action: "Create cycle",
+  hasPermission: hasMemberLevelPermission
+});
web/core/components/modules/modules-list-view.tsx (1)

34-37: Consider expanding the permission check for empty state actions.

The current permission check only allows ADMIN and MEMBER roles. Consider including other roles that might need to perform these actions, or make it configurable based on project settings.

-  const canPerformEmptyStateActions = allowPermissions(
-    [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
-    EUserPermissionsLevel.PROJECT
-  );
+  const canPerformEmptyStateActions = allowPermissions(
+    [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER, EUserProjectRoles.VIEWER],
+    EUserPermissionsLevel.PROJECT,
+    projectPermissions?.canCreateModule
+  );
web/core/components/issues/issue-layouts/empty-states/module.tsx (1)

34-38: Simplify the filter count calculation for better readability.

The current implementation using Object.fromEntries and Object.entries is verbose. Consider using a more straightforward approach.

-  const issueFilterCount = size(
-    Object.fromEntries(
-      Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
-    )
-  );
+  const issueFilterCount = Object.values(userFilters ?? {})
+    .filter((value): value is unknown[] => Array.isArray(value) && value.length > 0)
+    .length;
web/core/components/inbox/modals/select-duplicate.tsx (1)

Line range hint 65-67: Add translation for error toast message.

The error toast message is hardcoded in English. Consider using translation keys for consistency.

       return setToast({
-        title: "Error",
+        title: t("common.error"),
         type: TOAST_TYPE.ERROR,
       });
web/core/components/project/multi-select-modal.tsx (2)

Line range hint 166-175: Add translations for button text.

The "Cancel" and "Confirm"/"Confirming" button texts are hardcoded. Consider using translation keys for consistency.

         <Button variant="neutral-primary" size="sm" onClick={handleClose}>
-          Cancel
+          {t("common.cancel")}
         </Button>
         <Button
           ref={moveButtonRef}
           variant="primary"
           size="sm"
           onClick={handleSubmit}
           loading={isSubmitting}
           disabled={!areSelectedProjectsChanged}
         >
-          {isSubmitting ? "Confirming" : "Confirm"}
+          {isSubmitting ? t("common.confirming") : t("common.confirm")}
         </Button>

Line range hint 85-85: Add translation for search placeholder.

The search input placeholder is hardcoded. Consider using a translation key.

-            placeholder="Search for projects"
+            placeholder={t("workspace_projects.search.placeholder")}
web/core/components/integration/guide.tsx (1)

29-29: Consider creating a migration plan for this deprecated component

The FIXME comment indicates this component is deprecated, but it's still in use. To prevent technical debt, consider:

  1. Creating a new component to replace this one
  2. Planning a gradual migration
  3. Setting a timeline for removal
packages/i18n/src/locales/es/translations.json (1)

459-614: Consider making some descriptions more concise.

While the empty states for project sections are well-structured and informative, some descriptions could be shortened for better readability. For example, the project issues description (lines 510-511) is quite lengthy and could be more focused.

Example improvement for project issues:

-"Piensa en los problemas como trabajos, tareas, acciones, o JTBD (trabajos por hacer). Nos gusta eso. Un problema y sus subproblemas suelen ser acciones basadas en el tiempo asignadas a los miembros de tu equipo. Tu equipo crea, asigna y completa problemas para avanzar el proyecto hacia su objetivo."
+"Los problemas son tareas asignables que ayudan a tu equipo a avanzar el proyecto hacia su objetivo. Pueden contener subproblemas y tienen plazos definidos."
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ae1ce0 and e95a40c.

📒 Files selected for processing (60)
  • packages/i18n/src/locales/en/translations.json (1 hunks)
  • packages/i18n/src/locales/es/translations.json (1 hunks)
  • packages/i18n/src/locales/fr/translations.json (1 hunks)
  • packages/i18n/src/locales/ja/translations.json (1 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/notifications/page.tsx (3 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx (4 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx (3 hunks)
  • web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx (2 hunks)
  • web/app/profile/activity/page.tsx (2 hunks)
  • web/app/profile/notifications/page.tsx (1 hunks)
  • web/app/profile/page.tsx (1 hunks)
  • web/ce/components/cycles/active-cycle/root.tsx (4 hunks)
  • web/core/components/command-palette/command-modal.tsx (5 hunks)
  • web/core/components/core/modals/bulk-delete-issues-modal.tsx (3 hunks)
  • web/core/components/core/modals/issue-search-modal-empty-state.tsx (2 hunks)
  • web/core/components/cycles/active-cycle/cycle-stats.tsx (6 hunks)
  • web/core/components/cycles/active-cycle/productivity.tsx (3 hunks)
  • web/core/components/cycles/active-cycle/progress.tsx (4 hunks)
  • web/core/components/cycles/archived-cycles/root.tsx (2 hunks)
  • web/core/components/empty-state/empty-state.tsx (0 hunks)
  • web/core/components/empty-state/index.ts (0 hunks)
  • web/core/components/empty-state/simple-empty-state-root.tsx (2 hunks)
  • web/core/components/exporter/guide.tsx (3 hunks)
  • web/core/components/home/home-dashboard-widgets.tsx (3 hunks)
  • web/core/components/inbox/modals/select-duplicate.tsx (3 hunks)
  • web/core/components/inbox/root.tsx (3 hunks)
  • web/core/components/inbox/sidebar/root.tsx (4 hunks)
  • web/core/components/integration/guide.tsx (2 hunks)
  • web/core/components/issues/issue-detail/issue-activity/comments/root.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/empty-states/archived-issues.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/empty-states/cycle.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/empty-states/draft-issues.tsx (1 hunks)
  • web/core/components/issues/issue-layouts/empty-states/global-view.tsx (1 hunks)
  • web/core/components/issues/issue-layouts/empty-states/module.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/empty-states/profile-view.tsx (1 hunks)
  • web/core/components/issues/issue-layouts/empty-states/project-epic.tsx (1 hunks)
  • web/core/components/issues/issue-layouts/empty-states/project-issues.tsx (2 hunks)
  • web/core/components/issues/workspace-draft/empty-state.tsx (2 hunks)
  • web/core/components/issues/workspace-draft/root.tsx (3 hunks)
  • web/core/components/labels/project-setting-label-list.tsx (2 hunks)
  • web/core/components/modules/archived-modules/root.tsx (2 hunks)
  • web/core/components/modules/modules-list-view.tsx (3 hunks)
  • web/core/components/page-views/workspace-dashboard.tsx (4 hunks)
  • web/core/components/pages/pages-list-main-content.tsx (2 hunks)
  • web/core/components/project/card-list.tsx (3 hunks)
  • web/core/components/project/multi-select-modal.tsx (4 hunks)
  • web/core/components/stickies/layout/stickies-list.tsx (3 hunks)
  • web/core/components/views/views-list.tsx (2 hunks)
  • web/core/components/workspace-notifications/sidebar/empty-state.tsx (1 hunks)
  • web/core/components/workspace/sidebar/dropdown.tsx (1 hunks)
  • web/core/components/workspace/sidebar/user-menu.tsx (1 hunks)
  • web/core/constants/empty-state.tsx (0 hunks)
  • web/core/constants/profile.ts (1 hunks)
  • web/core/layouts/auth-layout/project-wrapper.tsx (5 hunks)
  • web/core/store/user/permissions.store.ts (5 hunks)
💤 Files with no reviewable changes (3)
  • web/core/components/empty-state/empty-state.tsx
  • web/core/components/empty-state/index.ts
  • web/core/constants/empty-state.tsx
✅ Files skipped from review due to trivial changes (1)
  • web/core/components/issues/issue-layouts/empty-states/draft-issues.tsx
🔇 Additional comments (82)
web/core/components/cycles/active-cycle/cycle-stats.tsx (1)

11-11: LGTM! Well-structured internationalization setup.

The addition of translation and asset path resolution hooks is well-organized and follows React best practices.

Also applies to: 18-18, 28-28, 51-55

web/core/components/home/home-dashboard-widgets.tsx (3)

3-4: Well-organized imports with clear grouping!

The imports are logically grouped with clear comments, and the new additions for translations and asset path resolution are properly placed in their respective sections.

Also applies to: 11-11


55-56: Good implementation of i18n translations!

The translation integration follows best practices with hierarchical keys and proper hook usage. The empty state messages are now properly internationalized.

Also applies to: 88-89


59-60: Good practice using asset path resolution!

The asset path is properly resolved using a custom hook, and the computation is correctly placed outside the render for better performance.

web/core/components/issues/issue-layouts/empty-states/cycle.tsx (3)

8-17: Well-organized imports with clear separation of concerns!

The imports are properly categorized with comments and include all necessary dependencies for internationalization, permissions, and empty state handling.


24-32: Excellent hooks organization and initialization!

The component properly initializes all necessary hooks for translations, permissions, and store access. The setup follows React best practices and enables internationalization support.


112-147: Well-structured empty state handling with proper internationalization!

The implementation demonstrates:

  • Clear conditional rendering based on cycle state
  • Proper use of translation keys for all text content
  • Appropriate permission checks for button actions
  • Integration with event tracking
web/core/components/inbox/sidebar/root.tsx (3)

5-21: Well-organized imports with clear grouping!

The imports are logically grouped with clear comments, making the code more maintainable. The new imports for translations and empty states align well with the refactoring objectives.


Line range hint 42-64: Good component organization with clear sections!

The component's internal organization has been improved with:

  • Clear section comments for router, hooks, and derived values
  • Proper initialization of translation and asset path hooks
  • Clean separation of concerns

139-157: Great implementation of dynamic empty states!

The empty state handling is now more robust with:

  • Conditional rendering based on filters and current tab
  • Proper internationalization support
  • Clean separation of content and presentation
web/core/constants/profile.ts (1)

13-13: Verify consistent key naming pattern across translations.

The change from "profile" to "profile.label" appears to be part of a broader localization effort. Let's verify that this naming pattern is consistently applied across translation files and other profile-related components.

✅ Verification successful

Translation key pattern is consistent across the codebase.

The change from "profile" to "profile.label" follows the established i18n pattern where translation keys are properly namespaced. This structure is consistently implemented across all language files and correctly referenced throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistency in profile-related translation keys and their usage

# Check for profile-related translation keys in translation files
echo "Checking translation files for profile keys:"
rg -g "*.json" '"profile\.' 

# Check for any remaining direct "profile" keys that might need updating
echo "Checking for potentially inconsistent profile keys:"
rg -g "*.{ts,tsx}" 'key: "profile"'

Length of output: 279


Script:

#!/bin/bash
# Find translation files first
echo "Locating translation files:"
fd -e json -e yaml -e yml

echo -e "\nChecking for profile-related keys in all formats:"
rg -g "*.{json,yaml,yml}" -i "profile"

echo -e "\nChecking TypeScript/React files for profile key usage:"
rg -g "*.{ts,tsx}" "profile" -A 1 -B 1

Length of output: 59368

web/core/components/stickies/layout/stickies-list.tsx (4)

10-21: LGTM! Well-organized imports.

The new imports are properly categorized and align with the added functionality for empty states, translations, and user permissions.


53-60: LGTM! Proper permission checks and asset path resolution.

The implementation correctly:

  • Checks for appropriate workspace-level permissions
  • Resolves asset paths for both general and search empty states

104-127: LGTM! Well-implemented empty state refactor.

The empty state implementation is well-structured with:

  • Proper conditional rendering based on search context
  • Internationalization support
  • Permission-based controls
  • Clear user feedback

Line range hint 139-140: Address the TypeScript error in Masonry component.

The @ts-expect-error comment suggests a type mismatch. Consider:

  1. Updating the react-masonry-component types
  2. Creating a proper type definition
  3. Using an alternative masonry library with better TypeScript support
web/core/components/cycles/active-cycle/productivity.tsx (3)

4-15: LGTM! Well-organized imports with clear sectioning.

The import changes align well with the empty state refactoring objective, properly introducing internationalization and asset resolution capabilities.


25-31: LGTM! Proper hook initialization and usage.

The translation and asset resolution hooks are well-integrated, supporting the internationalization requirements.


101-101: Verify translation key and asset path existence.

The empty state implementation looks good, but let's verify:

  1. The translation key active_cycle.empty_state.chart.title exists in the translation files
  2. The asset exists at the resolved path /empty-state/active-cycle/chart
✅ Verification successful

Empty state implementation verified successfully

The implementation is correct with:

  • Translation key active_cycle.empty_state.chart.title exists in the translations
  • Asset path follows the established pattern in web/public/empty-state/
  • Component usage is consistent with other empty states in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the translation key exists
rg -g '*.{json,ts,js}' "active_cycle\.empty_state\.chart\.title" 

# Check if the asset exists (common image extensions)
fd -e svg -e png -e jpg -e jpeg -e gif -p "chart" "empty-state/active-cycle"

Length of output: 253


Script:

#!/bin/bash
# Find translation files
fd -e json translations

# Find SimpleEmptyState component definition
ast-grep --pattern 'export $_ function SimpleEmptyState'

# Find any empty state related assets
fd -e svg -e png -e jpg -e jpeg -e gif "empty"

# Search for parts of the translation key
rg -g '*.{json,ts,js}' "active_cycle"

Length of output: 3770


Script:

#!/bin/bash
# Check complete translation structure under active_cycle
rg -g '*.json' -A 10 '"active_cycle": {' packages/i18n/src/locales/en/translations.json

# Find SimpleEmptyState with broader pattern
ast-grep --pattern 'SimpleEmptyState'

# Find any file containing SimpleEmptyState definition
rg "SimpleEmptyState.*=.*" -A 5

Length of output: 12046


Script:

#!/bin/bash
# Search for the specific translation key with proper JSON escaping
rg -g '*.json' --json -A 5 '"active_cycle"' packages/i18n/src/locales/en/translations.json | jq -r '.data.lines.text'

Length of output: 290

web/app/profile/page.tsx (1)

26-26: LGTM! Translation key update is consistent with localization strategy.

The change from profile to profile.label aligns with the broader localization improvements across the application.

web/core/components/issues/issue-layouts/empty-states/profile-view.tsx (2)

9-9: Address TODO comment regarding profileViewId stability.

The comment indicates potential stability issues with profileViewId changes. This should be investigated and resolved before merging.

Could you provide more details about the specific issues with profileViewId? This would help in proposing a more robust solution.


23-29: 🛠️ Refactor suggestion

Verify translation key existence for all possible profileViewId values.

The dynamic translation keys could fail if profileViewId doesn't match the available translations. Consider adding validation or fallback values.

+  const fallbackTitle = t("profile.empty_state.default.title");
+  const fallbackDescription = t("profile.empty_state.default.description");
+
+  const title = t(`profile.empty_state.${profileViewId.toString()}.title`, fallbackTitle);
+  const description = t(`profile.empty_state.${profileViewId.toString()}.description`, fallbackDescription);
+
   return (
     <DetailedEmptyState
-      title={t(`profile.empty_state.${profileViewId.toString()}.title`)}
-      description={t(`profile.empty_state.${profileViewId.toString()}.description`)}
+      title={title}
+      description={description}
       assetPath={resolvedPath}
     />
   );
web/app/profile/notifications/page.tsx (1)

28-28: LGTM! Translation key update is consistent with localization strategy.

The change from profile to profile.label aligns with the broader localization improvements across the application.

web/core/components/empty-state/simple-empty-state-root.tsx (1)

21-21: Verify the new dimensions value.

The dimensions property for "sm" size has been explicitly set to 78. Please confirm this value maintains the desired aspect ratio and visual consistency.

Consider extracting these magic numbers into a constants file for better maintainability and documentation of the design system values.

web/core/components/core/modals/issue-search-modal-empty-state.tsx (1)

32-44: LGTM! Well-structured conditional rendering.

The conditions for showing different empty states are clear and well-organized. The use of translation keys and resolved asset paths improves maintainability.

web/core/components/issues/issue-detail/issue-activity/comments/root.tsx (1)

54-58: LGTM! Clean empty state implementation.

The empty state implementation is well-structured with proper translations and asset handling.

web/app/profile/activity/page.tsx (2)

27-30: LGTM! Clean hook implementation.

The addition of translation and asset path resolution hooks follows best practices.


56-62: Verify translation key existence.

The empty state implementation looks good, but let's verify that the translation keys exist in the localization files.

✅ Verification successful

Translation keys verified successfully

Both translation keys exist in the English locale file with appropriate content:

  • profile.empty_state.activity.title: "No activities yet"
  • profile.empty_state.activity.description: "Get started by creating a new issue! Add details and properties to it. Explore more in Plane to see your activity."
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the translation keys exist in the English locale file
rg "profile\.empty_state\.activity\." -A 2 "packages/i18n/src/locales/en/translations.json"

Length of output: 91


Script:

#!/bin/bash
# Find translation files
fd -e json -e yaml -e yml . | grep -i "translation\|locale\|i18n"

# Also try finding files containing the specific translation key pattern
rg -l "profile\.empty_state\.activity" 

Length of output: 435


Script:

#!/bin/bash
# Check the structure of the English translations file
cat packages/i18n/src/locales/en/translations.json

# Also look for any similar patterns in case the key format is different
rg "profile" -A 5 packages/i18n/src/locales/en/translations.json

Length of output: 34069

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx (2)

5-7: LGTM! Well-structured permission handling.

The permission checks are properly implemented using the appropriate constants and hooks.

Also applies to: 28-28, 32-32


48-58: Verify disabled feature navigation.

The implementation looks good, but let's verify the settings navigation path.

✅ Verification successful

Navigation path to features settings is correctly implemented

The route /[workspaceSlug]/projects/[projectId]/settings/features exists and matches the navigation path used in the code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the features settings route exists
rg "settings/features" -A 2 "web/routes/"

Length of output: 91


Script:

#!/bin/bash
# Search for settings/features route in the app directory
fd . web/app -t d --full-path '.*settings/features.*'

# Search for any files containing settings/features path
rg "settings/features" -t typescript -t javascript

Length of output: 5008

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx (1)

5-6: LGTM! Consistent permission implementation.

The permission handling follows the same pattern as other files, maintaining consistency.

Also applies to: 29-31

web/core/components/cycles/archived-cycles/root.tsx (1)

21-22: LGTM! Clean hook implementation.

The hooks are properly implemented and follow the established pattern.

Also applies to: 29-29

web/core/components/modules/archived-modules/root.tsx (1)

21-29: LGTM! Good use of hooks for internationalization and asset path resolution.

The hooks are properly initialized and used consistently throughout the component.

web/core/components/views/views-list.tsx (1)

64-77: Verify translation keys exist.

Please ensure that the following translation keys are defined in the translation files:

  • project_views.empty_state.general.title
  • project_views.empty_state.general.description
  • project_views.empty_state.general.primary_button.text
  • project_views.empty_state.general.primary_button.comic.title
  • project_views.empty_state.general.primary_button.comic.description
✅ Verification successful

All translation keys are properly defined

All required translation keys exist in the i18n files (packages/i18n/src/locales/*/translations.json) with the correct structure and are available in multiple languages including English, Spanish, French, and Japanese.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the translation keys exist in the translation files
rg -l "project_views\.empty_state\.general\.(title|description|primary_button)" --type yaml

Length of output: 93


Script:

#!/bin/bash
# First, find all potential translation files
echo "=== Potential translation files ==="
fd -e yml -e yaml -e json

# Then search for any of these keys in those files
echo -e "\n=== Translation key search ==="
rg "project_views:" --type yaml --type json -A 5

Length of output: 1819


Script:

#!/bin/bash
# Search for project_views structure in translation files
echo "=== English translations ==="
cat packages/i18n/src/locales/en/translations.json | jq '.project_views.empty_state.general // empty'

# Also check raw content in case the structure is different
echo -e "\n=== Raw search in translations ==="
rg -A 10 '"project_views"' packages/i18n/src/locales/*/translations.json

Length of output: 5684

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx (2)

7-8: LGTM! Well-structured imports.

The imports are logically grouped and follow a consistent pattern.

Also applies to: 20-22


65-75: LGTM! Well-structured empty state implementation.

The DetailedEmptyState component is properly configured with:

  • Translated title and description
  • Resolved asset path
  • Permission-based button state
  • Clear navigation action
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx (1)

57-67: LGTM! Consistent empty state implementation.

The DetailedEmptyState implementation follows the same pattern as the views page, maintaining consistency across the application.

web/core/components/issues/issue-layouts/empty-states/project-issues.tsx (3)

37-43: LGTM! Well-structured asset path resolution.

The asset path resolution is properly handled for both filter and no-issues scenarios.


58-86: LGTM! Comprehensive empty state handling.

The implementation effectively handles both scenarios:

  1. When filters are applied but no issues match
  2. When there are no issues in the project

32-36: Consider broadening permission scope for issue creation.

The current permission check might be too restrictive. Consider allowing viewers to create issues if that aligns with your product requirements.

web/core/components/inbox/root.tsx (1)

4-5: LGTM! Clean import organization with clear sectioning.

The imports are well-organized with clear separation using comments.

Also applies to: 8-8, 16-16

web/ce/components/cycles/active-cycle/root.tsx (1)

51-55: LGTM! Comprehensive empty state implementation.

The DetailedEmptyState implementation provides clear feedback with both title and description.

web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx (1)

86-90: LGTM! Well-implemented empty state with proper translations.

The DetailedEmptyState implementation provides clear feedback with both title and description.

web/core/components/project/card-list.tsx (2)

48-52: LGTM! Well-implemented permission check for empty state actions.

The permission check ensures that only users with appropriate permissions can perform empty state actions.


58-73: LGTM! Comprehensive empty state with interactive elements.

The empty state implementation provides clear feedback and includes an interactive button with proper permission handling.

web/core/components/issues/workspace-draft/root.tsx (1)

38-41: LGTM! Well-structured permission check implementation.

The permission check is correctly implemented using allowPermissions to verify if the user has admin or member-level workspace permissions.

web/core/components/page-views/workspace-dashboard.tsx (1)

73-76: LGTM! Clear and concise permission check.

The permission check is well-implemented using allowPermissions to verify if the user has admin or member-level workspace permissions.

web/app/[workspaceSlug]/(projects)/analytics/page.tsx (1)

38-41: LGTM! Well-implemented permission check.

The permission check correctly uses allowPermissions to verify if the user has admin or member-level workspace permissions.

web/core/components/cycles/active-cycle/progress.tsx (1)

7-7: LGTM! Clean implementation of i18n and asset path resolution.

The changes effectively integrate internationalization and asset path resolution while maintaining the component's core functionality.

Also applies to: 11-11, 14-14, 25-26, 44-44, 106-106

web/core/components/pages/pages-list-main-content.tsx (1)

35-38: LGTM! Well-implemented permission check.

The permission check correctly validates user roles against project-level permissions.

web/core/components/labels/project-setting-label-list.tsx (1)

7-7: LGTM! Clean implementation of i18n and empty state.

The changes effectively integrate internationalization and empty state components while maintaining the component's core functionality.

Also applies to: 10-10, 19-19, 32-33, 39-39, 98-102

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx (1)

42-46: LGTM! Well-structured permission checks.

The code clearly distinguishes between admin and member level permissions, making it easy to understand and maintain.

web/core/layouts/auth-layout/project-wrapper.tsx (3)

11-11: Good addition of hooks for better maintainability!

The introduction of useResolvedAssetPath and proper hook organization improves code maintainability and asset management.

Also applies to: 27-27, 58-60


157-161: Well-implemented permission check for empty state actions.

Good security practice to verify user permissions before allowing empty state actions.


180-195: Enhanced empty state with better user guidance.

The DetailedEmptyState implementation with ComicBoxButton provides:

  • Clear title and description
  • Proper asset path resolution
  • Permission-aware action button
  • Event tracking for analytics
web/core/components/core/modals/bulk-delete-issues-modal.tsx (3)

9-11: Good organization of imports and dependencies.

Clean separation of plane-specific imports and proper addition of i18n and asset resolution capabilities.

Also applies to: 15-15, 20-20


45-53: Well-structured hook initialization and asset path resolution.

Good practice:

  • Clear hook organization
  • Separate asset paths for search and issues states
  • Proper i18n integration

136-140: Improved empty state handling with localization.

The implementation properly handles different scenarios:

  • No issues case
  • Search with no results case
  • Uses translated messages for better user experience
web/core/components/exporter/guide.tsx (3)

11-12: Good addition of i18n and asset resolution capabilities.

Clean integration of:

  • Translation hook
  • Asset path resolution
  • Empty state components

Also applies to: 15-15, 24-24


41-42: Well-organized hooks and derived values.

Good separation of:

  • Plane-specific hooks
  • Translation hook
  • Asset path resolution

Also applies to: 47-48


171-175: Enhanced empty state with proper localization.

The DetailedEmptyState implementation includes:

  • Translated title and description
  • Proper asset path resolution
web/core/store/user/permissions.store.ts (2)

42-42: Well-implemented permission check with type safety.

The changes:

  • Support both old and new role types
  • Maintain type safety with explicit casting
  • Preserve existing permission logic

Also applies to: 121-121, 146-146


6-6: Good preparation for permission system migration.

The temporary type ETempUserRole helps manage the transition to enums while maintaining backward compatibility.

Let's verify the usage of these roles across the codebase:

Also applies to: 26-27

✅ Verification successful

Migration preparation is properly implemented and actively used

The enums EUserProjectRoles and EUserWorkspaceRoles are extensively used across the codebase for permission management, with the temporary type ETempUserRole successfully bridging the transition. The implementation is consistent and well-structured.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of the new role types
rg "EUserProjectRoles|EUserWorkspaceRoles" -t typescript

Length of output: 93


Script:

#!/bin/bash
# Search for TypeScript files containing the enum references
fd '\.(ts|tsx)$' | xargs grep -l "EUserProjectRoles\|EUserWorkspaceRoles"

# Search for direct usage of these enums without file type filter
rg "EUserProjectRoles|EUserWorkspaceRoles"

# Search for import statements using ast-grep
ast-grep --pattern 'import { $$$ EUserProjectRoles $$$} from "@plane/constants"'
ast-grep --pattern 'import { $$$ EUserWorkspaceRoles $$$} from "@plane/constants"'

Length of output: 9428

web/core/components/workspace/sidebar/dropdown.tsx (1)

44-44: LGTM! Translation key update aligns with structured localization.

The change from t("workspace_settings") to t("workspace_settings.label") follows a more structured approach to translations, making them more maintainable and specific.

web/core/components/command-palette/command-modal.tsx (2)

5-5: LGTM! Enhanced imports improve code organization.

The imports are well-organized, separating plane-specific imports and adding necessary permission constants.

Also applies to: 11-12


268-268: LGTM! Empty state implementation follows new pattern.

The SimpleEmptyState component is properly implemented with translation and resolved asset path.

web/core/components/issues/workspace-draft/empty-state.tsx (2)

14-14: LGTM! Enhanced component with proper permissions.

The component is now properly reactive with mobx-react observer and includes appropriate permission checks for workspace actions.

Also applies to: 21-24


36-46: LGTM! Well-structured empty state implementation.

The DetailedEmptyState component is properly implemented with translations, resolved asset path, and permission-based button state.

web/core/components/integration/guide.tsx (1)

Line range hint 319-333: Well-structured empty state translations with good user guidance

The workspace dashboard empty state translations are well-organized with:

  • Clear title and description
  • Engaging comic section for user guidance
  • Consistent structure across all locales
web/core/components/issues/issue-layouts/empty-states/archived-issues.tsx (3)

4-6: LGTM! Well-structured permission and internationalization setup.

The integration of user permissions and translations is implemented correctly. The component properly imports and uses:

  • Permission enums and hooks for access control
  • Translation hooks for internationalization

Also applies to: 20-20, 23-23


33-36: LGTM! Proper permission check implementation.

The permission check is well-implemented using the correct roles and permission levels:

const canPerformEmptyStateActions = allowPermissions(
  [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
  EUserPermissionsLevel.PROJECT
);

58-79: LGTM! Well-structured conditional rendering with proper empty states.

The component effectively handles different empty states:

  1. When filters are applied - shows filter-specific empty state
  2. When no archived issues exist - shows default empty state
    Both states properly use translations and respect user permissions.
web/core/components/issues/issue-layouts/empty-states/global-view.tsx (3)

22-25: LGTM! Proper workspace-level permission check.

The permission check is correctly implemented at the workspace level:

const hasMemberLevelPermission = allowPermissions(
  [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
  EUserPermissionsLevel.WORKSPACE
);

35-56: LGTM! Well-implemented empty state for no projects.

The component properly handles the case when no projects exist:

  • Uses DetailedEmptyState with proper translations
  • Implements ComicBoxButton for enhanced UX
  • Correctly tracks user interactions
  • Properly disables actions based on permissions

59-74: LGTM! Proper handling of view-specific empty states.

The component effectively handles view-specific empty states with:

  • Dynamic translation keys based on the current view
  • Proper button configuration based on view type
  • Correct permission checks for actions
packages/i18n/src/locales/ja/translations.json (2)

317-318: LGTM! Proper key replacement.

The inbox key has been correctly replaced with connections, maintaining translation consistency.


319-780: LGTM! Well-structured and comprehensive empty state translations.

The translations are well-organized and complete:

  • Properly structured by feature/component
  • Includes all necessary UI elements (titles, descriptions, buttons)
  • Maintains consistent terminology throughout
  • Comic tooltips and button texts are properly localized
packages/i18n/src/locales/en/translations.json (2)

316-318: LGTM! Proper key replacement.

The inbox key has been correctly replaced with connections, establishing the base translation for other languages.


319-780: LGTM! Well-structured and comprehensive empty state content.

The English translations provide an excellent base for other languages:

  • Clear and consistent messaging
  • Proper organization by feature/component
  • Comprehensive coverage of all UI states
  • Helpful guidance in descriptions
packages/i18n/src/locales/es/translations.json (3)

316-317: LGTM! Well-structured key organization.

The replacement of "profile" with "connections" and the addition of new sections follows a clear, hierarchical structure that will make maintenance easier.


318-414: Excellent empty state messaging for workspace sections!

The empty states are well-crafted with:

  • Clear, action-oriented messages
  • Consistent structure across sections
  • Engaging comic elements for better user experience
  • Helpful descriptions that guide users on next steps

615-779: Well-crafted utility empty states!

The empty states for utility features (command palette, notifications, etc.) are concise and effective, maintaining consistency while being appropriately brief for these UI elements.

packages/i18n/src/locales/fr/translations.json (1)

317-780: Excellent consistency between Spanish and French translations!

The French translations maintain perfect structural parity with the Spanish version while appropriately adapting the content for French users. All sections are present with culturally appropriate translations.

However, verify that the comic references and metaphors (e.g., "10000 feet view") are culturally appropriate for both Spanish and French audiences.

Run this script to find potentially culture-specific metaphors:

✅ Verification successful

French translations show excellent cultural adaptation

The translations demonstrate thoughtful cultural considerations while maintaining consistency with the Spanish version. Notable adaptations include:

  • Using "Vue d'ensemble" instead of a direct translation of "10,000 feet view"
  • Preserving technical metaphors that are universally understood in both cultures
  • Maintaining comic references with culturally appropriate content
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find potentially culture-specific metaphors in translations
# Look for specific phrases that might need cultural adaptation

echo "Checking for culture-specific phrases..."
rg -i "feet|view|comic|car|fuel" packages/i18n/src/locales/{es,fr}/translations.json

Length of output: 8527

@@ -6,7 +6,7 @@ import Image from "next/image";
// utils
import { cn } from "@plane/utils";

type EmptyStateSize = "sm" | "md" | "lg";
type EmptyStateSize = "sm" | "lg";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Inconsistency found in empty state size types

The removal of "md" size from EmptyStateSize in simple-empty-state-root.tsx creates an inconsistency with detailed-empty-state-root.tsx, which still includes and uses the "md" size option. This needs to be synchronized across all empty state components.

  • web/core/components/empty-state/detailed-empty-state-root.tsx: Still defines and uses "md" size
  • web/core/components/empty-state/simple-empty-state-root.tsx: Removes "md" size
🔗 Analysis chain

Verify impact of removing "md" size option.

The removal of the "md" size option from EmptyStateSize type could impact existing components using this size. Please ensure all usages have been updated to use either "sm" or "lg".

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining usage of "md" size in empty states
rg -l '"md"' | xargs -I {} ast-grep --pattern 'size="md"' {}

Length of output: 66


Script:

#!/bin/bash
# Find all empty-state related files
fd empty-state

# Search for "md" string in these files
fd empty-state --exec rg -l "md" {}

# Search for any size prop with "md"
ast-grep --pattern 'size=$_("md")'

# Search for EmptyStateSize type usage
ast-grep --pattern 'EmptyStateSize'

Length of output: 1648


Script:

#!/bin/bash
# Check content of empty-state related files for "md" usage
rg "md" web/core/components/empty-state/detailed-empty-state-root.tsx -B 2 -A 2
rg "md" web/core/components/empty-state/section-empty-state-root.tsx -B 2 -A 2
rg "md" web/core/components/common/new-empty-state.tsx -B 2 -A 2

# Search specifically for size prop patterns
ast-grep --pattern 'size={$_}' web/core/components/empty-state/

Length of output: 2451

@@ -37,7 +37,7 @@ export const SidebarUserMenu = observer(() => {
},
{
key: "notifications",
labelTranslationKey: "inbox",
labelTranslationKey: "inbox.label",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Translation key structure is incorrect

The change to "inbox.label" will break translations as:

  • No translation files contain "inbox.label" key
  • Most language files use nested object structure with inbox.label
  • Chinese (zh-CN) uses direct string value for inbox
🔗 Analysis chain

Verify translation key exists.

The translation key has been changed from "inbox" to "inbox.label". Please ensure this key exists in all language files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the new translation key exists in all language files
find . -type f -name "translations.json" -exec sh -c 'echo "Checking {}" && jq -e ".\"inbox.label\"" {}' \;

Length of output: 426


Script:

#!/bin/bash
# Check for any keys ending with .label and the existence of "inbox" key
echo "Checking for .label pattern in English translations:"
jq 'to_entries | .[] | select(.key | endswith(".label")) | .key' ./packages/i18n/src/locales/en/translations.json

echo -e "\nChecking for 'inbox' key in all translation files:"
find . -type f -name "translations.json" -exec sh -c 'echo "Checking {}" && jq -e ".inbox" {}' \;

Length of output: 3134

Comment on lines +74 to +78
<DetailedEmptyState
title={t("project_module.empty_state.archived.title")}
description={t("project_module.empty_state.archived.description")}
assetPath={resolvedPath}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Translation keys are missing and need to be added

The following translation keys need to be added to all locale files under packages/i18n/src/locales/*/translations.json:

  • project_module.empty_state.archived.title
  • project_module.empty_state.archived.description

They should be added under the existing project_module.empty_state section, following the same structure as the general section.

🔗 Analysis chain

Verify translation keys exist.

Please ensure that the following translation keys are defined in the translation files:

  • project_module.empty_state.archived.title
  • project_module.empty_state.archived.description
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the translation keys exist in the translation files
rg -l "project_module\.empty_state\.archived\.(title|description)" --type yaml

Length of output: 80


Script:

#!/bin/bash
# Find potential translation files
echo "=== Looking for translation files ==="
fd -e yaml -e json -e yml . | grep -i "locale\|translation\|lang\|i18n"

echo -e "\n=== Looking for any project_module related translations ==="
rg "project_module" --type yaml --type json -l

Length of output: 821


Script:

#!/bin/bash
# Check English translations file for the specific empty state keys
echo "=== Checking English translations for empty state keys ==="
rg "empty_state.*archived" packages/i18n/src/locales/en/translations.json -C 2

Length of output: 207


Script:

#!/bin/bash
# Extract the project_module section from English translations
echo "=== Examining project_module translations structure ==="
rg -A 10 '"project_module"' packages/i18n/src/locales/en/translations.json

Length of output: 931

Comment on lines +26 to +29
const canPerformEmptyStateActions = allowPermissions(
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER, EUserProjectRoles.GUEST],
EUserPermissionsLevel.PROJECT
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Remove GUEST role from empty state actions permissions

The GUEST role should be removed from the permission check to maintain consistency with the rest of the codebase. All other similar components (modules, pages, issues) restrict empty state actions to ADMIN and MEMBER roles only.

  • In web/core/components/views/views-list.tsx, update the roles array to:
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER]
🔗 Analysis chain

Review the roles array for empty state actions.

Consider if GUEST role should have access to empty state actions. The current implementation allows guests to perform these actions if they have project-level permissions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there are any existing restrictions on guest role actions
rg -A 5 "EUserProjectRoles\.GUEST.*empty.*state" 

Length of output: 50


Script:

#!/bin/bash
# Check for guest role usage patterns
rg "EUserProjectRoles\.GUEST" -A 3

# Look for empty state actions implementation
rg "EmptyState.*ctions" -A 5

# Check for allowPermissions usage patterns
ast-grep --pattern 'allowPermissions([
  $$$
])'

Length of output: 23337

Comment on lines +42 to +46
<SimpleEmptyState
title={t("project_views.empty_state.filter.title")}
description={t("project_views.empty_state.filter.description")}
assetPath={filteredViewResolvedPath}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Translation keys exist but Chinese translation is missing

The translation keys are properly defined in English, Spanish, French, and Japanese locales. However, the Chinese (zh-CN) translation is missing these keys and should be added for completeness.

🔗 Analysis chain

Verify translation keys exist.

Please ensure that the following translation keys are defined in the translation files:

  • project_views.empty_state.filter.title
  • project_views.empty_state.filter.description
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the translation keys exist in the translation files
rg -l "project_views\.empty_state\.filter\.(title|description)" --type yaml

Length of output: 77


Script:

#!/bin/bash
# First, let's find translation-related files
fd -t f "translation|i18n|locale|lang" -E "*.test.*" -E "*.spec.*"

# Then, let's do a broader search for the project_views key
rg "project_views" -A 5 -B 5

Length of output: 7071


Script:

#!/bin/bash
# Search specifically for the filter keys in the translation files
rg -U "\"filter\":\s*{[^}]*\"title\"[^}]*\"description\"" packages/i18n/src/locales/*/translations.json

Length of output: 430


Script:

#!/bin/bash
# Search for filter keys with simpler pattern and context
rg -A 3 -B 1 '"filter":\s*{' packages/i18n/src/locales/*/translations.json

Length of output: 358


Script:

#!/bin/bash
# Use jq to check for the specific keys in the translation files
for f in packages/i18n/src/locales/*/translations.json; do
  echo "=== $f ==="
  jq '.project_views.empty_state.filter' "$f"
done

Length of output: 1847

Comment on lines +20 to +32
{ENotificationTab.ALL ? (
<SimpleEmptyState
title={t("notification.empty_state.all.title")}
description={t("notification.empty_state.all.description")}
assetPath={resolvedPath}
/>
) : (
<SimpleEmptyState
title={t("notification.empty_state.mentions.title")}
description={t("notification.empty_state.mentions.description")}
assetPath={resolvedPath}
/>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the notification tab condition.

The condition ENotificationTab.ALL is used directly without comparison to the current tab state. This will always evaluate to a truthy value as it's comparing against an enum value.

Apply this fix:

-      {ENotificationTab.ALL ? (
+      {currentTab === ENotificationTab.ALL ? (

Note: You'll need to add the currentTab prop to the component and pass it from the parent.

Committable suggestion skipped: line range outside the PR's diff.

[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
EUserPermissionsLevel.WORKSPACE
);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/cycles" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect asset base path.

The asset base path references 'cycles' but this is a draft issues component.

Apply this fix:

-  const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/cycles" });
+  const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/draft-issues" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/cycles" });
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/draft-issues" });

@@ -137,7 +136,7 @@ const IntegrationGuide = observer(() => {
</div>
) : (
<div className="flex h-full w-full items-center justify-center">
<EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_IMPORT} size="sm" />
{/* <EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_IMPORT} size="sm" /> */}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented code instead of commenting it out

The EmptyState component is commented out but not removed. This creates unnecessary code clutter. If the code is no longer needed, it should be removed entirely. Git history will preserve the previous implementation if needed.

-                      {/* <EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_IMPORT} size="sm" /> */}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants