From 07e2c0f7b1dc9ffcfb5c2ca0d51275976f434528 Mon Sep 17 00:00:00 2001 From: Matchu Date: Sun, 25 Sep 2022 08:05:38 -0700 Subject: [PATCH] Add the /donate page Just doing some house-cleaning on easy pages that need converted before DTI Classic can retire! --- pages/donate.tsx | 105 +++++++++ scripts/setup-mysql.sql | 3 + src/app/DonationsPage.js | 127 ++++++++++ src/app/GlobalFooter.js | 3 + src/app/PrivacyPolicyPage.js | 372 ++++++++++++++---------------- src/app/TermsOfUsePage.js | 204 ++++++++-------- src/app/UserOutfitsPage.js | 6 +- src/app/components/TextContent.js | 31 +++ src/app/images/fastly-logo.svg | 21 ++ 9 files changed, 567 insertions(+), 305 deletions(-) create mode 100644 pages/donate.tsx create mode 100644 src/app/DonationsPage.js create mode 100644 src/app/components/TextContent.js create mode 100644 src/app/images/fastly-logo.svg diff --git a/pages/donate.tsx b/pages/donate.tsx new file mode 100644 index 0000000..350c17b --- /dev/null +++ b/pages/donate.tsx @@ -0,0 +1,105 @@ +import DonationsPage from "../src/app/DonationsPage"; +// @ts-ignore: doesn't understand module.exports +import connectToDb from "../src/server/db"; + +type Props = { campaigns: Campaign[] }; + +type Campaign = { + id: string; + name: string; + donationFeatures: DonationFeature[]; +}; +type DonationFeature = { + id: string; + donorName: string | null; + outfit: { id: string; name: string; updatedAt: string } | null; +}; + +export default function DonationsPageWrapper({ campaigns }: Props) { + return ; +} + +/** + * getStaticProps loads the donation info and organizes it into a convenient + * structure for the page props. + * + * This happens at build time! This data basically hasn't changed since 2017, + * so we'd rather have the performance benefits and reliability of just + * building it up-front than making this an SSR thing. (It also makes it harder + * for someone to add a surprise prank message years later when we're not + * paying attention, by editing the outfit title—it would still probably make + * it onto the site eventually, but not until the next build, which should be + * discouraging. But nobody's ever tried to prank this page before, so, shrug!) + * + * I also just went with a direct DB query, to avoid putting any of this in our + * GraphQL schema when it's really super-duper *just* for this page and *just* + * going to be requested in super-duper *one* way. + */ +export async function getStaticProps() { + const db = await connectToDb(); + + const [rows]: [QueryRow[]] = await db.query({ + sql: ` + SELECT + donation_features.id, + donations.donor_name, + campaigns.id, campaigns.name, + outfits.id, outfits.name, outfits.updated_at + FROM donation_features + INNER JOIN donations ON donations.id = donation_features.donation_id + INNER JOIN campaigns ON campaigns.id = donations.campaign_id + LEFT JOIN outfits ON outfits.id = donation_features.outfit_id + ORDER BY campaigns.created_at DESC, donations.created_at ASC; + `, + nestTables: true, + }); + + // Reorganize the query rows into campaign objects with lists of donation + // features. + const campaigns: Campaign[] = []; + for (const row of rows) { + // Find the campaign for this feature in our campaigns list, or add it if + // it's not present yet. + let campaign = campaigns.find((c) => c.id === String(row.campaigns.id)); + if (campaign == null) { + campaign = { + id: String(row.campaigns.id), + name: row.campaigns.name, + donationFeatures: [], + }; + campaigns.push(campaign); + } + + // Reformat the outfit and donation feature into safer and more + // serializable forms. + const outfit = + row.outfits.id != null + ? { + id: String(row.outfits.id), + name: row.outfits.name, + updatedAt: row.outfits.updated_at.toISOString(), + } + : null; + const donationFeature: DonationFeature = { + id: String(row.donation_features.id), + donorName: row.donations.donor_name, + outfit, + }; + + // Add this donation feature to the campaign. + campaign.donationFeatures.push(donationFeature); + } + + return { + props: { campaigns }, + }; +} + +type QueryRow = { + donation_features: { id: number }; + donations: { donor_name: string | null }; + campaigns: { id: string; name: string }; + outfits: + | { id: number; name: string; updated_at: Date } + | { id: null; name: null; updated_at: null }; +}; diff --git a/scripts/setup-mysql.sql b/scripts/setup-mysql.sql index c7909d9..2e07ee6 100644 --- a/scripts/setup-mysql.sql +++ b/scripts/setup-mysql.sql @@ -1,8 +1,11 @@ USE openneo_impress; -- Public data tables: read +GRANT SELECT ON campaigns TO impress2020; GRANT SELECT ON colors TO impress2020; GRANT SELECT ON color_translations TO impress2020; +GRANT SELECT ON donation_features TO impress2020; +GRANT SELECT ON donations TO impress2020; GRANT SELECT ON items TO impress2020; GRANT SELECT ON item_translations TO impress2020; GRANT SELECT ON modeling_logs TO impress2020; diff --git a/src/app/DonationsPage.js b/src/app/DonationsPage.js new file mode 100644 index 0000000..c83f293 --- /dev/null +++ b/src/app/DonationsPage.js @@ -0,0 +1,127 @@ +import React from "react"; +import Head from "next/head"; +import { Heading1, Heading2 } from "./util"; +import TextContent from "./components/TextContent"; + +import FastlyLogoImg from "./images/fastly-logo.svg"; +import { Box, Wrap, WrapItem } from "@chakra-ui/react"; +import Image from "next/image"; +import { OutfitCard } from "./UserOutfitsPage"; + +function DonationsPage({ campaigns }) { + return ( + <> + + Donations | Dress to Impress + + + Our donors +

+ Dress to Impress has been around a long time—from back when Matchu was + in middle school! Without a real source of income, we used to depend a + lot on community donations to keep the site running. +

+

+ Since then, life has changed a lot, and we're able to comfortably fund + Dress to Impress out-of-pocket. But we're still very grateful to the + donors who got us through those tougher years! Here's a showcase of + their outfits 💖 +

+

+ {/* Thanking Fastly somewhere in a sponsors page on our site is a + * condition of the program, so here we are! But also it's a great + * deal and I mean what I say! */} + We're also grateful to , who offer us some CDN + hosting services under their non-profit & open-source partner + program! They help us load all the big images around the site much + faster! Thank you! +

+
+ {campaigns.map((campaign) => ( + + ))} + + ); +} + +function CampaignSection({ campaign }) { + const { donationFeatures, name } = campaign; + const featuresWithNames = donationFeatures.filter( + (f) => f.donorName?.length > 0 + ); + const allDonorNames = new Set(featuresWithNames.map((f) => f.donorName)); + const donorNamesWithOutfits = new Set( + featuresWithNames.filter((f) => f.outfit != null).map((f) => f.donorName) + ); + const donorNamesWithoutOutfits = [...allDonorNames] + .filter((n) => !donorNamesWithOutfits.has(n)) + .sort((a, b) => a.localeCompare(b)); + + return ( + + {name} donors + + {donationFeatures + .filter((f) => f.outfit != null) + .map((donationFeature) => ( + + + + ))} + + {donorNamesWithoutOutfits.length > 0 && ( + + And a few more: {donorNamesWithoutOutfits.join(", ")} + . Thank you! + + )} + + ); +} + +function DonationOutfitCard({ outfit, donorName }) { + return ( + + Thank you, {donorName}! + {outfit.name} + + } + alt={`Outfit thumbnail: ${outfit.name}`} + /> + ); +} + +function FastlyLogoLink() { + return ( + + + Fastly + + + ); +} + +export default DonationsPage; diff --git a/src/app/GlobalFooter.js b/src/app/GlobalFooter.js index d80e3f4..e2d2fa7 100644 --- a/src/app/GlobalFooter.js +++ b/src/app/GlobalFooter.js @@ -31,6 +31,9 @@ function GlobalFooter() { Privacy Policy (Sep 2022) + + Donors + Classic DTI diff --git a/src/app/PrivacyPolicyPage.js b/src/app/PrivacyPolicyPage.js index 728d8a1..bdc5def 100644 --- a/src/app/PrivacyPolicyPage.js +++ b/src/app/PrivacyPolicyPage.js @@ -1,10 +1,10 @@ import React from "react"; -import { css } from "@emotion/react"; import { VStack } from "@chakra-ui/react"; import { Heading1, Heading2, Heading3 } from "./util"; import { useAuthModeFeatureFlag } from "./components/useCurrentUser"; import Head from "next/head"; +import TextContent from "./components/TextContent"; function PrivacyPolicyPage() { const [authMode] = useAuthModeFeatureFlag(); @@ -15,204 +15,190 @@ function PrivacyPolicyPage() { Privacy Policy | Dress to Impress Our privacy policy - -
-

- Hi, friends! Dress to Impress collects certain personal data. Here's - how we use it! -

-

- First off, we'll never sell your private data, ever. It'll - only be available to you and our small trusted staff—and we'll only - use it to serve you directly, debug site issues, and help you share - your creations with others. -

-
- {authMode === "auth0" && ( + +
- Account management

- While our classic app{" "} - uses its own authentication, the app you're using now uses a - service called Auth0 to manage - account creation and login. + Hi, friends! Dress to Impress collects certain personal data. + Here's how we use it!

- We made this decision because authentication is difficult to write - and maintain securely. We felt that Auth0 was the smoothest and - most secure experience we could offer, especially as a small team - of volunteers{" "} - - 😅 - -

-

- - Auth0's terms of service - {" "} - commit to treating your user data as confidential information, not - to be shared with anyone else, and only to be used as part of - Dress to Impress. (The details are in Sections 6 and 7!) -

-

- When signing up, Auth0 will ask for a username, password, and - email address. They store your password as a hash (which, - colloquially, is like a one-way encryption), rather than as the - plain password itself. -

-

- Some user accounts were created before we moved to Auth0. For - those users, we imported their accounts from our custom database - into Auth0. This included username, password hash, and email - address. + First off, we'll never sell your private data, ever. + It'll only be available to you and our small trusted staff—and + we'll only use it to serve you directly, debug site issues, and + help you share your creations with others.

- )} -
- Analytics and logging -

- To understand how people use our site, we use a service called{" "} - Plausible. Every time you visit - a page, we send them a{" "} - - small packet of information - - . -

-

- Plausible is a privacy-focused service. It doesn't store your IP - address in a retrievable way, or add cookies to your browser, or - track you across multiple websites or over time.{" "} - - Here's their data policy. - -

-

- We also use a service called Sentry{" "} - to track errors. When you encounter an error on our site, we send a - copy of it to our Sentry account, to help us debug it later. This - might sometimes include personal data, but Sentry will only share it - with us.{" "} - - Here's their data policy. - -

-

- We also use Linode and{" "} - Fastly for web hosting. Linode - stores our database, and handles most web traffic dealing with - personal data. Personal data also travels through Fastly's servers - temporarily, but they only store aggregate usage logs for us, not - any personally-identifying data. -

-
-
- Creations and contributions -

- People use Dress to Impress to create, share, and communicate! Some - of these things are public, some are private, and some are - configurable. -

- Outfits -

- Outfits are the central creation on Dress to Impress: combining a - pet with items to make something that looks nice! -

-

- Users can log in and save outfits to their account. They can also - share outfits by URL without logging in. -

-

- When you save an outfit to your account, it's somewhat private, but - somewhat public. -

-

- It's private in the sense that there is no central place where - another user can look up your list of outfits. -

-

- But it's public in the sense that anyone with the URL can see - it—and, because the URLs are based on a simple incrementing global - outfit ID, it's easy to look up all the outfits on the site. -

-

- We might change this in the future, to make the URLs hard to guess - and genuinely private. Until then, we advise users to not - to include sensitive data in the outfits they save to their account. -

- Item lists -

- Logged-in users can track the Neopets customization items they own - and want, by saving item lists to their account. -

-

- These lists are private by default, but can be configured to either - be "public" or "trading" as well. -

-

- The "public" status means that anyone who knows your Dress to - Impress username, or item list URL, can see this list. -

-

- The "trading" status includes the same visibility as "public", and - additionally we'll advertise that you own/want this item on its - public list of trades. -

- Modeling contributions -

- When a logged-in user enters their Neopets's name on the site, we - look up that pet's public data on Neopets.com. -

-

- Sometimes, this will download new public outfit data that we've - never seen before. For example, you might show us a Draik (a species - of Neopet) wearing a new item, and we don't have data for a Draik - wearing that item yet. -

-

- When that happens, we'll extract that specific piece of data from - your pet's outfit, and save it to our database, for other users to - mix and match into their own outfits. This process is called - "modeling". -

-

- When you model new data for us, it's separated from your pet. Users - can't discover what pet modeled a certain piece of data, or what - else that pet was wearing. -

-

- But, if you're logged in when modeling, we'll publicly credit your - account for the new "contribution". This will appear in a number of - places, including a list of the most recent contributions, and it - will add points to your account that contribute to a public high - score list. This will publicly display your username. -

-

- Right now, modeling contributions from logged-in users are always - public. This is a limitation of our system, and we might change it - in the future! For now, if you would like to have your public - contributions removed from the site, please use the contact link at - the bottom of the page. -

-
-
+ {authMode === "auth0" && ( +
+ Account management +

+ While our classic app{" "} + uses its own authentication, the app you're using now uses a + service called Auth0 to manage + account creation and login. +

+

+ We made this decision because authentication is difficult to + write and maintain securely. We felt that Auth0 was the + smoothest and most secure experience we could offer, especially + as a small team of volunteers{" "} + + 😅 + +

+

+ + Auth0's terms of service + {" "} + commit to treating your user data as confidential information, + not to be shared with anyone else, and only to be used as part + of Dress to Impress. (The details are in Sections 6 and 7!) +

+

+ When signing up, Auth0 will ask for a username, password, and + email address. They store your password as a hash{" "} + (which, colloquially, is like a one-way encryption), rather than + as the plain password itself. +

+

+ Some user accounts were created before we moved to Auth0. For + those users, we imported their accounts from our custom database + into Auth0. This included username, password hash, and email + address. +

+
+ )} +
+ Analytics and logging +

+ To understand how people use our site, we use a service called{" "} + Plausible. Every time you + visit a page, we send them a{" "} + + small packet of information + + . +

+

+ Plausible is a privacy-focused service. It doesn't store your IP + address in a retrievable way, or add cookies to your browser, or + track you across multiple websites or over time.{" "} + + Here's their data policy. + +

+

+ We also use a service called{" "} + Sentry to track errors. When you + encounter an error on our site, we send a copy of it to our Sentry + account, to help us debug it later. This might sometimes include + personal data, but Sentry will only share it with us.{" "} + + Here's their data policy. + +

+

+ We also use Linode and{" "} + Fastly for web hosting. + Linode stores our database, and handles most web traffic dealing + with personal data. Personal data also travels through Fastly's + servers temporarily, but they only store aggregate usage logs for + us, not any personally-identifying data. +

+
+
+ Creations and contributions +

+ People use Dress to Impress to create, share, and communicate! + Some of these things are public, some are private, and some are + configurable. +

+ Outfits +

+ Outfits are the central creation on Dress to Impress: combining a + pet with items to make something that looks nice! +

+

+ Users can log in and save outfits to their account. They can also + share outfits by URL without logging in. +

+

+ When you save an outfit to your account, it's somewhat private, + but somewhat public. +

+

+ It's private in the sense that there is no central place where + another user can look up your list of outfits. +

+

+ But it's public in the sense that anyone with the URL can see + it—and, because the URLs are based on a simple incrementing global + outfit ID, it's easy to look up all the outfits on the site. +

+

+ We might change this in the future, to make the URLs hard to guess + and genuinely private. Until then, we advise users to not + to include sensitive data in the outfits they save to their + account. +

+ Item lists +

+ Logged-in users can track the Neopets customization items they own + and want, by saving item lists to their account. +

+

+ These lists are private by default, but can be configured to + either be "public" or "trading" as well. +

+

+ The "public" status means that anyone who knows your Dress to + Impress username, or item list URL, can see this list. +

+

+ The "trading" status includes the same visibility as "public", and + additionally we'll advertise that you own/want this item on its + public list of trades. +

+ Modeling contributions +

+ When a logged-in user enters their Neopets's name on the site, we + look up that pet's public data on Neopets.com. +

+

+ Sometimes, this will download new public outfit data that we've + never seen before. For example, you might show us a Draik (a + species of Neopet) wearing a new item, and we don't have data for + a Draik wearing that item yet. +

+

+ When that happens, we'll extract that specific piece of data from + your pet's outfit, and save it to our database, for other users to + mix and match into their own outfits. This process is called + "modeling". +

+

+ When you model new data for us, it's separated from your pet. + Users can't discover what pet modeled a certain piece of data, or + what else that pet was wearing. +

+

+ But, if you're logged in when modeling, we'll publicly credit your + account for the new "contribution". This will appear in a number + of places, including a list of the most recent contributions, and + it will add points to your account that contribute to a public + high score list. This will publicly display your username. +

+

+ Right now, modeling contributions from logged-in users are always + public. This is a limitation of our system, and we might change it + in the future! For now, if you would like to have your public + contributions removed from the site, please use the contact link + at the bottom of the page. +

+
+
+ ); } diff --git a/src/app/TermsOfUsePage.js b/src/app/TermsOfUsePage.js index 562fbbd..954c4bb 100644 --- a/src/app/TermsOfUsePage.js +++ b/src/app/TermsOfUsePage.js @@ -1,8 +1,8 @@ -import { css } from "@emotion/react"; import { VStack } from "@chakra-ui/react"; import Head from "next/head"; import { Heading1, Heading2 } from "./util"; +import TextContent from "./components/TextContent"; function TermsOfUsePage() { return ( @@ -11,114 +11,100 @@ function TermsOfUsePage() { Terms of Use | Dress to Impres Our terms of use - -
-

- Hi, friends! Here's some information about how Dress to Impress is - meant to be used. The rules here aren't very formal, but we hope - they're clear, and we take them very seriously. Thank you for taking - the time to read! -

-
-
- Who can use this service -

- No crypto or NFT projects. Dress to Impress must - not be used as part of a cryptocurrency-related or NFT-related - project, commercial or otherwise. If you use our code, service, or - data to generate NFTs or other products distributed on the - blockchain or similar technologies, the expected remediation is to - cease and desist all distribution of works derived from this - service, in addition to offering appropriate compensation. -

-

- Some users might get banned. We sometimes refuse - service to users we feel are detrimental to our community, at our - sole discretion. This includes users who post content that doesn't - adhere to our terms, which you can see below. -

-
-
- What you can post on this service -

- Keep it Neoboard-safe. Neopets.com allows links to - Dress to Impress, so everything needs to be safe for Neopians of all - ages! Please keep all content "PG" and appropriate for young - community members, just like you do on Neopets.com. (That said, the - rules on the Neoboards haven't always been morally right, such as - when LGBTQIA+ discussion was banned. We'll always diverge from those - rules when it's ethically appropriate!) -

-

- Don't sell things for real money here. We don't - have the capacity to validate who is and isn't a legitimate seller, - so we err on the side of safety and ban all sales. If - you're selling something, please do it in a community where trust - and reputation can be managed more appropriately, and please make - sure it's in line with Neopets's terms. -

-
-
- How you can use our data -

- Be thoughtful using Neopets's data. While Dress to - Impress has a license to distribute Neopets data and images, we - aren't authorized to extend all of the same permissions to you. - Please think carefully about how you use Neopets's art and data you - find on this site, and make sure you're complying with their - licensing agreements and fair use laws, especially for derived works - like outfits. But personal use, and usage that stays on our site, - are always okay! -

-

- Be thoughtful using user-generated data. Some data - posted to Dress to Impress is generated by our users, like their - outfits and item lists. When you post those to Dress to Impress, you - grant us a license to redistribute them with attribution as part of - the site's functionality, respecting your privacy settings when - applicable. But each user still owns their own creations, so only - they can grant you permission to use or share it yourself. -

-

- Please reach out before using our APIs! If you'd - like to use our data to build something new, please contact us! We'd - like to help if we can. But please don't use our APIs without - talking to us first: it can cause performance issues for us, and - reliability issues for you. But we have a few folks who use Dress to - Impress for things like Discord bots, and we'd like to support you - and your community too! -

-
-
- Warranty and liability -

- Our data won't always be correct. While we do our - best to keep the customization on our site in sync with Neopets.com, - sometimes our data is out-of-date, and sometimes an item looks - different on our site than on Neopets.com. We're glad to be a - resource for users buying Neocash items, but as an unofficial - service we simply can't make guarantees, and we encourage you to - check other sources before making a purchase. -

-
-
+ + +
+

+ Hi, friends! Here's some information about how Dress to Impress is + meant to be used. The rules here aren't very formal, but we hope + they're clear, and we take them very seriously. Thank you for + taking the time to read! +

+
+
+ Who can use this service +

+ No crypto or NFT projects. Dress to Impress must + not be used as part of a cryptocurrency-related or NFT-related + project, commercial or otherwise. If you use our code, service, or + data to generate NFTs or other products distributed on the + blockchain or similar technologies, the expected remediation is to + cease and desist all distribution of works derived from this + service, in addition to offering appropriate compensation. +

+

+ Some users might get banned. We sometimes refuse + service to users we feel are detrimental to our community, at our + sole discretion. This includes users who post content that doesn't + adhere to our terms, which you can see below. +

+
+
+ What you can post on this service +

+ Keep it Neoboard-safe. Neopets.com allows links + to Dress to Impress, so everything needs to be safe for Neopians + of all ages! Please keep all content "PG" and appropriate for + young community members, just like you do on Neopets.com. (That + said, the rules on the Neoboards haven't always been morally + right, such as when LGBTQIA+ discussion was banned. We'll always + diverge from those rules when it's ethically appropriate!) +

+

+ Don't sell things for real money here. We don't + have the capacity to validate who is and isn't a legitimate + seller, so we err on the side of safety and ban all{" "} + sales. If you're selling something, please do it in a community + where trust and reputation can be managed more appropriately, and + please make sure it's in line with Neopets's terms. +

+
+
+ How you can use our data +

+ Be thoughtful using Neopets's data. While Dress + to Impress has a license to distribute Neopets data and images, we + aren't authorized to extend all of the same permissions to you. + Please think carefully about how you use Neopets's art and data + you find on this site, and make sure you're complying with their + licensing agreements and fair use laws, especially for derived + works like outfits. But personal use, and usage that stays on our + site, are always okay! +

+

+ Be thoughtful using user-generated data. Some + data posted to Dress to Impress is generated by our users, like + their outfits and item lists. When you post those to Dress to + Impress, you grant us a license to redistribute them with + attribution as part of the site's functionality, respecting your + privacy settings when applicable. But each user still owns their + own creations, so only they can grant you permission to use or + share it yourself. +

+

+ Please reach out before using our APIs! If you'd + like to use our data to build something new, please contact us! + We'd like to help if we can. But please don't use our APIs without + talking to us first: it can cause performance issues for us, and + reliability issues for you. But we have a few folks who use Dress + to Impress for things like Discord bots, and we'd like to support + you and your community too! +

+
+
+ Warranty and liability +

+ Our data won't always be correct. While we do our + best to keep the customization on our site in sync with + Neopets.com, sometimes our data is out-of-date, and sometimes an + item looks different on our site than on Neopets.com. We're glad + to be a resource for users buying Neocash items, but as an + unofficial service we simply can't make guarantees, and we + encourage you to check other sources before making a purchase. +

+
+
+
); } diff --git a/src/app/UserOutfitsPage.js b/src/app/UserOutfitsPage.js index 0bd5076..4b58435 100644 --- a/src/app/UserOutfitsPage.js +++ b/src/app/UserOutfitsPage.js @@ -139,14 +139,14 @@ function UserOutfitsPageContent() { ); } -function OutfitCard({ outfit }) { +export function OutfitCard({ outfit, caption = null, alt = null }) { const image = ( {({ css }) => ( - +
); diff --git a/src/app/components/TextContent.js b/src/app/components/TextContent.js new file mode 100644 index 0000000..a786c1f --- /dev/null +++ b/src/app/components/TextContent.js @@ -0,0 +1,31 @@ +import React from "react"; +import { Box } from "@chakra-ui/react"; +import { css } from "@emotion/react"; + +/** + * TextContent is a wrapper for just, like, normal chill text-based content! + * Its children will receive some simple CSS for tags like

, , etc. + */ +function TextContent({ children, ...props }) { + return ( + + {children} + + ); +} + +export default TextContent; diff --git a/src/app/images/fastly-logo.svg b/src/app/images/fastly-logo.svg new file mode 100644 index 0000000..0660c88 --- /dev/null +++ b/src/app/images/fastly-logo.svg @@ -0,0 +1,21 @@ + + + + + + + +