diff --git a/package-lock.json b/package-lock.json index 05bd4c7..51a1253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.1.1", - "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.0", @@ -33,7 +33,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", @@ -55,7 +55,7 @@ "react-resizable-panels": "^2.1.3", "react-router-dom": "^6.28.0", "sonner": "^1.5.0", - "tailwind-merge": "^2.5.2", + "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "vaul": "^1.0.0", "zod": "^3.23.8" @@ -1545,11 +1545,12 @@ } }, "node_modules/@radix-ui/react-icons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", - "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x" + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "node_modules/@radix-ui/react-id": { @@ -2161,6 +2162,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==", + "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-collection": "1.1.0", @@ -3339,6 +3341,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5667,9 +5670,10 @@ } }, "node_modules/tailwind-merge": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.3.tgz", - "integrity": "sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", + "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/package.json b/package.json index 92c785d..e7254f2 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.1.1", - "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.0", @@ -35,7 +35,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", @@ -57,7 +57,7 @@ "react-resizable-panels": "^2.1.3", "react-router-dom": "^6.28.0", "sonner": "^1.5.0", - "tailwind-merge": "^2.5.2", + "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "vaul": "^1.0.0", "zod": "^3.23.8" diff --git a/public/images/success-stories/catherine-henry.jpg b/public/images/success-stories/catherine-henry.jpg new file mode 100644 index 0000000..f7bf7a3 Binary files /dev/null and b/public/images/success-stories/catherine-henry.jpg differ diff --git a/public/images/success-stories/edmund-fanny.jpg b/public/images/success-stories/edmund-fanny.jpg new file mode 100644 index 0000000..47e75c1 Binary files /dev/null and b/public/images/success-stories/edmund-fanny.jpg differ diff --git a/public/images/success-stories/elizabeth-darcy.jpg b/public/images/success-stories/elizabeth-darcy.jpg new file mode 100644 index 0000000..0a78fee Binary files /dev/null and b/public/images/success-stories/elizabeth-darcy.jpg differ diff --git a/public/images/success-stories/emma-knightley.jpg b/public/images/success-stories/emma-knightley.jpg new file mode 100644 index 0000000..d575dc5 Binary files /dev/null and b/public/images/success-stories/emma-knightley.jpg differ diff --git a/public/images/success-stories/marianne-brandon.jpg b/public/images/success-stories/marianne-brandon.jpg new file mode 100644 index 0000000..1582b3e Binary files /dev/null and b/public/images/success-stories/marianne-brandon.jpg differ diff --git a/public/images/vendors/catering.jpg b/public/images/vendors/catering.jpg new file mode 100644 index 0000000..769c760 Binary files /dev/null and b/public/images/vendors/catering.jpg differ diff --git a/public/images/vendors/flowers.jpg b/public/images/vendors/flowers.jpg new file mode 100644 index 0000000..cc6d863 Binary files /dev/null and b/public/images/vendors/flowers.jpg differ diff --git a/public/images/vendors/matchmaking.jpg b/public/images/vendors/matchmaking.jpg new file mode 100644 index 0000000..5cdd4f1 Binary files /dev/null and b/public/images/vendors/matchmaking.jpg differ diff --git a/public/images/vendors/meryton.jpg b/public/images/vendors/meryton.jpg new file mode 100644 index 0000000..8f99392 Binary files /dev/null and b/public/images/vendors/meryton.jpg differ diff --git a/public/images/vendors/modiste.jpg b/public/images/vendors/modiste.jpg new file mode 100644 index 0000000..ad96f19 Binary files /dev/null and b/public/images/vendors/modiste.jpg differ diff --git a/public/images/vendors/music.jpg b/public/images/vendors/music.jpg new file mode 100644 index 0000000..672102e Binary files /dev/null and b/public/images/vendors/music.jpg differ diff --git a/public/images/vendors/pemberley.jpg b/public/images/vendors/pemberley.jpg new file mode 100644 index 0000000..6ffec2b Binary files /dev/null and b/public/images/vendors/pemberley.jpg differ diff --git a/src/App.tsx b/src/App.tsx index 532cedb..bd3dd56 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,12 +5,12 @@ import Home from './pages/Home'; import Blogs from './pages/Blogs'; import BlogPost from './pages/BlogPost/BlogPost'; import { ErrorBoundary } from './components/ErrorBoundary'; +import SuccessStories from './pages/SuccessStories'; // Lazy load other pages const Quiz = React.lazy(() => import('./pages/Quiz')); const Advice = React.lazy(() => import('./pages/Advice')); const Vendors = React.lazy(() => import('./pages/Vendors')); -const Stories = React.lazy(() => import('./pages/Stories')); const MarketCalculator = React.lazy(() => import('./pages/MarketCalculator')); function App() { @@ -26,7 +26,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 66f8a93..f1b3ee0 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -1,4 +1,5 @@ import { Link } from 'react-router-dom'; +import { Toaster } from '@/components/ui/toaster'; const MainLayout = ({ children }: { children: React.ReactNode }) => { return ( @@ -16,7 +17,7 @@ const MainLayout = ({ children }: { children: React.ReactNode }) => { Bride Quiz Dear Jane Vendors - Success Stories + Success Stories Market Value @@ -35,6 +36,8 @@ const MainLayout = ({ children }: { children: React.ReactNode }) => { + + ); }; diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index c6b87a3..48e2b11 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -1,8 +1,6 @@ import * as React from 'react'; -import { Cross2Icon } from '@radix-ui/react-icons'; import * as ToastPrimitives from '@radix-ui/react-toast'; -import { cva, type VariantProps } from 'class-variance-authority'; - +import { Cross2Icon } from '@radix-ui/react-icons'; import { cn } from '@/lib/utils'; const ToastProvider = ToastPrimitives.Provider; @@ -22,52 +20,27 @@ const ToastViewport = React.forwardRef< )); ToastViewport.displayName = ToastPrimitives.Viewport.displayName; -const toastVariants = cva( - 'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', - { - variants: { - variant: { - default: 'border bg-background text-foreground', - destructive: - 'destructive group border-destructive bg-destructive text-destructive-foreground', - }, - }, - defaultVariants: { - variant: 'default', - }, - } -); - const Toast = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, variant, ...props }, ref) => { + React.ComponentPropsWithoutRef & { + variant?: 'default' | 'destructive'; + } +>(({ className, variant = 'default', ...props }, ref) => { return ( ); }); Toast.displayName = ToastPrimitives.Root.displayName; -const ToastAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; - const ToastClose = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -75,7 +48,7 @@ const ToastClose = React.forwardRef< (({ className, ...props }, ref) => ( )); @@ -110,18 +83,11 @@ const ToastDescription = React.forwardRef< )); ToastDescription.displayName = ToastPrimitives.Description.displayName; -type ToastProps = React.ComponentPropsWithoutRef; - -type ToastActionElement = React.ReactElement; - export { - type ToastProps, - type ToastActionElement, ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, - ToastAction, }; diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index a408ba5..228bf9e 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -1,4 +1,3 @@ -import { useToast } from '@/hooks/use-toast'; import { Toast, ToastClose, @@ -7,13 +6,14 @@ import { ToastTitle, ToastViewport, } from '@/components/ui/toast'; +import { useToast } from '@/components/ui/use-toast'; export function Toaster() { const { toasts } = useToast(); return ( - {toasts.map(function ({ id, title, description, action, ...props }) { + {toasts.map(({ id, title, description, action, ...props }) => { return (
diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts new file mode 100644 index 0000000..b29afda --- /dev/null +++ b/src/components/ui/use-toast.ts @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react'; + +interface Toast { + id: string; + title?: string; + description?: string; + action?: React.ReactNode; + variant?: 'default' | 'destructive'; +} + +interface ToastOptions { + title?: string; + description?: string; + action?: React.ReactNode; + variant?: 'default' | 'destructive'; + duration?: number; +} + +const DEFAULT_TOAST_DURATION = 5000; // 5 seconds + +export function useToast() { + const [toasts, setToasts] = useState([]); + + useEffect(() => { + const timeouts = toasts.map((toast) => { + return setTimeout(() => { + setToasts((current) => current.filter((t) => t.id !== toast.id)); + }, DEFAULT_TOAST_DURATION); + }); + + return () => { + timeouts.forEach((timeout) => clearTimeout(timeout)); + }; + }, [toasts]); + + function toast(options: ToastOptions) { + const id = Math.random().toString(36).slice(2); + setToasts((current) => [ + ...current, + { + id, + ...options, + }, + ]); + } + + function dismiss(toastId: string) { + setToasts((current) => current.filter((t) => t.id !== toastId)); + } + + return { + toasts, + toast, + dismiss, + }; +} diff --git a/src/components/vendor/VendorCard.tsx b/src/components/vendor/VendorCard.tsx new file mode 100644 index 0000000..b9c16bd --- /dev/null +++ b/src/components/vendor/VendorCard.tsx @@ -0,0 +1,83 @@ +import { Star } from 'lucide-react'; +import { VendorListing } from '@/types/vendors'; +import { cn } from '@/lib/utils'; + +type VendorCardProps = { + vendor: VendorListing; + onClick?: () => void; +}; + +const VendorCard = ({ vendor, onClick }: VendorCardProps) => { + const { name, description, category, location, priceRange, rating, imageUrl } = vendor; + + const renderRatingStars = (value: number) => { + return Array.from({ length: 5 }).map((_, index) => ( + + )); + }; + + return ( +
+
+ {imageUrl ? ( + {name} + ) : ( +
+
+ {name} +
+
+ )} +
+ +
+
+
+

{name}

+ {priceRange} +
+

{location}

+ + {category} + +
+ +

{description}

+ +
+
+ Reputation +
{renderRatingStars(rating.reputation)}
+
+
+ Elegance +
{renderRatingStars(rating.elegance)}
+
+
+ Value +
{renderRatingStars(rating.value)}
+
+
+ + +
+
+ ); +}; + +export default VendorCard; diff --git a/src/components/vendor/VendorModal.tsx b/src/components/vendor/VendorModal.tsx new file mode 100644 index 0000000..9be6275 --- /dev/null +++ b/src/components/vendor/VendorModal.tsx @@ -0,0 +1,145 @@ +import { Star, X } from 'lucide-react'; +import { VendorListing } from '@/types/vendors'; +import { cn } from '@/lib/utils'; + +type VendorModalProps = { + vendor: VendorListing; + onClose: () => void; +}; + +const VendorModal = ({ vendor, onClose }: VendorModalProps) => { + const { + name, + description, + category, + location, + priceRange, + rating, + features, + contactPerson, + testimonials, + imageUrl + } = vendor; + + const renderRatingStars = (value: number) => { + return Array.from({ length: 5 }).map((_, index) => ( + + )); + }; + + return ( +
+
+ + +
+
+ {imageUrl ? ( + {name} + ) : ( +
+ {name} +
+ )} +
+ +
+
+
+

{name}

+

{location}

+
+ {priceRange} +
+ + {category} + +
+ +
+
+
+

About

+

{description}

+
+ +
+

Features

+
    + {features.map((feature, index) => ( +
  • {feature}
  • + ))} +
+
+ + {contactPerson && ( +
+

Contact Person

+

{contactPerson}

+
+ )} +
+ +
+
+

Ratings

+
+
+ Reputation +
{renderRatingStars(rating.reputation)}
+
+
+ Elegance +
{renderRatingStars(rating.elegance)}
+
+
+ Value +
{renderRatingStars(rating.value)}
+
+
+
+ +
+

Testimonials

+
+ {testimonials.map((testimonial, index) => ( +
+

{testimonial.text}

+
— {testimonial.author}
+
+ ))} +
+
+
+
+ +
+ +
+
+
+
+ ); +}; + +export default VendorModal; diff --git a/src/data/blog-posts.ts b/src/data/blog-posts.ts index 1dc98a7..4dde5c5 100644 --- a/src/data/blog-posts.ts +++ b/src/data/blog-posts.ts @@ -4,51 +4,55 @@ export const blogPosts = { id: '1', title: 'The Pragmatic Path to Marriage', author: 'Charlotte Lucas', - authorImage: 'https://images.unsplash.com/photo-1551434678-e076c223a692?w=800&dpr=2&q=80', + authorImage: '/images/authors/charlotte.jpg', date: '1813-01-15', content: [ 'My dear readers, I must confess that happiness in marriage is entirely a matter of chance. There will always be vexation and grief, and it is better to know as little as possible of the defects of the person with whom you are to pass your life.', 'I am not romantic, you know. I never was. I ask only a comfortable home; and considering Mr. Collins\'s character, connections, and situation in life, I am convinced that my chance of happiness with him is as fair as most people can boast on entering the marriage state.', - 'Let us be practical in our pursuit of matrimony. A woman must secure her future while she has the power to do so. Youth and beauty fade, but a stable position in society endures.' - ] - }, - { - id: '2', - title: 'On the Management of Expectations', - author: 'Charlotte Lucas', - authorImage: 'https://images.unsplash.com/photo-1551434678-e076c223a692?w=800&dpr=2&q=80', - date: '1813-02-01', - content: [ - 'Marriage, my dear readers, is a business arrangement first and foremost. Let us not delude ourselves with notions of passionate love that novels so often promote.', - 'I have observed that those who enter marriage with the highest expectations often find themselves the most disappointed. Better to approach it with clear eyes and practical goals.', - 'Consider your position, your prospects, and your future security. These are the foundations upon which a lasting marriage is built.' + 'Let us be practical in our pursuit of matrimony. A woman must secure her future while she has the power to do so.' ] } ], marianne: [ { - id: '3', + id: '2', title: 'The Heart Must Lead', author: 'Marianne Dashwood', - authorImage: 'https://images.unsplash.com/photo-1551431009-a802eeec77b1?w=800&dpr=2&q=80', + authorImage: '/images/authors/marianne.jpg', date: '1813-03-15', content: [ 'To love is to burn, to be on fire! How can one possibly consider marriage without the deepest feelings of love and devotion?', 'I could not be happy with a man whose taste did not in every point coincide with my own. He must enter into all my feelings; the same books, the same music must charm us both.', 'Let others speak of prudence and practicality, but I shall never sacrifice the dictates of my heart to the opinions of the world.' ] - }, + } + ], + fanny: [ + { + id: '3', + title: 'On Principle and Affection', + author: 'Fanny Price', + authorImage: '/images/authors/fanny.jpg', + date: '1814-01-15', + content: [ + 'We must all be guided by our own understanding of what is right. No circumstance should persuade us to act against our principles.', + 'True affection grows slowly, nurtured by shared values and mutual respect. It cannot be forced or rushed by the expectations of others.', + 'In matters of the heart, we must trust our own judgment above all else.' + ] + } + ], + catherine: [ { id: '4', - title: 'Reflections on Love and Second Attachments', - author: 'Marianne Dashwood', - authorImage: 'https://images.unsplash.com/photo-1551431009-a802eeec77b1?w=800&dpr=2&q=80', - date: '1813-04-01', + title: 'Romance and Reality', + author: 'Catherine Morland', + authorImage: '/images/authors/catherine.jpg', + date: '1814-02-15', content: [ - 'I once believed that second attachments were impossible, that the heart could love truly only once. How wrong I was!', - 'Love, I have learned, can grow quietly, steadily, like a garden tended with care and patience. It need not always burst forth like a sudden flame.', - 'To my dear readers, I say: keep your hearts open to the possibility of finding love in unexpected places and unexpected ways.' + 'How thrilling it is to discover that real life can be just as fascinating as our favorite novels!', + 'Yet we must learn to distinguish between romantic fancy and true character. Not every ancient abbey holds a terrible secret, nor is every charming acquaintance hiding dark mysteries.', + 'The greatest adventures may be found in opening our hearts to genuine friendship and love.' ] } ] -}; \ No newline at end of file +}; diff --git a/src/data/dear-jane.ts b/src/data/dear-jane.ts index 17b8c81..3cd6471 100644 --- a/src/data/dear-jane.ts +++ b/src/data/dear-jane.ts @@ -1,26 +1,156 @@ -export const dearJaneLetters = [ +export interface DearJaneLetter { + id: string; + from: string; + subject: string; + category: 'courtship' | 'marriage' | 'family' | 'society' | 'heartbreak'; + question: string; + answer: string[]; + date: string; + relatedBook?: { + title: string; + character: string; + quote: string; + }; +} + +export const dearJaneLetters: DearJaneLetter[] = [ { id: '1', from: 'Hopelessly Romantic', subject: 'Should I Wait for True Love?', + category: 'courtship', + date: '1813-01-15', question: 'Dear Jane, I am twenty-seven and have received a proposal from a respectable gentleman. He is kind and well-situated, but I feel no passionate attachment. Should I accept for security\'s sake, or wait for true love?', answer: [ 'My dear Hopelessly Romantic,', 'While the heart must not be entirely silent in matters of marriage, neither should it be the only voice in the conversation. Consider that happiness in marriage is not merely a matter of passionate beginnings, but of compatible temperaments and mutual respect.', 'However, do not mistake mere security for contentment. A marriage without any affection is as imprudent as one based solely on passionate feelings. The ideal lies somewhere between Charlotte Lucas\'s pragmatism and Marianne Dashwood\'s romanticism.', 'Examine your feelings carefully. Is your lack of passion truly indifference, or merely the absence of drama that so many mistake for love? Sometimes the steadiest attachments grow from the most modest beginnings.' - ] + ], + relatedBook: { + title: 'Sense and Sensibility', + character: 'Marianne Dashwood', + quote: 'The more I know of the world, the more I am convinced that I shall never see a man whom I can really love.' + } }, { id: '2', from: 'Concerned Sister', subject: 'My Sister\'s Imprudent Attachment', + category: 'family', + date: '1813-02-01', question: 'Dear Jane, My younger sister has formed an attachment to a gentleman of questionable character. How can I guide her toward prudence without seeming to interfere?', answer: [ 'My dear Concerned Sister,', 'Ah, the delicate art of sisterly guidance! One must tread carefully when matters of the heart are concerned, particularly when dealing with a younger sister who may mistake experience for interference.', 'Remember how our dear Elizabeth Bennet handled her sister Lydia\'s situation. Direct opposition often strengthens such attachments. Instead, guide your sister to examine the gentleman\'s character through his actions rather than his words.', 'Perhaps arrange situations where his true nature might reveal itself naturally. The best advice is often that which allows the recipient to believe they have arrived at the conclusion independently.' - ] + ], + relatedBook: { + title: 'Pride and Prejudice', + character: 'Elizabeth Bennet', + quote: 'We all know him to be a proud, unpleasant sort of man; but this would be nothing if you really liked him.' + } + }, + { + id: '3', + from: 'Socially Anxious', + subject: 'Navigating Social Gatherings', + category: 'society', + date: '1813-03-15', + question: 'Dear Jane, I find myself overwhelmed at social gatherings, particularly when expected to dance and converse with potential suitors. How can I overcome my natural reserve without compromising my dignity?', + answer: [ + 'My dear Socially Anxious,', + 'Take heart in knowing that even the most accomplished among us have felt the weight of social expectations. Consider our dear Anne Elliot, whose quiet dignity and genuine nature eventually won the day over more boisterous displays.', + 'Rather than attempting to transform yourself into a social butterfly, focus on meaningful connections. A well-timed observation or thoughtful question often carries more weight than hours of idle chatter.', + 'Remember, those worth knowing will appreciate your authentic self. As for dancing, consider it an opportunity for observation rather than a test of social prowess.' + ], + relatedBook: { + title: 'Persuasion', + character: 'Anne Elliot', + quote: 'Time will explain.' + } + }, + { + id: '4', + from: 'Disappointed in Bath', + subject: 'Recovering from Heartbreak', + category: 'heartbreak', + date: '1813-04-01', + question: 'Dear Jane, I recently discovered that the gentleman I had set my hopes upon is engaged to another. How does one recover from such a disappointment while maintaining one\'s composure in society?', + answer: [ + 'My dear Disappointed,', + 'First, allow me to commend your strength in seeking guidance rather than retreating into despair. The pain of disappointed hopes is keen, but it need not define your future happiness.', + 'Consider how our dear Jane Bennet conducted herself when faced with similar circumstances. Her gentle nature and genuine goodwill, even toward those who had caused her pain, preserved both her dignity and her peace of mind.', + 'Channel your energies into self-improvement and the cultivation of true friendships. Time, that great healer, works most effectively when we give ourselves permission to grow from our disappointments.' + ], + relatedBook: { + title: 'Pride and Prejudice', + character: 'Jane Bennet', + quote: 'I would not wish to be hasty in censuring anyone; but I always speak what I think.' + } + }, + { + id: '5', + from: 'Newly Married', + subject: 'Adjusting to Married Life', + category: 'marriage', + date: '1813-05-15', + question: 'Dear Jane, Having recently entered the married state, I find myself struggling to balance my own pursuits with my husband\'s expectations. How can I maintain my independence while building a harmonious partnership?', + answer: [ + 'My dear Newly Married,', + 'The transition from maiden to wife need not mean the abandonment of one\'s individual character. Indeed, the finest marriages are those where both parties encourage each other\'s growth and interests.', + 'Consider how Emma Woodhouse and Mr. Knightley maintained their lively debates and individual pursuits even after marriage. Their relationship was strengthened by their ability to challenge and support one another.', + 'Establish habits of open communication early in your marriage. A wise partner will value your happiness and independence as much as your devotion.' + ], + relatedBook: { + title: 'Emma', + character: 'Emma Woodhouse', + quote: 'I may have lost my heart, but not my self-control.' + } } -]; \ No newline at end of file +]; + +export interface UserSubmittedQuestion { + id: string; + from: string; + subject: string; + category: DearJaneLetter['category']; + question: string; + date: string; + status: 'pending' | 'answered'; +} + +export const generateQuestionId = () => { + return `q-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; +}; + +export const saveUserQuestion = (question: Omit): UserSubmittedQuestion => { + const newQuestion: UserSubmittedQuestion = { + ...question, + id: generateQuestionId(), + date: new Date().toISOString(), + status: 'pending' + }; + + // Get existing questions from localStorage + const existingQuestions = getUserQuestions(); + + // Add new question to the list + const updatedQuestions = [newQuestion, ...existingQuestions]; + + // Save back to localStorage + localStorage.setItem('userQuestions', JSON.stringify(updatedQuestions)); + + return newQuestion; +}; + +export const getUserQuestions = (): UserSubmittedQuestion[] => { + const stored = localStorage.getItem('userQuestions'); + if (!stored) return []; + try { + return JSON.parse(stored); + } catch { + return []; + } +}; diff --git a/src/data/quotes.ts b/src/data/quotes.ts index 02dc44f..c5fb9b4 100644 --- a/src/data/quotes.ts +++ b/src/data/quotes.ts @@ -10,30 +10,12 @@ export const austenQuotes = [ character: "Charlotte Lucas", context: "Discussing her practical view of marriage with Elizabeth" }, - { - quote: "If I loved you less, I might be able to talk about it more.", - source: "Emma", - character: "Mr. Knightley", - context: "Confessing his love to Emma" - }, - { - quote: "A lady's imagination is very rapid; it jumps from admiration to love, from love to matrimony in a moment.", - source: "Pride and Prejudice", - character: "Mr. Darcy", - context: "Observing how quickly ladies form attachments" - }, { quote: "The more I know of the world, the more I am convinced that I shall never see a man whom I can really love. I require so much!", source: "Sense and Sensibility", character: "Marianne Dashwood", context: "Expressing her romantic ideals before meeting Colonel Brandon" }, - { - quote: "You pierce my soul. I am half agony, half hope.", - source: "Persuasion", - character: "Captain Wentworth", - context: "His letter to Anne expressing his enduring love" - }, { quote: "There is nothing I would not do for those who are really my friends. I have no notion of loving people by halves, it is not my nature.", source: "Northanger Abbey", @@ -41,9 +23,9 @@ export const austenQuotes = [ context: "Demonstrating the difference between words and actions in matters of the heart" }, { - quote: "A woman is not to marry a man merely because she is asked, or because he is attached to her, and can write a tolerable letter.", - source: "Emma", - character: "Emma Woodhouse", - context: "Advising Harriet Smith about marriage proposals" + quote: "I speak what I think, and therefore may appear abrupt and unpolished.", + source: "Mansfield Park", + character: "Edmund Bertram", + context: "Expressing his honest nature to Fanny Price" } ]; diff --git a/src/data/success-stories.ts b/src/data/success-stories.ts index c203e41..87f23df 100644 --- a/src/data/success-stories.ts +++ b/src/data/success-stories.ts @@ -1,34 +1,89 @@ -export const successStories = [ +export type SuccessStory = { + id: string; + couple: string; + imageUrl: string; + story: string; + date: string; + location: string; + quote: string; + details: { + ceremony: string; + reception: string; + specialMoments: string[]; + }; +}; + +export const successStories: SuccessStory[] = [ { - id: '1', - couple: 'Elizabeth & Darcy', - novel: 'Pride and Prejudice', - image: 'https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?w=800&dpr=2&q=80', - quote: "In vain have I struggled. It will not do. My feelings will not be repressed. You must allow me to tell you how ardently I admire and love you.", - story: "Their journey from mutual prejudice to profound understanding exemplifies how true love can overcome pride and first impressions. Through challenges and misunderstandings, they discovered that real connection grows from honest self-reflection and the courage to change." + id: 'elizabeth-darcy', + couple: 'Elizabeth Bennet & Fitzwilliam Darcy', + imageUrl: '/images/success-stories/elizabeth-darcy.jpg', + story: 'From initial prejudice to profound understanding, Elizabeth and Darcy\'s journey is a testament to how love can overcome pride and preconceptions. Their story began with a series of misunderstandings at the Meryton assembly but blossomed into a deep connection built on mutual respect and admiration.', + date: 'Spring 1813', + location: 'Longbourn Church, Hertfordshire', + quote: "It has been coming on so gradually, that I hardly know when it began.", + details: { + ceremony: 'Traditional ceremony at Longbourn Church', + reception: 'An elegant affair at Pemberley', + specialMoments: [ + 'First dance at the Netherfield Ball', + 'Surprise visit to Pemberley', + 'Morning walk in the gardens where they reconciled' + ] + } }, { - id: '2', - couple: 'Emma & Mr. Knightley', - novel: 'Emma', - image: 'https://images.unsplash.com/photo-1515934751635-c81c6bc9a2d8?w=800&dpr=2&q=80', - quote: "If I loved you less, I might be able to talk about it more.", - story: "A tale of friendship blossoming into love, their story shows how the best partnerships are built on mutual respect and the ability to help each other grow. Through Emma's journey of self-discovery, she learns the value of Mr. Knightley's honest guidance." - }, - { - id: '3', - couple: 'Anne & Captain Wentworth', - novel: 'Persuasion', - image: 'https://images.unsplash.com/photo-1591604466107-ec97de577aff?w=800&dpr=2&q=80', - quote: "You pierce my soul. I am half agony, half hope.", - story: "Their reunion after eight years proves that true love can withstand time and circumstance. Despite past regrets and the interference of family, their constancy and growth as individuals led them back to each other." - }, - { - id: '4', - couple: 'Marianne & Colonel Brandon', - novel: 'Sense and Sensibility', - image: 'https://images.unsplash.com/photo-1595732301236-4a39e6a35b4a?w=800&dpr=2&q=80', + id: 'marianne-brandon', + couple: 'Marianne Dashwood & Colonel Brandon', + imageUrl: '/images/success-stories/marianne-brandon.jpg', + story: 'From youthful romantic ideals to discovering deep and lasting love, Marianne and Colonel Brandon\'s story demonstrates how true love can defy initial impressions and grow from genuine care and devotion.', + date: 'Summer 1814', + location: 'Barton Park Chapel, Devonshire', quote: "The more I know of the world, the more I am convinced that I shall never see a man whom I can really love.", - story: "Their story demonstrates how love can grow from initial indifference to deep appreciation. Marianne's journey from romantic idealism to finding happiness with the steadfast Colonel Brandon shows how real love often differs from youthful fantasies." + details: { + ceremony: 'Musical ceremony at Barton Park Chapel', + reception: 'Celebration at Delaford', + specialMoments: [ + 'First pianoforte duet', + 'Recovery at Cleveland', + 'Autumn walks at Delaford' + ] + } + }, + { + id: 'edmund-fanny', + couple: 'Edmund Bertram & Fanny Price', + imageUrl: '/images/success-stories/edmund-fanny.jpg', + story: 'A love that grew from childhood friendship to deep understanding, Fanny and Edmund\'s story shows how true worth and genuine feelings triumph over superficial attractions.', + date: 'Autumn 1814', + location: 'Mansfield Park Chapel', + quote: "We have all a better guide in ourselves, if we would attend to it, than any other person can be.", + details: { + ceremony: 'Intimate ceremony at Mansfield Park Chapel', + reception: 'Family gathering at the Parsonage', + specialMoments: [ + 'Reading together in the East room', + 'Walks in the wilderness', + 'Return to Mansfield Park' + ] + } + }, + { + id: 'catherine-henry', + couple: 'Catherine Morland & Henry Tilney', + imageUrl: '/images/success-stories/catherine-henry.jpg', + story: 'What began with a dance at the Bath Assembly Rooms grew into a love story that helped Catherine mature from an imaginative girl into a discerning young woman.', + date: 'Spring 1814', + location: 'Fullerton Parish Church', + quote: "The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.", + details: { + ceremony: 'Charming ceremony at Fullerton', + reception: 'Celebration at Woodston Parsonage', + specialMoments: [ + 'First dance in Bath', + 'Walks around Beechen Cliff', + 'Visit to Woodston' + ] + } } -]; \ No newline at end of file +]; diff --git a/src/data/vendors.ts b/src/data/vendors.ts new file mode 100644 index 0000000..2c1110c --- /dev/null +++ b/src/data/vendors.ts @@ -0,0 +1,112 @@ +import { VendorListing } from '../types/vendors'; + +export const VENDOR_LISTINGS: VendorListing[] = [ + { + id: '1', + name: 'Pemberley Estate', + description: 'The grandest estate in Derbyshire, offering elegant spaces for the most sophisticated celebrations. Our grounds provide stunning backdrops for ceremonies and receptions.', + category: 'venue', + location: 'Derbyshire', + imageUrl: '/images/vendors/pemberley.jpg', + priceRange: '££££', + rating: { + reputation: 5, + elegance: 5, + value: 4 + }, + features: [ + 'Grand ballroom', + 'Extensive gardens', + 'Lake view', + 'Multiple reception rooms', + 'Accommodation available' + ], + testimonials: [ + { + author: 'Elizabeth Bennet', + text: 'The perfect setting for our celebration. The grounds are beyond compare.' + } + ] + }, + { + id: '2', + name: 'Meryton Assembly Rooms', + description: 'The perfect venue for a country wedding, our assembly rooms have hosted countless memorable celebrations. Known for our excellent acoustics and spacious dance floor.', + category: 'venue', + location: 'Hertfordshire', + imageUrl: '/images/vendors/meryton.jpg', + priceRange: '£££', + rating: { + reputation: 4, + elegance: 4, + value: 5 + }, + features: [ + 'Large dance floor', + 'Excellent acoustics', + 'Catering kitchen', + 'Card room', + 'Garden access' + ], + testimonials: [ + { + author: 'Jane Bennet', + text: 'We could not have chosen a better venue. The rooms were perfectly sized for our gathering.' + } + ] + }, + { + id: '3', + name: 'Barton Park', + description: 'A charming country estate perfect for intimate celebrations. Our grounds offer beautiful views of the Devonshire countryside.', + category: 'venue', + location: 'Devonshire', + imageUrl: '/images/vendors/barton.jpg', + priceRange: '£££', + rating: { + reputation: 4, + elegance: 4, + value: 5 + }, + features: [ + 'Country house setting', + 'Music room', + 'Garden terrace', + 'Private chapel', + 'Countryside views' + ], + testimonials: [ + { + author: 'Marianne Dashwood', + text: 'The perfect blend of elegance and natural beauty. The music room is particularly delightful.' + } + ] + }, + { + id: '4', + name: 'Mansfield Park', + description: 'An elegant estate offering both grandeur and intimacy for your special day. Our chapel and grounds provide multiple options for ceremonies.', + category: 'venue', + location: 'Northamptonshire', + imageUrl: '/images/vendors/mansfield.jpg', + priceRange: '££££', + rating: { + reputation: 5, + elegance: 5, + value: 4 + }, + features: [ + 'Private chapel', + 'Formal gardens', + 'East room', + 'Multiple reception spaces', + 'Wilderness walk' + ], + testimonials: [ + { + author: 'Fanny Price', + text: 'The chapel holds such special memories, and the grounds are perfect for quiet moments of reflection.' + } + ] + } +]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2819a83..9ad0df4 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,4 @@ -import { clsx, type ClassValue } from 'clsx'; +import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { diff --git a/src/pages/DearJane.tsx b/src/pages/DearJane.tsx index 2eb53cf..81c4d18 100644 --- a/src/pages/DearJane.tsx +++ b/src/pages/DearJane.tsx @@ -1,39 +1,231 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { Mail } from 'lucide-react'; -import { dearJaneLetters } from '@/data/dear-jane'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Mail, Heart, Book, Users, Home, MessageCircle, PenTool, Clock } from 'lucide-react'; +import { dearJaneLetters, DearJaneLetter, UserSubmittedQuestion, saveUserQuestion, getUserQuestions } from '@/data/dear-jane'; +import { useToast } from '@/components/ui/use-toast'; + +const categoryIcons = { + courtship: , + marriage: , + family: , + society: , + heartbreak: +}; + +const categoryNames = { + courtship: 'Matters of Courtship', + marriage: 'Marriage & Partnership', + family: 'Family Relations', + society: 'Social Etiquette', + heartbreak: 'Healing Hearts' +}; + +const categoryDescriptions = { + courtship: 'Navigate the delicate dance of courtship with Jane\'s guidance on matters of the heart.', + marriage: 'Discover wisdom for maintaining harmony and growth in matrimonial life.', + family: 'Learn to handle family matters with grace, wisdom, and understanding.', + society: 'Master the art of social etiquette while staying true to yourself.', + heartbreak: 'Find comfort and strength in Jane\'s advice for healing a wounded heart.' +}; const DearJane = () => { - const [selectedLetter, setSelectedLetter] = useState(dearJaneLetters[0]); + const { toast } = useToast(); + const [selectedLetter, setSelectedLetter] = useState(dearJaneLetters[0]); + const [selectedCategory, setSelectedCategory] = useState('courtship'); + const [showSubmitForm, setShowSubmitForm] = useState(false); + const [userQuestions, setUserQuestions] = useState([]); + const [formData, setFormData] = useState({ + subject: '', + from: '', + question: '', + category: 'courtship' as DearJaneLetter['category'] + }); + + useEffect(() => { + setUserQuestions(getUserQuestions()); + }, []); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!formData.subject || !formData.from || !formData.question) { + toast({ + title: "Missing Information", + description: "Please fill in all fields before submitting.", + variant: "destructive" + }); + return; + } + + try { + const newQuestion = saveUserQuestion(formData); + setUserQuestions(prev => [newQuestion, ...prev]); + setShowSubmitForm(false); + setFormData({ + subject: '', + from: '', + question: '', + category: 'courtship' + }); + toast({ + title: "Question Submitted", + description: "Your question has been submitted successfully. Jane will respond soon!" + }); + } catch (err) { + console.error('Error submitting question:', err); + toast({ + title: "Submission Error", + description: err instanceof Error ? err.message : "There was an error submitting your question. Please try again.", + variant: "destructive" + }); + } + }; + + const allLetters = [...dearJaneLetters, ...userQuestions]; + const filteredLetters = allLetters.filter(letter => letter.category === selectedCategory); return ( -
+
-

Dear Jane

-

- Seeking matrimonial advice? Let Jane guide you with her timeless wisdom +

Dear Jane

+

+ Seeking counsel in matters of the heart? Let Jane Austen's timeless wisdom guide you through your romantic predicaments.

+
-
- + {showSubmitForm && ( + - Recent Letters + Submit Your Question +

+ Pour your heart out to Jane, and she shall guide you with her timeless wisdom. +

- -
- {dearJaneLetters.map((letter) => ( +
+
+ + setFormData(prev => ({ ...prev, subject: e.target.value }))} + className="w-full p-3 rounded-lg border-sage-200 focus:ring-sage-500 focus:border-sage-500 bg-white" + placeholder="e.g., Advice on a Delicate Matter" + required + /> +
+
+ +