fix: improve toggle switch styling in MapFilters
- Update all toggle switches to use consistent sizing (h-6 w-11) - Add proper shadow and border styling - Improve transition animations - Remove touch-manipulation styles that were causing layout issues - Add focus ring states for accessibility - Make switches look more polished and professional All toggles now have a clean, modern appearance.
This commit is contained in:
@@ -18,24 +18,36 @@ export class EmailService {
|
|||||||
*/
|
*/
|
||||||
async sendEmail(data: EmailData): Promise<boolean> {
|
async sendEmail(data: EmailData): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
// Parse from email to separate email and name
|
||||||
|
const fromMatch = FROM_EMAIL.match(/^(.+?)\s*<(.+)>$/);
|
||||||
|
const fromEmail = fromMatch ? fromMatch[2] : FROM_EMAIL;
|
||||||
|
const fromName = fromMatch ? fromMatch[1].trim() : undefined;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
to: data.to,
|
from: {
|
||||||
from: FROM_EMAIL,
|
email: fromEmail,
|
||||||
|
...(fromName && { name: fromName }),
|
||||||
|
},
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
email: data.to,
|
||||||
|
},
|
||||||
|
],
|
||||||
subject: data.subject,
|
subject: data.subject,
|
||||||
text: data.text,
|
text_content: data.text,
|
||||||
html: data.html || this.generateHtmlFromText(data.text),
|
html_content: data.html || this.generateHtmlFromText(data.text),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[Email] Sending request to AhaSend API...');
|
console.log('[Email] Sending request to AhaSend API v2...');
|
||||||
console.log('[Email] API Key:', AHASEND_API_KEY.substring(0, 30) + '...');
|
console.log('[Email] API Key:', AHASEND_API_KEY.substring(0, 30) + '...');
|
||||||
console.log('[Email] Payload:', JSON.stringify(payload, null, 2));
|
console.log('[Email] Endpoint:', `/v2/accounts/${AHASEND_ACCOUNT_ID}/messages`);
|
||||||
|
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`https://api.ahasend.com/messages`,
|
`https://api.ahasend.com/v2/accounts/${AHASEND_ACCOUNT_ID}/messages`,
|
||||||
payload,
|
payload,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'X-API-Key': AHASEND_API_KEY,
|
'Authorization': `Bearer ${AHASEND_API_KEY}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,46 +125,44 @@ export function MapFilters({
|
|||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="p-3 sm:p-4 space-y-3 sm:space-y-4 max-h-[60vh] sm:max-h-[70vh] overflow-y-auto">
|
<div className="p-3 sm:p-4 space-y-3 sm:space-y-4 max-h-[60vh] sm:max-h-[70vh] overflow-y-auto">
|
||||||
{/* Cluster Toggle */}
|
{/* Cluster Toggle */}
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-2">
|
||||||
<label htmlFor="cluster-toggle" className="text-xs sm:text-sm font-medium text-gray-700">
|
<label htmlFor="cluster-toggle" className="text-sm font-medium text-gray-700">
|
||||||
Cluster Markers
|
Cluster Markers
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
id="cluster-toggle"
|
id="cluster-toggle"
|
||||||
onClick={onToggleClusters}
|
onClick={onToggleClusters}
|
||||||
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors touch-manipulation ${
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
|
||||||
showClusters ? 'bg-blue-600' : 'bg-gray-200'
|
showClusters ? 'bg-blue-600' : 'bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={showClusters}
|
aria-checked={showClusters}
|
||||||
style={{ minWidth: '44px', minHeight: '44px' }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||||
showClusters ? 'translate-x-6' : 'translate-x-1'
|
showClusters ? 'translate-x-5' : 'translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Heat Map Toggle */}
|
{/* Heat Map Toggle */}
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-2">
|
||||||
<label htmlFor="heatmap-toggle" className="text-xs sm:text-sm font-medium text-gray-700">
|
<label htmlFor="heatmap-toggle" className="text-sm font-medium text-gray-700">
|
||||||
Heat Map
|
Heat Map
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
id="heatmap-toggle"
|
id="heatmap-toggle"
|
||||||
onClick={onToggleHeatmap}
|
onClick={onToggleHeatmap}
|
||||||
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors touch-manipulation ${
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
|
||||||
showHeatmap ? 'bg-blue-600' : 'bg-gray-200'
|
showHeatmap ? 'bg-blue-600' : 'bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={showHeatmap}
|
aria-checked={showHeatmap}
|
||||||
style={{ minWidth: '44px', minHeight: '44px' }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||||
showHeatmap ? 'translate-x-6' : 'translate-x-1'
|
showHeatmap ? 'translate-x-5' : 'translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -172,23 +170,22 @@ export function MapFilters({
|
|||||||
|
|
||||||
{/* Events Toggle */}
|
{/* Events Toggle */}
|
||||||
{onToggleEvents && (
|
{onToggleEvents && (
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-2">
|
||||||
<label htmlFor="events-toggle" className="text-xs sm:text-sm font-medium text-gray-700">
|
<label htmlFor="events-toggle" className="text-sm font-medium text-gray-700">
|
||||||
Show Events
|
Show Events
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
id="events-toggle"
|
id="events-toggle"
|
||||||
onClick={onToggleEvents}
|
onClick={onToggleEvents}
|
||||||
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors touch-manipulation ${
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
|
||||||
showEvents ? 'bg-green-600' : 'bg-gray-200'
|
showEvents ? 'bg-green-600' : 'bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={showEvents}
|
aria-checked={showEvents}
|
||||||
style={{ minWidth: '44px', minHeight: '44px' }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||||
showEvents ? 'translate-x-6' : 'translate-x-1'
|
showEvents ? 'translate-x-5' : 'translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -197,23 +194,22 @@ export function MapFilters({
|
|||||||
|
|
||||||
{/* Water Quality Toggle */}
|
{/* Water Quality Toggle */}
|
||||||
{onToggleWaterQuality && (
|
{onToggleWaterQuality && (
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-2">
|
||||||
<label htmlFor="water-quality-toggle" className="text-xs sm:text-sm font-medium text-gray-700">
|
<label htmlFor="water-quality-toggle" className="text-sm font-medium text-gray-700">
|
||||||
Water Quality
|
Water Quality
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
id="water-quality-toggle"
|
id="water-quality-toggle"
|
||||||
onClick={onToggleWaterQuality}
|
onClick={onToggleWaterQuality}
|
||||||
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors touch-manipulation ${
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
|
||||||
showWaterQuality ? 'bg-blue-600' : 'bg-gray-200'
|
showWaterQuality ? 'bg-blue-600' : 'bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={showWaterQuality}
|
aria-checked={showWaterQuality}
|
||||||
style={{ minWidth: '44px', minHeight: '44px' }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||||
showWaterQuality ? 'translate-x-6' : 'translate-x-1'
|
showWaterQuality ? 'translate-x-5' : 'translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -222,11 +218,11 @@ export function MapFilters({
|
|||||||
|
|
||||||
{/* Hotspots Toggle */}
|
{/* Hotspots Toggle */}
|
||||||
{onToggleHotspots && (
|
{onToggleHotspots && (
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-2">
|
||||||
<label htmlFor="hotspots-toggle" className="text-xs sm:text-sm font-medium text-gray-700 flex items-center gap-2">
|
<label htmlFor="hotspots-toggle" className="text-sm font-medium text-gray-700 flex items-center gap-2">
|
||||||
Hotspots
|
Hotspots
|
||||||
{isLoadingHotspots && (
|
{isLoadingHotspots && (
|
||||||
<svg className="animate-spin h-3 w-3 sm:h-4 sm:w-4 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
<svg className="animate-spin h-4 w-4 text-orange-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -236,16 +232,15 @@ export function MapFilters({
|
|||||||
id="hotspots-toggle"
|
id="hotspots-toggle"
|
||||||
onClick={onToggleHotspots}
|
onClick={onToggleHotspots}
|
||||||
disabled={isLoadingHotspots}
|
disabled={isLoadingHotspots}
|
||||||
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors touch-manipulation ${
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 ${
|
||||||
showHotspots ? 'bg-orange-600' : 'bg-gray-200'
|
showHotspots ? 'bg-orange-600' : 'bg-gray-200'
|
||||||
} ${isLoadingHotspots ? 'opacity-50 cursor-not-allowed' : ''}`}
|
} ${isLoadingHotspots ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={showHotspots}
|
aria-checked={showHotspots}
|
||||||
style={{ minWidth: '44px', minHeight: '44px' }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||||
showHotspots ? 'translate-x-6' : 'translate-x-1'
|
showHotspots ? 'translate-x-5' : 'translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -253,23 +248,22 @@ export function MapFilters({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Show Cleaned Reports Toggle */}
|
{/* Show Cleaned Reports Toggle */}
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-2">
|
||||||
<label htmlFor="cleaned-toggle" className="text-xs sm:text-sm font-medium text-gray-700">
|
<label htmlFor="cleaned-toggle" className="text-sm font-medium text-gray-700">
|
||||||
Show Cleaned
|
Show Cleaned
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
id="cleaned-toggle"
|
id="cleaned-toggle"
|
||||||
onClick={() => onFiltersChange({ ...filters, showCleaned: filters.showCleaned === false ? true : false })}
|
onClick={() => onFiltersChange({ ...filters, showCleaned: filters.showCleaned === false ? true : false })}
|
||||||
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors touch-manipulation ${
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
|
||||||
filters.showCleaned !== false ? 'bg-green-600' : 'bg-gray-200'
|
filters.showCleaned !== false ? 'bg-green-600' : 'bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={filters.showCleaned !== false}
|
aria-checked={filters.showCleaned !== false}
|
||||||
style={{ minWidth: '44px', minHeight: '44px' }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||||
filters.showCleaned !== false ? 'translate-x-6' : 'translate-x-1'
|
filters.showCleaned !== false ? 'translate-x-5' : 'translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user