Archived
add remaining cards
This commit is contained in:
1 parent
59d956b69b
commit
677d4be674
11 files changed
+352
-88
No files matched your search
@@ -0,0 +1,45 @@
|
||||
export interface Membership {
|
||||
id: string;
|
||||
title: string;
|
||||
image: string;
|
||||
perks: string[];
|
||||
specialPerks: string[];
|
||||
priceYear: number;
|
||||
endsAtNewYears: boolean;
|
||||
duration: number | false;
|
||||
}
|
||||
|
||||
export const membershipData: Record<string, Membership> = {
|
||||
uiog: {
|
||||
id: "uiog",
|
||||
title: "UiO Gaming",
|
||||
perks: [
|
||||
"access to Discord Channels",
|
||||
"access to all Weekly Events"
|
||||
],
|
||||
specialPerks: [
|
||||
"access to Cabin Trip",
|
||||
"access to LAN Events (2x / year)"
|
||||
],
|
||||
image: "/images/uiog.png",
|
||||
priceYear: 50,
|
||||
endsAtNewYears: true,
|
||||
duration: false
|
||||
},
|
||||
dns: {
|
||||
id: "dns",
|
||||
title: "DNS Standard",
|
||||
perks: [
|
||||
"access to specific Events",
|
||||
"discounts on select Events",
|
||||
"voting rights and ability to run for election at Neuf"
|
||||
],
|
||||
specialPerks: [
|
||||
"member pricing at Neuf, Samfundet in Trondheim, and Studentersamfunnet in Bergen"
|
||||
],
|
||||
image: "/images/neuf.png",
|
||||
priceYear: 100,
|
||||
endsAtNewYears: false,
|
||||
duration: 365
|
||||
}
|
||||
};
|
||||
@@ -1,25 +1,33 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import MembershipItem from './MembershipItem';
|
||||
import { Membership, membershipData } from './Membership';
|
||||
import { Subscription, getSubscriptionDetails } from './Subscription';
|
||||
|
||||
interface MembershipCard {
|
||||
hasUiogMembership: boolean;
|
||||
hasDnsMembership: boolean;
|
||||
interface MembershipCardProps {
|
||||
uiogSubscription: Subscription | null;
|
||||
dnsSubscription: Subscription | null;
|
||||
}
|
||||
|
||||
const membershipPerks = [
|
||||
|
||||
];
|
||||
|
||||
const MembershipCard: React.FC<MembershipCard> = ({
|
||||
hasUiogMembership,
|
||||
hasDnsMembership,
|
||||
const MembershipCard: React.FC<MembershipCardProps> = ({
|
||||
uiogSubscription,
|
||||
dnsSubscription
|
||||
}) => {
|
||||
return (
|
||||
<div className={`p-6 rounded-lg shadow-md text-white bg-primary transition-all duration-500p-7 m-5 gap-10 h-full`}>
|
||||
<div className="flex flex-col md:flex-row flex-1 rounded-lg gap-3 place-content-start">
|
||||
<p>My Perks</p>
|
||||
<div className={`p-5 rounded-lg text-white bg-primary transition-all duration-500 gap-10 h-full w-full max-w-3xl`}>
|
||||
<div className="flex flex-col flex-1 rounded-lg gap-3 place-content-start">
|
||||
<p className="">My Perks</p>
|
||||
{uiogSubscription && (
|
||||
<MembershipItem subscription={uiogSubscription} />
|
||||
)}
|
||||
{dnsSubscription && (
|
||||
<MembershipItem subscription={dnsSubscription} />
|
||||
)}
|
||||
{!uiogSubscription && !dnsSubscription && (
|
||||
<p>No current memberships. Please consider purchasing one.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MembershipCard;
|
||||
export default MembershipCard;
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Membership } from './Membership';
|
||||
import { Subscription, getSubscriptionDetails } from './Subscription';
|
||||
|
||||
interface MembershipItemProps {
|
||||
subscription: Subscription;
|
||||
}
|
||||
|
||||
const MembershipItem: React.FC<MembershipItemProps> = ({ subscription }) => {
|
||||
const { membership, daysLeft } = getSubscriptionDetails(subscription);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 rounded-lg gap-5 p-4 place-content-between bg-overlay">
|
||||
<div className="flex flex-row place-content-between">
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="w-10 h-10 rounded-md">
|
||||
<img
|
||||
src={membership.image}
|
||||
alt="User Icon"
|
||||
className="w-full h-full rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="ph">{membership.title}</p>
|
||||
<p className="opacity-50">kr {membership.priceYear} / year</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="opacity-50">{daysLeft} days left</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-5">
|
||||
<div>
|
||||
<p className="ph">Perks</p>
|
||||
{membership.perks.map((perk) => (
|
||||
<p className="p">+ {perk}</p>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
{membership.specialPerks.map((perk) => (
|
||||
<p className="p">+ {perk}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MembershipItem;
|
||||
@@ -6,7 +6,7 @@ interface ProfileCardProps {
|
||||
hasDnsMembership: boolean;
|
||||
}
|
||||
|
||||
const membershipStatuses = {
|
||||
const subscriptionStatuses = {
|
||||
Combined: {
|
||||
title: "Combined",
|
||||
description: "You have both memberships, and can enjoy all privileges.",
|
||||
@@ -66,17 +66,17 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
||||
? "DNS"
|
||||
: "None";
|
||||
|
||||
const membershipStatus = membershipStatuses[statusKey];
|
||||
const membershipStatus = subscriptionStatuses[statusKey];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-1 flex-col gap-5 p-6 rounded-lg shadow-md text-white bg-gradient-to-b from-primary ${membershipStatus.gradient} transition-all duration-500 m-5 h-full`}
|
||||
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`}
|
||||
>
|
||||
{/* Top Row */}
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex justify-between items-start mb-2 gap-2">
|
||||
{/* User Icon and Name */}
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-20 h-20 rounded-md">
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="w-12 h-12 rounded-md">
|
||||
<img
|
||||
src={membershipStatus.image}
|
||||
alt="User Icon"
|
||||
@@ -84,41 +84,43 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="pb">wailern</p>
|
||||
<p className="font-bold">wailern</p>
|
||||
<p className="opacity-50">{membershipStatus.tag}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Log Out Button */}
|
||||
<button className="flex flex-row bg-overlay-bright px-3 py-1 rounded-md outline outline-outline2 outline-2 items-center gap-3">
|
||||
<p className="ph text-outline2">Log Out</p>
|
||||
<LogoutIcon className="w-8 ml-2 text-outline2" />
|
||||
<button className="flex flex-row bg-overlay-bright px-3 py-0.25 rounded-mds outline outline-outline2 outline-2 items-center gap-3">
|
||||
<p className="font-bold text-outline2">Log Out</p>
|
||||
<LogoutIcon className="w-5 ml-2 text-outline2" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Membership Status */}
|
||||
{["UIOG", "DNS"].map((key) => {
|
||||
const isActive = key === "UIOG" ? hasUiogMembership : hasDnsMembership;
|
||||
const displayName = key === "UIOG" ? "UiO Gaming" : "DNS Standard";
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={`p-4 py-6 rounded-lg flex justify-between items-center ${
|
||||
isActive ? "bg-success" : "bg-failure"
|
||||
}`}
|
||||
>
|
||||
<p>{isActive ? `✅ ${displayName}` : `❌ ${displayName}`}</p>
|
||||
<div>{key === "UIOG" ? "🎮" : "🐟"}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
{["UIOG", "DNS"].map((key) => {
|
||||
const isActive = key === "UIOG" ? hasUiogMembership : hasDnsMembership;
|
||||
const displayName = key === "UIOG" ? "UiO Gaming" : "DNS Standard";
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={`font-semibold px-2 py-4 gap-1 rounded-lgs flex justify-between items-center ${
|
||||
isActive ? "bg-success" : "bg-failure"
|
||||
}`}
|
||||
>
|
||||
<p className="font-semibold pl">{isActive ? `✅ ${displayName}` : `❌ ${displayName}`}</p>
|
||||
<div>{key === "UIOG" ? "🎮" : "🐟"}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mt-2 space-y-5">
|
||||
<p className="ps">{membershipStatus.description}</p>
|
||||
<p className="">{membershipStatus.description}</p>
|
||||
{membershipStatus.blurb && (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<p className="psh">{membershipStatus.blurb}</p>
|
||||
<p className="font-semibold">{membershipStatus.blurb}</p>
|
||||
{membershipStatus.missingPerks.map((perk, index) => (
|
||||
<p className="ps" key={index}>
|
||||
+ {perk}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { membershipData } from "./Membership"; // Importing membership data from Membership.tsx
|
||||
import PurchaseItem from "./PurchaseItem"; // Importing PurchaseItem component
|
||||
|
||||
interface PurchaseCardProps {
|
||||
hasUiogMembership: boolean;
|
||||
hasDnsMembership: boolean;
|
||||
}
|
||||
|
||||
const PurchaseCard: React.FC<PurchaseCardProps> = ({
|
||||
hasUiogMembership,
|
||||
hasDnsMembership,
|
||||
}) => {
|
||||
const uiogMembership = membershipData.uiog
|
||||
const dnsMembership = membershipData.dns
|
||||
|
||||
return (
|
||||
<div className={`p-5 rounded-lg shadow-md text-white bg-primary transition-all duration-500 p-5 gap-10 h-full w-full max-w-3xl`}>
|
||||
<div className="flex flex-col flex-1 rounded-lg gap-3 place-content-start">
|
||||
<p className="pl">Become a Member</p>
|
||||
{!hasUiogMembership && uiogMembership && (
|
||||
<PurchaseItem membership={uiogMembership} />
|
||||
)}
|
||||
{!hasDnsMembership && dnsMembership && (
|
||||
<PurchaseItem membership={dnsMembership} />
|
||||
)}
|
||||
{(hasUiogMembership && hasDnsMembership) && (
|
||||
<p>No additional memberships are available for purchase.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PurchaseCard;
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
|
||||
interface PurchaseItem {
|
||||
membership: {
|
||||
id: string;
|
||||
title: string;
|
||||
image: string;
|
||||
priceYear: number;
|
||||
perks: string[];
|
||||
specialPerks: string[];
|
||||
}
|
||||
}
|
||||
|
||||
const PurchaseItem: React.FC<PurchaseItem> = ({
|
||||
membership
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 rounded-lg gap-5 p-5 place-content-between outline outline-1 outline-overlay">
|
||||
<div className="flex flex-row place-content-between">
|
||||
<div className="flex flex-row gap-5">
|
||||
<div className="w-10 h-10 rounded-md">
|
||||
<img
|
||||
src={membership.image}
|
||||
alt="User Icon"
|
||||
className="w-full h-full rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="ph">{membership.title}</p>
|
||||
<p className="opacity-50">kr {membership.priceYear} / year</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-md bg-overlay-2 justify-items-center content-center outline outline-1 outline-overlay-3">
|
||||
<p className="p2xl text-overlay-3">+</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col gap-5">
|
||||
<div>
|
||||
<p className="ph">Perks</p>
|
||||
{membership.perks.map((perk) => (
|
||||
<p className="p">+ {perk}</p>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
{membership.specialPerks.map((perk) => (
|
||||
<p className="p">+ {perk}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PurchaseItem;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Membership, membershipData } from './Membership';
|
||||
|
||||
export interface Subscription {
|
||||
membershipId: string;
|
||||
startDate: Date;
|
||||
}
|
||||
|
||||
export function calculateDaysLeft(subscription: Subscription): number {
|
||||
const today = new Date();
|
||||
const currentYear = today.getFullYear();
|
||||
const membership = membershipData[subscription.membershipId];
|
||||
let daysLeft = 0;
|
||||
|
||||
if (membership.endsAtNewYears) {
|
||||
// if the membership ends at new years, we need to calculate the days left until the next new years
|
||||
const nextYear = currentYear + 1;
|
||||
const newYearsDay = new Date(nextYear, 0, 1);
|
||||
|
||||
const timeDiff = newYearsDay.getTime() - today.getTime();
|
||||
daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
// if the membership does not end at new years, we can just calculate the days based on the duration of the membership
|
||||
if (!membership.duration) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const duration: number = parseInt(membership.duration.toString());
|
||||
|
||||
daysLeft = Math.ceil((subscription.startDate.getTime() + duration * 24 * 60 * 60 * 1000 - today.getTime()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
return daysLeft;
|
||||
}
|
||||
|
||||
export function getSubscriptionDetails(subscription: Subscription): { membership: Membership; daysLeft: number } {
|
||||
return {
|
||||
membership: membershipData[subscription.membershipId],
|
||||
daysLeft: calculateDaysLeft(subscription)
|
||||
};
|
||||
}
|
||||
+5
-42
@@ -7,6 +7,8 @@
|
||||
--foreground: #261F31;
|
||||
--primary: #392e49;
|
||||
--overlay: #4c3e62;
|
||||
--overlay-2: #55456D;
|
||||
--overlay-3: #8E73A4;
|
||||
--overlay-bright: #6d517b;
|
||||
--outline: #987cc4;
|
||||
--outline2: #dba0f6;
|
||||
@@ -23,6 +25,8 @@
|
||||
--foreground: #261F31;
|
||||
--primary: #392e49;
|
||||
--overlay: #4c3e62;
|
||||
--overlay-2: #55456D;
|
||||
--overlay-3: #8E73A4;
|
||||
--overlay-bright: #6d517b;
|
||||
--outline: #987cc4;
|
||||
--outline2: #dba0f6;
|
||||
@@ -52,40 +56,7 @@ h2 {
|
||||
p {
|
||||
font-family: var(--font-nunito);
|
||||
font-weight: 400;
|
||||
font-size: 1.45rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pb {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ph {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.po {
|
||||
color: var(--outline);
|
||||
}
|
||||
|
||||
.pd {
|
||||
font-size: 1.45rem;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.pp {
|
||||
font-size: 1.5rem;
|
||||
color: var(--foreground)
|
||||
}
|
||||
|
||||
.ps {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.psh {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -94,14 +65,6 @@ p {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.header-light {
|
||||
font-weight: 400;
|
||||
font-size: 2rem;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ButtonSpecial from "./components/ButtonSpecial";
|
||||
import Button from "./components/Button";
|
||||
import Image from "next/image";
|
||||
import ProfileCard from "./components/ProfileCard";
|
||||
|
||||
import { Nunito, Rubik } from 'next/font/google';
|
||||
|
||||
|
||||
+18
-3
@@ -2,12 +2,27 @@ import ProfileCard from '@/app/components/ProfileCard';
|
||||
import MembershipCard from '@/app/components/MembershipCard';
|
||||
import React from 'react';
|
||||
import "../app/globals.css";
|
||||
import PurchaseCard from '@/app/components/PurchaseCard';
|
||||
import { Subscription } from '@/app/components/Subscription';
|
||||
|
||||
let dnsSubscription: Subscription | null = null;
|
||||
let uiogSubscription: Subscription | null = null;
|
||||
|
||||
|
||||
uiogSubscription = {
|
||||
membershipId: "uiog",
|
||||
startDate: new Date(new Date().setDate(new Date().getDate() - 111)),
|
||||
};
|
||||
|
||||
const hasDns = dnsSubscription !== null;
|
||||
const hasUiog = uiogSubscription !== null;
|
||||
|
||||
export default function Main() {
|
||||
return (
|
||||
<div>
|
||||
<ProfileCard hasUiogMembership={true} hasDnsMembership={true}/>
|
||||
<MembershipCard hasUiogMembership={true} hasDnsMembership={true}/>
|
||||
<div className="flex flex-1 flex-col place-items-center p-2.5 gap-2.5">
|
||||
<ProfileCard hasUiogMembership={hasUiog} hasDnsMembership={hasDns}/>
|
||||
<MembershipCard uiogSubscription={uiogSubscription} dnsSubscription={dnsSubscription}/>
|
||||
<PurchaseCard hasUiogMembership={hasUiog} hasDnsMembership={hasDns}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -13,6 +13,8 @@ const config: Config = {
|
||||
'foreground': "var(--foreground)",
|
||||
'primary': "var(--primary)",
|
||||
'overlay': "var(--overlay)",
|
||||
'overlay-2': "var(--overlay-2)",
|
||||
'overlay-3': "var(--overlay-3)",
|
||||
'overlay-bright': "var(--overlay-bright)",
|
||||
'outline': "var(--outline)",
|
||||
'outline2': "var(--outline2)",
|
||||
@@ -22,15 +24,65 @@ const config: Config = {
|
||||
'failure': "var(--failure)",
|
||||
'mixed': "var(--mixed)",
|
||||
},
|
||||
fontWeight: {
|
||||
thin: "100",
|
||||
extralight: "200",
|
||||
light: "300",
|
||||
normal: "400",
|
||||
medium: "500",
|
||||
semibold: "600",
|
||||
bold: "700",
|
||||
extrabold: "800",
|
||||
black: "900",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: "0.5rem",
|
||||
sms: "0.1rem",
|
||||
sm: "0.125rem",
|
||||
sml: "0.25rem",
|
||||
|
||||
mds: "0.3rem",
|
||||
md: "0.375rem",
|
||||
mdl: "0.5rem",
|
||||
|
||||
lgs: "0.75rem",
|
||||
lg: "1rem",
|
||||
|
||||
xl: "2rem",
|
||||
full: "9999px",
|
||||
},
|
||||
fontSize: {
|
||||
xs: "0.75rem",
|
||||
sm: "0.875rem",
|
||||
base: "1rem",
|
||||
md: "1.105rem",
|
||||
lg: "1.125rem",
|
||||
xl: "1.25rem"
|
||||
},
|
||||
fontWeight: {
|
||||
thin: "100",
|
||||
extralight: "200",
|
||||
light: "300",
|
||||
normal: "400",
|
||||
medium: "500",
|
||||
semibold: "600",
|
||||
bold: "700",
|
||||
extrabold: "800",
|
||||
black: "900",
|
||||
},
|
||||
outlineWidth: {
|
||||
DEFAULT: "2px",
|
||||
1: "1px",
|
||||
1.25: "1.25px",
|
||||
1.5: "1.5px",
|
||||
1.75: "1.75px",
|
||||
2: "2px",
|
||||
3: "3px",
|
||||
4: "4px",
|
||||
6: "6px",
|
||||
8: "8px",
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user