@aws505/sheetsite 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws505/sheetsite",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -51,3 +51,6 @@ export * from './ui/Icons';
51
51
 
52
52
  export { AnimatedSection, StaggerContainer } from './ui/AnimatedSection';
53
53
  export type { AnimatedSectionProps, StaggerContainerProps } from './ui/AnimatedSection';
54
+
55
+ export { FloatingClaimBanner } from './ui/FloatingClaimBanner';
56
+ export type { FloatingClaimBannerProps } from './ui/FloatingClaimBanner';
@@ -27,6 +27,7 @@ export interface HeaderProps {
27
27
  const defaultNavigation: NavItem[] = [
28
28
  { label: 'Home', href: '/' },
29
29
  { label: 'Services', href: '/#services' },
30
+ { label: 'Gallery', href: '/#gallery' },
30
31
  { label: 'Hours', href: '/#hours' },
31
32
  { label: 'Reviews', href: '/#reviews' },
32
33
  { label: 'FAQ', href: '/#faq' },
@@ -20,11 +20,23 @@ export interface HeroProps {
20
20
  backgroundImage?: string;
21
21
  overlay?: boolean;
22
22
  className?: string;
23
+ /** Custom trust signals/selling points. Defaults based on business type if not provided. */
24
+ trustSignals?: string[];
23
25
  }
24
26
 
25
27
  /**
26
28
  * Hero section component.
27
29
  */
30
+ const defaultTrustSignals: Record<string, string[]> = {
31
+ salon: ['Expert Stylists', 'Walk-Ins Welcome', 'Quality Products'],
32
+ restaurant: ['Fresh Ingredients', 'Family Recipes', 'Friendly Service'],
33
+ repair: ['Certified Technicians', 'Honest Estimates', 'Quality Parts'],
34
+ tailor: ['Expert Craftsmanship', 'Perfect Fit Guaranteed', 'Quick Turnaround'],
35
+ professional: ['Quality Work', 'Fair Prices', 'Fast Service'],
36
+ retail: ['Quality Products', 'Great Selection', 'Friendly Service'],
37
+ default: ['Quality Work', 'Fair Prices', 'Fast Service'],
38
+ };
39
+
28
40
  export function Hero({
29
41
  business,
30
42
  variant = 'centered',
@@ -34,7 +46,11 @@ export function Hero({
34
46
  backgroundImage,
35
47
  overlay = true,
36
48
  className = '',
49
+ trustSignals,
37
50
  }: HeroProps) {
51
+ // Determine trust signals based on business type or use provided ones
52
+ const businessType = (business as any).type || 'default';
53
+ const signals = trustSignals || defaultTrustSignals[businessType] || defaultTrustSignals.default;
38
54
  const bgStyle = backgroundImage
39
55
  ? { backgroundImage: `url(${backgroundImage})` }
40
56
  : undefined;
@@ -133,14 +149,14 @@ export function Hero({
133
149
 
134
150
  {/* Description */}
135
151
  {business.aboutShort && (
136
- <p className="text-lg text-white/80 mb-8 max-w-xl">
152
+ <p className={`text-lg text-white/80 mb-8 max-w-xl ${variant === 'centered' ? 'mx-auto' : ''}`}>
137
153
  {business.aboutShort}
138
154
  </p>
139
155
  )}
140
156
 
141
157
  {/* Trust Signals */}
142
- <div className="flex flex-wrap justify-center gap-4 mb-8">
143
- {['Quality Work', 'Fair Prices', 'Fast Service'].map((signal) => (
158
+ <div className={`flex flex-wrap gap-4 mb-8 ${variant === 'centered' ? 'justify-center' : ''}`}>
159
+ {signals.map((signal) => (
144
160
  <div key={signal} className="flex items-center text-white/90">
145
161
  <svg className="w-5 h-5 text-accent-400 mr-2" fill="currentColor" viewBox="0 0 20 20">
146
162
  <path
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Floating Claim Banner Component
3
+ *
4
+ * A floating notification banner for business owners to claim their website.
5
+ * Used for sales outreach purposes.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React, { useState, useEffect } from 'react';
11
+
12
+ export interface FloatingClaimBannerProps {
13
+ /** The email address to send claim requests to */
14
+ contactEmail?: string;
15
+ /** The site URL to include in the email subject (defaults to window.location.hostname) */
16
+ siteUrl?: string;
17
+ /** Business name for the email */
18
+ businessName?: string;
19
+ /** Position of the banner */
20
+ position?: 'bottom-right' | 'bottom-left' | 'bottom-center';
21
+ /** Delay before showing the banner (ms) */
22
+ showDelay?: number;
23
+ /** Whether the banner can be dismissed */
24
+ dismissible?: boolean;
25
+ /** Custom message text */
26
+ message?: string;
27
+ /** Custom button text */
28
+ buttonText?: string;
29
+ /** Custom class name */
30
+ className?: string;
31
+ }
32
+
33
+ /**
34
+ * Floating banner for business owners to claim their site.
35
+ */
36
+ export function FloatingClaimBanner({
37
+ contactEmail = 'andrew@whotookmy.com',
38
+ siteUrl,
39
+ businessName,
40
+ position = 'bottom-right',
41
+ showDelay = 3000,
42
+ dismissible = true,
43
+ message = 'Is this your business?',
44
+ buttonText = 'Claim This Site',
45
+ className = '',
46
+ }: FloatingClaimBannerProps) {
47
+ const [isVisible, setIsVisible] = useState(false);
48
+ const [isDismissed, setIsDismissed] = useState(false);
49
+
50
+ useEffect(() => {
51
+ // Check if already dismissed in this session
52
+ const dismissed = sessionStorage.getItem('claimBannerDismissed');
53
+ if (dismissed) {
54
+ setIsDismissed(true);
55
+ return;
56
+ }
57
+
58
+ // Show banner after delay
59
+ const timer = setTimeout(() => {
60
+ setIsVisible(true);
61
+ }, showDelay);
62
+
63
+ return () => clearTimeout(timer);
64
+ }, [showDelay]);
65
+
66
+ const handleDismiss = () => {
67
+ setIsVisible(false);
68
+ setIsDismissed(true);
69
+ sessionStorage.setItem('claimBannerDismissed', 'true');
70
+ };
71
+
72
+ const handleClaim = () => {
73
+ const url = siteUrl || (typeof window !== 'undefined' ? window.location.hostname : 'unknown');
74
+ const subject = encodeURIComponent(`Claim my site - ${url}`);
75
+ const body = encodeURIComponent(
76
+ `Hi,\n\nI am the owner of ${businessName || 'this business'} and I would like to claim my website at ${url}.\n\nPlease contact me to discuss.\n\nThank you!`
77
+ );
78
+ window.location.href = `mailto:${contactEmail}?subject=${subject}&body=${body}`;
79
+ };
80
+
81
+ if (isDismissed || !isVisible) {
82
+ return null;
83
+ }
84
+
85
+ const positionClasses = {
86
+ 'bottom-right': 'bottom-4 right-4',
87
+ 'bottom-left': 'bottom-4 left-4',
88
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
89
+ };
90
+
91
+ return (
92
+ <div
93
+ className={`
94
+ fixed z-50 ${positionClasses[position]}
95
+ animate-slide-up
96
+ ${className}
97
+ `}
98
+ style={{
99
+ animation: 'slideUp 0.5s ease-out',
100
+ }}
101
+ >
102
+ <div className="bg-white rounded-lg shadow-2xl border border-gray-200 p-4 max-w-sm">
103
+ <div className="flex items-start gap-3">
104
+ {/* Icon */}
105
+ <div className="flex-shrink-0 w-10 h-10 bg-primary-100 rounded-full flex items-center justify-center">
106
+ <svg className="w-5 h-5 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
107
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
108
+ </svg>
109
+ </div>
110
+
111
+ {/* Content */}
112
+ <div className="flex-1 min-w-0">
113
+ <p className="text-sm font-medium text-gray-900">{message}</p>
114
+ <p className="text-xs text-gray-500 mt-1">
115
+ We built this site for you. Claim it today!
116
+ </p>
117
+ <button
118
+ onClick={handleClaim}
119
+ className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors"
120
+ >
121
+ <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
122
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
123
+ </svg>
124
+ {buttonText}
125
+ </button>
126
+ </div>
127
+
128
+ {/* Dismiss button */}
129
+ {dismissible && (
130
+ <button
131
+ onClick={handleDismiss}
132
+ className="flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors"
133
+ aria-label="Dismiss"
134
+ >
135
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
136
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
137
+ </svg>
138
+ </button>
139
+ )}
140
+ </div>
141
+ </div>
142
+
143
+ {/* Animation keyframes */}
144
+ <style jsx>{`
145
+ @keyframes slideUp {
146
+ from {
147
+ opacity: 0;
148
+ transform: translateY(20px);
149
+ }
150
+ to {
151
+ opacity: 1;
152
+ transform: translateY(0);
153
+ }
154
+ }
155
+ `}</style>
156
+ </div>
157
+ );
158
+ }
159
+
160
+ export default FloatingClaimBanner;