-
Notifications
You must be signed in to change notification settings - Fork 29
Vk/a2ac UI 001 prompt ca #5
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?
Conversation
- Create PricingPage with 3 tier plans (Starter/Pro/Ultimate) - Add animated pricing cards with hover effects - Include FAQ section and trust indicators - Add CreditDisplay component in navbar - Show credit count with low credit warning - Add pricing link to navigation - Match existing app theme and animations - Responsive design for all devices
- Add refundCredit function to useCredits hook - Add decrementAnonymousGeneration for anonymous users - Create refund_credit database function - Wrap generation in try-catch to refund on failure - Show toast notification when credit is refunded - Fix CreditDisplay to use credits.creditsRemaining - Ensure credits only deducted on successful generation
- **Type:** 🐛 Bug
- **Severity:** Medium
- **Component:** `PromptCard.tsx` / `HistoryGrid.tsx`
- **Description:** In the prompt history grid, cards with medium-length text are pushing the action toolbar (icons) out of the viewport. The bottom border cuts off the buttons.
- **Expected Behavior:** The footer buttons should always be visible. Long text should either be truncated with ellipses (`...`) or the card should expand vertically to fit the content.
The Prompt:
# Role
- Act as a Senior Frontend Developer expert in React and Tailwind CSS.
-
- # Context
- I have a UI bug in my \`PromptCard\` component (or the component used to display prompt history).
- - \*\*Current Behavior:\*\* When the prompt description text is too long, it pushes the bottom action buttons (Copy, Edit, Delete) out of the view, causing them to be clipped or hidden by the card's overflow.
- - \*\*Visual Reference:\*\* The card has a fixed height or a grid constraint that isn't adapting to the content size.
-
- # The Task
- Please refactor the CSS/Tailwind classes for this card to ensure the footer stays pinned and visible.
-
- 1. \*\*Flexbox Structure:\*\* Ensure the card container uses \`flex flex-col h-full justify-between\`.
- 2. \*\*Text Truncation:\*\* Apply Tailwind's \`line-clamp\` utility to the description text area so it doesn't expand infinitely.
- - Use \`line-clamp-4\` (or similar) for the body text.
- - Add \`overflow-hidden\` to the text container to handle spillover gracefully.
- 3. \*\*Spacing:\*\* Verify \`p-4\` or \`p-6\` padding is consistent and doesn't conflict with the footer alignment.
-
- # Example of Desired Output (Tailwind)
- \`\`\`tsx
- <div className="flex flex-col h-full bg-card border rounded-xl p-4 shadow-sm hover:shadow-md transition-all">
- {/\* Header \*/}
- <div className="mb-2">...</div>
-
- {/\* Body - THIS IS THE FIX \*/}
- <div className="flex-grow overflow-hidden">
- <p className="text-sm text-muted-foreground line-clamp-4">
- {promptText}
- </p>
- </div>
-
- {/\* Footer - Should always be visible \*/}
- <div className="flex items-center justify-end gap-2 mt-4 pt-2 border-t">
- <Button variant="ghost" size="icon">...</Button>
- <Button variant="ghost" size="icon">...</Button>
- <Button variant="ghost" size="icon" className="text-destructive">...</Button>
- </div>
- </div>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
| return input | ||
| .trim() | ||
| .replace(/[<>]/g, '') // Remove < and > | ||
| .replace(/javascript:/gi, '') // Remove javascript: protocol | ||
| .replace(/on\w+\s*=/gi, '') // Remove event handlers | ||
| // Remove HTML tags | ||
| .replace(/<\/?[^>]+(>|$)/g, '') | ||
| // Remove javascript: protocol | ||
| .replace(/javascript:/gi, '') | ||
| // Remove data: protocol (can be used for XSS) | ||
| .replace(/data:text\/html/gi, '') | ||
| // Remove event handlers | ||
| .replace(/on\w+\s*=/gi, '') |
Check failure
Code scanning / CodeQL
Incomplete multi-character sanitization High
on
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, to fix incomplete multi‑character sanitization, you either (a) use a well‑tested sanitization library or (b) ensure that any regex that removes multi‑character patterns is applied until the string no longer changes, so partially removed sequences cannot re‑combine into new dangerous patterns.
For this code, the least intrusive, behavior‑preserving fix is to wrap the existing chained .replace(...).slice(...) logic in a loop that repeats the entire sanitization pipeline until the output stops changing. This avoids having to reason about each individual regex and ensures that if any step ever creates a new on\w+\s*= (or similar multi‑character pattern) from previously safe content, it will be removed on the next iteration. We keep the existing functionality intact: same regexes, same maximum length, same trimming; the only difference is that we reapply them until the string reaches a fixed point.
Concretely, in src/lib/security.ts, within sanitizeInput, we will:
- Introduce a
let sanitized = input; let previous: string;pair. - Run a
do { ... } while (sanitized !== previous)loop. - Inside the loop, apply the current chain starting from
previous.trim()and assigning the result tosanitized. - Return
sanitizedat the end.
We will not change other functions or imports, and we will not introduce new dependencies.
-
Copy modified lines R23-R45
| @@ -20,21 +20,29 @@ | ||
| */ | ||
| export function sanitizeInput(input: string): string { | ||
| if (!input || typeof input !== 'string') return '' | ||
|
|
||
| return input | ||
| .trim() | ||
| // Remove HTML tags | ||
| .replace(/<\/?[^>]+(>|$)/g, '') | ||
| // Remove javascript: protocol | ||
| .replace(/javascript:/gi, '') | ||
| // Remove data: protocol (can be used for XSS) | ||
| .replace(/data:text\/html/gi, '') | ||
| // Remove event handlers | ||
| .replace(/on\w+\s*=/gi, '') | ||
| // Remove null bytes | ||
| .replace(/\0/g, '') | ||
| // Limit to reasonable length | ||
| .slice(0, 10000) | ||
|
|
||
| let previous: string | ||
| let sanitized = input | ||
|
|
||
| do { | ||
| previous = sanitized | ||
| sanitized = previous | ||
| .trim() | ||
| // Remove HTML tags | ||
| .replace(/<\/?[^>]+(>|$)/g, '') | ||
| // Remove javascript: protocol | ||
| .replace(/javascript:/gi, '') | ||
| // Remove data: protocol (can be used for XSS) | ||
| .replace(/data:text\/html/gi, '') | ||
| // Remove event handlers | ||
| .replace(/on\w+\s*=/gi, '') | ||
| // Remove null bytes | ||
| .replace(/\0/g, '') | ||
| // Limit to reasonable length | ||
| .slice(0, 10000) | ||
| } while (sanitized !== previous) | ||
|
|
||
| return sanitized | ||
| } | ||
|
|
||
| /** |
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.
Pull request overview
This pull request implements a major UI/UX redesign with a Neo-Brutalist theme and introduces several new features including a secure credit system, pricing page, dashboard, and enhanced security measures. The changes involve significant refactoring of the authentication flow, database schema, and prompt generation system.
Key changes:
- Neo-Brutalist UI theme with bold borders, shadows, and vibrant colors
- Secure credit system with atomic transactions and rate limiting
- New pages: Dashboard, Pricing, Explore, and secure prompt generation
- Enhanced security: CSRF protection, input sanitization, session management, CSP headers
Reviewed changes
Copilot reviewed 58 out of 61 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.ts | Added production optimizations: code splitting, console removal, terser minification |
| vercel.json | Implemented security headers (CSP, X-Frame-Options, HSTS-like policies) |
| tailwind.config.cjs | Complete Neo-Brutalist theme configuration with custom colors, shadows, animations |
| supabase/migrations/* | New secure credit system with RLS policies, rate limiting, and feedback table |
| supabase/functions/generate-prompt | Complete rewrite with authentication, rate limiting, and credit consumption |
| src/lib/security.ts | Added CSRF token management and enhanced input sanitization |
| src/lib/sessionManager.ts | New session timeout monitoring with activity tracking |
| src/lib/api.ts | New API client with retry logic and fallback mechanisms |
| src/hooks/useCreditsSecure.ts | Secure credit fetching with RPC fallback |
| src/pages/* | Major UI redesign across all pages with Neo-Brutalist styling |
| src/components/* | Updated Navbar, new CreditDisplay, enhanced UI components |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| errorInfo: ErrorInfo | null | ||
| } | ||
|
|
||
| export class ErrorBoundary extends Component<Props, State> { | ||
| public state: State = { | ||
| hasError: false, | ||
| error: null, | ||
| errorInfo: null, | ||
| } | ||
|
|
||
| public static getDerivedStateFromError(error: Error): State { | ||
| return { hasError: true, error, errorInfo: null } | ||
| } | ||
|
|
||
| public componentDidCatch(error: Error, errorInfo: ErrorInfo) { | ||
| console.error('ErrorBoundary caught an error:', error, errorInfo) | ||
| this.setState({ errorInfo }) |
Copilot
AI
Jan 6, 2026
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.
Component state property 'errorInfo' is written, but it is never read.
Component state property 'errorInfo' is written, but it is never read.
Component state property 'errorInfo' is written, but it is never read.
| errorInfo: ErrorInfo | null | |
| } | |
| export class ErrorBoundary extends Component<Props, State> { | |
| public state: State = { | |
| hasError: false, | |
| error: null, | |
| errorInfo: null, | |
| } | |
| public static getDerivedStateFromError(error: Error): State { | |
| return { hasError: true, error, errorInfo: null } | |
| } | |
| public componentDidCatch(error: Error, errorInfo: ErrorInfo) { | |
| console.error('ErrorBoundary caught an error:', error, errorInfo) | |
| this.setState({ errorInfo }) | |
| } | |
| export class ErrorBoundary extends Component<Props, State> { | |
| public state: State = { | |
| hasError: false, | |
| error: null, | |
| } | |
| public static getDerivedStateFromError(error: Error): State { | |
| return { hasError: true, error } | |
| } | |
| public componentDidCatch(error: Error, errorInfo: ErrorInfo) { | |
| console.error('ErrorBoundary caught an error:', error, errorInfo) |
| Users, | ||
| CheckCircle2 | ||
| } from 'lucide-react' | ||
| import { useTheme } from '@/context/ThemeContext' |
Copilot
AI
Jan 6, 2026
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.
Unused import CheckCircle2.
| Users, | |
| CheckCircle2 | |
| } from 'lucide-react' | |
| import { useTheme } from '@/context/ThemeContext' | |
| Users | |
| } from 'lucide-react' | |
| import { useTheme } from '@/context/ThemeContext' | |
| import { useTheme } from '@/context/ThemeContext' |
| const [mounted, setMounted] = useState(false) | ||
| const [demoStep, setDemoStep] = useState(0) | ||
| const [statsCounter, setStatsCounter] = useState({ prompts: 0, users: 0, satisfaction: 0 }) | ||
|
|
||
| useEffect(() => { | ||
| setMounted(true) | ||
|
|
Copilot
AI
Jan 6, 2026
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.
Unused variable mounted.
| const [mounted, setMounted] = useState(false) | |
| const [demoStep, setDemoStep] = useState(0) | |
| const [statsCounter, setStatsCounter] = useState({ prompts: 0, users: 0, satisfaction: 0 }) | |
| useEffect(() => { | |
| setMounted(true) | |
| const [demoStep, setDemoStep] = useState(0) | |
| const [statsCounter, setStatsCounter] = useState({ prompts: 0, users: 0, satisfaction: 0 }) | |
| useEffect(() => { |
| import { supabase } from './supabase' | ||
|
|
||
| // Retry configuration | ||
| const MAX_RETRIES = 3 |
Copilot
AI
Jan 6, 2026
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.
Unused variable MAX_RETRIES.
|
|
||
| // Retry configuration | ||
| const MAX_RETRIES = 3 | ||
| const INITIAL_RETRY_DELAY = 1000 // 1 second |
Copilot
AI
Jan 6, 2026
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.
Unused variable INITIAL_RETRY_DELAY.
| 'General Development': Code, | ||
| } | ||
|
|
||
| const CATEGORY_COLORS: Record<string, string> = { |
Copilot
AI
Jan 6, 2026
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.
Unused variable CATEGORY_COLORS.
|
|
||
| export default function ExplorePage() { | ||
| const { theme } = useTheme() | ||
| const { user } = useAuth() |
Copilot
AI
Jan 6, 2026
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.
Unused variable user.
| export default function ExplorePage() { | ||
| const { theme } = useTheme() | ||
| const { user } = useAuth() | ||
| const navigate = useNavigate() |
Copilot
AI
Jan 6, 2026
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.
Unused variable navigate.
| import { useAuth } from '@/context/AuthContext' | ||
| import { useNavigate } from 'react-router-dom' | ||
| import { useCredits } from '@/hooks/useCredits' | ||
| import { useCredits } from '@/hooks/useCreditsSecure' |
Copilot
AI
Jan 6, 2026
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.
Unused import useCredits.
| CREATE OR REPLACE FUNCTION consume_user_credits( | ||
| p_user_id UUID, | ||
| p_credits_to_consume INTEGER, | ||
| p_request_id TEXT, | ||
| p_action TEXT DEFAULT 'generate_prompt', | ||
| p_metadata JSONB DEFAULT '{}' | ||
| ) | ||
| RETURNS JSONB | ||
| LANGUAGE plpgsql | ||
| SECURITY DEFINER | ||
| AS $$ | ||
| DECLARE | ||
| v_current_credits INTEGER; | ||
| v_new_credits INTEGER; | ||
| v_tier TEXT; | ||
| BEGIN | ||
| -- Check for duplicate request (idempotency) | ||
| IF EXISTS (SELECT 1 FROM request_log WHERE request_id = p_request_id) THEN | ||
| RETURN jsonb_build_object( | ||
| 'success', false, | ||
| 'error', 'duplicate_request', | ||
| 'message', 'This request has already been processed' | ||
| ); | ||
| END IF; | ||
|
|
||
| -- Lock the user's row and get current credits | ||
| SELECT credits, tier INTO v_current_credits, v_tier | ||
| FROM profiles | ||
| WHERE id = p_user_id | ||
| FOR UPDATE; -- Critical: prevents race conditions | ||
|
|
||
| -- Check if user exists | ||
| IF NOT FOUND THEN | ||
| RETURN jsonb_build_object( | ||
| 'success', false, | ||
| 'error', 'user_not_found', | ||
| 'message', 'User profile not found' | ||
| ); | ||
| END IF; | ||
|
|
||
| -- Check sufficient credits | ||
| IF v_current_credits < p_credits_to_consume THEN | ||
| RETURN jsonb_build_object( | ||
| 'success', false, | ||
| 'error', 'insufficient_credits', | ||
| 'message', 'Not enough credits', | ||
| 'current_credits', v_current_credits, | ||
| 'required_credits', p_credits_to_consume | ||
| ); | ||
| END IF; | ||
|
|
||
| -- Deduct credits | ||
| v_new_credits := v_current_credits - p_credits_to_consume; | ||
|
|
||
| UPDATE profiles | ||
| SET credits = v_new_credits, | ||
| updated_at = NOW() | ||
| WHERE id = p_user_id; |
Copilot
AI
Jan 6, 2026
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 consume_user_credits function is declared SECURITY DEFINER and later granted EXECUTE to the authenticated role, but it fully trusts the caller-supplied p_user_id and p_credits_to_consume parameters without checking auth.uid() or enforcing that the credit delta is positive. An attacker with an authenticated Supabase session can call this RPC directly via /rest/v1/rpc/consume_user_credits using arbitrary p_user_id (including other users) and even negative p_credits_to_consume values, allowing them to arbitrarily change credits (e.g., inflate their own balance) while bypassing the intended Edge Function and business logic. To fix this, tightly bind operations to the caller by validating p_user_id = auth.uid(), enforcing p_credits_to_consume > 0, and/or restricting EXECUTE on this SECURITY DEFINER function to a server-side role only.




No description provided.