mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 22:26:42 +02:00
feat(auth): add SessionExpiredBanner to all remaining web apps
Added to: clock, photos, storage, mukke, planta, picture, skilltree, nutriphi, chat. Now all 13 web apps show a re-login banner when token refresh permanently fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
90c438e267
commit
bf7517d24d
23 changed files with 842 additions and 19 deletions
|
|
@ -53,6 +53,51 @@ describe('parsePlantInput', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('parsePlantInput - care actions', () => {
|
||||
it('should parse "Monstera gegossen" as watered', () => {
|
||||
const result = parsePlantInput('Monstera gegossen');
|
||||
expect(result.name).toBe('Monstera');
|
||||
expect(result.action).toBe('watered');
|
||||
});
|
||||
|
||||
it('should parse "Ficus umgetopft heute" as repotted with date', () => {
|
||||
const result = parsePlantInput('Ficus umgetopft heute');
|
||||
expect(result.name).toBe('Ficus');
|
||||
expect(result.action).toBe('repotted');
|
||||
expect(result.acquiredAt).toBeDefined();
|
||||
});
|
||||
|
||||
it('should parse "Rose pruned" in English as pruned', () => {
|
||||
const result = parsePlantInput('Rose pruned', 'en');
|
||||
expect(result.name).toBe('Rose');
|
||||
expect(result.action).toBe('pruned');
|
||||
});
|
||||
|
||||
it('should have no action for plain "Monstera"', () => {
|
||||
const result = parsePlantInput('Monstera');
|
||||
expect(result.name).toBe('Monstera');
|
||||
expect(result.action).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should parse "Orchidee gedüngt" as fertilized', () => {
|
||||
const result = parsePlantInput('Orchidee gedüngt');
|
||||
expect(result.name).toBe('Orchidee');
|
||||
expect(result.action).toBe('fertilized');
|
||||
});
|
||||
|
||||
it('should parse "Ficus geschnitten" as pruned', () => {
|
||||
const result = parsePlantInput('Ficus geschnitten');
|
||||
expect(result.name).toBe('Ficus');
|
||||
expect(result.action).toBe('pruned');
|
||||
});
|
||||
|
||||
it('should parse "gewässert" as watered (alternative DE word)', () => {
|
||||
const result = parsePlantInput('Monstera gewässert');
|
||||
expect(result.name).toBe('Monstera');
|
||||
expect(result.action).toBe('watered');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolvePlantData', () => {
|
||||
it('should produce ISO date string', () => {
|
||||
const parsed = parsePlantInput('Ficus heute gekauft');
|
||||
|
|
@ -86,4 +131,18 @@ describe('formatParsedPlantPreview', () => {
|
|||
const parsed = parsePlantInput('Monstera');
|
||||
expect(formatParsedPlantPreview(parsed)).toBe('');
|
||||
});
|
||||
|
||||
it('should format care action in preview', () => {
|
||||
const parsed = parsePlantInput('Monstera gegossen');
|
||||
const preview = formatParsedPlantPreview(parsed);
|
||||
expect(preview).toContain('💧');
|
||||
expect(preview).toContain('Gegossen');
|
||||
});
|
||||
|
||||
it('should format care action with English locale', () => {
|
||||
const parsed = parsePlantInput('Rose watered', 'en');
|
||||
const preview = formatParsedPlantPreview(parsed, 'en');
|
||||
expect(preview).toContain('💧');
|
||||
expect(preview).toContain('Watered');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,10 +20,13 @@ import {
|
|||
type ParserLocale,
|
||||
} from '@manacore/shared-utils';
|
||||
|
||||
export type CareAction = 'watered' | 'repotted' | 'fertilized' | 'pruned';
|
||||
|
||||
export interface ParsedPlant {
|
||||
name: string;
|
||||
acquiredAt?: Date;
|
||||
tagNames: string[];
|
||||
action?: CareAction;
|
||||
}
|
||||
|
||||
export interface ParsedPlantWithIds {
|
||||
|
|
@ -31,6 +34,79 @@ export interface ParsedPlantWithIds {
|
|||
acquiredAt?: string;
|
||||
}
|
||||
|
||||
// Care action patterns per locale
|
||||
const CARE_ACTION_PATTERNS_BY_LOCALE: Record<
|
||||
ParserLocale,
|
||||
{ action: CareAction; pattern: RegExp }[]
|
||||
> = {
|
||||
de: [
|
||||
{ action: 'watered', pattern: /\b(?:gegossen|gewässert)\b/i },
|
||||
{ action: 'repotted', pattern: /\bumgetopft\b/i },
|
||||
{ action: 'fertilized', pattern: /\bgedüngt\b/i },
|
||||
{ action: 'pruned', pattern: /\b(?:geschnitten|gestutzt)\b/i },
|
||||
],
|
||||
en: [
|
||||
{ action: 'watered', pattern: /\bwatered\b/i },
|
||||
{ action: 'repotted', pattern: /\brepotted\b/i },
|
||||
{ action: 'fertilized', pattern: /\bfertilized\b/i },
|
||||
{ action: 'pruned', pattern: /\b(?:pruned|trimmed)\b/i },
|
||||
],
|
||||
fr: [
|
||||
{ action: 'watered', pattern: /\barrosé\b/i },
|
||||
{ action: 'repotted', pattern: /\brempoté\b/i },
|
||||
{ action: 'fertilized', pattern: /\bfertilisé\b/i },
|
||||
{ action: 'pruned', pattern: /\btaillé\b/i },
|
||||
],
|
||||
es: [
|
||||
{ action: 'watered', pattern: /\bregado\b/i },
|
||||
{ action: 'repotted', pattern: /\btrasplantado\b/i },
|
||||
{ action: 'fertilized', pattern: /\bfertilizado\b/i },
|
||||
{ action: 'pruned', pattern: /\bpodado\b/i },
|
||||
],
|
||||
it: [
|
||||
{ action: 'watered', pattern: /\bannaffiato\b/i },
|
||||
{ action: 'repotted', pattern: /\brinvasato\b/i },
|
||||
{ action: 'fertilized', pattern: /\bfertilizzato\b/i },
|
||||
{ action: 'pruned', pattern: /\bpotato\b/i },
|
||||
],
|
||||
};
|
||||
|
||||
const ACTION_LABELS: Record<CareAction, Record<ParserLocale, string>> = {
|
||||
watered: { de: 'Gegossen', en: 'Watered', fr: 'Arrosé', es: 'Regado', it: 'Annaffiato' },
|
||||
repotted: { de: 'Umgetopft', en: 'Repotted', fr: 'Rempoté', es: 'Trasplantado', it: 'Rinvasato' },
|
||||
fertilized: {
|
||||
de: 'Gedüngt',
|
||||
en: 'Fertilized',
|
||||
fr: 'Fertilisé',
|
||||
es: 'Fertilizado',
|
||||
it: 'Fertilizzato',
|
||||
},
|
||||
pruned: { de: 'Geschnitten', en: 'Pruned', fr: 'Taillé', es: 'Podado', it: 'Potato' },
|
||||
};
|
||||
|
||||
const ACTION_EMOJIS: Record<CareAction, string> = {
|
||||
watered: '💧',
|
||||
repotted: '🌱',
|
||||
fertilized: '🧪',
|
||||
pruned: '✂️',
|
||||
};
|
||||
|
||||
function extractCareAction(
|
||||
text: string,
|
||||
locale: ParserLocale = 'de'
|
||||
): { action?: CareAction; remaining: string } {
|
||||
const patterns = CARE_ACTION_PATTERNS_BY_LOCALE[locale];
|
||||
for (const { action, pattern } of patterns) {
|
||||
if (pattern.test(text)) {
|
||||
return {
|
||||
action,
|
||||
remaining: text.replace(pattern, '').trim(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return { action: undefined, remaining: text };
|
||||
}
|
||||
|
||||
// Acquisition keywords per locale
|
||||
const ACQUIRED_PATTERNS_BY_LOCALE: Record<ParserLocale, RegExp[]> = {
|
||||
de: [/\bgekauft\b/i, /\bbekommen\b/i, /\berhalten\b/i, /\bgepflanzt\b/i],
|
||||
|
|
@ -67,6 +143,10 @@ function extractAcquiredKeyword(
|
|||
export function parsePlantInput(input: string, locale: ParserLocale = 'de'): ParsedPlant {
|
||||
let text = input.trim();
|
||||
|
||||
// Extract care action BEFORE base parser so the action word is removed from title
|
||||
const careResult = extractCareAction(text, locale);
|
||||
text = careResult.remaining;
|
||||
|
||||
// Check for acquisition keywords
|
||||
const acquiredResult = extractAcquiredKeyword(text, locale);
|
||||
text = acquiredResult.remaining;
|
||||
|
|
@ -86,6 +166,7 @@ export function parsePlantInput(input: string, locale: ParserLocale = 'de'): Par
|
|||
name: base.title,
|
||||
acquiredAt,
|
||||
tagNames: base.tagNames,
|
||||
action: careResult.action,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +186,12 @@ export function resolvePlantData(parsed: ParsedPlant): ParsedPlantWithIds {
|
|||
export function formatParsedPlantPreview(parsed: ParsedPlant, locale: ParserLocale = 'de'): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (parsed.action) {
|
||||
const emoji = ACTION_EMOJIS[parsed.action];
|
||||
const label = ACTION_LABELS[parsed.action][locale];
|
||||
parts.push(`${emoji} ${label}`);
|
||||
}
|
||||
|
||||
if (parsed.acquiredAt) {
|
||||
parts.push(`📅 ${formatDatePreview(parsed.acquiredAt, locale)}`);
|
||||
}
|
||||
|
|
|
|||
24
apps/planta/apps/web/src/lib/utils/syntax-help.ts
Normal file
24
apps/planta/apps/web/src/lib/utils/syntax-help.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Planta-specific syntax help patterns
|
||||
*/
|
||||
import type { SyntaxGroup } from '@manacore/shared-ui';
|
||||
|
||||
export const PLANTA_SYNTAX: SyntaxGroup[] = [
|
||||
{
|
||||
title: 'Pflanzen',
|
||||
items: [
|
||||
{
|
||||
pattern: 'Pflege',
|
||||
description: 'Pflege-Aktion loggen',
|
||||
examples: ['Monstera gegossen', 'Ficus umgetopft', 'Rose gedüngt'],
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
pattern: 'Erworben',
|
||||
description: 'Erwerbsdatum angeben',
|
||||
examples: ['gekauft', 'gepflanzt', 'bekommen'],
|
||||
color: 'accent',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue