-
-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add in-app sponsorship prompts and reminders #28
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
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -93,6 +93,10 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| const [ownerAffiliations, setOwnerAffiliations] = useState<OwnerAffiliation[]>(['OWNER']); | ||||||
| const [orgSwitcherOpen, setOrgSwitcherOpen] = useState(false); | ||||||
|
|
||||||
| // Sponsor reminder state | ||||||
| const [operationCount, setOperationCount] = useState(0); | ||||||
| const [showSponsorReminder, setShowSponsorReminder] = useState(false); | ||||||
|
|
||||||
| // Search state (server-side) | ||||||
| const [searchItems, setSearchItems] = useState<RepoNode[]>([]); | ||||||
| const [searchEndCursor, setSearchEndCursor] = useState<string | null>(null); | ||||||
|
|
@@ -189,6 +193,19 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| })(); | ||||||
| }, [initialOrgSlug, token, prefsLoaded, client, addDebugMessage]); | ||||||
|
|
||||||
| // Helper to track successful operations and show sponsor reminder | ||||||
| function trackSuccessfulOperation() { | ||||||
| const newCount = operationCount + 1; | ||||||
| setOperationCount(newCount); | ||||||
|
|
||||||
| // Show sponsor reminder every 5 operations | ||||||
| if (newCount % 5 === 0) { | ||||||
| setShowSponsorReminder(true); | ||||||
| // Hide the reminder after 5 seconds | ||||||
| setTimeout(() => setShowSponsorReminder(false), 5000); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
Comment on lines
+196
to
+208
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. π οΈ Refactor suggestion Make the counter update atomic and clear the hide timer to avoid leaks Use functional setState to prevent stale reads, and store/clear the timeout so it doesnβt fire after unmount or stack with multiple operations. - function trackSuccessfulOperation() {
- const newCount = operationCount + 1;
- setOperationCount(newCount);
-
- // Show sponsor reminder every 5 operations
- if (newCount % 5 === 0) {
- setShowSponsorReminder(true);
- // Hide the reminder after 5 seconds
- setTimeout(() => setShowSponsorReminder(false), 5000);
- }
- }
+ function trackSuccessfulOperation() {
+ setOperationCount(prev => {
+ const next = prev + 1;
+ if (next % 5 === 0) {
+ setShowSponsorReminder(true);
+ // Reset any existing timer and start a new one
+ if (sponsorTimerRef.current) clearTimeout(sponsorTimerRef.current);
+ sponsorTimerRef.current = setTimeout(() => setShowSponsorReminder(false), 5000);
+ }
+ return next;
+ });
+ }Additions needed outside this hunk: // near other refs (e.g., around the copyToastTimerRef)
const sponsorTimerRef = useRef<NodeJS.Timeout | null>(null);
// extend existing unmount cleanup
useEffect(() => {
return () => {
if (copyToastTimerRef.current) clearTimeout(copyToastTimerRef.current);
if (sponsorTimerRef.current) clearTimeout(sponsorTimerRef.current);
};
}, []);π€ Prompt for AI Agents |
||||||
| function closeArchiveModal() { | ||||||
| setArchiveMode(false); | ||||||
| setArchiveTarget(null); | ||||||
|
|
@@ -301,6 +318,7 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| setItems(prev => prev.map(updateRepo)); | ||||||
| setSearchItems(prev => prev.map(updateRepo)); | ||||||
|
|
||||||
| trackSuccessfulOperation(); // Track the successful operation | ||||||
| closeArchiveModal(); | ||||||
| } catch (e) { | ||||||
| setArchiving(false); | ||||||
|
|
@@ -511,6 +529,7 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| setSearchTotalCount((c) => Math.max(0, c - 1)); | ||||||
| } | ||||||
|
|
||||||
| trackSuccessfulOperation(); // Track the successful operation | ||||||
| setDeleteMode(false); | ||||||
| setDeleteTarget(null); | ||||||
| setTypedCode(''); | ||||||
|
|
@@ -1621,6 +1640,19 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| {/* Header bar */} | ||||||
| {headerBar} | ||||||
|
|
||||||
| {/* Sponsor reminder notification */} | ||||||
| {showSponsorReminder && ( | ||||||
| <Box marginX={1} marginBottom={1}> | ||||||
| <Box borderStyle="single" borderColor="yellow" paddingX={2} paddingY={1}> | ||||||
| <Box flexDirection="column" alignItems="center"> | ||||||
| <Text color="yellow">π Thanks for using gh-manager-cli!</Text> | ||||||
| <Text color="gray">Your support helps craft more open-source tools</Text> | ||||||
| <Text color="cyan">β buymeacoffee.com/wiiiimm</Text> | ||||||
| </Box> | ||||||
| </Box> | ||||||
| </Box> | ||||||
| )} | ||||||
|
Comment on lines
+1643
to
+1654
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. π οΈ Refactor suggestion Use chalk for colouring and adjust layout to account for the bannerβs height
- {showSponsorReminder && (
- <Box marginX={1} marginBottom={1}>
- <Box borderStyle="single" borderColor="yellow" paddingX={2} paddingY={1}>
- <Box flexDirection="column" alignItems="center">
- <Text color="yellow">π Thanks for using gh-manager-cli!</Text>
- <Text color="gray">Your support helps craft more open-source tools</Text>
- <Text color="cyan">β buymeacoffee.com/wiiiimm</Text>
- </Box>
- </Box>
- </Box>
- )}
+ {showSponsorReminder && (
+ <Box marginX={1} marginBottom={1}>
+ <Box borderStyle="single" borderColor="yellow" paddingX={2} paddingY={1}>
+ <Text>
+ {chalk.yellow('π Thanks for using gh-manager-cli!')}{'\n'}
+ {chalk.gray('Your support helps craft more openβsource tools')}{'\n'}
+ {chalk.cyan('β https://buymeacoffee.com/wiiiimm')}
+ </Text>
+ </Box>
+ </Box>
+ )}Add this outside to fix height: // after footer/header constants
const sponsorHeight = showSponsorReminder ? 4 : 0;
// subtract sponsorHeight
const contentHeight = Math.max(1, availableHeight - headerHeight - footerHeight - containerPadding - sponsorHeight);Optional: centralise the sponsor URL/message in a constants module to avoid drift across files. π€ Prompt for AI Agents |
||||||
|
|
||||||
| {/* Main content container with border - fixed height */} | ||||||
| <Box borderStyle="single" borderColor={modalOpen ? 'gray' : 'yellow'} paddingX={1} paddingY={1} marginX={1} height={contentHeight + containerPadding + 2} flexDirection="column"> | ||||||
| {deleteMode && deleteTarget ? ( | ||||||
|
|
@@ -2167,7 +2199,7 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| )} | ||||||
| </Box> | ||||||
|
|
||||||
| {/* Help footer - 4 lines */} | ||||||
| {/* Help footer - 5 lines */} | ||||||
|
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. Footer now has 5 lines but footerHeight is still 4 β list area can be clipped Update the footerHeight constant accordingly (or compute dynamically). Outside this hunk: -const footerHeight = 4; // Footer with border + margin (flexible height)
+const footerHeight = 5; // Footer with border + margin (matches 5 rendered lines)π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||
| <Box marginTop={1} paddingX={1} flexDirection="column"> | ||||||
| {/* Line 1: Basic navigation */} | ||||||
| <Box width={terminalWidth} justifyContent="center"> | ||||||
|
|
@@ -2193,6 +2225,12 @@ export default function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, | |||||
| K Cache Info β’ W Org Switch β’ Del/Backspace Delete β’ Ctrl+L Logout β’ Q Quit | ||||||
| </Text> | ||||||
| </Box> | ||||||
| {/* Line 5: Sponsorship */} | ||||||
| <Box width={terminalWidth} justifyContent="center" marginTop={1}> | ||||||
| <Text color="yellow" dimColor={modalOpen ? true : undefined}> | ||||||
| π Support the project: buymeacoffee.com/wiiiimm | ||||||
| </Text> | ||||||
| </Box> | ||||||
|
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: Footer Height Mismatch Causes UI IssuesThe help footer now renders 5 lines (including the new sponsorship line), but the |
||||||
| </Box> | ||||||
|
|
||||||
| {/* Debug panel */} | ||||||
|
|
||||||
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.
Bug: Unmanaged Timers Cause React Warnings and Memory Leaks
The
setTimeoutintrackSuccessfulOperationisn't managed, which can lead to React warnings and memory leaks if the component unmounts. Additionally, multiple operations in quick succession can cause the sponsor reminder to disappear prematurely due to overlapping timers.