feat: team creation & team switcher functionality#37
Conversation
- Added create_team and switch_team API methods to manage team operations. - Introduced CreateTeamDialog component for user interface to create new teams. - Updated user store to handle team creation and switching with success/error notifications. - Enhanced FPTeam document to automatically set the current team upon creation.
📝 WalkthroughWalkthroughAdds backend endpoints to create and switch teams, updates FPTeam to auto-add the owner and create DocShare on insert, removes automatic current-team assignment in roles, adds a CreateTeam dialog and UI menu integration, introduces frontend store actions/watchers, and adds tests and install updates for the test user. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Frontend UI
participant Store as User Store
participant API as Team API
participant DB as FPTeam Model
UI->>Store: createTeam(teamName)
Store->>API: POST /api/method/forms_pro.api.team.create_team {team_name}
API->>DB: insert FPTeam document
DB-->>API: after_insert -> add_to_team(owner), add_docshare, set_current_team
API-->>Store: 200 {team_id, name}
Store->>Store: reload userTeamsResource
Store-->>UI: update currentTeam and menu
sequenceDiagram
participant UI as Frontend UI
participant Store as User Store
participant API as Team API
participant Defaults as User Defaults
UI->>Store: switchTeam(team)
Store->>API: POST /api/method/forms_pro.api.team.switch_team {team_id}
API->>Defaults: set_current_team(user, team_id)
API-->>Store: 200 OK
Store->>Store: reload userTeamsResource -> fetch members
Store-->>UI: update header/menu
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
forms_pro/overrides/roles.py (1)
38-50:⚠️ Potential issue | 🔴 CriticalDuplicate team member:
after_insertalready adds the owner.
team.insert()on Line 43 triggersFPTeam.after_insert(seeforms_pro/forms_pro/doctype/fp_team/fp_team.pyLines 67-70), which callsself.add_to_team(self.owner)andself.save(). Lines 44-49 then append the same user again and save, creating a duplicate member entry.Proposed fix — remove the redundant append+save
def create_default_team_for_user(user: User) -> None: from forms_pro.forms_pro.doctype.fp_team.fp_team import FPTeam team: FPTeam = frappe.new_doc("FP Team") team.team_name = f"{user.first_name}'s Team" team.insert(ignore_permissions=True) - team.append( - "users", - { - "user": user.name, - }, - ) - team.save(ignore_permissions=True)forms_pro/forms_pro/doctype/fp_team/fp_team.py (1)
55-65:⚠️ Potential issue | 🟠 Major
is_team_memberalways returnsFalse.
self.team_membersreturns a list of dicts (frommodel_dump()), butuseris a plain string. Theincheck on Line 65 compares a string against dicts, which never matches. This should compare against theProposed fix
def is_team_member(self, user: str) -> bool: - return user in self.team_members + return any(m["email"] == user for m in self.team_members)
🤖 Fix all issues with AI agents
In `@forms_pro/api/team.py`:
- Around line 45-60: create_team currently accepts any string for team_name
(including empty or whitespace-only) and lacks server-side validation; update
the create_team function to validate team_name before creating the FP Team: trim
whitespace, verify length >= 2, and raise a Frappe validation error (e.g.,
frappe.throw or frappe.ValidationError) if validation fails; keep the existing
flow using frappe.new_doc("FP Team"), set team.team_name only after validation,
and only call team.insert() when the name passes validation.
In `@forms_pro/forms_pro/doctype/fp_team/fp_team.py`:
- Around line 72-91: The add_to_team method currently appends a user and creates
a docshare unconditionally; modify add_to_team (in fp_team.FPTeam) to first
check the team child-table (self.users) for an existing entry matching the user
argument and do nothing (or return) if found, otherwise append and call
add_docshare; ensure you reference the users child table lookup (self.users) and
the add_docshare call so both the duplicate-row append and duplicate docshare
are prevented.
- Around line 79-80: The thrown error message is not wrapped for translation:
update the frappe.throw call that uses the variable user and the literal message
in fp_team.py so the user-facing string is wrapped with Frappe's translation
function (use _(...)) — keep the comparison to the user variable (user ==
"Administrator") as-is but change the frappe.throw invocation (the
frappe.throw(...) that currently contains "Administrator cannot be added to a
team") to pass a translated string using _(...) so the linter passes.
In `@frontend/src/components/team/CreateTeamDialog.vue`:
- Around line 42-51: The createTeam function currently calls
user.createTeam(form.team_name) and closes the dialog (model.value = false) even
when validation fails; update createTeam (the function using
formSchema.safeParse, formErrors.value, user.createTeam and model.value) to only
invoke user.createTeam and set model.value = false when result.success is
true—either by moving those two statements inside the else block or returning
early after populating formErrors.value when validation fails, so invalid forms
do not trigger the API call or close the dialog.
- Around line 71-73: The template uses the Button component (see <Button
`@click`="createTeam" :disabled="formErrors !== ''"> in CreateTeamDialog.vue) but
Button is not imported; update the component imports at the top of
CreateTeamDialog.vue to explicitly import Button from frappe-ui (alongside any
existing imports) and add it to the component registration/definition so the
CreateTeamDialog component can reference Button reliably when createTeam is
invoked.
In `@frontend/src/stores/user.ts`:
- Around line 30-35: The onSuccess handler currently returns early when
_currTeam is undefined, leaving currentTeam null; update onSuccess (the function
handling UserTeam[] responses) to fall back to a sensible team and/or surface
feedback: if data.find(...) yields no _currTeam then pick data[0] (if any) and
call setCurrentTeam with that fallback, otherwise trigger a user
notification/Toast indicating no current team exists so the UI isn’t silently
unchanged; ensure you still handle the empty-array case (leave null and notify)
and keep references to onSuccess, _currTeam, setCurrentTeam and currentTeam for
locating the change.
🧹 Nitpick comments (5)
frontend/src/stores/team.ts (1)
34-36: Guard against nullcurrentTeamand potential double-fetch withinitialize().The watcher fires unconditionally, even when
currentTeam.valueisnull(e.g., during initial load before user teams resolve). This sends a request withteam_id: undefined. Additionally,initialize()on Line 30 already callsfetch(), so the watcher may trigger a redundant second fetch whencurrentTeamis first set.Suggested fix
watch(currentTeam, () => { - teamMembersResource.fetch(); + if (currentTeam.value) { + teamMembersResource.fetch(); + } });forms_pro/api/team.py (1)
63-77: Consider usingthrow=Truefor consistency withget_team_members.
get_team_members(Line 31) usesfrappe.has_permission(..., throw=True)directly, whileswitch_teammanually checks the return value and raises. Using the same pattern would be more consistent and reduce boilerplate.Proposed fix
`@frappe.whitelist`(methods=["POST"]) def switch_team(team_id: str) -> None: """ Switch to a new team """ - - if not frappe.has_permission( + frappe.has_permission( doctype="FP Team", ptype="read", doc=team_id, user=frappe.session.user, - ): - raise frappe.PermissionError("You do not have permission to switch to this team") + throw=True, + ) set_current_team(team_id, frappe.session.user)frontend/src/stores/user.ts (1)
65-84:switchTeamandcreateTeamare fire-and-forget — callers can't await completion.Both functions create a one-shot resource and call
.submit()without returning a promise. This means callers (e.g.,CreateTeamDialog.vue) have no way to wait for success/failure before taking follow-up actions like closing a dialog. Consider returning the promise so callers canawaitthe result:Sketch for `createTeam` (apply similarly to `switchTeam`)
- function createTeam(teamName: string) { - createResource({ + function createTeam(teamName: string) { + return createResource({ url: "forms_pro.api.team.create_team", ... }).submit(); }Also applies to: 86-105
frontend/src/layouts/BaseLayout.vue (2)
68-77: Props shadow computed properties with identical names — consider renaming.
sidebarHeaderandsidebarSectionsexist as both props (lines 69, 73) and computed properties (lines 79, 113). Vue's<script setup>resolves the template bindings to the local computed (which internally readsprops.sidebarHeader/props.sidebarSections), so this works — but the name collision is a readability trap.A simple rename of the props (e.g.,
headerOverride/sectionsOverride) or the computed properties (e.g.,resolvedHeader/resolvedSections) would make the intent self-documenting.Also applies to: 79-111, 113-133
118-130:isActive: trueis hardcoded — Dashboard will appear selected on every page.Once additional sidebar items are introduced, this will be visually misleading. Consider deriving
isActivefrom the current route:♻️ Suggested fix
+import { useRoute } from "vue-router"; + +const route = useRoute();{ label: "Dashboard", to: "/", - isActive: true, + isActive: route.path === "/", icon: LayoutDashboard, },
| @frappe.whitelist(methods=["POST"]) | ||
| def create_team(team_name: str) -> FPTeam: | ||
| """ | ||
| Create a new team | ||
|
|
||
| Args: | ||
| team_name: Name of the team | ||
|
|
||
| Returns: | ||
| FPTeam - The created team | ||
| """ | ||
|
|
||
| team: FPTeam = frappe.new_doc("FP Team") | ||
| team.team_name = team_name | ||
| team.insert() | ||
| return team |
There was a problem hiding this comment.
No server-side validation on team_name.
create_team accepts any string, including empty or whitespace-only names. The frontend enforces min(2) via Zod, but the API endpoint is publicly callable and should validate independently.
Proposed fix
def create_team(team_name: str) -> FPTeam:
+ team_name = team_name.strip()
+ if not team_name:
+ frappe.throw("Team name is required")
+
team: FPTeam = frappe.new_doc("FP Team")
team.team_name = team_name
team.insert()
return team📝 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.
| @frappe.whitelist(methods=["POST"]) | |
| def create_team(team_name: str) -> FPTeam: | |
| """ | |
| Create a new team | |
| Args: | |
| team_name: Name of the team | |
| Returns: | |
| FPTeam - The created team | |
| """ | |
| team: FPTeam = frappe.new_doc("FP Team") | |
| team.team_name = team_name | |
| team.insert() | |
| return team | |
| `@frappe.whitelist`(methods=["POST"]) | |
| def create_team(team_name: str) -> FPTeam: | |
| """ | |
| Create a new team | |
| Args: | |
| team_name: Name of the team | |
| Returns: | |
| FPTeam - The created team | |
| """ | |
| team_name = team_name.strip() | |
| if not team_name: | |
| frappe.throw("Team name is required") | |
| team: FPTeam = frappe.new_doc("FP Team") | |
| team.team_name = team_name | |
| team.insert() | |
| return team |
🤖 Prompt for AI Agents
In `@forms_pro/api/team.py` around lines 45 - 60, create_team currently accepts
any string for team_name (including empty or whitespace-only) and lacks
server-side validation; update the create_team function to validate team_name
before creating the FP Team: trim whitespace, verify length >= 2, and raise a
Frappe validation error (e.g., frappe.throw or frappe.ValidationError) if
validation fails; keep the existing flow using frappe.new_doc("FP Team"), set
team.team_name only after validation, and only call team.insert() when the name
passes validation.
There was a problem hiding this comment.
this won't be needed, since we have the validation coming in from frappe. team_name is a mandatory field in doctype
There was a problem hiding this comment.
@harshtandiya, you're absolutely right! I appreciate the clarification. Frappe's DocType mandatory field validation will handle this automatically when team.insert() is called, so the explicit validation is indeed redundant. Thank you for pointing that out! 👍
✏️ Learnings added
Learnt from: harshtandiya
Repo: BuildWithHussain/forms_pro PR: 37
File: forms_pro/api/team.py:45-60
Timestamp: 2026-02-07T20:33:14.636Z
Learning: In Frappe framework (Python backend), when a field is marked as mandatory in a DocType definition, the framework automatically validates it during the `.insert()` operation. This validation includes checking for empty values, so explicit server-side validation for mandatory fields is typically redundant in API endpoints.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| <Button @click="createTeam" :disabled="formErrors !== ''" variant="subtle"> | ||
| Create Team | ||
| </Button> |
There was a problem hiding this comment.
Button is used but not imported.
Button is referenced in the template but is not listed in the imports on Line 2. This works only if Button is globally registered by frappe-ui. Add it to the import for explicitness and to avoid breakage if global registration changes.
-import { Dialog, ErrorMessage, FormControl } from "frappe-ui";
+import { Button, Dialog, ErrorMessage, FormControl } from "frappe-ui";📝 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.
| <Button @click="createTeam" :disabled="formErrors !== ''" variant="subtle"> | |
| Create Team | |
| </Button> | |
| import { Button, Dialog, ErrorMessage, FormControl } from "frappe-ui"; |
🤖 Prompt for AI Agents
In `@frontend/src/components/team/CreateTeamDialog.vue` around lines 71 - 73, The
template uses the Button component (see <Button `@click`="createTeam"
:disabled="formErrors !== ''"> in CreateTeamDialog.vue) but Button is not
imported; update the component imports at the top of CreateTeamDialog.vue to
explicitly import Button from frappe-ui (alongside any existing imports) and add
it to the component registration/definition so the CreateTeamDialog component
can reference Button reliably when createTeam is invoked.
- Added a check to ensure that a user cannot be added to the team if they are already a member, throwing a DuplicateEntryError if the condition is met.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@forms_pro/forms_pro/doctype/fp_team/test_fp_team.py`:
- Around line 32-39: The test sets the user with frappe.set_user(self.test_user)
but does not guarantee restoration if team.insert() or team.reload() raises;
wrap the user change in a safety mechanism such as using self.addCleanup(lambda:
frappe.set_user("Administrator")) immediately after setting the user, or convert
the block to a try/finally where frappe.set_user("Administrator") is called in
the finally; ensure you reference the existing calls (frappe.set_user,
team.insert, team.reload) so the restore always runs even on exceptions.
🧹 Nitpick comments (4)
forms_pro/install.py (1)
4-5: Production module imports from test package.
install.pyrunsafter_installin production, yet it unconditionally imports fromforms_pro.testsat module level. Today the test module is trivial (a single constant), so this won't break anything, but it creates an undesirable production → test dependency. Consider either moving the constant to a shared non-test module, or deferring the import insidebefore_tests/create_test_user.forms_pro/forms_pro/doctype/fp_team/fp_team.py (2)
66-69:self.save()insideafter_insert— consider usingdb_updateorflagsto avoid re-triggering the full save lifecycle.Calling
self.save()withinafter_insertre-runsvalidate,before_save,on_update, and any associated hooks. This works today but is fragile — future hooks added toon_updateorbefore_savewill silently run a second time on every team creation. A lighter approach is to directly insert the child row and skip the full lifecycle:Proposed alternative
def after_insert(self) -> None: self.add_to_team(self.owner) set_current_team(self.name, self.owner) - self.save() + self.save(ignore_permissions=True)Or even better, insert the child record and docshare directly (avoiding a second full save cycle) and call
self.db_update()if only field-level changes need persisting.
34-35:cached_propertyforteam_membersmay return stale data afteradd_to_teammutatesself.users.
team_membersis a@cached_property, so once accessed it is frozen for the lifetime of the instance. If any code path accessesteam_members(oris_team_member) before the team is saved/reloaded, then callsadd_to_team, subsequentis_team_memberchecks on the same instance will miss the newly added member. This is safe today only because the test explicitly callsreload(), but it's a latent trap.Consider either:
- Switching to a regular
@property(small perf cost, always correct), or- Invalidating the cache in
add_to_teamby deleting the cached attribute.Option B: Invalidate cache in add_to_team
def add_to_team(self, user: str) -> None: ... self.append("users", {"user": user}) + # Invalidate cached team_members so is_team_member sees the new user + self.__dict__.pop("team_members", None) add_docshare(Also applies to: 64-64
forms_pro/forms_pro/doctype/fp_team/test_fp_team.py (1)
44-44: Copy-paste comment: line 44 duplicates line 41.Line 44 says "Check that the user is added to the team" but the assertion on lines 45–55 actually verifies the
FP Team Memberchild record exists. Update the comment to reflect what's being checked.- # Check that the user is added to the team + # Check that the FP Team Member record exists for the user
- Updated createTeam function to return early on validation failure, preventing unnecessary team creation attempts. - Ensured formErrors are cleared appropriately based on validation results.
- Added a check to set the first team as the current team if no current team is found in the user store.
There was a problem hiding this comment.
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 (1)
forms_pro/forms_pro/doctype/fp_team/fp_team.py (1)
54-64:⚠️ Potential issue | 🟠 Major
is_team_membertriggers N+1 queries unnecessarily.
self.team_memberscallsget_user()for every member (one DB query each) just to build a list of emails. Sinceself.usersalready contains the user email in the child-table rows, the membership check can be done without any extra queries:Proposed fix
def is_team_member(self, user: str) -> bool: - return user in [member["email"] for member in self.team_members] + return any(member.user == user for member in self.users)This also eliminates the issue where
team_membersreturns early with[]for a team with no persisted members yet (e.g., duringafter_insertbeforesave()), sinceself.usersreflects the in-memory child-table state.
🤖 Fix all issues with AI agents
In `@frontend/src/components/team/CreateTeamDialog.vue`:
- Line 14: The button is enabled because formErrors is initialized to an empty
string before validation runs; change initialization so validation runs eagerly:
either set formErrors to a non-empty error or call the existing validation
function on mount (or immediately after defining the watcher) to populate
formErrors based on team_name. Specifically, update the CreateTeamDialog
component to invoke the validation routine (the same logic the watcher uses)
during setup/mounted (so formErrors reflects the current validity of team_name
on render) or initialize formErrors with a suitable error message; keep the
guard in createTeam() unchanged.
- Around line 42-51: The createTeam handler closes the dialog immediately and
never resets the form; change createTeam to await the store call (ensure
user.createTeam returns a Promise) and only set model.value = false after the
awaited call succeeds (handle errors to keep the dialog open and show toast),
add a loading/disabled state while awaiting, and reset form.team_name and
formErrors when the dialog closes (preferably by adding a watcher on model that
clears form.team_name and formErrors when model becomes false); reference
createTeam, user.createTeam, model, form.team_name, and formErrors when making
these changes.
In `@frontend/src/stores/user.ts`:
- Around line 68-87: The switchTeam and createTeam functions currently call
createResource(...).submit() and don't return its promise, making them
fire-and-forget; change both functions (switchTeam and createTeam) to return the
promise from createResource(...).submit() so callers can await the result,
handle success/failure (e.g., close dialog only on success), and optionally
dedupe/cancel in caller code; locate the createResource(...) usages in
switchTeam and createTeam and ensure their submit() return value is propagated
(so user.createTeam can be awaited from CreateTeamDialog.vue).
🧹 Nitpick comments (5)
frontend/src/stores/user.ts (2)
60-62:getCurrentTeamFromAllTeamsis not exported and appears unused.This function is defined but not included in the store's return object (lines 124–135), making it dead code.
98-98: Unuseddataparameter inonSuccesscallback.The
data: CreateTeamResponseparameter is declared but never referenced. Remove it or use it (e.g., to auto-switch to the newly created team).forms_pro/forms_pro/doctype/fp_team/fp_team.py (3)
81-85: Duplicate member check throws instead of returning silently — verify this is intentional.In
after_insert, the owner is always added viaadd_to_team. If, for any reason, the owner is already present inself.usersat that point, this will throwDuplicateEntryErrorand abort the entire insert flow. The Administrator check on Line 78 returns silently — consider whether the duplicate case should also return silently (or log a warning) rather than hard-fail withfrappe.throw.Option: silent return for duplicates
if self.is_team_member(user): - frappe.throw( - frappe._("User is already a member of the team"), - frappe.DuplicateEntryError, - ) + returnIf the hard error is intentional for external callers (e.g., an explicit "add member" API), consider splitting the internal
after_insertpath from the public-facing path.
71-96:add_to_teammodifies the document in-memory but doesn't persist — relies on caller tosave().
self.append(...)modifies the child table in memory;add_docshare(...)writes directly to the DB. If the caller forgets to save (orsave()fails), you get a docshare without the corresponding team membership row. This asymmetry could leave data in an inconsistent state.This is currently safe because
after_insertcallsself.save()afterwards, but any future caller ofadd_to_teammust remember to also persist. Consider documenting this contract or havingadd_to_teamcallself.save()itself.
66-69: Child table persistence requiresself.save()—this call is necessary, not avoidable.The
self.append("users", {"user": user})inadd_to_team()creates an in-memory child row that is not persisted to the database untilself.save()is called. The test attest_fp_team.py:31-59confirms this is working correctly.That said, calling
save()inafter_insert()is unconventional and can mask permission issues. Since no hooks (before_save,validate,on_update, etc.) currently exist in this doctype, the immediate risk of side effects is low. However, for better safety and clarity, consider:
- Using
self.save(ignore_permissions=True)if the owner is guaranteed to have write access post-insert, or- Using
self.db_update()combined with explicitdb_insert()for child rows to avoid re-triggering the full save lifecycle.
| required: true, | ||
| }); | ||
|
|
||
| const formErrors = ref<string>(""); |
There was a problem hiding this comment.
Button is initially enabled despite empty (invalid) input.
formErrors starts as "", and the watcher hasn't fired yet on mount, so the "Create Team" button appears enabled even though team_name is empty. Clicking it triggers the guard in createTeam() (which is safe), but the UX is misleading.
A simple fix: initialize formErrors by running validation eagerly, or start with a non-empty error string.
Option: validate on mount
-const formErrors = ref<string>("");
+const formErrors = ref<string>("");
+
+// Validate immediately so button starts disabled
+const initialResult = formSchema.safeParse(form);
+if (!initialResult.success) {
+ formErrors.value = initialResult.error.issues.map((i) => i.message).join(", ");
+}Also applies to: 29-40
🤖 Prompt for AI Agents
In `@frontend/src/components/team/CreateTeamDialog.vue` at line 14, The button is
enabled because formErrors is initialized to an empty string before validation
runs; change initialization so validation runs eagerly: either set formErrors to
a non-empty error or call the existing validation function on mount (or
immediately after defining the watcher) to populate formErrors based on
team_name. Specifically, update the CreateTeamDialog component to invoke the
validation routine (the same logic the watcher uses) during setup/mounted (so
formErrors reflects the current validity of team_name on render) or initialize
formErrors with a suitable error message; keep the guard in createTeam()
unchanged.
| 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); | ||
| model.value = false; | ||
| } |
There was a problem hiding this comment.
Dialog closes before API call completes and form state is never reset.
Two related UX issues:
-
model.value = false(Line 50) runs immediately after the fire-and-forgetuser.createTeam()call — the dialog closes before the server responds. If the request fails, the user sees an error toast with no dialog context. This ties into the store-level issue wherecreateTeamdoesn't return a promise. -
form.team_nameis never cleared when the dialog closes or re-opens. A user who opens the dialog a second time will see the previously entered team name.
Suggested reset on close
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);
model.value = false;
+ form.team_name = "";
}Or better yet, watch model and reset on close:
watch(model, (open) => {
if (!open) {
form.team_name = "";
formErrors.value = "";
}
});🤖 Prompt for AI Agents
In `@frontend/src/components/team/CreateTeamDialog.vue` around lines 42 - 51, The
createTeam handler closes the dialog immediately and never resets the form;
change createTeam to await the store call (ensure user.createTeam returns a
Promise) and only set model.value = false after the awaited call succeeds
(handle errors to keep the dialog open and show toast), add a loading/disabled
state while awaiting, and reset form.team_name and formErrors when the dialog
closes (preferably by adding a watcher on model that clears form.team_name and
formErrors when model becomes false); reference createTeam, user.createTeam,
model, form.team_name, and formErrors when making these changes.
| function switchTeam(team: UserTeam) { | ||
| createResource({ | ||
| url: "forms_pro.api.team.switch_team", | ||
| method: "POST", | ||
| makeParams() { | ||
| return { | ||
| team_id: team.name, | ||
| }; | ||
| }, | ||
| onSuccess() { | ||
| userTeamsResource.reload(); | ||
| toast.success("Team switched successfully"); | ||
| }, | ||
| onError(error: Error) { | ||
| toast.error("Failed to switch team", { | ||
| description: error.message, | ||
| }); | ||
| }, | ||
| }).submit(); | ||
| } |
There was a problem hiding this comment.
switchTeam and createTeam are fire-and-forget — callers cannot await completion.
Both functions create an ephemeral resource via createResource(...).submit() without returning a promise. This means:
- In
CreateTeamDialog.vue, the dialog closes immediately (model.value = false) before the API responds — if creation fails, the user sees a closed dialog + an error toast, which is a confusing UX. - Rapid successive calls create orphaned resource instances with no way to cancel or deduplicate them.
Consider returning the promise from .submit() so callers can await the result and react accordingly (e.g., close the dialog only on success).
Sketch
- function createTeam(teamName: string) {
- createResource({
+ async function createTeam(teamName: string) {
+ return createResource({
url: "forms_pro.api.team.create_team",
...
- }).submit();
+ }).submit();
}Then in CreateTeamDialog.vue:
async function createTeam() {
// ...validation...
await user.createTeam(form.team_name);
model.value = false;
}Also applies to: 89-108
🤖 Prompt for AI Agents
In `@frontend/src/stores/user.ts` around lines 68 - 87, The switchTeam and
createTeam functions currently call createResource(...).submit() and don't
return its promise, making them fire-and-forget; change both functions
(switchTeam and createTeam) to return the promise from
createResource(...).submit() so callers can await the result, handle
success/failure (e.g., close dialog only on success), and optionally
dedupe/cancel in caller code; locate the createResource(...) usages in
switchTeam and createTeam and ensure their submit() return value is propagated
(so user.createTeam can be awaited from CreateTeamDialog.vue).
Summary by CodeRabbit