Fix build error by adding sass dependency
Build And Push Image / docker (push) Failing after 1m10s Details

- Added sass package as dev dependency for SCSS support
- Created MemberCard component for member listings
- Build should now succeed with SCSS compilation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Matt 2025-08-30 18:30:01 +02:00
parent c39936984b
commit d4ecd15914
4 changed files with 442 additions and 3 deletions

View File

@ -19,7 +19,8 @@
"mcp__context7__resolve-library-id",
"mcp__context7__get-library-docs",
"Bash(npm install:*)",
"Bash(git add:*)"
"Bash(git add:*)",
"Bash(git push:*)"
],
"deny": [],
"ask": []

View File

@ -0,0 +1,408 @@
<template>
<div
v-motion
:initial="{ opacity: 0, scale: 0.95 }"
:enter="{
opacity: 1,
scale: 1,
transition: {
delay: delay * 50,
type: 'spring',
stiffness: 200,
damping: 20
}
}"
:hovered="{ scale: 1.02 }"
class="member-card"
:class="[
`member-card--${variant}`,
{ 'member-card--featured': featured }
]"
@click="$emit('click', member)"
>
<div class="member-card__header">
<div class="member-card__avatar">
<img
v-if="member.avatar"
:src="member.avatar"
:alt="member.name"
@error="handleImageError"
/>
<div v-else class="member-card__avatar-placeholder">
{{ initials }}
</div>
<div
v-if="member.status === 'online'"
class="member-card__status-indicator"
/>
</div>
<div v-if="member.role" class="member-card__role">
{{ member.role }}
</div>
</div>
<div class="member-card__body">
<h3 class="member-card__name">{{ member.name }}</h3>
<p v-if="member.title" class="member-card__title">{{ member.title }}</p>
<p v-if="member.company" class="member-card__company">{{ member.company }}</p>
<div v-if="member.tags && member.tags.length" class="member-card__tags">
<span
v-for="tag in member.tags.slice(0, 3)"
:key="tag"
class="member-card__tag"
>
{{ tag }}
</span>
<span
v-if="member.tags.length > 3"
class="member-card__tag member-card__tag--more"
>
+{{ member.tags.length - 3 }}
</span>
</div>
</div>
<div class="member-card__footer">
<div class="member-card__stats">
<div v-if="member.joinDate" class="member-card__stat">
<span class="member-card__stat-label">Member Since</span>
<span class="member-card__stat-value">{{ member.joinDate }}</span>
</div>
<div v-if="member.connections !== undefined" class="member-card__stat">
<span class="member-card__stat-label">Connections</span>
<span class="member-card__stat-value">{{ member.connections }}</span>
</div>
</div>
<div class="member-card__actions">
<button
class="member-card__action"
@click.stop="$emit('connect', member)"
>
<span>{{ member.connected ? '✓' : '+' }}</span>
{{ member.connected ? 'Connected' : 'Connect' }}
</button>
<button
class="member-card__action member-card__action--secondary"
@click.stop="$emit('message', member)"
>
<span></span>
Message
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
interface Member {
id: string | number
name: string
avatar?: string
title?: string
company?: string
role?: string
status?: 'online' | 'offline' | 'away'
tags?: string[]
joinDate?: string
connections?: number
connected?: boolean
}
interface Props {
member: Member
variant?: 'glass' | 'solid' | 'outline'
featured?: boolean
delay?: number
}
const props = withDefaults(defineProps<Props>(), {
variant: 'glass',
featured: false,
delay: 0
})
defineEmits<{
click: [member: Member]
connect: [member: Member]
message: [member: Member]
}>()
const initials = computed(() => {
const names = props.member.name.split(' ')
return names.map(n => n[0]).join('').toUpperCase().slice(0, 2)
})
const handleImageError = (e: Event) => {
const target = e.target as HTMLImageElement
target.style.display = 'none'
}
</script>
<style scoped lang="scss">
.member-card {
position: relative;
display: flex;
flex-direction: column;
padding: 1.5rem;
border-radius: 16px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
// Glass variant
&--glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
&:hover {
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
transform: translateY(-4px);
}
}
// Solid variant
&--solid {
background: white;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
&:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
transform: translateY(-4px);
}
}
// Outline variant
&--outline {
background: transparent;
border: 2px solid rgba(220, 38, 38, 0.2);
&:hover {
background: rgba(220, 38, 38, 0.05);
border-color: rgba(220, 38, 38, 0.3);
transform: translateY(-4px);
}
}
// Featured state
&--featured {
border: 2px solid #dc2626;
box-shadow: 0 8px 32px rgba(220, 38, 38, 0.15);
&::before {
content: '⭐';
position: absolute;
top: -0.5rem;
right: 1rem;
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: #dc2626;
border-radius: 50%;
font-size: 1rem;
}
}
&__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
&__avatar {
position: relative;
width: 4rem;
height: 4rem;
border-radius: 12px;
overflow: hidden;
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
&__avatar-placeholder {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
font-size: 1.5rem;
font-weight: 600;
color: #dc2626;
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.1) 0%,
rgba(220, 38, 38, 0.05) 100%);
}
&__status-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 1rem;
height: 1rem;
background: #10b981;
border: 2px solid white;
border-radius: 50%;
}
&__role {
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
color: white;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
&__body {
flex: 1;
margin-bottom: 1rem;
}
&__name {
margin: 0 0 0.25rem;
font-size: 1.125rem;
font-weight: 600;
color: #27272a;
}
&__title {
margin: 0 0 0.125rem;
font-size: 0.875rem;
font-weight: 500;
color: #dc2626;
}
&__company {
margin: 0 0 0.75rem;
font-size: 0.875rem;
color: #6b7280;
}
&__tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
&__tag {
padding: 0.25rem 0.5rem;
background: rgba(220, 38, 38, 0.1);
color: #dc2626;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
&--more {
background: rgba(107, 114, 128, 0.1);
color: #6b7280;
}
}
&__footer {
padding-top: 1rem;
border-top: 1px solid rgba(220, 38, 38, 0.1);
}
&__stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 1rem;
}
&__stat {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
&__stat-label {
font-size: 0.75rem;
color: #6b7280;
}
&__stat-value {
font-size: 0.875rem;
font-weight: 600;
color: #27272a;
}
&__actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
}
&__action {
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
padding: 0.5rem;
background: #dc2626;
color: white;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
span {
font-size: 0.875rem;
}
&:hover {
background: #b91c1c;
transform: translateY(-1px);
}
&--secondary {
background: rgba(220, 38, 38, 0.1);
color: #dc2626;
&:hover {
background: rgba(220, 38, 38, 0.2);
}
}
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
.member-card {
&--glass {
background: rgba(30, 30, 30, 0.7);
border-color: rgba(255, 255, 255, 0.1);
}
&--solid {
background: #27272a;
}
&__name {
color: white;
}
&__stat-value {
color: #e5e5e5;
}
}
}
</style>

31
package-lock.json generated
View File

@ -36,7 +36,8 @@
"@types/cookie": "^0.6.0",
"@types/formidable": "^3.4.5",
"@types/mime-types": "^3.0.1",
"@types/node": "^20.0.0"
"@types/node": "^20.0.0",
"sass": "^1.91.0"
}
},
"node_modules/@ampproject/remapping": {
@ -9592,6 +9593,13 @@
"integrity": "sha512-K6acvFaelNxx8wc2VjbIzXKDVB0Khs0QT35U6NkGfTdCmjLNcO2945m7RFNR9/RPVFm48hq7QPzK8uGH18HCGw==",
"license": "MIT"
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/importx": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/importx/-/importx-0.4.4.tgz",
@ -14195,6 +14203,27 @@
"node": ">=10"
}
},
"node_modules/sass": {
"version": "1.91.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.91.0.tgz",
"integrity": "sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",

View File

@ -39,6 +39,7 @@
"@types/cookie": "^0.6.0",
"@types/formidable": "^3.4.5",
"@types/mime-types": "^3.0.1",
"@types/node": "^20.0.0"
"@types/node": "^20.0.0",
"sass": "^1.91.0"
}
}