add remaining cards

This commit is contained in:
centauridesigns committed 2024-12-22 02:18:03 +01:00
1 parent 59d956b69b
commit 677d4be674
11 files changed
+352 -88

No files matched your search

+45
View File
@@ -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
}
};
+23 -15
View File
@@ -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;
+48
View File
@@ -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;
+29 -27
View File
@@ -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}
+35
View File
@@ -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;
+54
View File
@@ -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;
+43
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
);
};
+52
View File
@@ -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: [],
};