Nucleus transcripts, fixed season api (again), fixed hydration issues
diff --git a/pages/api/season.js b/pages/api/season.js
index c3066cc..2603a52 100644
--- a/pages/api/season.js
+++ b/pages/api/season.js
@@ -2,6 +2,14 @@
     return Math.floor((new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24))
 }
 
+function daysSinceDate(date) {
+    // If the date is after today, make it last year
+    if (date > new Date()) {
+        date.setFullYear(date.getFullYear() - 1);
+    }
+    return Math.floor((new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
+}
+
 function getSeason() {
     let year = new Date().getFullYear();
 
@@ -10,7 +18,8 @@
         halloween: [new Date(`${year}-10-25`), new Date(`${year}-11-1`)],
         trans: [new Date(`${year}-11-13`), new Date(`${year}-11-19`)],
         christmas: [new Date(`${year}-12-1`), new Date(`${year}-12-26`)],
-        aprilFools: [new Date(`${year}-04-01`), new Date(`${year}-04-01`)]
+        aprilFools: [new Date(`${year}-04-01`), new Date(`${year}-04-01`)],
+        fake: [new Date(`${year}-01-01`), new Date(`${year}-01-01`)]
     }
     const filePaths = {
         normal: "normal",
@@ -23,19 +32,19 @@
 
     let current = new Date();
     let currentSeason = Object.keys(dates).find((str) => current >= dates[str][0] && current <= dates[str][1]) || "normal";
-    let daysIntoSeason;
+    let currentDaysIntoSeason;
     if (currentSeason !== "normal") {
-        daysIntoSeason = daysIntoSeason(dates[currentSeason][0])
+        currentDaysIntoSeason = daysIntoSeason(dates[currentSeason][0])
     } else {
-        // Calculate the days from the end of each season
-        let days = Object.keys(dates).map((str) => daysIntoSeason(dates[str][1])).filter((num) => num > 0);
-        daysIntoSeason = Math.min(...days);
+        // Calculate how many days it has been since each season ended
+        let days = Object.keys(dates).map((str) => daysSinceDate(dates[str][1]))
+        currentDaysIntoSeason = Math.min(...days);
     }
 
     return {
         season: currentSeason,
         filePath: filePaths[currentSeason],
-        daysIntoSeason: daysIntoSeason
+        daysIntoSeason: currentDaysIntoSeason
     }
 }
 
diff --git a/pages/nucleus/transcript/about.js b/pages/nucleus/transcript/about.js
new file mode 100644
index 0000000..088e2d3
--- /dev/null
+++ b/pages/nucleus/transcript/about.js
@@ -0,0 +1,71 @@
+import React, { Component } from 'react'
+import Header from '../../../Components/Header'
+import { AutoLayout, Panel, Title, Subtitle, Text, Divider } from '../../../Components/Panels'
+import { Code } from '../../../Components/Texttools'
+import { Card, CardRow } from '../../../Components/Card'
+import HCaptcha from 'react-hcaptcha';
+import { useReward } from 'react-rewards';
+
+function About(props) {
+    const { reward, isAnimating } = useReward('confetti', 'confetti', {
+        elementSize: 12,
+        spread: 85,
+        position: "absolute",
+        colors: ["#F27878", "#E5AB71", "#E5DC71", "#A1CC65", "#68D49E", "#71AFE5", "#6576CC", "#8D58B2", "#BF5E9F"]
+    });
+
+    return (
+        <>
+            <Header
+                name="Nucleus Transcripts"
+                subtext="Review purged messages in a channel"
+                customImage={"https://assets.clicks.codes/web/logos/nucleus.svg"}
+                embedImage={"https://assets.clicks.codes/bots/nucleus/normal.png"}
+                gradient={["F27878", "D96B6B"]}
+                wave="web/waves/header/nucleus"
+                buttons={[
+                    {color: "424242", buttonText: "FFFFFF", link: "#about", text: "About"},
+                    {color: "424242", buttonText: "FFFFFF", link: "#privacy", text: "Privacy"},
+                    {color: "F27878", buttonText: "FFFFFF", link: "#invite", text: "Invite"}
+                ]}
+                season={props.season}
+            />
+            <AutoLayout>
+                <Panel halfSize={false} id="about">
+                    <Title>About Transcripts</Title>
+                    <Divider />
+                    <Text>Transcripts are generated whenever a purge command is run, or a ticket is deleted (and transcripts are enabled).</Text>
+                    <Text>These store the messages, authors, and the content for future reference.</Text>
+                    <Text>When the transcript is created, you will get a link to our website to view it.</Text>
+                    <Text>Images and files are not stored, and are not available in transcripts.</Text>
+                </Panel>
+                <Panel halfSize={true} id="privacy">
+                    <Title>Privacy</Title>
+                    <Divider />
+                    <Text>Transcripts can be viewed by anyone online if they have the URL. These are random to avoid this happening by chance.</Text>
+                    <Text>The code is long enough that it is hard to guess any specific code. The URL is not linked to your server in any way.</Text>
+                    <Text>All transcripts for your server can be deleted from <Code colour="F27878">/privacy</Code>.</Text>
+                </Panel>
+                <Panel halfSize={true} id="invite">
+                    <Title>Invite</Title>
+                    <Divider />
+                    <CardRow>
+                        <Card
+                            wave="nucleus"
+                            icon="bots/nucleus/circle"
+                            buttonText={"FFFFFF"} gradient={["F27878", "D96B6B"]}
+                            title="Nucleus"
+                            subtext="Invite Nucleus to your server"
+                            buttons={[
+                                {color: "424242", link: "https://discord.com/api/oauth2/authorize?client_id=715989276382462053&permissions=121295465718&scope=bot%20applications.commands", text: "Invite"}
+                            ]}
+                            url="https://discord.com/api/oauth2/authorize?client_id=715989276382462053&permissions=121295465718&scope=bot%20applications.commands"
+                        />
+                    </CardRow>
+                </Panel>
+            </AutoLayout>
+        </>
+    )
+}
+
+export default About;
\ No newline at end of file
diff --git a/pages/nucleus/transcript/index.js b/pages/nucleus/transcript/index.js
new file mode 100644
index 0000000..1c9e88f
--- /dev/null
+++ b/pages/nucleus/transcript/index.js
@@ -0,0 +1,57 @@
+import Axios from 'axios';
+import React from 'react';
+
+function Index(props) {
+    return <>
+    <div style={{
+        width: "100vw",
+        height: "40px",
+        backgroundColor: "#F27878",
+        display: "flex",
+        justifyContent: "left",
+        alignItems: "center",
+        paddingLeft: "25px",
+        color: "white",
+        fontSize: "1.5em",
+    }}>Nucleus Transcripts</div>
+    <div style={{
+        height: "100vw",
+        width: "100vw",
+        backgroundColor: "var(--theme-ui-colors-background)",
+        margintop: "-50px",
+        padding: "25px",
+        paddingTop: "10px",
+        transition: "all 0.3s ease-in-out"
+    }}>
+        <p>{props.humanReadable}</p>
+    </div>
+</>
+}
+
+export default Index;
+export async function getServerSideProps(ctx) {
+    if(!ctx.query.code) {
+        return {
+            redirect: {
+                destination: '/nucleus/transcript/about',
+                permanent: true
+            }
+        }
+    }
+    let code;
+    try {
+        code = await Axios.get(`http://localhost:10000/transcript/${ctx.query.code}/human`);
+    } catch (e) {
+        return {
+            redirect: {
+                destination: '/nucleus/transcript/invalid',
+                permanent: true
+            }
+        }
+    }
+    return {
+        props: {
+            humanReadable: code.data
+        }
+    }
+}
\ No newline at end of file
diff --git a/pages/nucleus/transcript/invalid.js b/pages/nucleus/transcript/invalid.js
new file mode 100644
index 0000000..8491aeb
--- /dev/null
+++ b/pages/nucleus/transcript/invalid.js
@@ -0,0 +1,26 @@
+import { Component } from 'react'
+import Link from 'next/link'
+import Header from '../../../Components/Header'
+
+class Failed extends Component {
+    constructor(props) {
+        super(props)
+    }
+
+    render() {
+        return (
+            <Header
+                name="Transcript does not exist"
+                subtext={<p>No transcripts matched that code. If you believe this is a mistake, please submit a report in <Link href="https://discord.gg/bPaNnxe" target="_blank" rel="noopener noreferrer">our server</Link></p>}
+                gradient={["F27878", "D96B6B"]}
+                wave="web/waves/header/nucleus"
+                buttons={[]}
+                fullscreen={true}
+                season={this.props.season}
+            />
+        )
+    }
+
+}
+
+export default Failed;
\ No newline at end of file
diff --git a/pages/nucleus/transcript/testing.js b/pages/nucleus/transcript/testing.js
new file mode 100644
index 0000000..ac0240b
--- /dev/null
+++ b/pages/nucleus/transcript/testing.js
@@ -0,0 +1,54 @@
+import React from 'react';
+
+function Testing(props) {
+    return <>
+        <div style={{
+            width: "100vw",
+            height: "40px",
+            backgroundColor: "#F27878",
+            display: "flex",
+            justifyContent: "left",
+            alignItems: "center",
+            paddingLeft: "25px",
+            color: "white",
+            fontSize: "1.5em",
+        }}>Nucleus Transcripts</div>
+        <div style={{
+            height: "100vw",
+            width: "100vw",
+            backgroundColor: "var(--theme-ui-colors-background)",
+            margintop: "-50px",
+            padding: "25px",
+            paddingTop: "10px",
+            transition: "all 0.3s ease-in-out"
+        }}>
+            <p>{props.humanReadable}</p>
+        </div>
+    </>
+}
+
+export default Testing;
+export async function getServerSideProps(ctx) {
+    if(!ctx.query.code) {
+        return {
+            redirect: {
+                destination: '/nucleus/transcript/about',
+                permanent: true
+            }
+        }
+    }
+    if (ctx.query.code === "test") {
+        return {
+            props: {
+                humanReadable: "This is a test string! It should render correctly on the page"
+            }
+        }
+    } else {
+        return {
+            redirect: {
+                destination: '/nucleus/transcript/invalid',
+                permanent: true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/pages/nucleus/verify/failure.js b/pages/nucleus/verify/failure.js
index 8581c18..12fdc26 100644
--- a/pages/nucleus/verify/failure.js
+++ b/pages/nucleus/verify/failure.js
@@ -1,4 +1,5 @@
 import { Component } from 'react'
+import Link from 'next/link'
 import Header from '../../../Components/Header'
 
 class Failed extends Component {
@@ -10,7 +11,9 @@
         return (
             <Header
                 name="Verification failed"
-                subtext={<p>Please try again, and if the error persists please contact us at <a href="mailto:verification@clicks.codes" target="_blank" rel="noopener noreferrer">verification@clicks.codes</a></p>}
+                subtext={
+                    <p>Please try again, and if the error persists please contact us at <Link href="mailto:verification@clicks.codes" target="_blank" rel="noopener noreferrer">verification@clicks.codes</Link></p>
+                }
                 gradient={["F27878", "D96B6B"]}
                 wave="web/waves/header/nucleus"
                 buttons={[]}