Add product applicability controls to promotion forms

Introduces product selection and exclusion controls to both new and edit promotion forms, allowing promotions to target all, specific, or all-but-specific products. Adds a reusable ProductSelector component, updates promotion types to support new fields, and adjusts cookie max-age for authentication. Also adds two new business quotes.
This commit is contained in:
NotII
2025-08-07 16:05:31 +01:00
parent db1ebcb19d
commit 2c48ecd2b4
8 changed files with 584 additions and 86 deletions

View File

@@ -31,8 +31,8 @@ export async function POST(req: NextRequest) {
const cookieOptions = { const cookieOptions = {
// HttpOnly for security - prevents JavaScript access // HttpOnly for security - prevents JavaScript access
httpOnly: true, httpOnly: true,
// Valid for 7 days (same as the JWT) // Valid for 3 hours
maxAge: 7 * 24 * 60 * 60, maxAge: 3 * 60 * 60,
// Only send in requests to our domain // Only send in requests to our domain
path: '/', path: '/',
// Strict same-site policy to prevent CSRF // Strict same-site policy to prevent CSRF

View File

@@ -66,7 +66,7 @@ export default function LoginForm() {
if (response.ok && data.token) { if (response.ok && data.token) {
// Store the token in both cookie and localStorage for redundancy // Store the token in both cookie and localStorage for redundancy
document.cookie = `Authorization=${data.token}; path=/; Secure; SameSite=Strict; max-age=604800`; document.cookie = `Authorization=${data.token}; path=/; Secure; SameSite=Strict; max-age=10800`;
localStorage.setItem("Authorization", data.token); localStorage.setItem("Authorization", data.token);
// Show success notification // Show success notification

View File

@@ -361,8 +361,6 @@ export default function OrderDetailsPage() {
const productNamesMap = await fetchProductNames(productIds, authToken); const productNamesMap = await fetchProductNames(productIds, authToken);
setProductNames(productNamesMap); setProductNames(productNamesMap);
// Add a short timeout to ensure any products still showing as loading
// are marked as deleted/unknown
setTimeout(() => { setTimeout(() => {
setProductNames(prev => { setProductNames(prev => {
const newMap = {...prev}; const newMap = {...prev};
@@ -373,7 +371,7 @@ export default function OrderDetailsPage() {
}); });
return newMap; return newMap;
}); });
}, 3000); // 3 second timeout }, 3000);
if (data.status === "paid") { if (data.status === "paid") {
setIsPaid(true); setIsPaid(true);

View File

@@ -22,6 +22,7 @@ import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui/use-toast'; import { toast } from '@/components/ui/use-toast';
import { Promotion, PromotionFormData } from '@/lib/types/promotion'; import { Promotion, PromotionFormData } from '@/lib/types/promotion';
import { fetchClient } from '@/lib/api'; import { fetchClient } from '@/lib/api';
import ProductSelector from './ProductSelector';
// Form schema validation with Zod (same as NewPromotionForm) // Form schema validation with Zod (same as NewPromotionForm)
const formSchema = z.object({ const formSchema = z.object({
@@ -47,6 +48,9 @@ const formSchema = z.object({
startDate: z.string().optional(), startDate: z.string().optional(),
endDate: z.string().nullable().optional(), endDate: z.string().nullable().optional(),
description: z.string().max(200, 'Description cannot exceed 200 characters').optional(), description: z.string().max(200, 'Description cannot exceed 200 characters').optional(),
blacklistedProducts: z.array(z.string()).default([]),
applicableProducts: z.enum(['all', 'specific', 'exclude_specific']).default('all'),
specificProducts: z.array(z.string()).default([]),
}); });
interface EditPromotionFormProps { interface EditPromotionFormProps {
@@ -78,6 +82,9 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
description: promotion.description, description: promotion.description,
startDate: formatDateForInput(promotion.startDate), startDate: formatDateForInput(promotion.startDate),
endDate: formatDateForInput(promotion.endDate), endDate: formatDateForInput(promotion.endDate),
blacklistedProducts: promotion.blacklistedProducts || [],
applicableProducts: promotion.applicableProducts || 'all',
specificProducts: promotion.specificProducts || [],
}, },
}); });
@@ -308,6 +315,110 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
)} )}
/> />
{/* Product Applicability Section */}
<div className="space-y-4 border rounded-lg p-4">
<h3 className="text-lg font-semibold">Product Applicability</h3>
<FormField
control={form.control}
name="applicableProducts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">Apply Promotion To</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-col space-y-2"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="all" id="edit-all-products" />
<label htmlFor="edit-all-products" className="cursor-pointer">All products</label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="specific" id="edit-specific-products" />
<label htmlFor="edit-specific-products" className="cursor-pointer">Only specific products</label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="exclude_specific" id="edit-exclude-products" />
<label htmlFor="edit-exclude-products" className="cursor-pointer">All products except specific ones</label>
</div>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Show blacklist selector for "all" and "exclude_specific" modes */}
{(form.watch('applicableProducts') === 'all' || form.watch('applicableProducts') === 'exclude_specific') && (
<FormField
control={form.control}
name="blacklistedProducts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
{form.watch('applicableProducts') === 'all'
? 'Exclude Products (Blacklist)'
: 'Products to Exclude'}
</FormLabel>
<FormControl>
<ProductSelector
selectedProductIds={field.value}
onSelectionChange={field.onChange}
placeholder={
form.watch('applicableProducts') === 'all'
? "Select products to exclude from this promotion..."
: "Select additional products to exclude..."
}
/>
</FormControl>
<FormDescription className="text-xs">
{form.watch('applicableProducts') === 'all'
? 'Select products that should not be eligible for this promotion'
: 'Select products to exclude in addition to those selected above'}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
{/* Show specific products selector for "specific" and "exclude_specific" modes */}
{(form.watch('applicableProducts') === 'specific' || form.watch('applicableProducts') === 'exclude_specific') && (
<FormField
control={form.control}
name="specificProducts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
{form.watch('applicableProducts') === 'specific'
? 'Select Specific Products'
: 'Products to Exclude'}
</FormLabel>
<FormControl>
<ProductSelector
selectedProductIds={field.value}
onSelectionChange={field.onChange}
placeholder={
form.watch('applicableProducts') === 'specific'
? "Select products eligible for this promotion..."
: "Select products to exclude..."
}
/>
</FormControl>
<FormDescription className="text-xs">
{form.watch('applicableProducts') === 'specific'
? 'Only selected products will be eligible for this promotion'
: 'Selected products will be excluded from this promotion'}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
<FormField <FormField
control={form.control} control={form.control}
name="description" name="description"

View File

@@ -23,6 +23,7 @@ import { toast } from '@/components/ui/use-toast';
import { PromotionFormData } from '@/lib/types/promotion'; import { PromotionFormData } from '@/lib/types/promotion';
import { fetchClient } from '@/lib/api'; import { fetchClient } from '@/lib/api';
import { DatePicker } from '@/components/ui/date-picker'; import { DatePicker } from '@/components/ui/date-picker';
import ProductSelector from './ProductSelector';
// Form schema validation with Zod // Form schema validation with Zod
const formSchema = z.object({ const formSchema = z.object({
@@ -48,6 +49,9 @@ const formSchema = z.object({
startDate: z.string().optional(), startDate: z.string().optional(),
endDate: z.string().nullable().optional(), endDate: z.string().nullable().optional(),
description: z.string().max(200, 'Description cannot exceed 200 characters').optional(), description: z.string().max(200, 'Description cannot exceed 200 characters').optional(),
blacklistedProducts: z.array(z.string()).default([]),
applicableProducts: z.enum(['all', 'specific', 'exclude_specific']).default('all'),
specificProducts: z.array(z.string()).default([]),
}); });
interface NewPromotionFormProps { interface NewPromotionFormProps {
@@ -72,6 +76,9 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
description: '', description: '',
startDate: new Date().toISOString().split('T')[0], // Today startDate: new Date().toISOString().split('T')[0], // Today
endDate: null, endDate: null,
blacklistedProducts: [],
applicableProducts: 'all',
specificProducts: [],
}, },
}); });
@@ -303,6 +310,110 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
)} )}
/> />
{/* Product Applicability Section */}
<div className="space-y-4 border rounded-lg p-4">
<h3 className="text-lg font-semibold">Product Applicability</h3>
<FormField
control={form.control}
name="applicableProducts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">Apply Promotion To</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-col space-y-2"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="all" id="all-products" />
<label htmlFor="all-products" className="cursor-pointer">All products</label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="specific" id="specific-products" />
<label htmlFor="specific-products" className="cursor-pointer">Only specific products</label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="exclude_specific" id="exclude-products" />
<label htmlFor="exclude-products" className="cursor-pointer">All products except specific ones</label>
</div>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Show blacklist selector for "all" and "exclude_specific" modes */}
{(form.watch('applicableProducts') === 'all' || form.watch('applicableProducts') === 'exclude_specific') && (
<FormField
control={form.control}
name="blacklistedProducts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
{form.watch('applicableProducts') === 'all'
? 'Exclude Products (Blacklist)'
: 'Products to Exclude'}
</FormLabel>
<FormControl>
<ProductSelector
selectedProductIds={field.value}
onSelectionChange={field.onChange}
placeholder={
form.watch('applicableProducts') === 'all'
? "Select products to exclude from this promotion..."
: "Select additional products to exclude..."
}
/>
</FormControl>
<FormDescription className="text-xs">
{form.watch('applicableProducts') === 'all'
? 'Select products that should not be eligible for this promotion'
: 'Select products to exclude in addition to those selected above'}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
{/* Show specific products selector for "specific" and "exclude_specific" modes */}
{(form.watch('applicableProducts') === 'specific' || form.watch('applicableProducts') === 'exclude_specific') && (
<FormField
control={form.control}
name="specificProducts"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
{form.watch('applicableProducts') === 'specific'
? 'Select Specific Products'
: 'Products to Exclude'}
</FormLabel>
<FormControl>
<ProductSelector
selectedProductIds={field.value}
onSelectionChange={field.onChange}
placeholder={
form.watch('applicableProducts') === 'specific'
? "Select products eligible for this promotion..."
: "Select products to exclude..."
}
/>
</FormControl>
<FormDescription className="text-xs">
{form.watch('applicableProducts') === 'specific'
? 'Only selected products will be eligible for this promotion'
: 'Selected products will be excluded from this promotion'}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
<FormField <FormField
control={form.control} control={form.control}
name="description" name="description"

View File

@@ -0,0 +1,243 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import { Check, ChevronDown, X, Search } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Product } from '@/lib/types/promotion';
import { fetchClient } from '@/lib/api';
interface ProductSelectorProps {
selectedProductIds: string[];
onSelectionChange: (productIds: string[]) => void;
placeholder?: string;
maxHeight?: string;
}
export default function ProductSelector({
selectedProductIds,
onSelectionChange,
placeholder = "Select products...",
maxHeight = "200px"
}: ProductSelectorProps) {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const triggerRef = useRef<HTMLButtonElement>(null);
// Handle escape key to close dropdown
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && open) {
setOpen(false);
}
};
if (open) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [open]);
// Fetch products when component mounts
useEffect(() => {
const fetchProducts = async () => {
setLoading(true);
try {
const response = await fetchClient('/promotions/products/all');
setProducts(response);
} catch (error) {
console.error('Error fetching products:', error);
} finally {
setLoading(false);
}
};
fetchProducts();
}, []);
// Filter products based on search term
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
product.description?.toLowerCase().includes(searchTerm.toLowerCase())
);
// Get selected products for display
const selectedProducts = products.filter(product =>
selectedProductIds.includes(product._id)
);
const handleProductToggle = (productId: string) => {
const updatedSelection = selectedProductIds.includes(productId)
? selectedProductIds.filter(id => id !== productId)
: [...selectedProductIds, productId];
onSelectionChange(updatedSelection);
};
const handleRemoveProduct = (productId: string) => {
onSelectionChange(selectedProductIds.filter(id => id !== productId));
};
const clearAll = () => {
onSelectionChange([]);
};
return (
<div className="space-y-2">
<div className="relative">
<Button
ref={triggerRef}
type="button"
variant="outline"
role="combobox"
aria-expanded={open}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setOpen(!open);
}}
className="w-full justify-between h-auto min-h-[40px] p-3"
>
<div className="flex items-center gap-2 flex-1">
{selectedProducts.length === 0 ? (
<span className="text-muted-foreground">{placeholder}</span>
) : (
<span className="text-sm">
{selectedProducts.length} product{selectedProducts.length !== 1 ? 's' : ''} selected
</span>
)}
</div>
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
{/* Dropdown using absolute positioning within relative container */}
{open && (
<div
className="absolute top-full left-0 right-0 z-50 mt-1 bg-background border border-border rounded-md shadow-lg max-h-64 overflow-hidden"
style={{ minWidth: '300px' }}
>
{/* Search Header */}
<div className="flex items-center gap-2 p-3 border-b bg-muted/30">
<Search className="h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search products..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="border-0 bg-transparent p-0 h-auto focus-visible:ring-0 focus-visible:ring-offset-0"
autoFocus
/>
</div>
{/* Products List */}
<ScrollArea className="max-h-48">
{loading ? (
<div className="p-4 text-center text-sm text-muted-foreground">
Loading products...
</div>
) : filteredProducts.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
{searchTerm ? 'No products found matching your search.' : 'No products found.'}
</div>
) : (
<div className="p-1">
{filteredProducts.map((product) => (
<div
key={product._id}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleProductToggle(product._id);
}}
className="flex items-center gap-3 p-2 hover:bg-accent rounded-sm cursor-pointer transition-colors"
>
<div className={`h-4 w-4 border rounded-sm flex items-center justify-center ${
selectedProductIds.includes(product._id)
? 'bg-primary border-primary text-primary-foreground'
: 'border-input'
}`}>
{selectedProductIds.includes(product._id) && (
<Check className="h-3 w-3" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium text-sm">{product.name}</div>
{product.description && (
<div className="text-xs text-muted-foreground truncate">
{product.description}
</div>
)}
</div>
{!product.enabled && (
<Badge variant="secondary" className="text-xs">
Disabled
</Badge>
)}
</div>
))}
</div>
)}
</ScrollArea>
</div>
)}
{/* Backdrop to close dropdown */}
{open && (
<div
className="fixed inset-0 z-40"
onClick={() => setOpen(false)}
/>
)}
</div>
{/* Selected products display */}
{selectedProducts.length > 0 && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Selected Products:</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
clearAll();
}}
className="h-6 px-2 text-xs"
>
Clear All
</Button>
</div>
<div className="flex flex-wrap gap-2">
{selectedProducts.map((product) => (
<Badge
key={product._id}
variant="secondary"
className="flex items-center gap-1 pr-1"
>
<span className="text-xs">{product.name}</span>
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleRemoveProduct(product._id);
}}
className="ml-1 hover:bg-secondary-foreground/20 rounded-full p-0.5"
>
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -4,82 +4,108 @@ export interface Quote {
} }
export const businessQuotes: Quote[] = [ export const businessQuotes: Quote[] = [
// Steve Jobs quotes { text: 'Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work.', author: 'Steve Jobs' },
{ text: "Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work.", author: "Steve Jobs" }, { text: 'Innovation distinguishes between a leader and a follower.', author: 'Steve Jobs' },
{ text: "Innovation distinguishes between a leader and a follower.", author: "Steve Jobs" }, { text: 'If you really look closely, most overnight successes took a long time.', author: 'Steve Jobs' },
{ text: "If you really look closely, most overnight successes took a long time.", author: "Steve Jobs" }, { text: 'The only way to do great work is to love what you do.', author: 'Steve Jobs' },
{ text: 'Get closer than ever to your customers. So close that you tell them what they need well before they realize it themselves.', author: 'Steve Jobs' },
// Entrepreneurs and CEOs { text: 'Innovation is the ability to see change as an opportunity not a threat.', author: 'Steve Jobs' },
{ text: "Your most unhappy customers are your greatest source of learning.", author: "Bill Gates" }, { text: 'Simple can be harder than complex: You have to work hard to get your thinking clean to make it simple.', author: 'Steve Jobs' },
{ text: "The way to get started is to quit talking and begin doing.", author: "Walt Disney" }, { text: 'Your most unhappy customers are your greatest source of learning.', author: 'Bill Gates' },
{ text: "Opportunities don't happen. You create them.", author: "Chris Grosser" }, { text: 'As we look ahead into the next century, leaders will be those who empower others.', author: 'Bill Gates' },
{ text: "The best way to predict the future is to create it.", author: "Peter Drucker" }, { text: 'The way to get started is to quit talking and begin doing.', author: 'Walt Disney' },
{ text: "If you are not willing to risk the usual, you will have to settle for the ordinary.", author: "Jim Rohn" }, { text: 'Chase the vision, not the money; the money will end up following you.', author: 'Tony Hsieh' },
{ text: "Chase the vision, not the money; the money will end up following you.", author: "Tony Hsieh" }, { text: "Opportunities don't happen. You create them.", author: 'Chris Grosser' },
{ text: "It's not about ideas. It's about making ideas happen.", author: "Scott Belsky" }, { text: 'The best way to predict the future is to create it.', author: 'Peter Drucker' },
{ text: "If you do build a great experience, customers tell each other about that. Word of mouth is very powerful.", author: "Jeff Bezos" }, { text: 'Culture eats strategy for breakfast.', author: 'Peter Drucker' },
{ text: 'Entrepreneurship is neither a science nor an art. It is a practice.', author: 'Peter Drucker' },
// Persistence and growth { text: 'If you are not willing to risk the usual, you will have to settle for the ordinary.', author: 'Jim Rohn' },
{ text: "The secret of getting ahead is getting started.", author: "Mark Twain" }, { text: "It's not about ideas. It's about making ideas happen.", author: 'Scott Belsky' },
{ text: "Success is not final; failure is not fatal: It is the courage to continue that counts.", author: "Winston Churchill" }, { text: 'If you do build a great experience, customers tell each other about that. Word of mouth is very powerful.', author: 'Jeff Bezos' },
{ text: "Don't watch the clock; do what it does. Keep going.", author: "Sam Levenson" }, { text: 'Work like hell. I mean you just have to put in 80 to 100 hour weeks every week.', author: 'Elon Musk' },
{ text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt" }, { text: 'When something is important enough, you do it even if the odds are not in your favor.', author: 'Elon Musk' },
{ text: "If you can't fly, then run. If you can't run, then walk. If you can't walk, then crawl. But whatever you do, you have to keep moving forward.", author: "Martin Luther King Jr." }, { text: 'The secret of getting ahead is getting started.', author: 'Mark Twain' },
{ text: 'Success is not final; failure is not fatal: It is the courage to continue that counts.', author: 'Winston Churchill' },
// Risk and innovation { text: 'Success is walking from failure to failure with no loss of enthusiasm.', author: 'Winston Churchill' },
{ text: "The biggest risk is not taking any risk. In a world that's changing quickly, the only strategy that is guaranteed to fail is not taking risks.", author: "Mark Zuckerberg" }, { text: 'Dont watch the clock; do what it does. Keep going.', author: 'Sam Levenson' },
{ text: "I have not failed. I've just found 10,000 ways that won't work.", author: "Thomas Edison" }, { text: 'The future belongs to those who believe in the beauty of their dreams.', author: 'Eleanor Roosevelt' },
{ text: "What would you do if you weren't afraid?", author: "Sheryl Sandberg" }, { text: "Believe you can and you're halfway there.", author: 'Theodore Roosevelt' },
{ text: "When everything seems to be going against you, remember that the airplane takes off against the wind, not with it.", author: "Henry Ford" }, { text: "If you can't fly, then run. If you can't run, then walk. If you can't walk, then crawl. But whatever you do, you have to keep moving forward.", author: 'Martin Luther King Jr.' },
{ text: "If you're not embarrassed by the first version of your product, you've launched too late.", author: "Reid Hoffman" }, { text: 'Strength and growth come only through continuous effort and struggle.', author: 'Napoleon Hill' },
{ text: 'The biggest risk is not taking any risk.', author: 'Mark Zuckerberg' },
// Quality and execution { text: 'Move fast and break things. Unless you are breaking stuff, you are not moving fast enough.', author: 'Mark Zuckerberg' },
{ text: "The only place where success comes before work is in the dictionary.", author: "Vidal Sassoon" }, { text: "I have not failed. I've just found 10,000 ways that won't work.", author: 'Thomas Edison' },
{ text: "Make every detail perfect and limit the number of details to perfect.", author: "Jack Dorsey" }, { text: 'Vision without execution is hallucination.', author: 'Thomas Edison' },
{ text: "There's no shortage of remarkable ideas, what's missing is the will to execute them.", author: "Seth Godin" }, { text: "What would you do if you weren't afraid?", author: 'Sheryl Sandberg' },
{ text: "Always deliver more than expected.", author: "Larry Page" }, { text: 'Done is better than perfect.', author: 'Sheryl Sandberg' },
{ text: "Your reputation is more important than your paycheck, and your integrity is worth more than your career.", author: "Ryan Freitas" }, { text: 'When everything seems to be going against you, remember that the airplane takes off against the wind, not with it.', author: 'Henry Ford' },
{ text: 'Whether you think you can or you think you cant, youre right.', author: 'Henry Ford' },
// Teamwork and determination { text: "If you're not embarrassed by the first version of your product, you've launched too late.", author: 'Reid Hoffman' },
{ text: "No matter how brilliant your mind or strategy, if you're playing a solo game, you'll always lose out to a team.", author: "Reid Hoffman" }, { text: 'The only place where success comes before work is in the dictionary.', author: 'Vidal Sassoon' },
{ text: "If you want to achieve greatness stop asking for permission.", author: "Anonymous" }, { text: 'Make every detail perfect and limit the number of details to perfect.', author: 'Jack Dorsey' },
{ text: "Things work out best for those who make the best of how things work out.", author: "John Wooden" }, { text: "There's no shortage of remarkable ideas, what's missing is the will to execute them.", author: 'Seth Godin' },
{ text: "The most valuable businesses of coming decades will be built by entrepreneurs who seek to empower people rather than try to make them obsolete.", author: "Peter Thiel" }, { text: 'Always deliver more than expected.', author: 'Larry Page' },
{ text: 'Your reputation is more important than your paycheck.', author: 'Ryan Freitas' },
// Additional quotes - Vision and leadership { text: 'Ideas are easy. Implementation is hard.', author: 'Guy Kawasaki' },
{ text: "Leadership is the capacity to translate vision into reality.", author: "Warren Bennis" }, { text: 'Action is the foundational key to all success.', author: 'Pablo Picasso' },
{ text: "A goal without a plan is just a wish.", author: "Antoine de Saint-Exupéry" }, { text: 'Success usually comes to those who are too busy to be looking for it.', author: 'Henry David Thoreau' },
{ text: "Good business leaders create a vision, articulate the vision, passionately own the vision, and relentlessly drive it to completion.", author: "Jack Welch" }, { text: "No matter how brilliant your mind or strategy, if you're playing a solo game, you'll always lose out to a team.", author: 'Reid Hoffman' },
{ text: 'A leader is one who knows the way, goes the way, and shows the way.', author: 'John C. Maxwell' },
// Additional quotes - Risk, failure, and learning { text: 'Leadership is the capacity to translate vision into reality.', author: 'Warren Bennis' },
{ text: "Fail often so you can succeed sooner.", author: "Tom Kelley" }, { text: 'Good business leaders create a vision, articulate the vision, passionately own the vision, and relentlessly drive it to completion.', author: 'Jack Welch' },
{ text: "Dont worry about failure; you only have to be right once.", author: "Drew Houston" }, { text: 'The function of leadership is to produce more leaders, not more followers.', author: 'Ralph Nader' },
{ text: "In the middle of difficulty lies opportunity.", author: "Albert Einstein" }, { text: 'A goal without a plan is just a wish.', author: 'Antoine de Saint-Exupéry' },
{ text: "Risk more than others think is safe. Dream more than others think is practical.", author: "Howard Schultz" }, { text: 'Be so good they cant ignore you.', author: 'Steve Martin' },
{ text: 'Play long-term games with long-term people.', author: 'Naval Ravikant' },
// Additional quotes - Action and hustle { text: 'Make something people want and sell that.', author: 'Paul Graham' },
{ text: "Ideas are easy. Implementation is hard.", author: "Guy Kawasaki" }, { text: 'Its not whether you get knocked down, its whether you get up.', author: 'Vince Lombardi' },
{ text: "Success usually comes to those who are too busy to be looking for it.", author: "Henry David Thoreau" }, { text: 'Hardships often prepare ordinary people for an extraordinary destiny.', author: 'C.S. Lewis' },
{ text: "Done is better than perfect.", author: "Sheryl Sandberg" }, { text: 'Resilience is knowing that you are the only one that has the power and the responsibility to pick yourself up.', author: 'Mary Holloway' },
{ text: "Action is the foundational key to all success.", author: "Pablo Picasso" }, { text: 'Success is how high you bounce when you hit bottom.', author: 'George S. Patton' },
{ text: "People don't buy what you do; they buy why you do it.", author: 'Simon Sinek' },
// Additional quotes - Customer and product { text: 'The customer is the most important part of the production line.', author: 'W. Edwards Deming' },
{ text: "People don't buy what you do; they buy why you do it.", author: "Simon Sinek" }, { text: "Customers may forget what you said but they'll never forget how you made them feel.", author: 'Maya Angelou' },
{ text: "The customer is the most important part of the production line.", author: "W. Edwards Deming" }, { text: 'The true entrepreneur is a doer, not a dreamer.', author: 'Nolan Bushnell' },
{ text: "Make something people want and sell that, or be someone people need and sell yourself.", author: "Naval Ravikant" }, { text: 'Entrepreneurs are willing to work 80 hours a week to avoid working 40 hours a week.', author: 'Lori Greiner' },
{ text: 'Build something 100 people love, not something 1 million people kind of like.', author: 'Brian Chesky' },
// Additional quotes - Resilience and mindset { text: 'Risk more than others think is safe. Dream more than others think is practical.', author: 'Howard Schultz' },
{ text: "Success is walking from failure to failure with no loss of enthusiasm.", author: "Winston Churchill" }, { text: 'Fail often so you can succeed sooner.', author: 'Tom Kelley' },
{ text: "Whether you think you can or you think you cant, youre right.", author: "Henry Ford" }, { text: 'Dont worry about failure; you only have to be right once.', author: 'Drew Houston' },
{ text: "Strength and growth come only through continuous effort and struggle.", author: "Napoleon Hill" }, { text: 'Try not to become a man of success, but rather try to become a man of value.', author: 'Albert Einstein' },
{ text: "Believe you can and you're halfway there.", author: "Theodore Roosevelt" }, { text: "Compound interest is the eighth wonder of the world. He who understands it, earns it; he who doesn't, pays it.", author: 'Albert Einstein' },
{ text: 'In the middle of difficulty lies opportunity.', author: 'Albert Einstein' },
// Additional quotes - Money and value { text: 'Price is what you pay. Value is what you get.', author: 'Warren Buffett' },
{ text: "Try not to become a man of success, but rather try to become a man of value.", author: "Albert Einstein" }, { text: 'The best way to predict the future is to invent it.', author: 'Alan Kay' },
{ text: "Price is what you pay. Value is what you get.", author: "Warren Buffett" }, { text: "Strategy is about making choices, trade-offs; it's about deliberately choosing to be different.", author: 'Michael Porter' },
{ text: 'Growth and comfort do not coexist.', author: 'Ginni Rometty' },
{ text: 'Disruption is about risk-taking, trusting your intuition, and rejecting the way things are supposed to be.', author: 'Whitney Wolfe Herd' },
{ text: "Don't be afraid to give up the good to go for the great.", author: 'John D. Rockefeller' },
{ text: 'A goal is a dream with a deadline.', author: 'Napoleon Hill' },
{ text: 'The man who moves a mountain begins by carrying away small stones.', author: 'Confucius' },
{ text: 'Every strike brings me closer to the next home run.', author: 'Babe Ruth' },
{ text: 'Do not be embarrassed by your failures, learn from them and start again.', author: 'Richard Branson' },
{ text: "Business opportunities are like buses, there's always another one coming.", author: 'Richard Branson' },
{ text: 'The most dangerous poison is the feeling of achievement. The antidote is to every evening think what can be done better tomorrow.', author: 'Ingvar Kamprad' },
{ text: 'Dont count the days, make the days count.', author: 'Muhammad Ali' },
{ text: 'To win big, you sometimes have to take big risks.', author: 'Bill Gates' },
{ text: "If everything seems under control, you're not going fast enough.", author: 'Mario Andretti' },
{ text: "If you really want to do something, you'll find a way. If you don't, you'll find an excuse.", author: 'Jim Rohn' },
{ text: 'One day or day one. You decide.', author: 'Anonymous' },
{ text: 'Dont be busy. Be productive.', author: 'Anonymous' },
{ text: 'Success isnt owned, its leased. And rent is due every day.', author: 'J.J. Watt' },
{ text: 'Never give up on a dream just because of the time it will take to accomplish it. The time will pass anyway.', author: 'Earl Nightingale' },
{ text: "If you don't build your dream, someone else will hire you to help them build theirs.", author: 'Tony Gaskins' },
{ text: 'The way to get ahead is to start now. The secret is to get started.', author: 'Mark Twain' },
{ text: 'Success is not in what you have, but who you are.', author: 'Bo Bennett' },
{ text: "It's fine to celebrate success but it is more important to heed the lessons of failure.", author: 'Bill Gates' },
{ text: 'Success is getting what you want. Happiness is wanting what you get.', author: 'Dale Carnegie' },
{ text: 'Work until you no longer have to introduce yourself.', author: 'Anonymous' },
{ text: 'Dont limit your challenges. Challenge your limits.', author: 'Jerry Dunn' },
{ text: "Don't settle. Don't finish crappy books. If you don't like the menu, leave the restaurant. If you're not on the right path, get off it.", author: 'Chris Brogan' },
{ text: 'A person who never made a mistake never tried anything new.', author: 'Albert Einstein' },
{ text: 'Business has only two functions — marketing and innovation.', author: 'Peter Drucker' },
]; ];
// For backward compatibility with existing code
export const quotes = businessQuotes; export const quotes = businessQuotes;
export default businessQuotes; export default businessQuotes;
@@ -99,11 +125,7 @@ export function getRandomQuoteByAuthor(author: string): Quote {
const authorQuotes = businessQuotes.filter(quote => const authorQuotes = businessQuotes.filter(quote =>
quote.author.toLowerCase() === author.toLowerCase() quote.author.toLowerCase() === author.toLowerCase()
); );
if (authorQuotes.length === 0) return getRandomQuote();
if (authorQuotes.length === 0) {
return getRandomQuote();
}
const randomIndex = Math.floor(Math.random() * authorQuotes.length); const randomIndex = Math.floor(Math.random() * authorQuotes.length);
return authorQuotes[randomIndex]; return authorQuotes[randomIndex];
} }
@@ -115,4 +137,4 @@ export function getQuotesByTheme(keyword: string): Quote[] {
return businessQuotes.filter(quote => return businessQuotes.filter(quote =>
quote.text.toLowerCase().includes(keyword.toLowerCase()) quote.text.toLowerCase().includes(keyword.toLowerCase())
); );
} }

View File

@@ -11,6 +11,9 @@ export interface Promotion {
startDate: string; startDate: string;
endDate: string | null; endDate: string | null;
description: string; description: string;
blacklistedProducts: string[];
applicableProducts: 'all' | 'specific' | 'exclude_specific';
specificProducts: string[];
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
@@ -25,4 +28,14 @@ export interface PromotionFormData {
startDate: string; startDate: string;
endDate: string | null; endDate: string | null;
description: string; description: string;
blacklistedProducts: string[];
applicableProducts: 'all' | 'specific' | 'exclude_specific';
specificProducts: string[];
}
export interface Product {
_id: string;
name: string;
description: string;
enabled: boolean;
} }