Skip to content

fix: improve click UX and styling for Auth Token page #11863

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

Merged
merged 32 commits into from
Feb 1, 2024

Conversation

Parkreiner
Copy link
Member

@Parkreiner Parkreiner commented Jan 26, 2024

No issue to link – ended up adding this to the workpile, since Blueberry decided that we'd be launching with token auth for Backstage's alpha release

Changes made

  • Revamps the useClipboard hook to be based on a more "synchronization-based" API
    • No more timeouts
    • The hook now "listens" more directly to the clipboard and updates state whenever the clipboard changes
    • It also is set up to revalidate the clipboard if the user leaves the Coder tab and then comes back
  • Adds test file for useClipboard
  • Revamps the CodeExample component to have a much larger clickable area (underlying HTML semantics have not been changed)
    • Also increases the security when the secret prop is true (and also makes sure that the value defaults to true)
  • Updates how useQuery is integrated into the page to follow newer Coder conventions
  • Touches up styling for auth page (mainly spacing and text formatting)

Notes

  • I had to do some slightly hokey tests for useClipboard because even though userEvent.setup sets up a clipboard mock automatically (it normally doesn't exist in JSDOM), I couldn't get it to trigger events predictably. If anybody has suggestions for how to get rid of my home-spun mock, I have a bunch of tacos waiting for you

@Parkreiner Parkreiner self-assigned this Jan 26, 2024
@Parkreiner Parkreiner changed the title chore: clean up AuthPage fix: improve URL syncs and styling for Auth page Jan 26, 2024
Comment on lines 29 to 36
const {
text,
ctaCopy,
wrapperStyles,
buttonStyles,
onClick: outsideOnClick,
tooltipTitle = Language.tooltipTitle,
} = props;
Copy link
Member Author

Choose a reason for hiding this comment

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

Generally try to keep prop definitions inlined, but Prettier's auto-formatting made the code super ugly to look at

Comment on lines 48 to 51
onClick={(event) => {
void copyToClipboard();
outsideOnClick?.(event);
}}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the only major change for the component. The rest of the diff is just from the forwardRef call increasing the indentation

@Parkreiner Parkreiner requested review from a team, code-asher and BrunoQuaresma and removed request for a team and code-asher January 27, 2024 03:11
@Parkreiner Parkreiner marked this pull request as ready for review January 27, 2024 03:21
@Parkreiner Parkreiner changed the title fix: improve URL syncs and styling for Auth page fix: improve URL syncs and styling for Auth Token page Jan 27, 2024
Comment on lines 159 to 175
// Absolutely cartoonish logic, but it's how you do things with the exec API
const previousFocusTarget = document.activeElement;
const dummyInput = document.createElement("input");
dummyInput.style.visibility = "hidden";
document.body.appendChild(dummyInput);
dummyInput.focus();

// Confusingly, you want to use the command opposite of what you actually want
// to do to interact with the execCommand method
const command = operation === "read" ? "paste" : "copy";
const isExecSupported = document.execCommand(command);
const value = dummyInput.value;
dummyInput.remove();

if (previousFocusTarget instanceof HTMLElement) {
previousFocusTarget.focus();
}
Copy link
Member Author

@Parkreiner Parkreiner Jan 27, 2024

Choose a reason for hiding this comment

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

All of these changes together might be a bit overkill, but I didn't want to leave things to chance. The biggest issue before was that when the dummy input received focus, there was nothing in place to put it back on the original element once the function was done

Comment on lines +27 to +34
Copy the session token below and
{/*
* This looks silly, but it's a case where you want to hide the space
* visually because it messes up the centering, but you want the space
* to still be available to screen readers
*/}
<span css={{ ...visuallyHidden }}>{VISUALLY_HIDDEN_SPACE}</span>
<strong css={{ display: "block" }}>paste it in your terminal.</strong>
Copy link
Member Author

@Parkreiner Parkreiner Jan 27, 2024

Choose a reason for hiding this comment

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

Changed the previous markup because the nowrap styling had a risk of breaking the text rendering at smaller viewport sizes

Copy link


🚀 Deploying PR 11863 ...

@bpmct
Copy link
Member

bpmct commented Jan 29, 2024

On Safari, the button doesn't revert to the clipboard icon (the copy still works)

Screen.Recording.2024-01-28.at.6.35.03.PM.mov

I haven't seen a copy UX like this (that is clipboard-aware and maintains confirmation state), but I see the immediate feedback being quite common. Here is Iconfinder, for example:

https://medium.com/iconfinder/copy-to-clipboard-b4e37b6fe20b

I could be wrong and this is a more common paradigm, but as much as possible I'd love to avoid complexity on this page (or manually test with various browsers) as it's a critical CLI flow

variant="text"
onClick={(event) => {
void copyToClipboard();
outsideOnClick?.(event);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need this?

Copy link
Member Author

Choose a reason for hiding this comment

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

More just for flexibility. It might be because I've been in a Backstage mindset, where we need a lot more API flexibility, but my thinking was this would let someone attach an onClick handler without overriding the main copy functionality

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the Coder app context + this component context, what would be a common use case for this?

Copy link
Member Author

@Parkreiner Parkreiner Jan 30, 2024

Choose a reason for hiding this comment

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

For this context, it is over-engineering, yeah. I'll remove it


type Result<T = unknown> = void extends T ? VoidResult : ResultWithData<T>;

async function readFromClipboard(): Promise<Result<string>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I did not understand this part. Why do we need to read something from the clipboard? I would expect to just write things in it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe listing the flow using bullet points would make things easier to understand for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's mainly for setting up a way to watch the clipboard, since there isn't an observer-based API for it, like there is for a lot of other browser APIs

The main idea is that isCopied is always a derived value that comes from checking whether the text passed into the hook matches the text currently in the clipboard. And the only way to check the current clipboard is by reading from it – if we only write to it, then we can't make any assumptions about what's actually in the clipboard, because it can also change from sources outside React

So basically, there's three main "sync cues":

  1. The user copies our text to the clipboard
  2. The user changes their clipboard text while in the same page
  3. The user changes to a different tab, and then comes back

In all of the cases, we don't have a direct, event-based way to check the clipboard contents to verify that the content matches. You would think that writing to the clipboard would expose that information, but the event objects don't actually contain the new clipboard text. So in all three cases, the only choice is reading from it

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need to check if the content in the clipboard matches the one passed into the hook? Is there a use case in the app where this can happen?

Copy link
Member Author

@Parkreiner Parkreiner Jan 30, 2024

Choose a reason for hiding this comment

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

So, this is splitting semantic hairs, but I guess the question boils down to, "What do we want isCopied to represent?

  • Do we want it to indicate that we've just finished handling a copy operation, and this value represents how long we want to tell the user that the text is copied?
  • Or do we want this property to act as a source of truth, where isCopied answers the question, "Each time the component renders, is the text we provided into the hook copied into the clipboard?"
    • If we don't want to do that, maybe that's a sign we should rename the property, but because the clipboard is one of the most mutable values in web development, the only way to have 100% confidence to that question is by checking the clipboard

That mismatch between the property name and actual functionality is what led me to consider a more synchronization-based solution, but maybe this did end up putting me on a weird, wild goose chase


// Comments for this function mirror the ones for readFromClipboard
async function writeToClipboard(textToCopy: string): Promise<Result<void>> {
if (!document.hasFocus()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm... why is this necessary?

Thinking about the most common use case, like the user clicking the button and the value being copied, I don't see how this could happen if the document is not in focus 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

I had a comment in readToClipboard, but it's mainly to suppress errors when trying to do stuff in development mode

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you think is there a way to improve readability without adding a comment mentioning another comment? Thinking on a named variable or function like isDevMode?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, now that you mention it, I can use an assertion function. I'll get that swapped in

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's always good to share screenshots and demos when making UI changes or features. Some design improvements I see we can make:
Screenshot 2024-01-29 at 09 07 22

  • The space between the title, description text, and code example looks strange
  • I would make the "Go to workspaces" link a normal link. I think it's not that important to be considered a secondary action like a button.
  • The code example looks bigger than normal input sizes to me... I think it would be nice to have it the same height.

Copy link
Collaborator

@BrunoQuaresma BrunoQuaresma left a comment

Choose a reason for hiding this comment

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

I'm a little concerned about the added complexity and some things I didn't understand, but after getting some explanation I can quickly come back to this PR and review it 👍

@Parkreiner
Copy link
Member Author

@bpmct @BrunoQuaresma So, Safari support is something I forgot to test for, and the more I look into things, it looks like their handling of the clipboard/navigator APIs is a little wonkier than most browsers. I could see this becoming a rabbit hole, and I think making more sweeping changes with Backstage launch coming up was a mistake

Now that you mention, Ben, I also agree that reducing complexity is even more important in core user flows

This is what I'm going to do:

  1. Revert to the old clipboard logic, but bring in the new bug-fixes
  2. Apply Bruno's suggestions, and fix up the old styling even more

@Parkreiner
Copy link
Member Author

@BrunoQuaresma
Before:
Screenshot 2024-01-29 at 9 34 34 AM

After:
Screenshot 2024-01-29 at 9 33 29 AM

@BrunoQuaresma
Copy link
Collaborator

@Parkreiner I liked the design changes 👍

Wondering if we should use some package for the clipboard that would take care of the complexity for us. 33K starts on GitHub https://github.com/zenorocha/clipboard.js

Copy link
Collaborator

@BrunoQuaresma BrunoQuaresma left a comment

Choose a reason for hiding this comment

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

I'm going to leave the final decision to you 🤗 so we don't block work for Backstage integration.

@Parkreiner
Copy link
Member Author

@BrunoQuaresma Hopefully my tone didn't come across as bad in my responses, but I want to clear: I really appreciate your questions, and the pushback I've gotten. You're making me double-check my own assumptions, and pushing me to make the code as good as it can be

@BrunoQuaresma
Copy link
Collaborator

@Parkreiner 100%

@Parkreiner
Copy link
Member Author

Wondering if we should use some package for the clipboard that would take care of the complexity for us. 33K starts on GitHub https://github.com/zenorocha/clipboard.js

@BrunoQuaresma Yeah, I was going to ask about that at tomorrow's FE Variety before I had to change my flight plans. I feel like the stance we've taken before is "don't bring in a package if it's easy enough to do things yourself", but just because it feels like clipboards are so surprisingly complicated, maybe bringing something in would be for the best

@Parkreiner Parkreiner changed the title fix: improve URL syncs and styling for Auth Token page fix: improve click UX and styling for Auth Token page Feb 1, 2024
@Parkreiner Parkreiner merged commit b0a855c into main Feb 1, 2024
@Parkreiner Parkreiner deleted the mes/clipboard-fix branch February 1, 2024 02:25
@github-actions github-actions bot locked and limited conversation to collaborators Feb 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants