Skip to content

Conversation

@harshtandiya
Copy link
Collaborator

@harshtandiya harshtandiya commented Feb 12, 2026

Summary by CodeRabbit

  • New Features

    • Team creation now accepts an optional logo and shows team logos in creation, switcher, and sidebar.
    • New Team Switcher UI with logo display, deterministic avatar fallbacks, and quick team switching.
  • Documentation

    • Added avatar attribution details.
  • Chores

    • Updated frontend UI dependency and related type exports.

- Added TeamSwitcher and TeamSwitcherItem components for team management.
- Integrated TeamSwitcher into BaseLayout, replacing the previous sidebar header.
- Updated auto-imports.d.ts to include global declarations.
- Enhanced BaseLayout by removing unused code related to team switching functionality.
- Introduced a new logo field in the GetUserTeamsResponseSchema and FPTeam document.
- Updated the TeamSwitcher and TeamSwitcherItem components to display team logos.
- Added TeamLogo component for handling logo display logic.
- Enhanced user store to include logo information for teams.
- Changed props to use optional types for logoUrl and classNames.
- Simplified avatar index calculation for consistent avatar mapping based on team name.
- Updated template to bind classNames dynamically for better styling flexibility.
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds optional team logo support: new FPTeam logo field, backend API accepts logo_url in create_team and returns logos in team listings, and frontend UI/components and store flows to upload, display, and switch teams with logos.

Changes

Cohort / File(s) Summary
Backend: team API & utils
forms_pro/api/team.py, forms_pro/api/user.py, forms_pro/utils/teams.py
create_team signature now accepts optional logo_url and assigns team.logo when provided; team responses and user team queries include logo.
DocType: FPTeam
forms_pro/forms_pro/doctype/fp_team/fp_team.json, forms_pro/forms_pro/doctype/fp_team/fp_team.py
Added logo field (Attach Image) to FPTeam DocType and prepended it in field_order; added logo type annotation under TYPE_CHECKING.
Frontend: components (new/updated)
frontend/src/components/team/TeamLogo.vue, frontend/src/components/team/CreateTeamDialog.vue, frontend/src/components/team/TeamSwitcher.vue, frontend/src/components/team/TeamSwitcherItem.vue
New TeamLogo, TeamSwitcher, TeamSwitcherItem components; CreateTeamDialog extended to upload/remove logos, manage form.logo_url, and send logo_url to API on create.
Frontend: store & layout
frontend/src/stores/user.ts, frontend/src/layouts/BaseLayout.vue
UserTeam now includes logo; createTeam signature and payload include logoUrl/logo_url; BaseLayout header replaced with TeamSwitcher integration.
Frontend: minor types & deps
frontend/src/components/fields/Attachment.vue, frontend/package.json
Exported FileType type; bumped frappe-ui dependency ^0.1.244 → ^0.1.262.
Public assets / attribution
forms_pro/public/avatars/avatar_attribution.md
New avatar attribution file documenting source and CC BY 4.0 license.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant FE as "CreateTeamDialog (Frontend)"
    participant Uploader as "FileUploader (Frontend)"
    participant API as "Backend API (create_team)"
    participant DB as "Database / FPTeam"

    User->>FE: enter team name + upload/logo action
    FE->>Uploader: upload file (optional)
    Uploader->>API: POST file -> store attachment
    API->>DB: persist attachment record
    DB-->>API: return attachment URL
    API-->>Uploader: attachment URL
    Uploader-->>FE: provide logo_url
    User->>FE: submit create team
    FE->>API: POST /create_team {team_name, logo_url}
    API->>DB: create FPTeam row (logo set if provided)
    DB-->>API: created team
    API-->>FE: return team object (includes logo)
    FE-->>User: update UI / close dialog
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped with a tiny logo bright,

Stitched pixels into teams with delight.
From upload to DB, a ribbon unfurled—
Now every team wears a badge in the world.
I nibble code and celebrate with a bite! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: team logos' directly and concisely summarizes the main change—adding logo support to teams across backend and frontend components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/sidebar-teams

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/components/team/CreateTeamDialog.vue (1)

45-54: ⚠️ Potential issue | 🟡 Minor

Form state is not reset when the dialog closes.

After creating a team (or dismissing the dialog), form.team_name, form.logo_url, and formErrors retain their previous values. The next time the dialog opens, the user sees stale data.

🐛 Proposed fix — reset on close

Add a reset helper and call it when the dialog closes:

+function resetForm() {
+    form.team_name = "";
+    form.logo_url = undefined;
+    formErrors.value = "";
+}
+
 function createTeam() {
     const result = formSchema.safeParse(form);
     if (!result.success) {
         formErrors.value = result.error.issues.map((issue) => issue.message).join(", ");
         return;
     }
     formErrors.value = "";
     user.createTeam(form.team_name, form.logo_url);
+    resetForm();
     model.value = false;
 }

Also watch model to reset on external close:

watch(model, (open) => {
    if (!open) resetForm();
});
frontend/src/components/fields/Attachment.vue (1)

19-40: ⚠️ Potential issue | 🔴 Critical

export type inside <script setup> violates Vue standards — this must be fixed.

Vue's <script setup> does not allow any ES module exports, including type-only exports. While TypeScript may allow it to compile, it contradicts Vue's design principles. Move FileType to a dedicated .ts file (e.g., @/types/file.ts) or use a separate <script> block.

The current import in CreateTeamDialog.vue (line 7) will break once this is corrected, so it must also be updated.

🤖 Fix all issues with AI agents
In `@frontend/src/components/team/TeamSwitcher.vue`:
- Around line 55-66: The template uses non-null assertions on
userStore.currentTeam in TeamLogo and TeamSwitcherItem which will throw if
currentTeam is null; update the TeamSwitcher.vue template to guard against null
by conditionally rendering those components only when userStore.currentTeam
exists (e.g., v-if="userStore.currentTeam") or provide safe fallbacks for
team_name and logo (e.g., using optional chaining or default strings) so
TeamLogo and TeamSwitcherItem receive valid props; locate usages of currentTeam
in the template (TeamLogo and TeamSwitcherItem) and wrap them with a null-check
before accessing team_name or logo.
- Around line 47-49: The template uses CreateTeamDialog and TeamLogo but they
aren't imported in the <script setup> block, causing runtime "not defined"
errors; add explicit imports for these components (e.g., import CreateTeamDialog
from "./CreateTeamDialog.vue" and import TeamLogo from "./TeamLogo.vue") inside
the <script setup> so CreateTeamDialog and TeamLogo are registered and available
to the template.

In `@frontend/src/components/team/TeamSwitcherItem.vue`:
- Around line 4-13: The prop type for logoUrl in TeamSwitcherItem.vue is
currently defined as string | null which causes a TS2322 when passing it to the
TeamLogo component that expects string | undefined; update the props definition
used by defineProps (the props type near the type props = { label: string;
logoUrl: ... } and the call defineProps<props>()) to use string | undefined for
logoUrl (or coerce/transform null to undefined before passing to the TeamLogo
component) so the TeamLogo :logo-url binding receives the expected string |
undefined type.
🧹 Nitpick comments (4)
forms_pro/api/user.py (1)

12-12: Add a default value for the optional logo field.

If logo is absent from the input dict (not just None), Pydantic will raise a ValidationError since there's no default. Other optional fields in this codebase (e.g., last_name on Line 19) use = None.

Proposed fix
-    logo: str | None = Field(description="Logo of the team")
+    logo: str | None = Field(default=None, description="Logo of the team")
forms_pro/api/team.py (1)

47-55: Update the docstring to document the new logo_url parameter.

Minor nit: the Args section doesn't mention logo_url.

Proposed fix
     """
     Create a new team
 
     Args:
         team_name: Name of the team
+        logo_url: Optional URL of the team logo
 
     Returns:
         FPTeam - The created team
     """
frontend/src/components/team/CreateTeamDialog.vue (1)

56-60: Unnecessary indirection — bind form.team_name directly.

The separate teamName ref synced via a watcher is redundant. You can use v-model="form.team_name" on the input (line 136) and pass form.team_name to TeamLogo directly. This removes 5 lines and a watcher allocation.

♻️ Proposed simplification

Remove lines 56-60 and update the template:

-const teamName = ref<string>("");
-
-watch(teamName, (newVal) => {
-    form.team_name = newVal;
-});

In the template:

 <TeamLogo
-    :team-name="teamName"
+    :team-name="form.team_name"
     class="size-12"
     :logo-url="form.logo_url"
 />
 ...
 <input
     type="text"
-    v-model="teamName"
+    v-model="form.team_name"
     class="text-lg text-ink-gray-9 !outline-0 !ring-0 !border-0 bg-inherit text-center"
     placeholder="Enter team name"
 />
frontend/src/layouts/BaseLayout.vue (1)

63-68: sidebarHeader prop and SidebarHeaderProps type are now unused.

Since the header is now rendered via the #header slot with TeamSwitcher, the sidebarHeader prop (lines 66-68) and SidebarHeaderProps type (line 63) are dead code.

Comment on lines 55 to 66
<TeamLogo
v-if="isSidebarCollapsed"
:teamName="userStore.currentTeam!.team_name"
:logoUrl="userStore.currentTeam!.logo"
/>
<div v-else class="flex items-center gap-2 justify-between w-full">
<TeamSwitcherItem
:label="userStore.currentTeam!.team_name"
:logo-url="userStore.currentTeam!.logo"
/>
<ChevronsUpDown class="size-4 text-ink-gray-6" />
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Non-null assertion on currentTeam will crash if the user has no teams.

Lines 57-58 and 62-63 use userStore.currentTeam! which throws a runtime error if currentTeam is null. This can happen before teams are loaded or if the user hasn't joined any team yet. Add a guard or fallback.

🐛 Proposed fix — guard against null
+            <template v-if="userStore.currentTeam">
                 <TeamLogo
                     v-if="isSidebarCollapsed"
-                    :teamName="userStore.currentTeam!.team_name"
-                    :logoUrl="userStore.currentTeam!.logo"
+                    :teamName="userStore.currentTeam.team_name"
+                    :logoUrl="userStore.currentTeam.logo"
                 />
                 <div v-else class="flex items-center gap-2 justify-between w-full">
                     <TeamSwitcherItem
-                        :label="userStore.currentTeam!.team_name"
-                        :logo-url="userStore.currentTeam!.logo"
+                        :label="userStore.currentTeam.team_name"
+                        :logo-url="userStore.currentTeam.logo"
                     />
                     <ChevronsUpDown class="size-4 text-ink-gray-6" />
                 </div>
+            </template>
📝 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
<TeamLogo
v-if="isSidebarCollapsed"
:teamName="userStore.currentTeam!.team_name"
:logoUrl="userStore.currentTeam!.logo"
/>
<div v-else class="flex items-center gap-2 justify-between w-full">
<TeamSwitcherItem
:label="userStore.currentTeam!.team_name"
:logo-url="userStore.currentTeam!.logo"
/>
<ChevronsUpDown class="size-4 text-ink-gray-6" />
</div>
<template v-if="userStore.currentTeam">
<TeamLogo
v-if="isSidebarCollapsed"
:teamName="userStore.currentTeam.team_name"
:logoUrl="userStore.currentTeam.logo"
/>
<div v-else class="flex items-center gap-2 justify-between w-full">
<TeamSwitcherItem
:label="userStore.currentTeam.team_name"
:logo-url="userStore.currentTeam.logo"
/>
<ChevronsUpDown class="size-4 text-ink-gray-6" />
</div>
</template>
🤖 Prompt for AI Agents
In `@frontend/src/components/team/TeamSwitcher.vue` around lines 55 - 66, The
template uses non-null assertions on userStore.currentTeam in TeamLogo and
TeamSwitcherItem which will throw if currentTeam is null; update the
TeamSwitcher.vue template to guard against null by conditionally rendering those
components only when userStore.currentTeam exists (e.g.,
v-if="userStore.currentTeam") or provide safe fallbacks for team_name and logo
(e.g., using optional chaining or default strings) so TeamLogo and
TeamSwitcherItem receive valid props; locate usages of currentTeam in the
template (TeamLogo and TeamSwitcherItem) and wrap them with a null-check before
accessing team_name or logo.

Comment on lines +4 to +13
type props = {
label: string;
logoUrl: string | null;
};

const props = defineProps<props>();
</script>
<template>
<div class="flex items-center gap-2 shrink-0">
<TeamLogo :team-name="label" :logo-url="logoUrl" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix TypeScript error: string | null is not assignable to string | undefined.

The CI pipeline reports TS2322 on Line 13. The logoUrl prop is typed as string | null, but TeamLogo's logo-url prop likely expects string | undefined.

Proposed fix
-        <TeamLogo :team-name="label" :logo-url="logoUrl" />
+        <TeamLogo :team-name="label" :logo-url="logoUrl ?? undefined" />

Alternatively, align the prop type with TeamLogo's expectation by changing string | null to string | undefined on Line 6, if the parent components can accommodate that.

📝 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
type props = {
label: string;
logoUrl: string | null;
};
const props = defineProps<props>();
</script>
<template>
<div class="flex items-center gap-2 shrink-0">
<TeamLogo :team-name="label" :logo-url="logoUrl" />
type props = {
label: string;
logoUrl: string | null;
};
const props = defineProps<props>();
</script>
<template>
<div class="flex items-center gap-2 shrink-0">
<TeamLogo :team-name="label" :logo-url="logoUrl ?? undefined" />
🧰 Tools
🪛 GitHub Actions: Frontend TypeScript

[error] 13-13: TS2322: Type 'string | null' is not assignable to type 'string | undefined'.

🤖 Prompt for AI Agents
In `@frontend/src/components/team/TeamSwitcherItem.vue` around lines 4 - 13, The
prop type for logoUrl in TeamSwitcherItem.vue is currently defined as string |
null which causes a TS2322 when passing it to the TeamLogo component that
expects string | undefined; update the props definition used by defineProps (the
props type near the type props = { label: string; logoUrl: ... } and the call
defineProps<props>()) to use string | undefined for logoUrl (or coerce/transform
null to undefined before passing to the TeamLogo component) so the TeamLogo
:logo-url binding receives the expected string | undefined type.

Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/team/CreateTeamDialog.vue (1)

45-54: ⚠️ Potential issue | 🟡 Minor

Form state should be reset when the dialog closes.

The createTeam call does have error handling via toast notifications, but the form state (form.team_name, form.logo_url, teamName) is never reset. When the user opens the dialog again, the previous entry is still visible, which is a minor UX inconsistency. Reset the form fields either when the dialog closes or on successful team creation.

🤖 Fix all issues with AI agents
In `@frontend/src/components/team/CreateTeamDialog.vue`:
- Around line 56-60: Remove the unnecessary teamName ref and its watch: delete
the declaration of teamName and the watch(...) that assigns form.team_name, then
update the template bindings to use form.team_name directly (use
v-model="form.team_name" on the input and :team-name="form.team_name" on the
TeamLogo component) so the reactive form object is the single source of truth
and avoids potential desync between teamName and form.team_name.
🧹 Nitpick comments (2)
frontend/src/components/team/CreateTeamDialog.vue (2)

101-130: Minor: simplify event handler and trim unused slot bindings.

@success="(file: FileType) => setTeamLogo(file)" can be shortened to @success="setTeamLogo". The large destructured slot scope only uses error, uploading, and openFileSelector — the rest (file, progress, uploaded, message, total, success) are dead bindings. Keeping only the three you need improves readability and removes the need for the @vue-ignore comment.


141-148: Button is enabled on initial render despite empty (invalid) form.

formErrors starts as "" and the watcher only fires on change, so the "Create Team" button is clickable before the user types anything. createTeam() does guard with its own validation, so this is safe — but for a polished UX you could either run an initial validation pass or derive the disabled state from the form content (e.g., form.team_name.length < 2).

@harshtandiya harshtandiya merged commit 01234d5 into develop Feb 12, 2026
1 check was pending
@harshtandiya harshtandiya deleted the feat/sidebar-teams branch February 12, 2026 18:47
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.

1 participant