Fix Icon component to use Lucide icons library
Build And Push Image / docker (push) Successful in 1m56s Details

- Installed lucide-vue-next package
- Rewrote Icon.vue to use Lucide icons instead of non-existent icon files
- Added comprehensive icon mapping for all commonly used icons
- Build now succeeds without errors
This commit is contained in:
Matt 2025-08-30 18:44:18 +02:00
parent c1f986bc07
commit 27e38d98e5
6 changed files with 1321 additions and 34 deletions

View File

@ -21,7 +21,8 @@
"Bash(npm install:*)", "Bash(npm install:*)",
"Bash(git add:*)", "Bash(git add:*)",
"Bash(git push:*)", "Bash(git push:*)",
"Bash(git commit:*)" "Bash(git commit:*)",
"Bash(npm run build:*)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@ -2,48 +2,196 @@
<component <component
:is="iconComponent" :is="iconComponent"
v-if="iconComponent" v-if="iconComponent"
v-bind="$attrs" :size="size"
:stroke-width="strokeWidth"
:color="color"
class="lucide-icon"
/> />
<span v-else class="icon-placeholder">
{{ name }}
</span>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineAsyncComponent } from 'vue' import { computed } from 'vue'
import * as icons from 'lucide-vue-next'
interface Props { interface Props {
name: string name: string
size?: number | string
strokeWidth?: number
color?: string
} }
const props = defineProps<Props>() const props = withDefaults(defineProps<Props>(), {
size: 24,
strokeWidth: 2,
color: 'currentColor'
})
// Map common icon names to Heroicons components // Convert kebab-case to PascalCase for icon component names
// This is a simplified version - in production you'd import the actual icons const toPascalCase = (str: string) => {
const iconMap: Record<string, any> = { return str
'chevron-down': defineAsyncComponent(() => import('./icons/ChevronDownIcon.vue')), .split('-')
'chevron-up': defineAsyncComponent(() => import('./icons/ChevronUpIcon.vue')), .map(word => word.charAt(0).toUpperCase() + word.slice(1))
'x': defineAsyncComponent(() => import('./icons/XIcon.vue')), .join('')
'check': defineAsyncComponent(() => import('./icons/CheckIcon.vue')),
'alert-circle': defineAsyncComponent(() => import('./icons/AlertCircleIcon.vue')),
'trending-up': defineAsyncComponent(() => import('./icons/TrendingUpIcon.vue')),
'trending-down': defineAsyncComponent(() => import('./icons/TrendingDownIcon.vue')),
'minus': defineAsyncComponent(() => import('./icons/MinusIcon.vue')),
} }
const iconComponent = computed(() => iconMap[props.name]) const iconComponent = computed(() => {
// Handle special cases and common mappings
const iconMap: Record<string, string> = {
'alert-circle': 'AlertCircle',
'chevron-down': 'ChevronDown',
'chevron-up': 'ChevronUp',
'x': 'X',
'check': 'Check',
'trending-up': 'TrendingUp',
'trending-down': 'TrendingDown',
'minus': 'Minus',
'search': 'Search',
'filter': 'Filter',
'calendar': 'Calendar',
'map-pin': 'MapPin',
'users': 'Users',
'clock': 'Clock',
'star': 'Star',
'grid': 'Grid',
'list': 'List',
'plus': 'Plus',
'user': 'User',
'mail': 'Mail',
'phone': 'Phone',
'globe': 'Globe',
'briefcase': 'Briefcase',
'building': 'Building',
'award': 'Award',
'shield': 'Shield',
'heart': 'Heart',
'edit': 'Edit',
'settings': 'Settings',
'log-out': 'LogOut',
'bell': 'Bell',
'home': 'Home',
'activity': 'Activity',
'message-square': 'MessageSquare',
'arrow-right': 'ArrowRight',
'external-link': 'ExternalLink',
'download': 'Download',
'upload': 'Upload',
'share': 'Share',
'copy': 'Copy',
'trash': 'Trash',
'eye': 'Eye',
'eye-off': 'EyeOff',
'lock': 'Lock',
'unlock': 'Unlock',
'camera': 'Camera',
'image': 'Image',
'video': 'Video',
'file-text': 'FileText',
'bar-chart': 'BarChart',
'pie-chart': 'PieChart',
'dollar-sign': 'DollarSign',
'credit-card': 'CreditCard',
'gift': 'Gift',
'bookmark': 'Bookmark',
'tag': 'Tag',
'folder': 'Folder',
'layers': 'Layers',
'zap': 'Zap',
'sun': 'Sun',
'moon': 'Moon',
'more-horizontal': 'MoreHorizontal',
'more-vertical': 'MoreVertical',
'menu': 'Menu',
'arrow-left': 'ArrowLeft',
'arrow-up': 'ArrowUp',
'arrow-down': 'ArrowDown',
'chevron-left': 'ChevronLeft',
'chevron-right': 'ChevronRight',
'check-circle': 'CheckCircle',
'x-circle': 'XCircle',
'alert-triangle': 'AlertTriangle',
'info': 'Info',
'help-circle': 'HelpCircle',
'loader': 'Loader',
'refresh-cw': 'RefreshCw',
'link': 'Link',
'paperclip': 'Paperclip',
'send': 'Send',
'inbox': 'Inbox',
'archive': 'Archive',
'flag': 'Flag',
'save': 'Save',
'wifi': 'Wifi',
'wifi-off': 'WifiOff',
'mic': 'Mic',
'mic-off': 'MicOff',
'volume': 'Volume',
'volume-x': 'VolumeX',
'play': 'Play',
'pause': 'Pause',
'skip-forward': 'SkipForward',
'skip-back': 'SkipBack',
'maximize': 'Maximize',
'minimize': 'Minimize',
'expand': 'Expand',
'compass': 'Compass',
'map': 'Map',
'navigation': 'Navigation',
'target': 'Target',
'crown': 'Crown',
'key': 'Key',
'code': 'Code',
'terminal': 'Terminal',
'database': 'Database',
'server': 'Server',
'cpu': 'Cpu',
'hard-drive': 'HardDrive',
'monitor': 'Monitor',
'smartphone': 'Smartphone',
'tablet': 'Tablet',
'watch': 'Watch',
'printer': 'Printer',
'headphones': 'Headphones',
'bluetooth': 'Bluetooth',
'battery': 'Battery',
'battery-charging': 'BatteryCharging',
'clipboard': 'Clipboard',
'hash': 'Hash',
'at-sign': 'AtSign',
'percent': 'Percent',
'thumbs-up': 'ThumbsUp',
'thumbs-down': 'ThumbsDown',
'smile': 'Smile',
'frown': 'Frown',
'coffee': 'Coffee',
'shopping-cart': 'ShoppingCart',
'shopping-bag': 'ShoppingBag',
'package': 'Package',
'truck': 'Truck',
'book': 'Book',
'book-open': 'BookOpen',
'feather': 'Feather',
'sliders': 'Sliders',
'toggle-left': 'ToggleLeft',
'toggle-right': 'ToggleRight',
'power': 'Power',
'log-in': 'LogIn',
'circle': 'Circle',
'square': 'Square',
'triangle': 'Triangle'
}
// Get the icon name from the map or convert from kebab-case
const iconName = iconMap[props.name] || toPascalCase(props.name)
// Return the icon component from lucide-vue-next
return (icons as any)[iconName] || (icons as any)[iconName + 'Icon'] || null
})
</script> </script>
<style scoped> <style scoped>
.icon-placeholder { .lucide-icon {
display: inline-flex; display: inline-block;
align-items: center; vertical-align: middle;
justify-content: center; flex-shrink: 0;
width: 1em;
height: 1em;
font-size: 0.75em;
color: currentColor;
background: rgba(220, 38, 38, 0.1);
border-radius: 4px;
} }
</style> </style>

View File

@ -1,5 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</template>

10
package-lock.json generated
View File

@ -22,6 +22,7 @@
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.12.10", "libphonenumber-js": "^1.12.10",
"lucide-vue-next": "^0.542.0",
"mime-types": "^3.0.1", "mime-types": "^3.0.1",
"minio": "^8.0.5", "minio": "^8.0.5",
"nodemailer": "^7.0.5", "nodemailer": "^7.0.5",
@ -11535,6 +11536,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lucide-vue-next": {
"version": "0.542.0",
"resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.542.0.tgz",
"integrity": "sha512-cJfyhFoneDgYTouHwUJEutXaCW5EQuRrBsvfELudWnMiwfqvcEtpZTFZLdZ5Nrqow+znzn+Iyhu3KeYIfa3mEg==",
"license": "ISC",
"peerDependencies": {
"vue": ">=3.0.1"
}
},
"node_modules/luxon": { "node_modules/luxon": {
"version": "3.7.1", "version": "3.7.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",

View File

@ -25,6 +25,7 @@
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.12.10", "libphonenumber-js": "^1.12.10",
"lucide-vue-next": "^0.542.0",
"mime-types": "^3.0.1", "mime-types": "^3.0.1",
"minio": "^8.0.5", "minio": "^8.0.5",
"nodemailer": "^7.0.5", "nodemailer": "^7.0.5",

1132
pages/profile/mockup.vue Normal file

File diff suppressed because it is too large Load Diff