Archived
Merge pull request #1 from UiO-Gaming/feature/galtinn-oauth
Add Galtinn OAuth login
This commit is contained in:
16 files changed
+923
-164
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
GALTINN_API_URL=http://localhost:8000/api
|
||||||
|
GALTINN_CLIENT_ID=
|
||||||
|
GALTINN_CLIENT_SECRET=
|
||||||
@@ -34,3 +34,6 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
# privacy
|
||||||
|
.env.local
|
||||||
Generated
+714
-96
File diff suppressed because it is too large.
Load diff
+7
-6
@@ -9,18 +9,19 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18",
|
"next": "^15.2.0",
|
||||||
"react-dom": "^18",
|
"next-auth": "^4.24.11",
|
||||||
"next": "14.2.13"
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.13",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"eslint": "^8",
|
"typescript": "^5"
|
||||||
"eslint-config-next": "14.2.13"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NextAuthProvider = ({ children }: Props) => {
|
||||||
|
return <SessionProvider>{children}</SessionProvider>;
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Membership } from './Membership';
|
import Image from 'next/image';
|
||||||
import { Subscription, getSubscriptionDetails } from './Subscription';
|
import { Subscription, getSubscriptionDetails } from './Subscription';
|
||||||
|
|
||||||
interface MembershipItemProps {
|
interface MembershipItemProps {
|
||||||
@@ -14,9 +14,11 @@ const MembershipItem: React.FC<MembershipItemProps> = ({ subscription }) => {
|
|||||||
<div className="flex flex-row place-content-between">
|
<div className="flex flex-row place-content-between">
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<div className="w-10 h-10 rounded-md">
|
<div className="w-10 h-10 rounded-md">
|
||||||
<img
|
<Image
|
||||||
src={membership.image}
|
src={membership.image}
|
||||||
alt="User Icon"
|
alt="User Icon"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
className="w-full h-full rounded-md"
|
className="w-full h-full rounded-md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,13 +33,13 @@ const MembershipItem: React.FC<MembershipItemProps> = ({ subscription }) => {
|
|||||||
<div className="flex flex-1 flex-col gap-5">
|
<div className="flex flex-1 flex-col gap-5">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Perks</p>
|
<p className="font-semibold">Perks</p>
|
||||||
{membership.perks.map((perk) => (
|
{membership.perks.map((perk, index) => (
|
||||||
<p className="p">+ {perk}</p>
|
<p key={index} className="p">+ {perk}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{membership.specialPerks.map((perk) => (
|
{membership.specialPerks.map((perk, index) => (
|
||||||
<p className="p">+ {perk}</p>
|
<p key={index} className="p">+ {perk}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { signOut, useSession } from "next-auth/react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
import LogoutIcon from "../../../public/icons/LogoutIcon";
|
import LogoutIcon from "../../../public/icons/LogoutIcon";
|
||||||
|
|
||||||
interface ProfileCardProps {
|
interface ProfileCardProps {
|
||||||
@@ -58,6 +61,8 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|||||||
hasUiogMembership,
|
hasUiogMembership,
|
||||||
hasDnsMembership,
|
hasDnsMembership,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const statusKey = hasUiogMembership
|
const statusKey = hasUiogMembership
|
||||||
? hasDnsMembership
|
? hasDnsMembership
|
||||||
? "Combined"
|
? "Combined"
|
||||||
@@ -70,27 +75,32 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-1 flex-col gap-5 p-5 rounded-lg shadow-md text-white bg-gradient-to-b from-primary ${membershipStatus.gradient} transition-all duration-500 h-full w-full max-w-3xl`}
|
className={`flex flex-1 flex-col gap-5 p-5 rounded-lg border border-overlay-3 text-white bg-gradient-to-b from-primary ${membershipStatus.gradient} transition-all duration-500 h-full w-full max-w-3xl`}
|
||||||
>
|
>
|
||||||
{/* Top Row */}
|
{/* Top Row */}
|
||||||
<div className="flex justify-between items-start mb-2 gap-2">
|
<div className="flex justify-between items-start mb-2 gap-2">
|
||||||
{/* User Icon and Name */}
|
{/* User Icon and Name */}
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="w-12 h-12 rounded-md">
|
<div className="w-12 h-12 rounded-md">
|
||||||
<img
|
<Image
|
||||||
src={membershipStatus.image}
|
src={membershipStatus.image}
|
||||||
alt="User Icon"
|
alt="Membership Icon"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
className="w-full h-full rounded-md"
|
className="w-full h-full rounded-md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="font-bold">wailern</p>
|
<p className="font-bold">{session?.user?.name}</p>
|
||||||
<p className="opacity-50">{membershipStatus.tag}</p>
|
<p className="opacity-50">{membershipStatus.tag}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Log Out Button */}
|
{/* Log Out Button */}
|
||||||
<button className="flex flex-row bg-overlay-bright px-3 py-0.25 rounded-mds outline outline-outline2 outline-1 items-center gap-3">
|
<button
|
||||||
|
className="flex flex-row bg-overlay-bright px-3 py-0.25 rounded-mds outline outline-outline2 outline-1 items-center gap-3"
|
||||||
|
onClick={() => signOut()}
|
||||||
|
>
|
||||||
<p className="font-bold text-outline2">Log Out</p>
|
<p className="font-bold text-outline2">Log Out</p>
|
||||||
<LogoutIcon className="w-5 ml-2 text-outline2" />
|
<LogoutIcon className="w-5 ml-2 text-outline2" />
|
||||||
</button>
|
</button>
|
||||||
@@ -99,7 +109,8 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|||||||
{/* Membership Status */}
|
{/* Membership Status */}
|
||||||
<div className="flex flex-1 flex-col gap-2">
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
{["UIOG", "DNS"].map((key) => {
|
{["UIOG", "DNS"].map((key) => {
|
||||||
const isActive = key === "UIOG" ? hasUiogMembership : hasDnsMembership;
|
const isActive =
|
||||||
|
key === "UIOG" ? hasUiogMembership : hasDnsMembership;
|
||||||
const displayName = key === "UIOG" ? "UiO Gaming" : "DNS Standard";
|
const displayName = key === "UIOG" ? "UiO Gaming" : "DNS Standard";
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -108,10 +119,10 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|||||||
isActive ? "bg-success" : "bg-failure"
|
isActive ? "bg-success" : "bg-failure"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="font-semibold pl">{isActive ? `✅` : `❌`}</p>
|
<p className="font-semibold pl">{isActive ? `✅` : `❌`}</p>
|
||||||
<p className="font-semibold pl">{displayName}</p>
|
<p className="font-semibold pl">{displayName}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
interface PurchaseItem {
|
interface PurchaseItem {
|
||||||
membership: {
|
membership: {
|
||||||
@@ -19,9 +20,11 @@ const PurchaseItem: React.FC<PurchaseItem> = ({
|
|||||||
<div className="flex flex-row place-content-between">
|
<div className="flex flex-row place-content-between">
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<div className="w-10 h-10 rounded-md">
|
<div className="w-10 h-10 rounded-md">
|
||||||
<img
|
<Image
|
||||||
src={membership.image}
|
src={membership.image}
|
||||||
alt="User Icon"
|
alt="Membership Icon"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
className="w-full h-full rounded-md"
|
className="w-full h-full rounded-md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,20 +33,20 @@ const PurchaseItem: React.FC<PurchaseItem> = ({
|
|||||||
<p className="opacity-50">kr {membership.priceYear} / year</p>
|
<p className="opacity-50">kr {membership.priceYear} / year</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-10 h-10 rounded-md bg-overlay-2 justify-items-center content-center outline outline-1 outline-overlay-3">
|
<div className="w-10 h-10 rounded-md bg-overlay-2 flex items-center justify-center outline outline-1 outline-overlay-3">
|
||||||
<p className="p2xl text-overlay-3">+</p>
|
<p className="text-2xl text-overlay-3">+</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col gap-5">
|
<div className="flex flex-1 flex-col gap-5">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Perks</p>
|
<p className="font-semibold">Perks</p>
|
||||||
{membership.perks.map((perk) => (
|
{membership.perks.map((perk, index) => (
|
||||||
<p className="p">+ {perk}</p>
|
<p key={index} className="p">+ {perk}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{membership.specialPerks.map((perk) => (
|
{membership.specialPerks.map((perk, index) => (
|
||||||
<p className="p">+ {perk}</p>
|
<p key={index} className="p">+ {perk}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Membership, membershipData } from './Membership';
|
|||||||
|
|
||||||
export interface Subscription {
|
export interface Subscription {
|
||||||
membershipId: string;
|
membershipId: string;
|
||||||
startDate: Date;
|
endDate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateDaysLeft(subscription: Subscription): number {
|
export function calculateDaysLeft(subscription: Subscription): number {
|
||||||
@@ -22,14 +22,13 @@ export function calculateDaysLeft(subscription: Subscription): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
// if the membership does not end at new years, we can just calculate the days based on the duration of the membership
|
// if the membership does not end at new years, we read the end date from the subscription
|
||||||
if (!membership.duration) {
|
if (!membership.duration) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration: number = parseInt(membership.duration.toString());
|
const timeDiff = subscription.endDate.getTime() - today.getTime();
|
||||||
|
daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
||||||
daysLeft = Math.ceil((subscription.startDate.getTime() + duration * 24 * 60 * 60 * 1000 - today.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return daysLeft;
|
return daysLeft;
|
||||||
|
|||||||
+8
-6
@@ -2,7 +2,9 @@ import type { Metadata } from "next";
|
|||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
import { Nunito, Rubik } from "next/font/google"
|
import { NextAuthProvider } from "./SessionProvider";
|
||||||
|
|
||||||
|
import { Nunito, Rubik } from "next/font/google";
|
||||||
|
|
||||||
const geistSans = localFont({
|
const geistSans = localFont({
|
||||||
src: "./fonts/GeistVF.woff",
|
src: "./fonts/GeistVF.woff",
|
||||||
@@ -19,15 +21,15 @@ const nunito = Nunito({
|
|||||||
weight: ["400", "500", "600", "800"],
|
weight: ["400", "500", "600", "800"],
|
||||||
style: ["normal", "italic"],
|
style: ["normal", "italic"],
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-nunito"
|
variable: "--font-nunito",
|
||||||
})
|
});
|
||||||
|
|
||||||
const rubik = Rubik({
|
const rubik = Rubik({
|
||||||
weight: ["400", "500", "600", "800"],
|
weight: ["400", "500", "600", "800"],
|
||||||
style: ["normal", "italic"],
|
style: ["normal", "italic"],
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-rubik"
|
variable: "--font-rubik",
|
||||||
})
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "mus",
|
title: "mus",
|
||||||
@@ -44,7 +46,7 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased ${nunito.variable} ${rubik.variable}`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased ${nunito.variable} ${rubik.variable}`}
|
||||||
>
|
>
|
||||||
{children}
|
<NextAuthProvider>{children}</NextAuthProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+14
-2
@@ -1,9 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
import ButtonSpecial from "./components/ButtonSpecial";
|
import ButtonSpecial from "./components/ButtonSpecial";
|
||||||
import Button from "./components/Button";
|
import Button from "./components/Button";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import LoginIcon from "../../public/icons/LoginIcon";
|
import LoginIcon from "../../public/icons/LoginIcon";
|
||||||
|
|
||||||
|
import Main from "../pages/main";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
return <Main />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col p-2.5 gap-2.5 place-items-center place-content-center h-full">
|
<div className="flex flex-col p-2.5 gap-2.5 place-items-center place-content-center h-full">
|
||||||
<div className="flex flex-1 flex-col bg-foreground rounded-lg p-5 gap-6 h-full w-full max-w-3xl">
|
<div className="flex flex-1 flex-col bg-foreground rounded-lg p-5 gap-6 h-full w-full max-w-3xl">
|
||||||
@@ -64,8 +76,8 @@ export default function Home() {
|
|||||||
<Button
|
<Button
|
||||||
text="Log in with Galtinn"
|
text="Log in with Galtinn"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<LoginIcon className="w-5 text-black"/>}
|
icon={<LoginIcon className="w-5 text-black" />}
|
||||||
link="https://galtinn.neuf.no/home/"
|
link="/api/auth/signin" // TODO: use signin() function with onClick event
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full md:w-2/5">
|
<div className="w-full md:w-2/5">
|
||||||
|
|||||||
+9
-6
@@ -1,15 +1,18 @@
|
|||||||
import { Nunito } from 'next/font/google';
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
import { Nunito } from "next/font/google";
|
||||||
|
|
||||||
const nunito = Nunito({
|
const nunito = Nunito({
|
||||||
subsets: ['latin']
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
import { AppProps } from 'next/app';
|
import { AppProps } from "next/app";
|
||||||
|
|
||||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<main className={nunito.className}>
|
<SessionProvider session={pageProps.session}>
|
||||||
<Component {...pageProps} />
|
<main className={nunito.className}>
|
||||||
</main>
|
<Component {...pageProps} />
|
||||||
|
</main>
|
||||||
|
</SessionProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import NextAuth from "next-auth";
|
||||||
|
|
||||||
|
export const authOptions = {
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
id: "galtinn",
|
||||||
|
name: "Galtinn",
|
||||||
|
type: "oauth",
|
||||||
|
wellKnown: `${process.env.GALTINN_API_URL}/oauth/.well-known/openid-configuration/`,
|
||||||
|
userinfo: `${process.env.GALTINN_API_URL}/me/oauth`,
|
||||||
|
checks: ["pkce", "state"],
|
||||||
|
clientId: process.env.GALTINN_ID,
|
||||||
|
clientSecret: process.env.GALTINN_SECRET,
|
||||||
|
authorization: { params: { scope: "openid email profile" } },
|
||||||
|
|
||||||
|
profile(profile) {
|
||||||
|
return {
|
||||||
|
id: profile.sub,
|
||||||
|
name: profile.name,
|
||||||
|
email: profile.email,
|
||||||
|
username: profile.preferred_username,
|
||||||
|
is_volunteer: profile.is_volunteer,
|
||||||
|
is_member: profile.is_member,
|
||||||
|
last_membership: profile.last_membership,
|
||||||
|
groups: profile.groups,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, user }) {
|
||||||
|
// Initial sign in
|
||||||
|
if (user) {
|
||||||
|
token.id = user.id;
|
||||||
|
token.username = user.username;
|
||||||
|
token.is_volunteer = user.is_volunteer;
|
||||||
|
token.is_member = user.is_member;
|
||||||
|
token.last_membership = user.last_membership;
|
||||||
|
token.groups = user.groups;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
async session({ session, token, user }) {
|
||||||
|
session.user.username = token.username;
|
||||||
|
session.user.is_volunteer = token.is_volunteer;
|
||||||
|
session.user.is_member = token.is_member;
|
||||||
|
session.user.last_membership = token.last_membership;
|
||||||
|
session.user.groups = token.groups;
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NextAuth(authOptions);
|
||||||
+31
-13
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import "../app/globals.css";
|
import "../app/globals.css";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
import ProfileCard from '@/app/components/ProfileCard';
|
import ProfileCard from '@/app/components/ProfileCard';
|
||||||
import MembershipCard from '@/app/components/MembershipCard';
|
import MembershipCard from '@/app/components/MembershipCard';
|
||||||
@@ -7,24 +8,41 @@ import PurchaseCard from '@/app/components/PurchaseCard';
|
|||||||
import { Subscription } from '@/app/components/Subscription';
|
import { Subscription } from '@/app/components/Subscription';
|
||||||
import Navbar from '@/app/components/Navbar';
|
import Navbar from '@/app/components/Navbar';
|
||||||
|
|
||||||
let dnsSubscription: Subscription | null = null;
|
let hasDns = false;
|
||||||
let uiogSubscription: Subscription | null = null;
|
let hasUiog = false;
|
||||||
|
|
||||||
|
let dnsSubscription: Subscription = {
|
||||||
// uiogSubscription = {
|
membershipId: "",
|
||||||
// membershipId: "uiog",
|
endDate: new Date(),
|
||||||
// startDate: new Date(new Date().setDate(new Date().getDate() - 111)),
|
|
||||||
// };
|
|
||||||
|
|
||||||
dnsSubscription = {
|
|
||||||
membershipId: "dns",
|
|
||||||
startDate: new Date(new Date().setDate(new Date().getDate() - 150)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasDns = dnsSubscription !== null;
|
let uiogSubscription: Subscription = {
|
||||||
const hasUiog = uiogSubscription !== null;
|
membershipId: "",
|
||||||
|
endDate: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
if (session?.user?.groups.includes("Aktiv")) {
|
||||||
|
hasDns = true;
|
||||||
|
|
||||||
|
const membershipDates = session?.user?.last_membership.split(" - ");
|
||||||
|
dnsSubscription = {
|
||||||
|
membershipId: "dns",
|
||||||
|
endDate: new Date(membershipDates[1])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session?.user?.groups.includes("UiO Gaming")) {
|
||||||
|
hasUiog = true;
|
||||||
|
|
||||||
|
uiogSubscription = {
|
||||||
|
membershipId: "uiog",
|
||||||
|
endDate: new Date(session?.user?.last_membership)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="max-w-5xl w-full">
|
<div className="max-w-5xl w-full">
|
||||||
|
|||||||
+5
-1
@@ -63,7 +63,11 @@ const config: Config = {
|
|||||||
mdl: "1.1rem",
|
mdl: "1.1rem",
|
||||||
|
|
||||||
lg: "1.125rem",
|
lg: "1.125rem",
|
||||||
xl: "1.25rem"
|
xl: "1.25rem",
|
||||||
|
|
||||||
|
"2xl": "1.5rem",
|
||||||
|
"3xl": "1.875rem",
|
||||||
|
"4xl": "2.25rem",
|
||||||
},
|
},
|
||||||
fontWeight: {
|
fontWeight: {
|
||||||
thin: "100",
|
thin: "100",
|
||||||
|
|||||||
+19
-5
@@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -18,9 +22,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
}
|
"./src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"target": "ES2017"
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user