-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from timia2109/feature/create-invitations
Feature/create invitations
- Loading branch information
Showing
26 changed files
with
619 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
prisma/migrations/20240803161929_add_expiration_to_invitation/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
Warnings: | ||
- Added the required column `expiresAt` to the `MealPlanInvite` table without a default value. This is not possible if the table is not empty. | ||
*/ | ||
-- AlterTable | ||
ALTER TABLE `MealPlanInvite` ADD COLUMN `expiresAt` DATETIME(3) NOT NULL; |
17 changes: 17 additions & 0 deletions
17
prisma/migrations/20240804103216_add_relation_for_invitation/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
Warnings: | ||
- Made the column `createdByUserId` on table `MealPlanInvite` required. This step will fail if there are existing NULL values in that column. | ||
*/ | ||
-- DropForeignKey | ||
ALTER TABLE `MealPlanInvite` DROP FOREIGN KEY `MealPlanInvite_createdByUserId_fkey`; | ||
|
||
-- AlterTable | ||
ALTER TABLE `MealPlanInvite` MODIFY `createdByUserId` VARCHAR(191) NOT NULL; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE `MealPlanInvite` ADD CONSTRAINT `MealPlanInvite_createdByUserId_fkey` FOREIGN KEY (`createdByUserId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE `MealPlanInvite` ADD CONSTRAINT `MealPlanInvite_mealPlanId_fkey` FOREIGN KEY (`mealPlanId`) REFERENCES `MealPlan`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
"use server"; | ||
|
||
import { getInvitation } from "@/dal/user/getInvitation"; | ||
import { redeemMealPlanInvitation } from "@/dal/user/redeemMealPlanInvitation"; | ||
import { getUserId } from "@/functions/user/getUserId"; | ||
import { redirectWithLocale } from "@/functions/user/redirectWithLocale"; | ||
|
||
export async function acceptInvitationAction(invitationCode: string) { | ||
const userId = await getUserId(null); | ||
const invitation = await getInvitation(invitationCode, userId); | ||
|
||
if (invitation.result != "OK") { | ||
throw new Error("Invitation not found"); | ||
} | ||
|
||
await redeemMealPlanInvitation(invitation.invitation, userId); | ||
redirectWithLocale(`/mealPlan/${invitation.invitation.mealPlan.id}`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 48 additions & 2 deletions
50
src/app/[locale]/(userArea)/mealPlan/invite/[mealPlanId]/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,49 @@ | ||
export default function InvitePage() { | ||
return <div>InvitePage</div>; | ||
import { Heading } from "@/components/common/Heading"; | ||
import { SocialShareLinks } from "@/components/common/SocialShareLinks"; | ||
import { createMealPlanInvitation } from "@/dal/mealPlans/createMealPlanInvitation"; | ||
import { getMealPlan } from "@/dal/mealPlans/getMealPlan"; | ||
import { buildUrl } from "@/functions/buildUrl"; | ||
import { getUserId } from "@/functions/user/getUserId"; | ||
import { getScopedI18n } from "@/locales/server"; | ||
import { notFound } from "next/navigation"; | ||
|
||
type Props = { | ||
params: { | ||
mealPlanId: string; | ||
}; | ||
}; | ||
|
||
export default async function InvitePage({ params }: Props) { | ||
const userId = await getUserId(true); | ||
const mealPlan = await getMealPlan(userId, params.mealPlanId); | ||
if (mealPlan == null) notFound(); | ||
|
||
const t = await getScopedI18n("invite"); | ||
|
||
const invitation = await createMealPlanInvitation(params.mealPlanId, userId); | ||
|
||
const invitationLink = buildUrl({ | ||
path: "/", | ||
search: { | ||
invitationCode: invitation.invitationCode, | ||
}, | ||
}); | ||
|
||
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> | ||
</div> | ||
); | ||
} |
20 changes: 20 additions & 0 deletions
20
src/app/[locale]/(userArea)/mealPlan/join/[invitationCode]/AcceptButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
"use client"; | ||
|
||
import { acceptInvitationAction } from "@/actions/acceptInvitationAction"; | ||
import type { FC, PropsWithChildren } from "react"; | ||
|
||
type Props = { | ||
invitationCode: string; | ||
}; | ||
|
||
export const AcceptButton: FC<PropsWithChildren<Props>> = ({ | ||
invitationCode, | ||
children, | ||
}) => ( | ||
<button | ||
className="btn btn-primary" | ||
onClick={() => acceptInvitationAction(invitationCode)} | ||
> | ||
{children} | ||
</button> | ||
); |
61 changes: 61 additions & 0 deletions
61
src/app/[locale]/(userArea)/mealPlan/join/[invitationCode]/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { ProfileImage } from "@/components/common/ProfileImage"; | ||
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 { AcceptButton } from "./AcceptButton"; | ||
|
||
type Props = { | ||
params: { | ||
invitationCode: string; | ||
}; | ||
}; | ||
|
||
export default async function InvitationPage({ params }: Props) { | ||
const userId = await getUserId(true); | ||
const invitation = await getInvitation(params.invitationCode, userId); | ||
|
||
// Redirect if joined | ||
if (invitation.result === "JOINED") { | ||
redirectWithLocale(`/mealPlan/${invitation.invitation.mealPlan.id}`); | ||
} | ||
|
||
const t = await getScopedI18n("invitation"); | ||
const mealPlanTitle = | ||
invitation.result === "OK" | ||
? await getMealPlanLabel(invitation.invitation.mealPlan, await getI18n()) | ||
: ""; | ||
|
||
return ( | ||
<div className="container mx-auto flex justify-center align-middle"> | ||
<InvitationHeader invitation={invitation} hideOnSuccess /> | ||
{invitation.result === "OK" && ( | ||
<div className="card w-96 bg-base-100 shadow-xl"> | ||
<div className="flex justify-center pt-1"> | ||
<ProfileImage user={invitation.invitation.user} /> | ||
</div> | ||
<div className="card-body items-center text-center"> | ||
<h2 className="card-title">{t("header")}</h2> | ||
<p> | ||
{t("loginToJoinTitle", { | ||
mealPlanTitle, | ||
name: invitation.invitation.user.name ?? t("unknownUser"), | ||
})} | ||
</p> | ||
<div className="card-actions"> | ||
<AcceptButton | ||
invitationCode={invitation.invitation.invitationCode} | ||
> | ||
{t("accept", { | ||
mealPlanTitle, | ||
})} | ||
</AcceptButton> | ||
</div> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} |
Oops, something went wrong.