managarten/apps-archived/memoro/apps/landing/src/components/ChangelogCard.astro
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

136 lines
3.4 KiB
Text

---
interface Props {
title: string;
description: string;
version: string;
releaseDate: Date;
type: 'major' | 'minor' | 'patch';
category: ('feature' | 'improvement' | 'bugfix' | 'security' | 'performance' | 'other')[];
highlights?: string[];
breaking?: boolean;
platforms?: ('web' | 'ios' | 'android' | 'api')[];
slug: string;
lang: string;
}
const {
title,
description,
version,
releaseDate,
type,
category,
highlights = [],
breaking = false,
platforms = [],
slug,
lang,
} = Astro.props;
// Helper function to get version badge color
function getVersionBadgeColor(type: string) {
switch (type) {
case 'major':
return 'bg-danger/30 text-danger border border-danger';
case 'minor':
return 'bg-primary/30 text-primary border border-primary';
case 'patch':
return 'bg-success/30 text-success border border-success';
default:
return 'bg-text-muted/30 text-text-muted border border-text-muted';
}
}
// Helper function to get category icon
function getCategoryIcon(category: string) {
switch (category) {
case 'feature':
return 'feature';
case 'improvement':
return 'improvement';
case 'bugfix':
return 'bugfix';
case 'security':
return 'security';
case 'performance':
return 'performance';
default:
return 'other';
}
}
const formattedDate = releaseDate.toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---
<a href={`/${lang}/changelog/${slug}`} class="block">
<article
class="bg-background-card rounded-xl overflow-hidden group relative hover:bg-background-cardHover transition-all duration-DEFAULT border border-border hover:border-border-hover"
>
<div class="relative p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<span class={`px-2 py-1 rounded-full text-xs font-medium ${getVersionBadgeColor(type)}`}>
v{version}
</span>
{
breaking && (
<span class="px-2 py-1 rounded-full text-xs font-medium bg-danger/30 text-danger border border-danger">
Breaking Changes
</span>
)
}
</div>
<time class="text-sm text-text-muted" datetime={releaseDate.toISOString()}>
{formattedDate}
</time>
</div>
<h3 class="text-xl font-medium text-text-primary opacity-90 group-hover:opacity-100 mb-2">
{title}
</h3>
<p class="text-text-secondary opacity-80 group-hover:opacity-90 mb-4">
{description}
</p>
{
highlights && highlights.length > 0 && (
<div class="mb-4">
<ul class="space-y-1">
{highlights.slice(0, 3).map((highlight) => (
<li class="text-sm text-text-muted flex items-start">
<span class="text-primary mr-2">•</span>
<span>{highlight}</span>
</li>
))}
</ul>
</div>
)
}
<div class="flex items-center justify-between">
<div class="flex flex-wrap gap-2">
{
category
.slice(0, 3)
.map((cat) => (
<span class="text-xs text-text-muted bg-background-page px-2 py-1 rounded">
{getCategoryIcon(cat)}
</span>
))
}
</div>
<span
class="text-primary text-sm font-medium flex items-center gap-1 group-hover:translate-x-1 transition-all duration-DEFAULT"
>
{lang === 'de' ? 'Mehr erfahren' : 'Learn more'}
<span aria-hidden="true" class="transition-transform duration-DEFAULT">→</span>
</span>
</div>
</div>
</article>
</a>