mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 15:46:41 +02:00
feat(contacts): add onboarding wizard — sort preference, import option, tips
3-step onboarding using shared-app-onboarding package (same as calendar): 1. Sort order: first name vs last name 2. Import: Google, vCard/CSV, or skip — navigates to import page on completion 3. Tips: self-contact card, quick input, focus mode, tags Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
78526f1d92
commit
16fe3aa61e
4 changed files with 572 additions and 446 deletions
|
|
@ -36,6 +36,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@manacore/shared-api-client": "workspace:*",
|
"@manacore/shared-api-client": "workspace:*",
|
||||||
|
"@manacore/shared-app-onboarding": "workspace:*",
|
||||||
"@manacore/shared-auth": "workspace:*",
|
"@manacore/shared-auth": "workspace:*",
|
||||||
"@manacore/shared-auth-ui": "workspace:*",
|
"@manacore/shared-auth-ui": "workspace:*",
|
||||||
"@manacore/shared-branding": "workspace:*",
|
"@manacore/shared-branding": "workspace:*",
|
||||||
|
|
|
||||||
112
apps/contacts/apps/web/src/lib/stores/app-onboarding.svelte.ts
Normal file
112
apps/contacts/apps/web/src/lib/stores/app-onboarding.svelte.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { createAppOnboardingStore, type AppOnboardingStep } from '@manacore/shared-app-onboarding';
|
||||||
|
import { userSettings } from './user-settings.svelte';
|
||||||
|
import { contactsFilterStore } from './filter.svelte';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contacts-specific onboarding steps
|
||||||
|
*/
|
||||||
|
const contactsOnboardingSteps: AppOnboardingStep[] = [
|
||||||
|
{
|
||||||
|
id: 'sortOrder',
|
||||||
|
type: 'select',
|
||||||
|
question: 'Wie sortierst du Kontakte?',
|
||||||
|
description: 'Bestimmt die Reihenfolge deiner Kontaktliste.',
|
||||||
|
emoji: '🔤',
|
||||||
|
gradient: { from: 'blue-500', to: 'blue-700' },
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'firstName',
|
||||||
|
label: 'Vorname',
|
||||||
|
description: 'Anna, Max, Till ...',
|
||||||
|
emoji: '👤',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lastName',
|
||||||
|
label: 'Nachname',
|
||||||
|
description: 'Müller, Schmidt, Weber ...',
|
||||||
|
emoji: '📋',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: 'firstName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'importSource',
|
||||||
|
type: 'select',
|
||||||
|
question: 'Kontakte importieren?',
|
||||||
|
description: 'Du kannst jederzeit später über Daten importieren.',
|
||||||
|
emoji: '📥',
|
||||||
|
gradient: { from: 'indigo-500', to: 'indigo-700' },
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'google',
|
||||||
|
label: 'Google Kontakte',
|
||||||
|
description: 'Über dein Google-Konto',
|
||||||
|
emoji: '🔗',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'file',
|
||||||
|
label: 'Datei (vCard/CSV)',
|
||||||
|
description: 'Aus einer exportierten Datei',
|
||||||
|
emoji: '📄',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'later',
|
||||||
|
label: 'Später',
|
||||||
|
description: 'Erstmal ohne Import starten',
|
||||||
|
emoji: '⏭️',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: 'later',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'welcome',
|
||||||
|
type: 'info',
|
||||||
|
question: 'Deine Kontakte sind bereit!',
|
||||||
|
description: 'Hier sind einige Tipps für den Start:',
|
||||||
|
emoji: '🎉',
|
||||||
|
gradient: { from: 'primary', to: 'primary/70' },
|
||||||
|
bullets: [
|
||||||
|
'Deine eigene Kontaktkarte ist schon angelegt — klick sie an und füll sie aus',
|
||||||
|
'Nutze die Schnelleingabe unten, um Kontakte per Text zu erstellen',
|
||||||
|
'Drücke "F" für den Fokus-Modus ohne Ablenkungen',
|
||||||
|
'Tagge Kontakte für bessere Organisation',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contacts app onboarding store
|
||||||
|
*
|
||||||
|
* Usage in components:
|
||||||
|
* ```svelte
|
||||||
|
* <script>
|
||||||
|
* import { contactsOnboarding } from '$lib/stores/app-onboarding.svelte';
|
||||||
|
* import { MiniOnboardingModal } from '@manacore/shared-app-onboarding';
|
||||||
|
* </script>
|
||||||
|
*
|
||||||
|
* {#if contactsOnboarding.shouldShow}
|
||||||
|
* <MiniOnboardingModal
|
||||||
|
* store={contactsOnboarding}
|
||||||
|
* appName="Kontakte"
|
||||||
|
* appEmoji="👥"
|
||||||
|
* />
|
||||||
|
* {/if}
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const contactsOnboarding = createAppOnboardingStore({
|
||||||
|
appId: 'contacts',
|
||||||
|
steps: contactsOnboardingSteps,
|
||||||
|
userSettings,
|
||||||
|
onComplete: async (preferences) => {
|
||||||
|
// Apply sort order preference
|
||||||
|
const sortOrder = preferences.sortOrder as string;
|
||||||
|
if (sortOrder === 'firstName' || sortOrder === 'lastName') {
|
||||||
|
contactsFilterStore.setSortField(sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import navigation is handled by the layout after onboarding completes
|
||||||
|
},
|
||||||
|
onSkip: async () => {
|
||||||
|
// Defaults are sensible, nothing to do
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -42,6 +42,8 @@
|
||||||
formatParsedContactPreview,
|
formatParsedContactPreview,
|
||||||
} from '$lib/utils/contact-parser';
|
} from '$lib/utils/contact-parser';
|
||||||
import ContactsToolbar from '$lib/components/ContactsToolbar.svelte';
|
import ContactsToolbar from '$lib/components/ContactsToolbar.svelte';
|
||||||
|
import { contactsOnboarding } from '$lib/stores/app-onboarding.svelte';
|
||||||
|
import { MiniOnboardingModal } from '@manacore/shared-app-onboarding';
|
||||||
|
|
||||||
// Tags state for Quick-Create
|
// Tags state for Quick-Create
|
||||||
let availableTags = $state<{ id: string; name: string }[]>([]);
|
let availableTags = $state<{ id: string; name: string }[]>([]);
|
||||||
|
|
@ -243,6 +245,22 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigate to import page after onboarding if user chose to import
|
||||||
|
let previousOnboardingShow = true;
|
||||||
|
$effect(() => {
|
||||||
|
const showing = contactsOnboarding.shouldShow;
|
||||||
|
if (previousOnboardingShow && !showing) {
|
||||||
|
// Onboarding just closed
|
||||||
|
const importSource = contactsOnboarding.preferences.importSource as string;
|
||||||
|
if (importSource === 'google') {
|
||||||
|
goto('/data?tab=import&source=google');
|
||||||
|
} else if (importSource === 'file') {
|
||||||
|
goto('/data?tab=import&source=file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousOnboardingShow = showing;
|
||||||
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Initialize auth and redirect if not authenticated
|
// Initialize auth and redirect if not authenticated
|
||||||
await authStore.initialize();
|
await authStore.initialize();
|
||||||
|
|
@ -367,6 +385,11 @@
|
||||||
{#if newContactModalStore.isOpen}
|
{#if newContactModalStore.isOpen}
|
||||||
<NewContactModal onClose={() => newContactModalStore.close()} />
|
<NewContactModal onClose={() => newContactModalStore.close()} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Onboarding Modal -->
|
||||||
|
{#if contactsOnboarding.shouldShow}
|
||||||
|
<MiniOnboardingModal store={contactsOnboarding} appName="Kontakte" appEmoji="👥" />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</SplitPaneContainer>
|
</SplitPaneContainer>
|
||||||
|
|
||||||
|
|
|
||||||
882
pnpm-lock.yaml
generated
882
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue