feat: preferences, exclusion-aware planner, /onboarding (PRD §5.1, §3)
Preferences (PRD §5.1) - @ketopath/shared: zod schemas + EXCLUSION_GROUPS, CUISINE_TAGS, COOKING_TIMES, FASTING_PROTOCOL_VALUES - apps/api: GET/PATCH /me/preferences (exclusions, cuisinePreferences, cookingTime, fastingProtocol) - apps/web: PreferencesForm chip-based + PreferencesSection wired into /profile so existing users can edit - Plan generation now aggregates Ingredient.exclusionGroups → Recipe exclusionTags so matchMeals filters out excluded recipes Onboarding (PRD §3) - /onboarding multi-step (profile → preferences → done) with smart-jump: starts at the first incomplete step - ProfileForm gains optional onSaved/saveLabel for reuse outside /profile - Redirect chain updated: /welcome accept → /onboarding (was /profile); /plan, /tracking, /shopping redirect missing profiles to /onboarding - /profile remains the editing surface for returning users i18n - New Onboarding + Preferences namespaces (it) Smoke tested end-to-end: selected fish exclusion, regenerated the plan, verified Salmone/Branzino/Tonno are filtered out and unaffected recipes (Frittata, Pollo, Mandorle, Polpette) remain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
cf7361f735
commit
624152b29b
18 files changed
+864
-22
No files matched your search
@@ -9,6 +9,8 @@ import { getServerSession } from '@/lib/auth';
|
||||
import { fetchProfile } from './actions';
|
||||
import { fetchNotificationConfig } from './notifications-actions';
|
||||
import { NotificationsPanel } from './notifications-panel';
|
||||
import { fetchPreferences } from './preferences-actions';
|
||||
import { PreferencesSection } from './preferences-section';
|
||||
import { ProfileForm } from './profile-form';
|
||||
|
||||
export default async function ProfilePage({ params: { locale } }: { params: { locale: string } }) {
|
||||
@@ -24,16 +26,21 @@ export default async function ProfilePage({ params: { locale } }: { params: { lo
|
||||
|
||||
const profile = (await fetchProfile()) as Parameters<typeof ProfileForm>[0]['initial'];
|
||||
const notifications = await fetchNotificationConfig();
|
||||
const preferences = await fetchPreferences();
|
||||
|
||||
return <ProfilePageContent initial={profile} notifications={notifications} />;
|
||||
return (
|
||||
<ProfilePageContent initial={profile} notifications={notifications} preferences={preferences} />
|
||||
);
|
||||
}
|
||||
|
||||
function ProfilePageContent({
|
||||
initial,
|
||||
notifications,
|
||||
preferences,
|
||||
}: {
|
||||
initial: Parameters<typeof ProfileForm>[0]['initial'];
|
||||
notifications: Awaited<ReturnType<typeof fetchNotificationConfig>>;
|
||||
preferences: Awaited<ReturnType<typeof fetchPreferences>>;
|
||||
}) {
|
||||
const t = useTranslations('Profile');
|
||||
|
||||
@@ -56,9 +63,15 @@ function ProfilePageContent({
|
||||
<ProfileForm initial={initial} />
|
||||
</div>
|
||||
|
||||
<div className="rule animate-rule-in my-16 [animation-delay:480ms]" />
|
||||
<div className="rule animate-rule-in my-16 [animation-delay:420ms]" />
|
||||
|
||||
<div className="animate-fade-up [animation-delay:540ms]">
|
||||
<div className="animate-fade-up [animation-delay:480ms]">
|
||||
<PreferencesSection initial={preferences} />
|
||||
</div>
|
||||
|
||||
<div className="rule animate-rule-in my-16 [animation-delay:600ms]" />
|
||||
|
||||
<div className="animate-fade-up [animation-delay:660ms]">
|
||||
<NotificationsPanel initial={notifications} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in new issue
Block a user