feat(contacts): add complete contacts app with backend, web, and landing

- Add NestJS backend with CRUD endpoints for contacts, groups, tags, notes, and activities
- Add SvelteKit web app with auth pages (login, register, forgot-password)
- Add Astro landing page
- Add ContactsLogo to shared-branding package
- Add contacts to MANA_APPS configuration
- Update shared-storage with contacts bucket support
- Update environment scripts and Docker configuration for contacts database
- Integrate mana-core-auth for JWT authentication
- Follow existing app architecture patterns (route groups, PillNavigation)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-02 13:17:42 +01:00
parent 00176a25e0
commit 45d70150f4
76 changed files with 3812 additions and 1 deletions

View file

@ -45,6 +45,9 @@ const moodlitSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill
// Nutriphi icon (nutrition/heart with gradient)
const nutriphiSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#nutriGrad)"/><path d="M512 760C512 760 280 600 280 420C280 340 344 280 424 280C472 280 512 308 512 308C512 308 552 280 600 280C680 280 744 340 744 420C744 600 512 760 512 760Z" fill="white"/><path d="M512 280V200" stroke="white" stroke-width="24" stroke-linecap="round"/><path d="M512 200C512 200 560 160 600 180" stroke="white" stroke-width="24" stroke-linecap="round"/><defs><linearGradient id="nutriGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#10b981"/><stop offset="1" stop-color="#059669"/></linearGradient></defs></svg>`;
// Contacts icon (address book/person with gradient)
const contactsSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#contactsGrad)"/><circle cx="512" cy="380" r="100" fill="white"/><path d="M320 620C320 540 408 480 512 480C616 480 704 540 704 620V680C704 702.091 685.091 720 663 720H361C338.909 720 320 702.091 320 680V620Z" fill="white"/><rect x="240" y="300" width="24" height="80" rx="12" fill="white" fill-opacity="0.6"/><rect x="240" y="420" width="24" height="80" rx="12" fill="white" fill-opacity="0.6"/><rect x="240" y="540" width="24" height="80" rx="12" fill="white" fill-opacity="0.6"/><defs><linearGradient id="contactsGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#3b82f6"/><stop offset="1" stop-color="#2563eb"/></linearGradient></defs></svg>`;
/**
* App icons as data URLs
* Use these directly in <img src={APP_ICONS.memoro}> or CSS background-image
@ -62,6 +65,7 @@ export const APP_ICONS = {
wisekeep: svgToDataUrl(wisekeepSvg),
moodlit: svgToDataUrl(moodlitSvg),
nutriphi: svgToDataUrl(nutriphiSvg),
contacts: svgToDataUrl(contactsSvg),
} as const;
export type AppIconId = keyof typeof APP_ICONS;

View file

@ -131,6 +131,19 @@ export const APP_BRANDING: Record<AppId, AppBranding> = {
logoStroke: true,
logoStrokeWidth: 1.5,
},
contacts: {
id: 'contacts',
name: 'Contacts',
tagline: 'Contact Management',
primaryColor: '#3b82f6',
secondaryColor: '#60a5fa',
// Users/contacts icon
logoPath:
'M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z',
logoViewBox: '0 0 24 24',
logoStroke: true,
logoStrokeWidth: 1.5,
},
};
/**

View file

@ -24,6 +24,7 @@ export {
PresiLogo,
NutriPhiLogo,
ZitareLogo,
ContactsLogo,
} from './logos';
// Configuration

View file

@ -0,0 +1,13 @@
<script lang="ts">
import AppLogo from '../AppLogo.svelte';
interface Props {
size?: number;
color?: string;
class?: string;
}
let { size = 55, color, class: className = '' }: Props = $props();
</script>
<AppLogo app="contacts" {size} {color} class={className} />

View file

@ -11,3 +11,4 @@ export { default as ChatLogo } from './ChatLogo.svelte';
export { default as PresiLogo } from './PresiLogo.svelte';
export { default as NutriPhiLogo } from './NutriPhiLogo.svelte';
export { default as ZitareLogo } from './ZitareLogo.svelte';
export { default as ContactsLogo } from './ContactsLogo.svelte';

View file

@ -180,6 +180,22 @@ export const MANA_APPS: ManaApp[] = [
status: 'development',
archived: true,
},
{
id: 'contacts',
name: 'ManaContacts',
description: {
de: 'Kontaktverwaltung',
en: 'Contact Management',
},
longDescription: {
de: 'Verwalte deine Kontakte übersichtlich mit Gruppen, Tags und Notizen.',
en: 'Manage your contacts clearly with groups, tags, and notes.',
},
icon: APP_ICONS.contacts,
color: '#3b82f6',
comingSoon: false,
status: 'development',
},
];
/**
@ -260,6 +276,7 @@ export const APP_URLS: Record<AppIconId, { dev: string; prod: string }> = {
manacore: { dev: 'http://localhost:5173', prod: 'https://manacore.app' },
mana: { dev: 'http://localhost:5173', prod: 'https://manacore.app' },
moodlit: { dev: 'http://localhost:5183', prod: 'https://moodlit.manacore.app' },
contacts: { dev: 'http://localhost:5184', prod: 'https://contacts.manacore.app' },
};
/**

View file

@ -11,7 +11,8 @@ export type AppId =
| 'presi'
| 'nutriphi'
| 'zitare'
| 'picture';
| 'picture'
| 'contacts';
/**
* App branding configuration

View file

@ -29,6 +29,7 @@ The following buckets are automatically created:
| `presi-storage` | Presi | Presentation slides |
| `calendar-storage` | Calendar | Calendar attachments |
| `contacts-storage` | Contacts | Contact avatars/files |
| `storage-storage` | Storage | Cloud drive files |
## Usage
@ -89,6 +90,7 @@ import {
createPresiStorage,
createCalendarStorage,
createContactsStorage,
createStorageStorage,
} from '@manacore/shared-storage';
```

View file

@ -119,3 +119,13 @@ export function createCalendarStorage(): StorageClient {
export function createContactsStorage(): StorageClient {
return createStorageClient({ name: BUCKETS.CONTACTS });
}
/**
* Create a storage client for the Storage project (cloud drive)
*/
export function createStorageStorage(publicUrl?: string): StorageClient {
return createStorageClient({
name: BUCKETS.STORAGE,
publicUrl: publicUrl ?? process.env.STORAGE_S3_PUBLIC_URL,
});
}

View file

@ -12,6 +12,7 @@ export {
createPresiStorage,
createCalendarStorage,
createContactsStorage,
createStorageStorage,
} from './factory.js';
// Utilities

View file

@ -83,6 +83,7 @@ export const BUCKETS = {
PRESI: 'presi-storage',
CALENDAR: 'calendar-storage',
CONTACTS: 'contacts-storage',
STORAGE: 'storage-storage',
} as const;
export type BucketName = (typeof BUCKETS)[keyof typeof BUCKETS];