Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HeaderComponent #27

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions front/src/app/components/headerComponent/HeaderComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTelegram } from "@fortawesome/free-brands-svg-icons";

import Status from "../statusComponent/StatusComponent";

import { StatusProps } from "../statusComponent/type";
Comment on lines +3 to +6
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import Status from "../statusComponent/StatusComponent";
import { StatusProps } from "../statusComponent/type";
import Status from "../statusComponent/StatusComponent";
import { StatusProps } from "../statusComponent/type";


export default function HeaderComponent({
error,
data,
connectionRestored,
setConnectionRestored,
lastMessage,
}: StatusProps) {
return (
<div className="HeaderComponent flex justify-evenly w-full">
<div className="flex flex-col justify-center">
<div>Receive notifications on Telegram</div>
<a className="flex text-blue-400 space-x-1" href="https://t.me/KimsufiNotifierBot">
<div>
<FontAwesomeIcon icon={faTelegram} />
</div>
<div>t.me/KimsufiNotifierBot</div>
</a>
</div>
<h1 className="text-center text-xl font-bold">OVH Eco server availability</h1>
<Status
error={error}
data={data}
connectionRestored={connectionRestored}
setConnectionRestored={setConnectionRestored}
lastMessage={lastMessage}
/>
</div>
);
}
127 changes: 69 additions & 58 deletions front/src/app/components/server.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
'use client';
"use client";

import React from 'react';
import { useEffect, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheckCircle, faTimesCircle, faCircle } from '@fortawesome/free-regular-svg-icons';
import { Server, Status } from './types';
import React from "react";
import { useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheckCircle, faTimesCircle, faCircle } from "@fortawesome/free-regular-svg-icons";
import { Server, Status } from "./types";

interface Props {
data: Server[];
data: Server[];
}

// ServerLines displays the servers table lines
function ServerLines({category, servers} : {category: string, servers: Server[]}) {
function ServerLines({ category, servers }: { category: string; servers: Server[] }) {
// Define the color and icon the status column
const statusColor = new Map<string, Status>([
["available", {color:"text-lime-600", icon:faCheckCircle}],
["unavailable", {color:"text-rose-600", icon:faTimesCircle}],
["available", { color: "text-lime-600", icon: faCheckCircle }],
["unavailable", { color: "text-rose-600", icon: faTimesCircle }],
]);

// Highlight changed rows
Expand All @@ -30,7 +30,7 @@ function ServerLines({category, servers} : {category: string, servers: Server[]}
const currentData = server;

// Check if the data has changed
const equal = JSON.stringify(previousData) === JSON.stringify(currentData)
const equal = JSON.stringify(previousData) === JSON.stringify(currentData);
if (previousData && !equal) {
setChangedRows((prevChangedRows) => new Set(prevChangedRows).add(server.planCode));
}
Expand All @@ -53,64 +53,75 @@ function ServerLines({category, servers} : {category: string, servers: Server[]}
}
}, [changedRows]);

return (
servers.map((server) => (
<tr ref={React.createRef()} key={category + server.planCode} className={`${changedRows.has(server.planCode) ? 'bg-yellow-200' : 'transition duration-1000 delay-150 even:bg-blue-300 odd:bg-blue-100'} font-mono`}>
<td>{server.name}</td>
<td>{server.cpu}</td>
<td>{server.memory}</td>
<td>{server.storage}</td>
<td>{server.bandwidth}</td>
<td>{server.price} {server.currencyCode}</td>
<td className="flex flex-row justify-end">{server.status}<div className={statusColor.get(server.status)?.color + " pl-2"}><FontAwesomeIcon icon={statusColor.get(server.status)?.icon||faCircle} /></div></td>
<td>{server.datacenters?.join(", ")||"-"}</td>
</tr>
))
)
return servers.map((server) => (
<tr
ref={React.createRef()}
key={category + server.planCode}
className={`font-mono text-sm font-medium ${changedRows.has(server.planCode) ? "bg-yellow-200" : "transition duration-1000 delay-150 shadow-md rounded-xl bg-slate-100"}`}
>
<td className="rounded-l-xl">{server.name}</td>
<td>{server.cpu}</td>
<td>{server.memory}</td>
<td>{server.storage}</td>
<td>{server.bandwidth}</td>
<td>{`${server.price} ${server.currencyCode}`}</td>
<td className="flex justify-end py-6">
<div>{server.status}</div>
<div className={statusColor.get(server.status)?.color + " pl-2"}>
<FontAwesomeIcon icon={statusColor.get(server.status)?.icon || faCircle} />
</div>
</td>
<td className="rounded-r-xl">{server.datacenters?.join(", ") || "-"}</td>
</tr>
));
}

// ServerCategories displays the servers table lines with a category line before
function ServerCategories({ordered} : {ordered: {[key: string]: Server[]}}) {
return (
Object.entries(ordered).map(([category, servers]) => (
<>
<tr><td className="p-2 font-mono" colSpan={8}>{category}</td></tr>
<ServerLines category={category} servers={servers} />
</>
))
)
function ServerCategories({ ordered }: { ordered: { [key: string]: Server[] } }) {
return Object.entries(ordered).map(([category, servers]) => (
<React.Fragment key={category}>
<tr>
<td className="p-4 font-mono font-semibold border-b-2 border-gray-300" colSpan={8}>
{category}
</td>
</tr>
<ServerLines category={category} servers={servers} />
</React.Fragment>
));
}

const ServersTable = ({data} : Props) => {
if (!data || data.length === 0) {
return
}

const ServersTable = ({ data }: Props) => {
if (!data || data.length === 0) return;
// Group servers by category
const serversByCategory = Object.groupBy(data, ( server: Server ) => server.category);
const serversByCategory = Object.groupBy(data, (server: Server) => server.category);
// Define the order of categories
const categoryOrder: {[key: string] :Server[]} = { "Kimsufi": [], "So you Start": [], "Rise": [], "uncategorized": [], }
const categoryOrder: { [key: string]: Server[] } = {
Kimsufi: [],
"So you Start": [],
Rise: [],
uncategorized: [],
};
// Merge the server and respect the categories order
const ordered = Object.assign(categoryOrder, serversByCategory);

return (
<table className="text-nowrap">
<thead>
<tr>
<th className="p-4">Name</th>
<th className="p-4">CPU</th>
<th className="p-4">RAM</th>
<th className="p-4">Storage</th>
<th className="p-4">Bandwidth</th>
<th className="p-4">Price</th>
<th className="p-4">Status</th>
<th className="p-4">Datacenters</th>
</tr>
</thead>
<tbody>
<ServerCategories ordered={ordered} />
</tbody>
</table>
<table className="text-nowrap border-separate border-spacing-x-0 border-spacing-y-4">
<thead>
<tr>
<th className="p-4">Name</th>
<th className="p-4">CPU</th>
<th className="p-4">RAM</th>
<th className="p-4">Storage</th>
<th className="p-4">Bandwidth</th>
<th className="p-4">Price</th>
<th className="p-4">Status</th>
<th className="p-4">Datacenters</th>
</tr>
</thead>
<tbody>
<ServerCategories ordered={ordered} />
</tbody>
</table>
);
};

Expand Down
11 changes: 3 additions & 8 deletions front/src/app/components/statusComponent/StatusComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,10 @@ export default function Status({

const loaderDisplay: ReactNode = <div>Loading ...</div>;

const restoredDisplay: ReactNode = (
<div className="text-green-700">Websocket connected</div>
);
const restoredDisplay: ReactNode = <div className="text-green-700">Websocket connected</div>;

const lastMessageDisplay: ReactNode = (
<div>
Last update received at{" "}
{new Date(lastMessage).toTimeString().split(" ")[0]}
</div>
<div>Last update received at {new Date(lastMessage).toTimeString().split(" ")[0]}</div>
);

// Display error message
Expand All @@ -46,7 +41,7 @@ export default function Status({
return (
<div
className={`
basis-1/4 flex flex-col justify-center font-mono
flex flex-col justify-center font-mono
${!statusDisplay ? "hidden" : ""}
${connectionRestored ? fadeOut : ""}
`}
Expand Down
6 changes: 1 addition & 5 deletions front/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
<GoogleAnalytics gaId="G-5XXSRSMV4K" />
</html>
);
Expand Down
44 changes: 12 additions & 32 deletions front/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import { useState } from "react";
import useWebSocket from "react-use-websocket";

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

import ServersTable from "./components/server";
import Status from "./components/statusComponent/StatusComponent";
import { Server, ErrorNull } from "./components/types";
import HeaderComponent from "./components/headerComponent/HeaderComponent";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTelegram } from "@fortawesome/free-brands-svg-icons";
import { Server, ErrorNull } from "./components/types";

export default function Home() {
// Get the websocket URL from the environment variable
Expand Down Expand Up @@ -42,34 +41,15 @@ export default function Home() {
});

return (
<div className="flex flex-row justify-center">
<div className="pt-10 pb-20 px-10">
<div className="flex flex-row min-w-fit justify-center flex-nowrap text-nowrap">
<div className="basis-1/4 flex flex-col justify-center items-center">
Comment on lines -45 to -48
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's some magic in there which broke the page center alignment when being removed.

<div>Receive notifications on Telegram</div>
<a
className="flex text-blue-400 space-x-1"
href="https://t.me/KimsufiNotifierBot"
>
<div>
<FontAwesomeIcon icon={faTelegram} />
</div>
<div>t.me/KimsufiNotifierBot</div>
</a>
</div>
<div className="basis-2/4 px-40 py-5 flex-none text-center text-xl font-bold">
OVH Eco server availability
</div>
<Status
error={error}
data={data}
connectionRestored={connectionRestored}
setConnectionRestored={setConnectionRestored}
lastMessage={lastMessage}
/>
</div>
<ServersTable data={data} />
</div>
<div className="Home flex flex-col items-center pt-10 pb-20 px-10">
<HeaderComponent
error={error}
data={data}
connectionRestored={connectionRestored}
setConnectionRestored={setConnectionRestored}
lastMessage={lastMessage}
/>
<ServersTable data={data} />
</div>
);
}