|
1 | | -"use client" |
2 | | - |
3 | | -import { useEffect } from "react" |
4 | | -import { useRouter } from "next/navigation" |
5 | | -import { confirmIdentityLinkAction } from "./actions" |
6 | | -import { Text } from "@dotkomonline/ui" |
7 | | - |
8 | | -export default function LinkIdentityPage() { |
9 | | - const router = useRouter() |
10 | | - |
11 | | - useEffect(() => { |
12 | | - confirmIdentityLinkAction() |
13 | | - .then(() => { |
14 | | - router.replace("/innstillinger/profil") |
15 | | - }) |
16 | | - .catch((error) => { |
17 | | - console.error("Failed to link identity:", error) |
18 | | - router.replace("/innstillinger/profil?error=link_failed") |
19 | | - }) |
20 | | - }, [router]) |
21 | | - |
22 | | - return <Text>Kobler sammen kontoer...</Text> |
| 1 | +import { auth } from "@/auth" |
| 2 | +import { createAuthorizeUrl, getStudyGrade } from "@dotkomonline/utils" |
| 3 | +import { Avatar, AvatarFallback, AvatarImage, cn, Text, Title } from "@dotkomonline/ui" |
| 4 | +import { redirect } from "next/navigation" |
| 5 | +import { ConfirmIdentityLinkButton } from "./ConfirmIdentityLinkButton" |
| 6 | +import { getIdentityLinkCookies } from "./actions" |
| 7 | +import { server } from "@/utils/trpc/server" |
| 8 | +import { findActiveMembership, getMembershipTypeName, type User } from "@dotkomonline/types" |
| 9 | +import { IconArrowNarrowLeft, IconUser, IconUserFilled } from "@tabler/icons-react" |
| 10 | + |
| 11 | +export default async function LinkIdentityPage() { |
| 12 | + const session = await auth.getServerSession() |
| 13 | + |
| 14 | + if (!session) { |
| 15 | + redirect(createAuthorizeUrl({ redirectAfter: "/innstillinger/bruker/link" })) |
| 16 | + } |
| 17 | + |
| 18 | + const { secondaryUserId } = await getIdentityLinkCookies().catch(() => { |
| 19 | + redirect("/innstillinger/bruker") |
| 20 | + }) |
| 21 | + |
| 22 | + const primaryUser = await server.user.getMe.query() |
| 23 | + const secondaryUser = await server.user.get.query(secondaryUserId) |
| 24 | + |
| 25 | + return ( |
| 26 | + <div className="flex max-w-3xl flex-col gap-6"> |
| 27 | + <div className="flex flex-col gap-2"> |
| 28 | + <Title size="xl">Bekreft kobling av brukere</Title> |
| 29 | + <Text className="text-sm"> |
| 30 | + Du er i ferd med å koble en ekstra innloggingsmetode til den eksisterende brukeren din. Brukerprofilen som |
| 31 | + hører til denne innloggingsmetoden blir samtidig koblet til kontoen din. Kontroller at informasjonen under |
| 32 | + stemmer før du fortsetter. |
| 33 | + </Text> |
| 34 | + </div> |
| 35 | + |
| 36 | + <div className="flex flex-col md:grid md:grid-cols-[1fr_auto_1fr] gap-4 items-center"> |
| 37 | + <UserColumn type="primary" user={primaryUser} /> |
| 38 | + |
| 39 | + <div className="flex flex-col items-center gap-2 md:gap-0"> |
| 40 | + <IconArrowNarrowLeft className="size-8 rotate-90 self-center md:rotate-0" strokeWidth={2} /> |
| 41 | + <Text className="text-[0.55rem] font-bold">Sammenslås</Text> |
| 42 | + </div> |
| 43 | + |
| 44 | + <UserColumn type="secondary" user={secondaryUser} /> |
| 45 | + </div> |
| 46 | + |
| 47 | + <Text className="text-xs text-gray-500 dark:text-stone-500"> |
| 48 | + Denne handlingen slår sammen de to brukerene til én bruker. All data og historikk overføres til primærbruker. |
| 49 | + Hvis både primærbruker og sekundærbruker har verdi i samme felt, beholdes verdien til primærbruker, og verdien |
| 50 | + til sekundærbruker går tapt. Sammenslåingen kan ikke angres. |
| 51 | + </Text> |
| 52 | + |
| 53 | + <ConfirmIdentityLinkButton /> |
| 54 | + </div> |
| 55 | + ) |
| 56 | +} |
| 57 | + |
| 58 | +type UserColumnProps = { |
| 59 | + user: User |
| 60 | + type: "primary" | "secondary" |
| 61 | +} |
| 62 | + |
| 63 | +function UserColumn({ user, type }: UserColumnProps) { |
| 64 | + const name = user.name ?? "<Ingen navn oppgitt>" |
| 65 | + const email = user.email ?? "<Ingen e-post oppgitt>" |
| 66 | + |
| 67 | + const membership = findActiveMembership(user) |
| 68 | + const membershipType = membership && getMembershipTypeName(membership.type) |
| 69 | + const grade = membership?.semester != null ? getStudyGrade(membership.semester) : null |
| 70 | + |
| 71 | + const title = type === "primary" ? "Primærbruker" : "Sekundærbruker" |
| 72 | + const caption = type === "primary" ? "Primærbrukeren beholdes." : "Sekundærbrukeren vil bli slått sammen." |
| 73 | + |
| 74 | + const cardColor = cn( |
| 75 | + type === "primary" && "bg-violet-100 border-violet-200 dark:bg-violet-900/60 dark:border-white/10", |
| 76 | + type === "secondary" && "bg-yellow-100 border-yellow-200 dark:bg-yellow-900/60 dark:border-white/10" |
| 77 | + ) |
| 78 | + |
| 79 | + const titleColor = cn( |
| 80 | + type === "primary" && "text-violet-700 dark:text-violet-300", |
| 81 | + type === "secondary" && "text-yellow-700 dark:text-yellow-300" |
| 82 | + ) |
| 83 | + |
| 84 | + const cardHeaderColor = cn( |
| 85 | + type === "primary" && "bg-violet-200 dark:bg-violet-900/75 border-violet-200 dark:border-white/10", |
| 86 | + type === "secondary" && "bg-yellow-200 dark:bg-yellow-900/75 border-yellow-200 dark:border-white/10" |
| 87 | + ) |
| 88 | + |
| 89 | + const icon = |
| 90 | + type === "primary" ? ( |
| 91 | + <IconUserFilled className={cn("size-3.5", titleColor)} /> |
| 92 | + ) : ( |
| 93 | + <IconUser className={cn("size-3.5", titleColor)} strokeWidth={2.5} /> |
| 94 | + ) |
| 95 | + |
| 96 | + return ( |
| 97 | + <div className="flex flex-col gap-2"> |
| 98 | + <div |
| 99 | + className={cn( |
| 100 | + "flex flex-col gap-1 rounded-xl border border-gray-200 bg-gray-50 dark:border-stone-700 dark:bg-stone-900", |
| 101 | + cardColor |
| 102 | + )} |
| 103 | + > |
| 104 | + <div className={cn("flex gap-2 items-center p-2 pb-1.5 rounded-t-xl", cardHeaderColor)}> |
| 105 | + {icon} |
| 106 | + <Title element="h3" size="sm" className={cn("text-sm", titleColor)}> |
| 107 | + {title} |
| 108 | + </Title> |
| 109 | + </div> |
| 110 | + |
| 111 | + <div className="flex gap-3 items-center p-3 pt-1.5"> |
| 112 | + <Avatar className="size-12"> |
| 113 | + <AvatarImage className="object-cover" src={user.imageUrl ?? undefined} /> |
| 114 | + <AvatarFallback className="bg-gray-500 dark:bg-stone-500"> |
| 115 | + <IconUser className="size-[1.5em]" /> |
| 116 | + </AvatarFallback> |
| 117 | + </Avatar> |
| 118 | + |
| 119 | + <div className="flex flex-col gap-1"> |
| 120 | + <Text className="font-medium text-sm truncate" title={name}> |
| 121 | + {name} |
| 122 | + </Text> |
| 123 | + |
| 124 | + <Text className="text-xs truncate" title={email}> |
| 125 | + {email} |
| 126 | + </Text> |
| 127 | + |
| 128 | + <Text className={cn("text-xs", membershipType === null && "text-gray-500 dark:text-stone-500")}> |
| 129 | + {membershipType ? `${membershipType} ${grade ? `(${grade}. klasse)` : ""}` : "Ingen medlemskap"} |
| 130 | + </Text> |
| 131 | + </div> |
| 132 | + </div> |
| 133 | + </div> |
| 134 | + |
| 135 | + <Text className="text-xs">{caption}</Text> |
| 136 | + </div> |
| 137 | + ) |
23 | 138 | } |
0 commit comments