Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces “vanity URL” customization support for the sqm-share-link component, adding UI and GraphQL operations to validate and save user-chosen link codes.
Changes:
- Adds a new
ShareLinkViewwith an edit mode UI (validation, save/cancel, edit-limit messaging). - Extends
useShareLinkto support vanity link validation/saving and edit limit tracking via new GraphQL queries/mutations. - Updates documentation, Storybook stories, and dependency graphs to reflect the new component relationships.
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/mint-components/src/index.html | Adds a commented-out example usage for sqm-share-link. |
| packages/mint-components/src/components/sqm-stencilbook/readme.md | Updates the component dependency graph to include sqm-share-link. |
| packages/mint-components/src/components/sqm-share-link/useShareLink.tsx | Implements vanity link customization logic, validation debounce, and new GraphQL operations. |
| packages/mint-components/src/components/sqm-share-link/sqm-share-link.tsx | Adds new props for vanity customization and switches rendering to ShareLinkView. |
| packages/mint-components/src/components/sqm-share-link/sqm-share-link-view.tsx | New view implementing the customization/edit UI and edit-limit message rendering. |
| packages/mint-components/src/components/sqm-share-link/readme.md | Documents new props and dependency graph updates. |
| packages/mint-components/src/components/sqm-share-link/UseShareLink.stories.tsx | Updates hook stories to use ShareLinkView and adds a customization-enabled story. |
| packages/mint-components/src/components/sqm-share-link/ShareLink.stories.tsx | Adds multiple ShareLinkView stories covering customization states. |
| packages/mint-components/src/components/sqm-share-code/sqm-share-code.tsx | Removes a now-irrelevant rewardStatus field in demo props and formats options. |
| packages/mint-components/src/components/sqm-form-message/readme.md | Updates docs dependency listing/graph to include sqm-share-link. |
| packages/mint-components/src/components.d.ts | Updates generated typings to include the new props and demo data type for sqm-share-link. |
| packages/mint-components/package.json | Bumps package version and updates @raisins/stencil-docs-target dependency. |
| packages/mint-components/package-lock.json | Updates lockfile for the dependency bump (but version metadata is currently inconsistent). |
Files not reviewed (1)
- packages/mint-components/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { isDemo } from "@saasquatch/component-boilerplate"; | ||
| import { useState, withHooks } from "@saasquatch/stencil-hooks"; | ||
| import { Component, Prop, h } from "@stencil/core"; | ||
| import { Component, Fragment, Prop, h } from "@stencil/core"; |
There was a problem hiding this comment.
Fragment is imported from @stencil/core but not used in this file. Remove the unused import to keep the module clean.
| import { Component, Fragment, Prop, h } from "@stencil/core"; | |
| import { Component, Prop, h } from "@stencil/core"; |
| { | ||
| "name": "@saasquatch/mint-components", | ||
| "version": "2.1.7", | ||
| "version": "2.1.8-18", | ||
| "lockfileVersion": 2, | ||
| "requires": true, | ||
| "packages": { | ||
| "": { | ||
| "name": "@saasquatch/mint-components", | ||
| "version": "2.1.7", | ||
| "version": "2.1.8-18", | ||
| "hasInstallScript": true, |
There was a problem hiding this comment.
package-lock.json version is 2.1.8-18 (both the top-level and the packages[""] entry), but package.json was bumped to 2.1.8-19. Update the lockfile version fields so they match the package version to avoid inconsistent dependency metadata.
| const MessageLinkQuery = gql` | ||
| query ($programId: ID, $engagementMedium: UserEngagementMedium!) { | ||
| query ($programId: ID) { | ||
| user: viewer { | ||
| ... on User { | ||
| shareLink( | ||
| programId: $programId | ||
| engagementMedium: $engagementMedium | ||
| shareMedium: DIRECT | ||
| ) | ||
| shareLink(programId: $programId) | ||
| } |
There was a problem hiding this comment.
shareLink GraphQL query here no longer passes engagementMedium (and previously also passed shareMedium: DIRECT). Other components in this repo still query shareLink with engagementMedium: UserEngagementMedium! (e.g. sqm-qr-code/useQRCode.ts), so this change is likely to cause a runtime GraphQL validation error or change link generation semantics. Restore the required arguments/variables (or update all shareLink queries consistently if the schema changed).
| tooltiplifespan: number; | ||
| linkOverride?: string; | ||
| customizeUrl?: boolean; | ||
| customizeUrlText?: string; |
There was a problem hiding this comment.
customizeUrlText is declared on ShareLinkProps but never used anywhere in the hook or view. If it’s not needed, remove it; if it’s meant to control UI copy, thread it through to ShareLinkView and document it in sqm-share-link/readme.md.
| customizeUrlText?: string; |
| if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); | ||
|
|
||
| if (!trimmed || trimmed.length < MIN_CHARACTERS) { | ||
| setIsValidating(false); | ||
| return; | ||
| } | ||
|
|
||
| setIsValidating(true); | ||
| debounceTimerRef.current = setTimeout(async () => { | ||
| try { | ||
| const result = await validateLinkCode({ linkCode: trimmed }); | ||
| if (!result?.validateLinkCode?.valid) { | ||
| const reason = result?.validateLinkCode | ||
| ?.invalidReason as ValidationErrorCode; | ||
| setValidationError(mapErrorCodeToInfo(reason)); | ||
| } | ||
| } catch { | ||
| // Validation query failed — don't block the user | ||
| } | ||
| setIsValidating(false); | ||
| }, 2000); |
There was a problem hiding this comment.
The debounced validation uses a setTimeout but the timer is only cleared when the input changes. If the user cancels/saves (or the component unmounts) while a timer is pending, the callback can still run later and update state, potentially reintroducing validation errors after leaving edit mode. Clear any pending timer on cancel/save and on unmount (cleanup effect), and consider guarding against stale async responses updating state for an older trimmed value.
| supportLink: ( | ||
| <a target="_blank" href="https://example.com"> | ||
| {supportLinkText} | ||
| </a> |
There was a problem hiding this comment.
The support link in the edit-limit message is hard-coded to https://example.com, which will ship users to a placeholder domain and isn’t configurable via props. Consider adding a supportLinkUrl/supportEmail prop (similar to other components) or otherwise sourcing the real destination. Also, since this uses target="_blank", add rel="noopener noreferrer" to prevent reverse-tabnabbing.
| "title": "Mint Components", | ||
| "version": "2.1.7", | ||
| "version": "2.1.8-19", | ||
| "description": "A minimal design library with components for referral and loyalty experiences. Built with Shoelace components by Saasquatch.", |
There was a problem hiding this comment.
package.json sets the package version to 2.1.8-19, but package-lock.json was updated to 2.1.8-18. This mismatch can cause confusing installs/publishes; align the version fields across both files (and the nested packages[""] entry in the lockfile).
| function onCancel() { | ||
| setIsEditing(false); | ||
| setEditValue(""); | ||
| setValidationError(null); | ||
| setIsValidating(false); | ||
| } |
There was a problem hiding this comment.
onCancel resets editing state but does not clear any pending debounce timer started by onEditValueChange. This can cause setValidationError/setIsValidating to run after cancel, leaving stale validation state the next time the editor is opened. Clear the pending timeout in onCancel (and similarly after a successful save).
| <button | ||
| class={sheet.classes.SaveButton} | ||
| onClick={onSave} | ||
| disabled={ | ||
| isSaving || isValidating || !!validationError || !editValue || editValue.length < minCharacters | ||
| } | ||
| > | ||
| {isSaving ? "Saving..." : saveLabelText} | ||
| </button> | ||
| <button | ||
| class={sheet.classes.CancelButton} | ||
| onClick={onCancel} | ||
| disabled={isSaving} | ||
| > |
There was a problem hiding this comment.
The Save/Cancel controls are plain <button> elements without an explicit type. If this component is ever rendered inside a form, these will default to type="submit" and may trigger unintended form submission. Set type="button" on both buttons (and keep disabled behavior as-is).
| > | ||
| {isSaving ? "Saving..." : saveLabelText} | ||
| </button> | ||
| <button |
There was a problem hiding this comment.
Is it possible to use shoelace components in the editing state?
Description of the change
Type of change
Links
Checklists
Development
Paperwork
Code review