diff --git a/.gitignore b/.gitignore index 63e78fe94..9c5b57e15 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ dump /cypress/videos /cypress/screenshots -.DS_Store \ No newline at end of file +.DS_Store +.excalidraw \ No newline at end of file diff --git a/app/components/SubNav.tsx b/app/components/SubNav.tsx index 7fd1895b8..451b2597a 100644 --- a/app/components/SubNav.tsx +++ b/app/components/SubNav.tsx @@ -5,7 +5,11 @@ import type * as React from "react"; import { ArrowUpIcon } from "./icons/ArrowUp"; export function SubNav({ children }: { children: React.ReactNode }) { - return ; + return ( +
+ +
+ ); } export function SubNavLink({ diff --git a/app/components/layout/Footer.tsx b/app/components/layout/Footer.tsx new file mode 100644 index 000000000..c3c33b4e5 --- /dev/null +++ b/app/components/layout/Footer.tsx @@ -0,0 +1,92 @@ +import { Link, useLoaderData } from "@remix-run/react"; +import type { RootLoaderData } from "~/root"; +import { discordFullName } from "~/utils/strings"; +import { + FAQ_PAGE, + SENDOU_INK_DISCORD_URL, + SENDOU_INK_GITHUB_URL, + SENDOU_INK_PATREON_URL, + SENDOU_TWITTER_URL, + userPage, +} from "~/utils/urls"; +import { DiscordIcon } from "../icons/Discord"; +import { GitHubIcon } from "../icons/GitHub"; +import { PatreonIcon } from "../icons/Patreon"; +import { TwitterIcon } from "../icons/Twitter"; + +// xxx: add some sendouLove +export function Footer() { + const data = useLoaderData(); + + return ( + + ); +} diff --git a/app/components/layout/index.tsx b/app/components/layout/index.tsx index 66d821da5..4607e1925 100644 --- a/app/components/layout/index.tsx +++ b/app/components/layout/index.tsx @@ -5,6 +5,7 @@ import { UserItem } from "./UserItem"; import navItems from "./nav-items.json"; import { useLocation } from "@remix-run/react"; import { Image } from "../Image"; +import { Footer } from "./Footer"; export const Layout = React.memo(function Layout({ children, @@ -19,7 +20,7 @@ export const Layout = React.memo(function Layout({ ); return ( - <> +
{currentPagesNavItem ? (

@@ -45,6 +46,7 @@ export const Layout = React.memo(function Layout({

setMenuOpen(false)} /> {children} - +
+
); }); diff --git a/app/db/models/users.server.ts b/app/db/models/users.server.ts index b3b42dcbc..b4b8bdbca 100644 --- a/app/db/models/users.server.ts +++ b/app/db/models/users.server.ts @@ -145,3 +145,25 @@ export function findAllPlusMembers() { plusTier: NonNullable; }>; } + +const findAllPatronsStm = sql.prepare(` + select + "id", + "discordId", + "discordName", + "discordDiscriminator", + "patronTier" + from "User" + where "patronTier" is not null + order by "patronTier" desc, "patronSince" asc +`); + +export type FindAllPatrons = Array< + Pick< + User, + "id" | "discordId" | "discordName" | "discordDiscriminator" | "patronTier" + > +>; +export function findAllPatrons() { + return findAllPatronsStm.all() as FindAllPatrons; +} diff --git a/app/db/seed.ts b/app/db/seed.ts index a6544637d..9c89bbf5d 100644 --- a/app/db/seed.ts +++ b/app/db/seed.ts @@ -9,6 +9,7 @@ import { sql } from "~/db/sql"; import type { UpsertManyPlusVotesArgs } from "./models/plusVotes.server"; import { ADMIN_DISCORD_ID } from "~/constants"; import shuffle from "just-shuffle"; +import { dateToDatabaseTimestamp } from "~/utils/dates"; const ADMIN_TEST_AVATAR = "fcfd65a3bea598905abb9ca25296816b"; @@ -26,6 +27,7 @@ const basicSeeds = [ badgesToAdmin, badgesToUsers, badgeManagers, + patrons, ]; export function seed() { @@ -42,7 +44,7 @@ function wipeDB() { "PlusVote", "PlusSuggestion", "PlusVote", - "BadgeOwner", + "TournamentBadgeOwner", "BadgeManager", ]; @@ -226,7 +228,7 @@ function badgesToAdmin() { for (const id of badgesWithDuplicates) { sql .prepare( - `insert into "BadgeOwner" ("badgeId", "userId") values ($id, $userId)` + `insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)` ) .run({ id, userId: 1 }); } @@ -259,7 +261,7 @@ function badgesToUsers() { const userToGetABadge = userIds.shift()!; sql .prepare( - `insert into "BadgeOwner" ("badgeId", "userId") values ($id, $userId)` + `insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)` ) .run({ id, userId: userToGetABadge }); @@ -278,3 +280,22 @@ function badgeManagers() { .run({ id, userId: 2 }); } } + +function patrons() { + const userIds = sql + .prepare(`select "id" from "User" order by random() limit 50`) + .all() + .map((u) => u.id); + + for (const id of userIds) { + sql + .prepare( + `update user set "patronTier" = $patronTier, "patronSince" = $patronSince where id = $id` + ) + .run({ + id, + patronSince: dateToDatabaseTimestamp(faker.date.past()), + patronTier: faker.helpers.arrayElement([1, 1, 2, 2, 2, 3, 3, 4]), + }); + } +} diff --git a/app/root.tsx b/app/root.tsx index 10605e66d..9b37d72c4 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -20,6 +20,8 @@ import layoutStyles from "~/styles/layout.css"; import resetStyles from "~/styles/reset.css"; import { Catcher } from "./components/Catcher"; import { Layout } from "./components/layout"; +import { db } from "./db"; +import type { FindAllPatrons } from "./db/models/users.server"; import type { UserWithPlusTier } from "./db/types"; import { getUser } from "./modules/auth"; @@ -41,6 +43,7 @@ export const meta: MetaFunction = () => ({ }); export interface RootLoaderData { + patrons: FindAllPatrons; user?: Pick< UserWithPlusTier, "id" | "discordId" | "discordAvatar" | "plusTier" @@ -51,6 +54,7 @@ export const loader: LoaderFunction = async ({ request }) => { const user = await getUser(request); return json({ + patrons: db.users.findAllPatrons(), user: user ? { discordAvatar: user.discordAvatar, diff --git a/app/styles/global.css b/app/styles/global.css index 3776e2d7e..4f09cce7c 100644 --- a/app/styles/global.css +++ b/app/styles/global.css @@ -23,6 +23,7 @@ --theme-vibrant: hsl(255deg 78% 65%); --theme-transparent: hsl(255deg 66.7% 75% / 40%); --theme-transparent-vibrant: hsl(255deg 78% 65% / 54%); + --theme-semi-transparent-vibrant: hsl(255deg 78% 65% / 75%); --theme-secondary: hsl(85deg 66.7% 55.3%); /* background-pattern.svg not using this currently */ @@ -312,6 +313,11 @@ article { white-space: pre-line; } +ol, +ul { + padding: 0; +} + select { all: unset; width: 90%; diff --git a/app/styles/layout.css b/app/styles/layout.css index 578a46261..f31740247 100644 --- a/app/styles/layout.css +++ b/app/styles/layout.css @@ -1,3 +1,9 @@ +.layout__container { + display: flex; + flex-direction: column; + height: 100%; +} + .layout__header { --item-size: 2.75rem; @@ -35,6 +41,7 @@ margin: 0 auto; padding-block-end: var(--s-32); padding-inline: var(--s-3); + width: 100%; } .layout__burger { @@ -182,3 +189,97 @@ flex-direction: column; gap: var(--s-2); } + +.layout__footer { + background-color: var(--bg-darker); + margin-block-start: auto; + padding: var(--s-2-5); + display: flex; + flex-direction: column; + gap: var(--s-6); +} + +.layout__footer__link-list { + display: flex; + justify-content: space-between; + font-size: var(--fonts-xxs); +} + +.layout__footer__link-list > a { + flex: 1 1 0; + text-align: center; +} + +.layout__footer__socials { + display: flex; + gap: var(--s-2); + justify-content: center; +} + +.layout__footer__social-link { + background-color: var(--theme-transparent-vibrant); + border-radius: var(--rounded); + height: 16rem; + flex: 1 1 0; + display: flex; + align-items: center; + flex-direction: column; + justify-content: space-between; + padding: var(--s-4); + font-size: var(--fonts-lg); + cursor: pointer; + max-width: 10rem; +} + +.layout__footer__social-link:hover { + background-color: var(--theme-semi-transparent-vibrant); +} + +.layout__footer__social-link:hover > .layout__footer__social-icon { + transform: translateY(-0.3rem); +} + +/* .layout__footer__social-link:hover > .layout__footer__social-icon.github { + fill: #333; +} + +.layout__footer__social-link:hover > .layout__footer__social-icon.twitter { + fill: #1da1f2; +} + +.layout__footer__social-link:hover > .layout__footer__social-icon.discord { + fill: #5865f2; +} + +.layout__footer__social-link:hover > .layout__footer__social-icon.patreon { + fill: #f96854; +} */ + +.layout__footer__social-icon { + height: 2.25rem; + transition: transform 0.25s ease-in-out; +} + +.layout__footer__social-header { + text-align: center; +} + +.layout__footer__social-header > p { + font-size: var(--fonts-xxs); +} + +.layout__footer__patron-title { + text-align: center; + font-weight: var(--semi-bold); + font-size: var(--fonts-sm); +} + +.layout__footer__patron-list { + display: flex; + list-style: none; + flex-wrap: wrap; + font-size: var(--fonts-xs); + gap: var(--s-1); + justify-content: center; + margin-block-start: var(--s-2); +} diff --git a/app/utils/urls.ts b/app/utils/urls.ts index 9eaf8ea09..b4f5fa730 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -1,6 +1,7 @@ import type { Badge } from "~/db/types"; export const SENDOU_INK_DISCORD_URL = "https://discord.gg/sendou"; +export const SENDOU_TWITTER_URL = "https://twitter.com/sendouc"; export const SENDOU_INK_TWITTER_URL = "https://twitter.com/sendouink"; export const SENDOU_INK_PATREON_URL = "https://patreon.com/sendou"; export const SENDOU_INK_GITHUB_URL = "https://github.com/Sendouc/sendou.ink";