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:
@@ -31,8 +31,8 @@ export async function POST(req: NextRequest) {
|
||||
const cookieOptions = {
|
||||
// HttpOnly for security - prevents JavaScript access
|
||||
httpOnly: true,
|
||||
// Valid for 7 days (same as the JWT)
|
||||
maxAge: 7 * 24 * 60 * 60,
|
||||
// Valid for 3 hours
|
||||
maxAge: 3 * 60 * 60,
|
||||
// Only send in requests to our domain
|
||||
path: '/',
|
||||
// Strict same-site policy to prevent CSRF
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function LoginForm() {
|
||||
|
||||
if (response.ok && data.token) {
|
||||
// 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);
|
||||
|
||||
// Show success notification
|
||||
|
||||
@@ -361,8 +361,6 @@ export default function OrderDetailsPage() {
|
||||
const productNamesMap = await fetchProductNames(productIds, authToken);
|
||||
setProductNames(productNamesMap);
|
||||
|
||||
// Add a short timeout to ensure any products still showing as loading
|
||||
// are marked as deleted/unknown
|
||||
setTimeout(() => {
|
||||
setProductNames(prev => {
|
||||
const newMap = {...prev};
|
||||
@@ -373,7 +371,7 @@ export default function OrderDetailsPage() {
|
||||
});
|
||||
return newMap;
|
||||
});
|
||||
}, 3000); // 3 second timeout
|
||||
}, 3000);
|
||||
|
||||
if (data.status === "paid") {
|
||||
setIsPaid(true);
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { Promotion, PromotionFormData } from '@/lib/types/promotion';
|
||||
import { fetchClient } from '@/lib/api';
|
||||
import ProductSelector from './ProductSelector';
|
||||
|
||||
// Form schema validation with Zod (same as NewPromotionForm)
|
||||
const formSchema = z.object({
|
||||
@@ -47,6 +48,9 @@ const formSchema = z.object({
|
||||
startDate: z.string().optional(),
|
||||
endDate: z.string().nullable().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 {
|
||||
@@ -78,6 +82,9 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
|
||||
description: promotion.description,
|
||||
startDate: formatDateForInput(promotion.startDate),
|
||||
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
|
||||
control={form.control}
|
||||
name="description"
|
||||
|
||||
@@ -23,6 +23,7 @@ import { toast } from '@/components/ui/use-toast';
|
||||
import { PromotionFormData } from '@/lib/types/promotion';
|
||||
import { fetchClient } from '@/lib/api';
|
||||
import { DatePicker } from '@/components/ui/date-picker';
|
||||
import ProductSelector from './ProductSelector';
|
||||
|
||||
// Form schema validation with Zod
|
||||
const formSchema = z.object({
|
||||
@@ -48,6 +49,9 @@ const formSchema = z.object({
|
||||
startDate: z.string().optional(),
|
||||
endDate: z.string().nullable().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 {
|
||||
@@ -72,6 +76,9 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
|
||||
description: '',
|
||||
startDate: new Date().toISOString().split('T')[0], // Today
|
||||
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
|
||||
control={form.control}
|
||||
name="description"
|
||||
|
||||
243
components/dashboard/promotions/ProductSelector.tsx
Normal file
243
components/dashboard/promotions/ProductSelector.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
180
config/quotes.ts
180
config/quotes.ts
@@ -4,82 +4,108 @@ export interface 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: "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" },
|
||||
|
||||
// Entrepreneurs and CEOs
|
||||
{ text: "Your most unhappy customers are your greatest source of learning.", author: "Bill Gates" },
|
||||
{ text: "The way to get started is to quit talking and begin doing.", author: "Walt Disney" },
|
||||
{ text: "Opportunities don't happen. You create them.", author: "Chris Grosser" },
|
||||
{ text: "The best way to predict the future is to create it.", author: "Peter Drucker" },
|
||||
{ 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: "It's not about ideas. It's about making ideas happen.", author: "Scott Belsky" },
|
||||
{ text: "If you do build a great experience, customers tell each other about that. Word of mouth is very powerful.", author: "Jeff Bezos" },
|
||||
|
||||
// Persistence and growth
|
||||
{ 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" },
|
||||
{ text: "Don't watch the clock; do what it does. Keep going.", author: "Sam Levenson" },
|
||||
{ text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt" },
|
||||
{ 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." },
|
||||
|
||||
// Risk and innovation
|
||||
{ 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: "I have not failed. I've just found 10,000 ways that won't work.", author: "Thomas Edison" },
|
||||
{ text: "What would you do if you weren't afraid?", author: "Sheryl Sandberg" },
|
||||
{ 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're not embarrassed by the first version of your product, you've launched too late.", author: "Reid Hoffman" },
|
||||
|
||||
// Quality and execution
|
||||
{ text: "The only place where success comes before work is in the dictionary.", author: "Vidal Sassoon" },
|
||||
{ text: "Make every detail perfect and limit the number of details to perfect.", author: "Jack Dorsey" },
|
||||
{ text: "There's no shortage of remarkable ideas, what's missing is the will to execute them.", author: "Seth Godin" },
|
||||
{ text: "Always deliver more than expected.", author: "Larry Page" },
|
||||
{ text: "Your reputation is more important than your paycheck, and your integrity is worth more than your career.", author: "Ryan Freitas" },
|
||||
|
||||
// Teamwork and determination
|
||||
{ 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: "If you want to achieve greatness stop asking for permission.", author: "Anonymous" },
|
||||
{ text: "Things work out best for those who make the best of how things work out.", author: "John Wooden" },
|
||||
{ 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" },
|
||||
|
||||
// Additional quotes - Vision and leadership
|
||||
{ text: "Leadership is the capacity to translate vision into reality.", author: "Warren Bennis" },
|
||||
{ text: "A goal without a plan is just a wish.", author: "Antoine de Saint-Exupéry" },
|
||||
{ text: "Good business leaders create a vision, articulate the vision, passionately own the vision, and relentlessly drive it to completion.", author: "Jack Welch" },
|
||||
|
||||
// Additional quotes - Risk, failure, and learning
|
||||
{ text: "Fail often so you can succeed sooner.", author: "Tom Kelley" },
|
||||
{ text: "Don’t worry about failure; you only have to be right once.", author: "Drew Houston" },
|
||||
{ text: "In the middle of difficulty lies opportunity.", author: "Albert Einstein" },
|
||||
{ text: "Risk more than others think is safe. Dream more than others think is practical.", author: "Howard Schultz" },
|
||||
|
||||
// Additional quotes - Action and hustle
|
||||
{ text: "Ideas are easy. Implementation is hard.", author: "Guy Kawasaki" },
|
||||
{ text: "Success usually comes to those who are too busy to be looking for it.", author: "Henry David Thoreau" },
|
||||
{ text: "Done is better than perfect.", author: "Sheryl Sandberg" },
|
||||
{ text: "Action is the foundational key to all success.", author: "Pablo Picasso" },
|
||||
|
||||
// Additional quotes - Customer and product
|
||||
{ text: "People don't buy what you do; they buy why you do it.", author: "Simon Sinek" },
|
||||
{ text: "The customer is the most important part of the production line.", author: "W. Edwards Deming" },
|
||||
{ text: "Make something people want and sell that, or be someone people need and sell yourself.", author: "Naval Ravikant" },
|
||||
|
||||
// Additional quotes - Resilience and mindset
|
||||
{ text: "Success is walking from failure to failure with no loss of enthusiasm.", author: "Winston Churchill" },
|
||||
{ text: "Whether you think you can or you think you can’t, you’re right.", author: "Henry Ford" },
|
||||
{ text: "Strength and growth come only through continuous effort and struggle.", author: "Napoleon Hill" },
|
||||
{ text: "Believe you can and you're halfway there.", author: "Theodore Roosevelt" },
|
||||
|
||||
// Additional quotes - Money and value
|
||||
{ text: "Try not to become a man of success, but rather try to become a man of value.", author: "Albert Einstein" },
|
||||
{ text: "Price is what you pay. Value is what you get.", author: "Warren Buffett" },
|
||||
{ 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: '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' },
|
||||
{ text: 'Innovation is the ability to see change as an opportunity – not a threat.', author: 'Steve Jobs' },
|
||||
{ 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: 'Your most unhappy customers are your greatest source of learning.', author: 'Bill Gates' },
|
||||
{ text: 'As we look ahead into the next century, leaders will be those who empower others.', author: 'Bill Gates' },
|
||||
{ text: 'The way to get started is to quit talking and begin doing.', author: 'Walt Disney' },
|
||||
{ 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: 'The best way to predict the future is to create it.', author: 'Peter Drucker' },
|
||||
{ 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' },
|
||||
{ text: 'If you are not willing to risk the usual, you will have to settle for the ordinary.', author: 'Jim Rohn' },
|
||||
{ text: "It's not about ideas. It's about making ideas happen.", author: 'Scott Belsky' },
|
||||
{ text: 'If you do build a great experience, customers tell each other about that. Word of mouth is very powerful.', author: 'Jeff Bezos' },
|
||||
{ text: 'Work like hell. I mean you just have to put in 80 to 100 hour weeks every week.', author: 'Elon Musk' },
|
||||
{ text: 'When something is important enough, you do it even if the odds are not in your favor.', author: 'Elon Musk' },
|
||||
{ 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' },
|
||||
{ text: 'Success is walking from failure to failure with no loss of enthusiasm.', author: 'Winston Churchill' },
|
||||
{ text: 'Don’t watch the clock; do what it does. Keep going.', author: 'Sam Levenson' },
|
||||
{ text: 'The future belongs to those who believe in the beauty of their dreams.', author: 'Eleanor Roosevelt' },
|
||||
{ text: "Believe you can and you're halfway there.", author: 'Theodore Roosevelt' },
|
||||
{ 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: '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' },
|
||||
{ text: 'Move fast and break things. Unless you are breaking stuff, you are not moving fast enough.', author: 'Mark Zuckerberg' },
|
||||
{ text: "I have not failed. I've just found 10,000 ways that won't work.", author: 'Thomas Edison' },
|
||||
{ text: 'Vision without execution is hallucination.', author: 'Thomas Edison' },
|
||||
{ text: "What would you do if you weren't afraid?", author: 'Sheryl Sandberg' },
|
||||
{ text: 'Done is better than perfect.', author: 'Sheryl Sandberg' },
|
||||
{ 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 can’t, you’re right.', author: 'Henry Ford' },
|
||||
{ text: "If you're not embarrassed by the first version of your product, you've launched too late.", author: 'Reid Hoffman' },
|
||||
{ text: 'The only place where success comes before work is in the dictionary.', author: 'Vidal Sassoon' },
|
||||
{ text: 'Make every detail perfect and limit the number of details to perfect.', author: 'Jack Dorsey' },
|
||||
{ text: "There's no shortage of remarkable ideas, what's missing is the will to execute them.", author: 'Seth Godin' },
|
||||
{ text: 'Always deliver more than expected.', author: 'Larry Page' },
|
||||
{ text: 'Your reputation is more important than your paycheck.', author: 'Ryan Freitas' },
|
||||
{ text: 'Ideas are easy. Implementation is hard.', author: 'Guy Kawasaki' },
|
||||
{ text: 'Action is the foundational key to all success.', author: 'Pablo Picasso' },
|
||||
{ text: 'Success usually comes to those who are too busy to be looking for it.', author: 'Henry David Thoreau' },
|
||||
{ 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' },
|
||||
{ text: 'Leadership is the capacity to translate vision into reality.', author: 'Warren Bennis' },
|
||||
{ text: 'Good business leaders create a vision, articulate the vision, passionately own the vision, and relentlessly drive it to completion.', author: 'Jack Welch' },
|
||||
{ text: 'The function of leadership is to produce more leaders, not more followers.', author: 'Ralph Nader' },
|
||||
{ text: 'A goal without a plan is just a wish.', author: 'Antoine de Saint-Exupéry' },
|
||||
{ text: 'Be so good they can’t ignore you.', author: 'Steve Martin' },
|
||||
{ text: 'Play long-term games with long-term people.', author: 'Naval Ravikant' },
|
||||
{ text: 'Make something people want and sell that.', author: 'Paul Graham' },
|
||||
{ text: 'It’s not whether you get knocked down, it’s whether you get up.', author: 'Vince Lombardi' },
|
||||
{ text: 'Hardships often prepare ordinary people for an extraordinary destiny.', author: 'C.S. Lewis' },
|
||||
{ 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: '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' },
|
||||
{ text: 'The customer is the most important part of the production line.', author: 'W. Edwards Deming' },
|
||||
{ text: "Customers may forget what you said but they'll never forget how you made them feel.", author: 'Maya Angelou' },
|
||||
{ text: 'The true entrepreneur is a doer, not a dreamer.', author: 'Nolan Bushnell' },
|
||||
{ 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' },
|
||||
{ text: 'Risk more than others think is safe. Dream more than others think is practical.', author: 'Howard Schultz' },
|
||||
{ text: 'Fail often so you can succeed sooner.', author: 'Tom Kelley' },
|
||||
{ text: 'Don’t worry about failure; you only have to be right once.', author: 'Drew Houston' },
|
||||
{ text: 'Try not to become a man of success, but rather try to become a man of value.', author: 'Albert Einstein' },
|
||||
{ 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' },
|
||||
{ text: 'Price is what you pay. Value is what you get.', author: 'Warren Buffett' },
|
||||
{ text: 'The best way to predict the future is to invent it.', author: 'Alan Kay' },
|
||||
{ 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: 'Don’t 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: 'Don’t be busy. Be productive.', author: 'Anonymous' },
|
||||
{ text: 'Success isn’t owned, it’s 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: 'Don’t 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 default businessQuotes;
|
||||
|
||||
@@ -99,11 +125,7 @@ export function getRandomQuoteByAuthor(author: string): Quote {
|
||||
const authorQuotes = businessQuotes.filter(quote =>
|
||||
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);
|
||||
return authorQuotes[randomIndex];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ export interface Promotion {
|
||||
startDate: string;
|
||||
endDate: string | null;
|
||||
description: string;
|
||||
blacklistedProducts: string[];
|
||||
applicableProducts: 'all' | 'specific' | 'exclude_specific';
|
||||
specificProducts: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -25,4 +28,14 @@ export interface PromotionFormData {
|
||||
startDate: string;
|
||||
endDate: string | null;
|
||||
description: string;
|
||||
blacklistedProducts: string[];
|
||||
applicableProducts: 'all' | 'specific' | 'exclude_specific';
|
||||
specificProducts: string[];
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user