-
Notifications
You must be signed in to change notification settings - Fork 12
feat: team creation & team switcher functionality #37
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
Changes from all commits
f9f3501
727df85
c81f29b
d9d4971
fb1598b
d6eecfa
227c27d
73eecf8
e827881
bd0e6d5
63dd63e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Test user email address | ||
| FORMS_PRO_TEST_USER = "test_forms_pro_user@example.com" |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,77 @@ | ||||||||||
| <script setup lang="ts"> | ||||||||||
| import { Dialog, ErrorMessage, FormControl } from "frappe-ui"; | ||||||||||
| import * as z from "zod"; | ||||||||||
| import { reactive, ref, watch } from "vue"; | ||||||||||
| import { useUser } from "@/stores/user"; | ||||||||||
|
|
||||||||||
| const user = useUser(); | ||||||||||
|
|
||||||||||
| const model = defineModel<boolean>({ | ||||||||||
| type: Boolean, | ||||||||||
| required: true, | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| const formErrors = ref<string>(""); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Button is initially enabled despite empty (invalid) input.
A simple fix: initialize 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 |
||||||||||
|
|
||||||||||
| const formSchema = z.object({ | ||||||||||
| team_name: z | ||||||||||
| .string() | ||||||||||
| .min(2, "Team name must be at least 2 characters long") | ||||||||||
| .max(140, "Team name must be less than 140 characters long"), | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| type Form = z.infer<typeof formSchema>; | ||||||||||
|
|
||||||||||
| const form = reactive<Form>({ | ||||||||||
| team_name: "", | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| watch( | ||||||||||
| () => form, | ||||||||||
| (newVal) => { | ||||||||||
| const result = formSchema.safeParse(newVal); | ||||||||||
| if (!result.success) { | ||||||||||
| formErrors.value = result.error.issues.map((issue) => issue.message).join(", "); | ||||||||||
| } else { | ||||||||||
| formErrors.value = ""; | ||||||||||
| } | ||||||||||
| }, | ||||||||||
| { deep: true } | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| 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; | ||||||||||
| } | ||||||||||
|
Comment on lines
+42
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dialog closes before API call completes and form state is never reset. Two related UX issues:
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 watch(model, (open) => {
if (!open) {
form.team_name = "";
formErrors.value = "";
}
});🤖 Prompt for AI Agents |
||||||||||
| </script> | ||||||||||
| <template> | ||||||||||
| <Dialog | ||||||||||
| v-model="model" | ||||||||||
| :options="{ | ||||||||||
| title: 'Create New Team', | ||||||||||
| icon: 'users', | ||||||||||
| }" | ||||||||||
| > | ||||||||||
| <template #body-content> | ||||||||||
| <div class="flex flex-col gap-4"> | ||||||||||
| <FormControl | ||||||||||
| v-model="form.team_name" | ||||||||||
| label="Team Name" | ||||||||||
| variant="outline" | ||||||||||
| type="text" | ||||||||||
| required | ||||||||||
| /> | ||||||||||
| <ErrorMessage :message="formErrors" /> | ||||||||||
| <Button @click="createTeam" :disabled="formErrors !== ''" variant="subtle"> | ||||||||||
| Create Team | ||||||||||
| </Button> | ||||||||||
|
Comment on lines
+71
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
-import { Dialog, ErrorMessage, FormControl } from "frappe-ui";
+import { Button, Dialog, ErrorMessage, FormControl } from "frappe-ui";📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| </div> | ||||||||||
| </template> | ||||||||||
| </Dialog> | ||||||||||
| </template> | ||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No server-side validation on
team_name.create_teamaccepts any string, including empty or whitespace-only names. The frontend enforcesmin(2)via Zod, but the API endpoint is publicly callable and should validate independently.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@harshtandiya, you're absolutely right! I appreciate the clarification. Frappe's DocType mandatory field validation will handle this automatically whenteam.insert()is called, so the explicit validation is indeed redundant. Thank you for pointing that out! 👍✏️ Learnings added