-
Notifications
You must be signed in to change notification settings - Fork 36
Adding email to the menu #566
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
base: main
Are you sure you want to change the base?
Changes from all commits
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,51 @@ | ||
| import { CSSProperties } from 'react'; | ||
|
|
||
| export const getContainerStyle = (customStyle?: CSSProperties): CSSProperties => ({ | ||
| padding: '12px', | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| gap: '12px', | ||
| ...customStyle, | ||
| }); | ||
|
|
||
| export const innerContainerStyle: CSSProperties = { | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| gap: '12px', | ||
| width: '250px', | ||
| }; | ||
|
|
||
| export const formStyle: CSSProperties = { | ||
| width: '100%', | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| gap: '12px', | ||
| }; | ||
|
|
||
| export const labelStyle: CSSProperties = { | ||
| alignSelf: 'flex-start', | ||
| fontWeight: 600, | ||
| }; | ||
|
|
||
| export const inputCardStyle: CSSProperties = { | ||
| width: '100%', | ||
| }; | ||
|
|
||
| export const inputStyle: CSSProperties = { | ||
| width: '100%', | ||
| border: 'none', | ||
| background: 'transparent', | ||
| color: 'inherit', | ||
| fontSize: 'inherit', | ||
| fontWeight: 'inherit', | ||
| letterSpacing: 'inherit', | ||
| padding: 0, | ||
| }; | ||
|
|
||
| export const statusCardStyle: CSSProperties = { | ||
| textAlign: 'center', | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,15 @@ import React, { useState, useEffect, useId } from 'react'; | |
| import { VibesButton } from '../VibesButton/VibesButton.js'; | ||
| import { BrutalistCard } from '../BrutalistCard/BrutalistCard.js'; | ||
| import { generateFreshDataUrl, generateRemixUrl } from '../../utils/appSlug.js'; | ||
| import { | ||
| getContainerStyle, | ||
| innerContainerStyle, | ||
| formStyle, | ||
| labelStyle, | ||
| inputCardStyle, | ||
| inputStyle, | ||
| statusCardStyle, | ||
| } from './VibesPanel.styles.js'; | ||
|
|
||
| export interface VibesPanelProps { | ||
| /** Optional custom styling for the panel container */ | ||
|
|
@@ -16,23 +25,18 @@ export interface VibesPanelProps { | |
| * This component provides the standard three-button layout used | ||
| * throughout the Vibes DIY platform for authentication and actions. | ||
| */ | ||
| type PanelMode = 'default' | 'mutate' | 'invite'; | ||
| type PanelMode = 'default' | 'mutate' | 'invite' | 'accounts'; | ||
|
|
||
| export function VibesPanel({ style, className }: VibesPanelProps = {}) { | ||
| const emailId = useId(); | ||
| const [mode, setMode] = useState<PanelMode>('default'); | ||
| const [email, setEmail] = useState(''); | ||
| const [currentAccount, setCurrentAccount] = useState(''); | ||
| const [inviteStatus, setInviteStatus] = useState<'idle' | 'sending' | 'success' | 'error'>( | ||
| 'idle' | ||
| ); | ||
| const [inviteMessage, setInviteMessage] = useState(''); | ||
|
|
||
| const handleMutateClick = () => { | ||
| if (mode === 'default') { | ||
| setMode('mutate'); | ||
| } | ||
| }; | ||
|
|
||
| const handleInviteClick = () => { | ||
| if (mode === 'default') { | ||
| setMode('invite'); | ||
|
|
@@ -108,116 +112,99 @@ export function VibesPanel({ style, className }: VibesPanelProps = {}) { | |
| }; | ||
| }, []); | ||
|
|
||
| const containerStyle: React.CSSProperties = { | ||
| padding: '12px', | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| gap: '12px', | ||
| ...style, | ||
| }; | ||
| useEffect(() => { | ||
| //Logic to get the Current Account | ||
| setCurrentAccount('Amber@vibes.diy'); | ||
| }, []); | ||
|
Comment on lines
+115
to
+118
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. Hardcoding a specific email in production logic is brittle and leaks personal info. This will render an empty button on first paint (before Prefer sourcing the current account from auth state/props and rendering a fallback label. At minimum, remove the hardcoded email and show a safe placeholder when no account is available. Suggestion
Example minimal change to avoid shipping a hardcoded email and blank label: // Remove this effect entirely
// useEffect(() => {
// //Logic to get the Current Account
// setCurrentAccount('Amber@vibes.diy');
// }, []);
// And ensure the render uses a fallback label:
// <VibesButton variant="primary" onClick={() => setMode('accounts')}>
// {currentAccount || 'Accounts'}
// </VibesButton>Optionally, plumb Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change. |
||
|
|
||
| return ( | ||
| <div style={containerStyle} className={className}> | ||
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| gap: '12px', | ||
| width: '250px', | ||
| }} | ||
| > | ||
| {mode === 'mutate' ? ( | ||
| // Mutate mode buttons | ||
| <> | ||
| <VibesButton variant="primary" onClick={handleFreshDataClick}> | ||
| Fresh Start | ||
| </VibesButton> | ||
| <VibesButton variant="secondary" onClick={handleChangeCodeClick}> | ||
| Remix Code | ||
| const currentlyDisplay: Record<PanelMode, React.ReactNode> = { | ||
| default: ( | ||
| <> | ||
| <VibesButton variant="primary" onClick={() => setMode('accounts')}> | ||
| {currentAccount} | ||
| </VibesButton> | ||
| <VibesButton variant="secondary" onClick={() => setMode('mutate')}> | ||
| Mutate | ||
| </VibesButton> | ||
| <VibesButton variant="tertiary" onClick={handleInviteClick}> | ||
| Invite | ||
| </VibesButton> | ||
| </> | ||
| ), | ||
| accounts: ( | ||
| <> | ||
| <VibesButton variant="primary" onClick={handleLogoutClick}> | ||
| Logout | ||
| </VibesButton> | ||
| <VibesButton size="small" variant="tertiary" onClick={handleBackClick}> | ||
| ← Back | ||
| </VibesButton> | ||
| </> | ||
| ), | ||
| mutate: ( | ||
| <> | ||
| <VibesButton variant="primary" onClick={handleFreshDataClick}> | ||
| Fresh Start | ||
| </VibesButton> | ||
| <VibesButton variant="secondary" onClick={handleChangeCodeClick}> | ||
| Remix Code | ||
| </VibesButton> | ||
| <VibesButton size="small" variant="tertiary" onClick={handleBackClick}> | ||
| ← Back | ||
| </VibesButton> | ||
| </> | ||
| ), | ||
| invite: ( | ||
| <> | ||
| {inviteStatus === 'idle' ? ( | ||
| <form onSubmit={handleInviteSubmit} style={formStyle}> | ||
| <label htmlFor={emailId} style={labelStyle}> | ||
| Invite by email | ||
| </label> | ||
| <BrutalistCard size="md" style={inputCardStyle}> | ||
| <input | ||
| id={emailId} | ||
| type="email" | ||
| placeholder="friend@example.com" | ||
| value={email} | ||
| onChange={(e) => setEmail(e.target.value)} | ||
| style={inputStyle} | ||
| autoComplete="email" | ||
| required | ||
| /> | ||
| </BrutalistCard> | ||
| <VibesButton variant="primary" type="submit" disabled={!email.trim()}> | ||
| Send Invite | ||
| </VibesButton> | ||
| <VibesButton variant="tertiary" onClick={handleBackClick}> | ||
| ← Back | ||
| </VibesButton> | ||
| </> | ||
| ) : mode === 'invite' ? ( | ||
| // Invite mode form | ||
| <> | ||
| {inviteStatus === 'idle' ? ( | ||
| // Show form when idle | ||
| <form | ||
| onSubmit={handleInviteSubmit} | ||
| style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: '12px' }} | ||
| > | ||
| <label htmlFor={emailId} style={{ alignSelf: 'flex-start', fontWeight: 600 }}> | ||
| Invite by email | ||
| </label> | ||
| <BrutalistCard size="md" style={{ width: '100%' }}> | ||
| <input | ||
| id={emailId} | ||
| type="email" | ||
| placeholder="friend@example.com" | ||
| value={email} | ||
| onChange={(e) => setEmail(e.target.value)} | ||
| style={{ | ||
| width: '100%', | ||
| border: 'none', | ||
| background: 'transparent', | ||
| color: 'inherit', | ||
| fontSize: 'inherit', | ||
| fontWeight: 'inherit', | ||
| letterSpacing: 'inherit', | ||
| padding: 0, | ||
| }} | ||
| autoComplete="email" | ||
| required | ||
| /> | ||
| </BrutalistCard> | ||
| <VibesButton variant="primary" type="submit" disabled={!email.trim()}> | ||
| Send Invite | ||
| </VibesButton> | ||
| </form> | ||
| ) : ( | ||
| // Show status when sending/complete | ||
| <BrutalistCard | ||
| id="invite-status" | ||
| role="status" | ||
| aria-live="polite" | ||
| size="sm" | ||
| variant={ | ||
| inviteStatus === 'sending' | ||
| ? 'default' | ||
| : inviteStatus === 'error' | ||
| ? 'error' | ||
| : 'success' | ||
| } | ||
| style={{ textAlign: 'center' }} | ||
| > | ||
| {inviteStatus === 'sending' ? 'Inviting...' : inviteMessage} | ||
| </BrutalistCard> | ||
| )} | ||
| <VibesButton variant="tertiary" onClick={handleBackClick}> | ||
| ← Back | ||
| </VibesButton> | ||
| </> | ||
| </form> | ||
| ) : ( | ||
| // Default buttons | ||
| <> | ||
| <VibesButton variant="primary" onClick={handleLogoutClick}> | ||
| Logout | ||
| </VibesButton> | ||
| <VibesButton variant="secondary" onClick={handleMutateClick}> | ||
| Mutate | ||
| </VibesButton> | ||
| <VibesButton variant="tertiary" onClick={handleInviteClick}> | ||
| Invite | ||
| </VibesButton> | ||
| </> | ||
| <BrutalistCard | ||
| id="invite-status" | ||
| role="status" | ||
| aria-live="polite" | ||
| size="sm" | ||
| variant={ | ||
|
Comment on lines
+181
to
+186
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.
SuggestionGenerate a per-instance status ID: const statusId = useId();
...
<BrutalistCard
id={statusId}
role="status"
aria-live="polite"
...
>Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change. |
||
| inviteStatus === 'sending' | ||
| ? 'default' | ||
| : inviteStatus === 'error' | ||
| ? 'error' | ||
| : 'success' | ||
| } | ||
| style={statusCardStyle} | ||
| > | ||
| {inviteStatus === 'sending' ? 'Inviting...' : inviteMessage} | ||
| </BrutalistCard> | ||
| )} | ||
| </div> | ||
| <VibesButton size="small" variant="tertiary" onClick={handleBackClick}> | ||
| ← Back | ||
| </VibesButton> | ||
| </> | ||
| ), | ||
| }; | ||
|
|
||
| return ( | ||
| <div style={getContainerStyle(style)} className={className}> | ||
| <div style={innerContainerStyle}>{currentlyDisplay[mode]}</div> | ||
| </div> | ||
| ); | ||
| } | ||
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.
The new account menu button always displays
Amber@vibes.diybecause the effect that initializescurrentAccountsets it to that literal string. This means every signed-in user will see the same email regardless of who is actually authenticated, which misrepresents the current account and makes the logout action ambiguous. The value should be derived from the logged-in user’s data or omitted until real data is available.Useful? React with 👍 / 👎.