Skip to content

Commit

Permalink
Merge pull request #7 from timia2109/feature/meal-plan-creation
Browse files Browse the repository at this point in the history
Feature/meal plan creation
  • Loading branch information
timia2109 authored Aug 5, 2024
2 parents 6592511 + ea1181a commit 31bb3a3
Show file tree
Hide file tree
Showing 55 changed files with 749 additions and 214 deletions.
4 changes: 2 additions & 2 deletions src/actions/acceptInvitationAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { getInvitation } from "@/dal/user/getInvitation";
import { redeemMealPlanInvitation } from "@/dal/user/redeemMealPlanInvitation";
import { getUserId } from "@/functions/user/getUserId";
import { redirectWithLocale } from "@/functions/user/redirectWithLocale";
import { redirectRoute } from "@/routes";

export async function acceptInvitationAction(invitationCode: string) {
const userId = await getUserId(null);
Expand All @@ -14,5 +14,5 @@ export async function acceptInvitationAction(invitationCode: string) {
}

await redeemMealPlanInvitation(invitation.invitation, userId);
redirectWithLocale(`/mealPlan/${invitation.invitation.mealPlan.id}`);
redirectRoute("mealPlan", invitation.invitation.mealPlan.id);
}
19 changes: 19 additions & 0 deletions src/actions/createMealPlanAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use server";

import { createMealPlan } from "@/dal/mealPlans/createMealPlan";
import { getUserId } from "@/functions/user/getUserId";
import { revalidateRoute } from "@/routes";
import { z } from "zod";
import { zfd } from "zod-form-data";

const schema = zfd.formData({
mealPlanName: z.string(),
});

export async function createMealPlanAction(formData: FormData) {
const { mealPlanName } = schema.parse(formData);
const user = await getUserId(null);

await createMealPlan(user, mealPlanName, false);
revalidateRoute("manage");
}
14 changes: 10 additions & 4 deletions src/actions/leaveMealPlanAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import { leaveMealPlan } from "@/dal/mealPlans/leaveMealPlan";
import { getUserId } from "@/functions/user/getUserId";
import { getLinkWithLocale } from "@/functions/user/redirectWithLocale";
import { revalidatePath } from "next/cache";
import { revalidateRoute } from "@/routes";
import { z } from "zod";
import { zfd } from "zod-form-data";

export async function leaveMealPlanAction(mealPlanId: string) {
const schema = zfd.formData({
mealPlanId: z.string().max(25),
});

export async function leaveMealPlanAction(formData: FormData) {
const { mealPlanId } = schema.parse(formData);
const user = await getUserId();
if (user == null) return;

await leaveMealPlan(mealPlanId, user);
revalidatePath(getLinkWithLocale("/manage"));
revalidateRoute("manage");
}
26 changes: 26 additions & 0 deletions src/actions/renameMealPlanAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use server";

import { getMealPlan } from "@/dal/mealPlans/getMealPlan";
import { renameMealPlan } from "@/dal/mealPlans/renameMealPlan";
import { getUserId } from "@/functions/user/getUserId";
import { revalidateRoute } from "@/routes";
import { z } from "zod";
import { zfd } from "zod-form-data";

const schema = zfd.formData({
mealPlanName: z.string(),
mealPlanId: z.string().length(25),
});

export async function renameMealPlanAction(formData: FormData) {
const { mealPlanName, mealPlanId } = schema.parse(formData);
const user = await getUserId(null);

const mealPlan = await getMealPlan(user, mealPlanId);
if (mealPlan === null) {
throw new Error("Meal plan not found");
}

await renameMealPlan(mealPlanId, mealPlanName);
revalidateRoute("manage");
}
7 changes: 7 additions & 0 deletions src/actions/setCalendarLayoutAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use server";

import { setPreference } from "@/functions/user/preferences";

export async function setCalendarLayoutAction(layout: string) {
setPreference("calendarLayout", layout);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

import { setMealPlanAsDefault } from "@/dal/mealPlans/setMealPlanAsDefault";
import { getUserId } from "@/functions/user/getUserId";
import { getLinkWithLocale } from "@/functions/user/redirectWithLocale";
import { revalidatePath } from "next/cache";
import { revalidateRoute } from "@/routes";

export async function setDefaultMealPlan(mealPlanId: string) {
export async function setDefaultMealPlanAction(mealPlanId: string) {
const userId = await getUserId();
if (userId == null)
return {
message: "Login expected",
};

await setMealPlanAsDefault(userId, mealPlanId);
revalidatePath(getLinkWithLocale("/manage"));
revalidateRoute("manage");
}
7 changes: 7 additions & 0 deletions src/actions/setThemeAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use server";

import { setPreference } from "@/functions/user/preferences";

export async function setThemeAction(theme: string) {
setPreference("theme", theme);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const schema = zfd.formData({
meal: z.string(),
});

export async function updateMealEntry(formData: FormData) {
export async function updateMealEntryAction(formData: FormData) {
const data = schema.safeParse(formData);
if (!data.success) {
return {
Expand Down
8 changes: 3 additions & 5 deletions src/app/[locale]/(landing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { auth } from "@/auth";
import { InvitationHeader } from "@/components/invitation/InvitationHeader";
import { getInvitation } from "@/dal/user/getInvitation";
import { redirectWithLocale } from "@/functions/user/redirectWithLocale";
import { getScopedI18n } from "@/locales/server";
import { redirectRoute } from "@/routes";
import { SignInButtons } from "./SignInButtons";

type Props = {
Expand All @@ -20,8 +20,7 @@ async function handleInvitation(
const invitation = await getInvitation(invitationCode);

if (isSignedIn) {
// Redirect to join page
redirectWithLocale(`/mealPlan/join/${invitationCode}`);
redirectRoute("join", invitationCode);
}

return invitation;
Expand All @@ -35,8 +34,7 @@ export default async function LandingPage({ searchParams }: Props) {
searchParams.invitationCode,
currentUser != null
);
if (currentUser != null && invitation == null)
redirectWithLocale(`/mealPlan`);
if (currentUser != null && invitation == null) redirectRoute("mealPlan");

return (
<div className="p-12">
Expand Down
13 changes: 9 additions & 4 deletions src/app/[locale]/(userArea)/manage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { MealPlanEntry } from "@/components/mealEntries/MealPlanEntry";
import { Heading } from "@/components/common/Heading";
import { CreateMealPlanButton } from "@/components/mealPlan/CreateMealPlanButton";
import { MealPlanComponent } from "@/components/mealPlan/MealPlanComponent";
import { getMealPlans } from "@/dal/mealPlans/getMealPlans";
import { getUserId } from "@/functions/user/getUserId";
import { getScopedI18n } from "@/locales/server";
Expand All @@ -9,12 +11,15 @@ export default async function ManageMealPlansPage() {
const t = await getScopedI18n("manageMealPlans");

return (
<div className="container mx-auto">
<h1 className="mb-1 text-3xl font-extrabold">{t("manage")}</h1>
<div className="container mx-1 md:mx-auto">
<Heading>{t("manage")}</Heading>
<title>{t("manage")}</title>
<div className="mb-3">
<CreateMealPlanButton />
</div>
<div>
{mealPlanAssignments.map((assignment) => (
<MealPlanEntry
<MealPlanComponent
key={assignment.mealPlanId}
mealPlanAssignment={assignment}
mealPlan={assignment.mealPlan}
Expand Down
8 changes: 4 additions & 4 deletions src/app/[locale]/(userArea)/mealPlan/[[...selector]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MealPlanEntry } from "@/components/mealEntries/MealPlanEntry";
import { MealPlanContainer } from "@/components/mealPlan/MealPlanContainer";
import { MealPlanCalender } from "@/components/mealEntries/MealPlanCalender";
import { MealPlanComponent } from "@/components/mealPlan/MealPlanComponent";
import { getMealPlan } from "@/dal/mealPlans/getMealPlan";
import { createKeyDate } from "@/functions/dateTime/createKeyDate";
import { getUserId } from "@/functions/user/getUserId";
Expand All @@ -22,8 +22,8 @@ export default async function MealPlanPage({ params }: Props) {

return (
<div className="container mx-auto">
<MealPlanEntry mealPlan={mealPlan} withUsers />
<MealPlanContainer keyDate={keyDate} mealPlan={mealPlan} />
<MealPlanComponent mealPlan={mealPlan} withUsers />
<MealPlanCalender keyDate={keyDate} mealPlan={mealPlan} />
</div>
);
}
66 changes: 45 additions & 21 deletions src/app/[locale]/(userArea)/mealPlan/invite/[mealPlanId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Heading } from "@/components/common/Heading";
import { ProfileImage } from "@/components/common/ProfileImage";
import { SocialShareLinks } from "@/components/common/SocialShareLinks";
import { createMealPlanInvitation } from "@/dal/mealPlans/createMealPlanInvitation";
import { getMealPlan } from "@/dal/mealPlans/getMealPlan";
import { buildUrl } from "@/functions/buildUrl";
import { getMealPlanUsers } from "@/dal/mealPlans/getMealPlanUsers";
import { getMealPlanLabel } from "@/functions/user/getMealPlanLabel";
import { getUserId } from "@/functions/user/getUserId";
import { getScopedI18n } from "@/locales/server";
import { getI18n, getScopedI18n } from "@/locales/server";
import { getRouteUrl } from "@/routes";
import { notFound } from "next/navigation";

type Props = {
Expand All @@ -22,27 +25,48 @@ export default async function InvitePage({ params }: Props) {

const invitation = await createMealPlanInvitation(params.mealPlanId, userId);

const invitationLink = buildUrl({
path: "/",
search: {
invitationCode: invitation.invitationCode,
},
});
const invitationLink = getRouteUrl(
"invitationLink",
invitation.invitationCode
);

const mealPlanTitle = await getMealPlanLabel(mealPlan, await getI18n());
const users = await getMealPlanUsers(params.mealPlanId);

return (
<div className="container mx-auto">
<Heading>{t("invite", mealPlan)}</Heading>
<p>{t("inviteMessage")}</p>
<p>{t("inviteHint")}</p>
<div className="cursor-grab select-all rounded-sm bg-indigo-50 px-1 text-lg text-indigo-950">
<code>{invitationLink.toString()}</code>
</div>
<div className="mt-3">
<SocialShareLinks
messagePayload={t("shareText", {
invitationLink: invitationLink.toString(),
})}
/>
<div className="container mx-1 md:mx-auto">
<title>{t("invite", { title: mealPlanTitle })}</title>
<div className="grid md:grid-cols-2">
<div className="order-2 md:order-1">
<Heading>{mealPlanTitle}</Heading>
<p className="text-xl">{t("members")}</p>
<div className="py-3 pe-3">
{users.map((user) => (
<div
key={user.id}
className="flex items-center justify-start gap-2 border-e border-s border-t border-accent p-3 first:rounded-t last:rounded-b last:border-b"
>
<ProfileImage user={user} />
{user.name}
</div>
))}
</div>
</div>
<div className="order-1 md:order-2">
<Heading>{t("invite", { title: mealPlanTitle })}</Heading>
<p>{t("inviteMessage")}</p>
<p>{t("inviteHint")}</p>
<div className="cursor-grab select-all rounded-sm bg-indigo-50 px-1 text-lg text-indigo-950">
<code>{invitationLink.toString()}</code>
</div>
<div className="mt-3">
<SocialShareLinks
messagePayload={t("shareText", {
invitationLink: invitationLink.toString(),
})}
/>
</div>
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { InvitationHeader } from "@/components/invitation/InvitationHeader";
import { getInvitation } from "@/dal/user/getInvitation";
import { getMealPlanLabel } from "@/functions/user/getMealPlanLabel";
import { getUserId } from "@/functions/user/getUserId";
import { redirectWithLocale } from "@/functions/user/redirectWithLocale";
import { getI18n, getScopedI18n } from "@/locales/server";
import { redirectRoute } from "@/routes";
import { AcceptButton } from "./AcceptButton";

type Props = {
Expand All @@ -19,7 +19,7 @@ export default async function InvitationPage({ params }: Props) {

// Redirect if joined
if (invitation.result === "JOINED") {
redirectWithLocale(`/mealPlan/${invitation.invitation.mealPlan.id}`);
redirectRoute("mealPlan", invitation.invitation.mealPlan.id);
}

const t = await getScopedI18n("invitation");
Expand Down
44 changes: 44 additions & 0 deletions src/app/[locale]/(userArea)/profile/CalendarLayoutSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { setCalendarLayoutAction } from "@/actions/setCalendarLayoutAction";
import type { CalendarLayout } from "@/functions/user/preferences";
import { useScopedI18n } from "@/locales/client";
import classNames from "classnames";
import type { FC } from "react";

const CalendarPreference: FC<{
currentValue: CalendarLayout;
value: CalendarLayout;
label: string;
}> = ({ value, label, currentValue }) => (
<button
onClick={() => setCalendarLayoutAction(value)}
className={classNames({
"btn join-item": true,
"btn-active": value == currentValue,
})}
>
{label}
</button>
);

export const CalendarLayoutSelection: FC<{ current: CalendarLayout }> = ({
current,
}) => {
const t = useScopedI18n("profile");

return (
<div className="join join-vertical">
<CalendarPreference
currentValue={current}
value="RESPONSIVE"
label={t("calendarLayoutResponsive")}
/>
<CalendarPreference
currentValue={current}
value="FIXED"
label={t("calendarLayoutFixed")}
/>
</div>
);
};
19 changes: 19 additions & 0 deletions src/app/[locale]/(userArea)/profile/SelectableTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import { setThemeAction } from "@/actions/setThemeAction";
import type { FC } from "react";

export const SelectableTheme: FC<{ theme: string; active: boolean }> = ({
theme,
active,
}) => (
<input
type="radio"
name="selectedTheme"
className="theme-controller btn join-item"
aria-label={theme}
checked={active}
onChange={() => setThemeAction(theme)}
value={theme}
/>
);
Loading

0 comments on commit 31bb3a3

Please sign in to comment.