-
Notifications
You must be signed in to change notification settings - Fork 501
Payments UX update #863
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
Payments UX update #863
Changes from all commits
a30a864
d6397fa
5e5c0ae
d0e4e09
1018ee5
553ffe0
7e95b6b
da6f000
4b79dd1
4c546b8
c22bf3c
2b19be0
03c06a2
19d1d52
582f876
8626012
d5f0dd4
82ff799
eb7ee91
1a3b465
40ff247
4645d09
3488427
101c98a
b27c7c8
e537f6b
2198b63
9a5ac32
54bdf9a
d10452d
5e2b22a
90c0b24
dd749d9
dfdd231
8545274
59d375b
4b7f823
959dac2
ee93553
a3beae8
786855f
7ce7254
633e2e0
05095aa
8eab0e6
3ce3ce9
7f2c646
eadbd6f
6e1a689
c496ee3
0420aa3
f01efbb
b36ed7e
3d0d056
4c8d2de
72beb13
157c024
6f6d8e2
7b2ac57
c878b3f
33f1afc
afce228
d5eb23c
e50082f
c815d99
28523cd
3c3a8f2
c0b1094
3de5eda
844a986
aed61b5
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,123 @@ | ||||||||||||||||||||
| "use client"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Typography, SimpleTooltip } from "@stackframe/stack-ui"; | ||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| type CreateGroupDialogProps = { | ||||||||||||||||||||
| open: boolean, | ||||||||||||||||||||
| onOpenChange: (open: boolean) => void, | ||||||||||||||||||||
| onCreate: (group: { id: string, displayName: string }) => void, | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export function CreateGroupDialog({ open, onOpenChange, onCreate }: CreateGroupDialogProps) { | ||||||||||||||||||||
| const [groupId, setGroupId] = useState(""); | ||||||||||||||||||||
| const [displayName, setDisplayName] = useState(""); | ||||||||||||||||||||
| const [errors, setErrors] = useState<{ id?: string, displayName?: string }>({}); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const validateAndCreate = () => { | ||||||||||||||||||||
| const newErrors: { id?: string, displayName?: string } = {}; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Validate group ID | ||||||||||||||||||||
| if (!groupId.trim()) { | ||||||||||||||||||||
| newErrors.id = "Group ID is required"; | ||||||||||||||||||||
| } else if (!/^[a-z0-9-]+$/.test(groupId)) { | ||||||||||||||||||||
| newErrors.id = "Group ID must contain only lowercase letters, numbers, and hyphens"; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Validate display name | ||||||||||||||||||||
| if (!displayName.trim()) { | ||||||||||||||||||||
| newErrors.displayName = "Display name is required"; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (Object.keys(newErrors).length > 0) { | ||||||||||||||||||||
| setErrors(newErrors); | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| onCreate({ id: groupId.trim(), displayName: displayName.trim() }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Reset form | ||||||||||||||||||||
| setGroupId(""); | ||||||||||||||||||||
| setDisplayName(""); | ||||||||||||||||||||
| setErrors({}); | ||||||||||||||||||||
| onOpenChange(false); | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const handleClose = () => { | ||||||||||||||||||||
| setGroupId(""); | ||||||||||||||||||||
| setDisplayName(""); | ||||||||||||||||||||
| setErrors({}); | ||||||||||||||||||||
| onOpenChange(false); | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return ( | ||||||||||||||||||||
| <Dialog open={open} onOpenChange={handleClose}> | ||||||||||||||||||||
| <DialogContent className="sm:max-w-[425px]"> | ||||||||||||||||||||
|
Comment on lines
+53
to
+55
Contributor
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. Bug: onOpenChange forces close and prevents proper open-state control. Pass through the next state; only reset when closing. Apply: - <Dialog open={open} onOpenChange={handleClose}>
+ <Dialog
+ open={open}
+ onOpenChange={(next) => next ? onOpenChange(true) : handleClose()}
+ >📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| <DialogHeader> | ||||||||||||||||||||
| <DialogTitle>Create Offer Group</DialogTitle> | ||||||||||||||||||||
| <DialogDescription> | ||||||||||||||||||||
| Offer groups allow you to organize related offers. Customers can only have one active offer from each group at a time (except for add-ons). | ||||||||||||||||||||
| </DialogDescription> | ||||||||||||||||||||
| </DialogHeader> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div className="grid gap-4 py-4"> | ||||||||||||||||||||
| <div className="grid gap-2"> | ||||||||||||||||||||
| <Label htmlFor="group-id"> | ||||||||||||||||||||
| <SimpleTooltip tooltip="This is the unique identifier for your group, used in code"> | ||||||||||||||||||||
| Group ID | ||||||||||||||||||||
| </SimpleTooltip> | ||||||||||||||||||||
| </Label> | ||||||||||||||||||||
| <Input | ||||||||||||||||||||
| id="group-id" | ||||||||||||||||||||
| value={groupId} | ||||||||||||||||||||
| onChange={(e) => { | ||||||||||||||||||||
| setGroupId(e.target.value); | ||||||||||||||||||||
| setErrors(prev => ({ ...prev, id: undefined })); | ||||||||||||||||||||
| }} | ||||||||||||||||||||
| placeholder="e.g., pricing-tiers" | ||||||||||||||||||||
| className={errors.id ? "border-destructive" : ""} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| {errors.id && ( | ||||||||||||||||||||
| <Typography type="label" className="text-destructive"> | ||||||||||||||||||||
| {errors.id} | ||||||||||||||||||||
| </Typography> | ||||||||||||||||||||
| )} | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div className="grid gap-2"> | ||||||||||||||||||||
| <Label htmlFor="display-name"> | ||||||||||||||||||||
| <SimpleTooltip tooltip="This is how the group will be displayed to users"> | ||||||||||||||||||||
| Display Name | ||||||||||||||||||||
| </SimpleTooltip> | ||||||||||||||||||||
| </Label> | ||||||||||||||||||||
| <Input | ||||||||||||||||||||
| id="display-name" | ||||||||||||||||||||
| value={displayName} | ||||||||||||||||||||
| onChange={(e) => { | ||||||||||||||||||||
| setDisplayName(e.target.value); | ||||||||||||||||||||
| setErrors(prev => ({ ...prev, displayName: undefined })); | ||||||||||||||||||||
| }} | ||||||||||||||||||||
| placeholder="e.g., Pricing Tiers" | ||||||||||||||||||||
| className={errors.displayName ? "border-destructive" : ""} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| {errors.displayName && ( | ||||||||||||||||||||
| <Typography type="label" className="text-destructive"> | ||||||||||||||||||||
| {errors.displayName} | ||||||||||||||||||||
| </Typography> | ||||||||||||||||||||
| )} | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <DialogFooter> | ||||||||||||||||||||
| <Button variant="outline" onClick={handleClose}> | ||||||||||||||||||||
| Cancel | ||||||||||||||||||||
| </Button> | ||||||||||||||||||||
| <Button onClick={validateAndCreate}> | ||||||||||||||||||||
| Create Group | ||||||||||||||||||||
| </Button> | ||||||||||||||||||||
| </DialogFooter> | ||||||||||||||||||||
| </DialogContent> | ||||||||||||||||||||
| </Dialog> | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
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.
🛠️ Refactor suggestion
Ensure OrbitControls damping is enabled and state synced after camera mutation
Without enableDamping, dampingFactor is ignored. After programmatic camera changes, call controls.update() to avoid a first-interaction "jump."
📝 Committable suggestion
🤖 Prompt for AI Agents