From 32c97a2467199d55718e824a30516193410c3565 Mon Sep 17 00:00:00 2001
From: Kalle <38327916+Sendouc@users.noreply.github.com>
Date: Tue, 28 Jan 2025 17:22:45 +0200
Subject: [PATCH] Bluesky via Discord connection + upgrade remix-auth + remove
Twitter references (#2058)
* Remove Twitter references
* Upgrade remix auth, bsky via Discord
* Test
---
README.md | 2 +-
app/components/Dialog.tsx | 3 +-
app/components/icons/Twitter.tsx | 16 --
app/constants.ts | 1 -
app/db/seed/index.ts | 10 +-
app/db/tables.ts | 2 -
app/db/types.ts | 2 -
.../api-public/routes/user.$identifier.ts | 3 +-
app/features/api-public/schema.ts | 3 +-
app/features/articles/routes/a.$slug.tsx | 1 -
.../auth/core/DiscordStrategy.server.ts | 216 ++++++++----------
.../auth/core/authenticator.server.ts | 14 +-
app/features/auth/core/routes.server.ts | 39 +++-
app/features/info/routes/contributions.tsx | 90 ++------
.../routes/plus.suggestions.tsx | 1 -
app/features/sendouq/routes/q.info.tsx | 10 +-
app/features/sendouq/routes/q.rules.tsx | 2 +-
app/features/team/TeamRepository.server.ts | 5 +-
.../team/routes/t.$customUrl.edit.test.ts | 2 -
.../team/routes/t.$customUrl.edit.tsx | 24 +-
app/features/team/routes/t.$customUrl.tsx | 24 +-
app/features/team/team-constants.ts | 1 -
app/features/team/team-schemas.server.ts | 4 -
app/features/team/team.css | 13 --
.../components/SocialLinksList.tsx | 10 -
.../routes/org.$slug.tsx | 8 -
.../tournament-organization.css | 4 -
app/features/tournament/routes/to.$id.tsx | 13 --
.../user-page/UserRepository.server.ts | 7 +-
.../user-page/routes/u.$identifier.edit.tsx | 25 +-
.../user-page/routes/u.$identifier.index.tsx | 11 +-
app/styles/u.css | 9 -
app/utils/string.test.ts | 2 +-
app/utils/urls.ts | 8 -
e2e/team.spec.ts | 8 +-
locales/da/faq.json | 2 +-
locales/da/team.json | 1 -
locales/da/user.json | 3 +-
locales/de/faq.json | 2 +-
locales/de/team.json | 1 -
locales/de/user.json | 3 +-
locales/en/common.json | 2 +-
locales/en/faq.json | 2 +-
locales/en/team.json | 1 -
locales/en/user.json | 5 +-
locales/es-ES/common.json | 1 -
locales/es-ES/faq.json | 2 +-
locales/es-ES/team.json | 1 -
locales/es-ES/user.json | 3 +-
locales/es-US/common.json | 1 -
locales/es-US/faq.json | 2 +-
locales/es-US/team.json | 1 -
locales/es-US/user.json | 3 +-
locales/fr-CA/faq.json | 2 +-
locales/fr-CA/team.json | 1 -
locales/fr-CA/user.json | 3 +-
locales/fr-EU/faq.json | 2 +-
locales/fr-EU/team.json | 1 -
locales/fr-EU/user.json | 3 +-
locales/he/faq.json | 2 +-
locales/he/team.json | 1 -
locales/he/user.json | 3 +-
locales/it/faq.json | 2 +-
locales/it/user.json | 1 -
locales/ja/common.json | 1 -
locales/ja/faq.json | 2 +-
locales/ja/team.json | 1 -
locales/ja/user.json | 4 +-
locales/pl/faq.json | 1 -
locales/pl/team.json | 1 -
locales/pl/user.json | 1 -
locales/pt-BR/common.json | 1 -
locales/pt-BR/faq.json | 2 +-
locales/pt-BR/team.json | 1 -
locales/pt-BR/user.json | 3 +-
locales/ru/faq.json | 2 +-
locales/ru/team.json | 1 -
locales/ru/user.json | 3 +-
locales/zh/common.json | 1 -
locales/zh/faq.json | 2 +-
locales/zh/team.json | 1 -
locales/zh/user.json | 3 +-
migrations/079-remove-twitter.js | 8 +
package-lock.json | 126 +++++++---
package.json | 4 +-
85 files changed, 305 insertions(+), 514 deletions(-)
delete mode 100644 app/components/icons/Twitter.tsx
create mode 100644 migrations/079-remove-twitter.js
diff --git a/README.md b/README.md
index 6cd0fe937..90a14b0f4 100644
--- a/README.md
+++ b/README.md
@@ -132,7 +132,7 @@ sendou.ink/
│ ├── components/ -- React components
│ ├── db/ -- Database layer
│ ├── hooks/ -- React hooks
-│ ├── modules/ -- "nodu_modules but part of the app" https://twitter.com/ryanflorence/status/1535103735952658432
+│ ├── modules/ -- "node_modules but part of the app"
│ ├── routes/ -- Routes see: https://remix.run/docs/en/v1/guides/routing
│ ├── styles/ -- All .css files of the project for styling
│ ├── utils/ -- Random helper functions used in many places
diff --git a/app/components/Dialog.tsx b/app/components/Dialog.tsx
index 081f30fbe..6a0449123 100644
--- a/app/components/Dialog.tsx
+++ b/app/components/Dialog.tsx
@@ -1,6 +1,8 @@
import React from "react";
import invariant from "~/utils/invariant";
+// TODO: use react aria components
+
export function Dialog({
children,
isOpen,
@@ -59,7 +61,6 @@ function useDOMSync(isOpen: boolean) {
if (isOpen) {
dialog.showModal();
- // TODO: can be replaced with https://twitter.com/argyleink/status/1529869352660439048 once gets control
html.classList.add("lock-scroll");
} else {
dialog.close();
diff --git a/app/components/icons/Twitter.tsx b/app/components/icons/Twitter.tsx
deleted file mode 100644
index f55668dc9..000000000
--- a/app/components/icons/Twitter.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export function TwitterIcon({ className }: { className?: string }) {
- return (
-
- Twitter Icon
-
-
- );
-}
diff --git a/app/constants.ts b/app/constants.ts
index bfb0370b0..2babab8e3 100644
--- a/app/constants.ts
+++ b/app/constants.ts
@@ -14,7 +14,6 @@ export const USER = {
CUSTOM_NAME_MAX_LENGTH: 32,
CUSTOM_NAME_REGEXP: notAllEmptyCharactersRegExp,
BATTLEFY_MAX_LENGTH: 32,
- BSKY_MAX_LENGTH: 50,
IN_GAME_NAME_TEXT_MAX_LENGTH: 20,
IN_GAME_NAME_DISCRIMINATOR_MAX_LENGTH: 5,
WEAPON_POOL_MAX_SIZE: 5,
diff --git a/app/db/seed/index.ts b/app/db/seed/index.ts
index 0daf81510..f3946836c 100644
--- a/app/db/seed/index.ts
+++ b/app/db/seed/index.ts
@@ -245,7 +245,6 @@ async function adminUser() {
twitch: "Sendou",
youtubeId: "UCWbJLXByvsfQvTcR4HLPs5Q",
discordAvatar: ADMIN_TEST_AVATAR,
- twitter: "sendouc",
discordUniqueName: "sendou",
});
}
@@ -294,7 +293,6 @@ function nzapUser() {
twitch: null,
youtubeId: null,
discordAvatar: NZAP_TEST_AVATAR,
- twitter: null,
discordUniqueName: null,
});
}
@@ -474,7 +472,6 @@ function fakeUser(usedNames: Set) {
discordId: String(faker.string.numeric(17)),
discordName: uniqueDiscordName(usedNames),
twitch: null,
- twitter: null,
youtubeId: null,
discordUniqueName: null,
});
@@ -1578,12 +1575,11 @@ function detailedTeam() {
sql
.prepare(
/* sql */ `
- insert into "AllTeam" ("name", "customUrl", "inviteCode", "twitter", "bio", "avatarImgId", "bannerImgId")
+ insert into "AllTeam" ("name", "customUrl", "inviteCode", "bio", "avatarImgId", "bannerImgId")
values (
'Alliance Rogue',
'alliance-rogue',
'${nanoid(INVITE_CODE_LENGTH)}',
- 'AllianceRogueFR',
'${faker.lorem.paragraph()}',
1,
2
@@ -1643,13 +1639,12 @@ function otherTeams() {
sql
.prepare(
/* sql */ `
- insert into "AllTeam" ("id", "name", "customUrl", "inviteCode", "twitter", "bio")
+ insert into "AllTeam" ("id", "name", "customUrl", "inviteCode", "bio")
values (
@id,
@name,
@customUrl,
@inviteCode,
- @twitter,
@bio
)
`,
@@ -1659,7 +1654,6 @@ function otherTeams() {
name: teamName,
customUrl: teamCustomUrl,
inviteCode: nanoid(INVITE_CODE_LENGTH),
- twitter: faker.internet.username(),
bio: faker.lorem.paragraph(),
});
diff --git a/app/db/tables.ts b/app/db/tables.ts
index 6296064ed..d7fabf8e0 100644
--- a/app/db/tables.ts
+++ b/app/db/tables.ts
@@ -34,7 +34,6 @@ export interface Team {
id: GeneratedAlways;
inviteCode: string;
name: string;
- twitter: string | null;
bsky: string | null;
}
@@ -784,7 +783,6 @@ export interface User {
showDiscordUniqueName: Generated;
stickSens: number | null;
twitch: string | null;
- twitter: string | null;
bsky: string | null;
battlefy: string | null;
vc: Generated<"YES" | "NO" | "LISTEN_ONLY">;
diff --git a/app/db/types.ts b/app/db/types.ts
index c30d41099..1c1ffef56 100644
--- a/app/db/types.ts
+++ b/app/db/types.ts
@@ -23,7 +23,6 @@ export interface User {
discordUniqueName: string | null;
showDiscordUniqueName: number;
twitch: string | null;
- twitter: string | null;
youtubeId: string | null;
bio: string | null;
css: string | null;
@@ -419,7 +418,6 @@ export interface Team {
customUrl: string;
inviteCode: string;
css: string | null;
- twitter: string | null;
bio: string | null;
avatarImgId: number | null;
bannerImgId: number | null;
diff --git a/app/features/api-public/routes/user.$identifier.ts b/app/features/api-public/routes/user.$identifier.ts
index 3de0e7952..a720566ac 100644
--- a/app/features/api-public/routes/user.$identifier.ts
+++ b/app/features/api-public/routes/user.$identifier.ts
@@ -32,7 +32,6 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
"User.country",
"User.discordName",
"User.twitch",
- "User.twitter",
"User.battlefy",
"User.bsky",
"User.customUrl",
@@ -95,9 +94,9 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
plusServerTier: user.tier as GetUserResponse["plusServerTier"],
socials: {
twitch: user.twitch,
- twitter: user.twitter,
battlefy: user.battlefy,
bsky: user.bsky,
+ twitter: null, // deprecated field
},
peakXp:
user.xRankPlacements.length > 0
diff --git a/app/features/api-public/schema.ts b/app/features/api-public/schema.ts
index 5b11e09ed..5145a8167 100644
--- a/app/features/api-public/schema.ts
+++ b/app/features/api-public/schema.ts
@@ -26,7 +26,8 @@ export interface GetUserResponse {
country: string | null;
socials: {
twitch: string | null;
- twitter: string | null;
+ // @deprecated
+ twitter: null;
battlefy: string | null;
bsky: string | null;
};
diff --git a/app/features/articles/routes/a.$slug.tsx b/app/features/articles/routes/a.$slug.tsx
index 07ae6e5b0..871eb71ef 100644
--- a/app/features/articles/routes/a.$slug.tsx
+++ b/app/features/articles/routes/a.$slug.tsx
@@ -53,7 +53,6 @@ export const meta: MetaFunction = (args) => {
{ property: "og:title", content: data.title },
{ name: "description", content: description },
{ property: "og:description", content: description },
- { name: "twitter:card", content: "summary_large_image" },
{ property: "og:image", content: articlePreviewUrl(args.params.slug) },
{ property: "og:type", content: "article" },
{ property: "og:site_name", content: "sendou.ink" },
diff --git a/app/features/auth/core/DiscordStrategy.server.ts b/app/features/auth/core/DiscordStrategy.server.ts
index 1ad6e89c1..e1d26df24 100644
--- a/app/features/auth/core/DiscordStrategy.server.ts
+++ b/app/features/auth/core/DiscordStrategy.server.ts
@@ -1,15 +1,9 @@
-import type { OAuth2Profile } from "remix-auth-oauth2";
import { OAuth2Strategy } from "remix-auth-oauth2";
import { z } from "zod";
import type { User } from "~/db/types";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
-import { DISCORD_AUTH_KEY } from "./authenticator.server";
-
-interface DiscordExtraParams extends Record {
- scope: string;
-}
export type LoggedInUser = User["id"];
@@ -35,88 +29,14 @@ const discordUserDetailsSchema = z.tuple([
partialDiscordConnectionsSchema,
]);
-export class DiscordStrategy extends OAuth2Strategy<
- LoggedInUser,
- OAuth2Profile,
- DiscordExtraParams
-> {
- name = DISCORD_AUTH_KEY;
- scope: string;
+export const DiscordStrategy = () => {
+ const envVars = authEnvVars();
- constructor() {
- const envVars = authEnvVars();
-
- super(
- {
- authorizationURL: "https://discord.com/api/oauth2/authorize",
- tokenURL:
- process.env.AUTH_GATEWAY_TOKEN_URL ??
- "https://discord.com/api/oauth2/token",
- clientID: envVars.DISCORD_CLIENT_ID,
- clientSecret: envVars.DISCORD_CLIENT_SECRET,
- callbackURL: new URL("/auth/callback", envVars.BASE_URL).toString(),
- },
- async ({ accessToken }) => {
- try {
- const discordResponses = this.authGatewayEnabled()
- ? await this.fetchProfileViaGateway(accessToken)
- : await this.fetchProfileViaDiscordApi(accessToken);
-
- const [user, connections] =
- discordUserDetailsSchema.parse(discordResponses);
-
- const isAlreadyRegistered = Boolean(
- await UserRepository.identifierToUserId(user.id),
- );
-
- if (!isAlreadyRegistered && !user.verified) {
- logger.info(`User is not verified with id: ${user.id}`);
- throw new Error("Unverified user");
- }
-
- const userFromDb = await UserRepository.upsert({
- discordAvatar: user.avatar ?? null,
- discordId: user.id,
- discordName: user.global_name ?? user.username,
- discordUniqueName: user.global_name ? user.username : null,
- ...this.parseConnections(connections),
- });
-
- return userFromDb.id;
- } catch (e) {
- console.error("Failed to finish authentication:\n", e);
- throw new Error("Failed to finish authentication");
- }
- },
- );
-
- this.scope = "identify connections email";
- }
-
- private authGatewayEnabled() {
+ const authGatewayEnabled = () => {
return Boolean(process.env.AUTH_GATEWAY_TOKEN_URL);
- }
+ };
- private async fetchProfileViaDiscordApi(token: string) {
- const authHeader: [string, string] = ["Authorization", `Bearer ${token}`];
-
- return Promise.all([
- fetch("https://discord.com/api/users/@me", {
- headers: [authHeader],
- }).then(this.jsonIfOk),
- fetch("https://discord.com/api/users/@me/connections", {
- headers: [authHeader],
- }).then(this.jsonIfOk),
- ]);
- }
-
- private async fetchProfileViaGateway(token: string) {
- const url = `${process.env.AUTH_GATEWAY_PROFILE_URL}?token=${token}`;
-
- return fetch(url).then(this.jsonIfOk);
- }
-
- private jsonIfOk(res: Response) {
+ const jsonIfOk = (res: Response) => {
if (!res.ok) {
throw new Error(
`Auth related call failed with status code ${res.status}`,
@@ -124,48 +44,106 @@ export class DiscordStrategy extends OAuth2Strategy<
}
return res.json();
- }
+ };
- private parseConnections(
- connections: z.infer,
- ) {
- if (!connections) throw new Error("No connections");
+ const fetchProfileViaDiscordApi = (token: string) => {
+ const authHeader: [string, string] = ["Authorization", `Bearer ${token}`];
- const result: {
- twitch: string | null;
- twitter: string | null;
- youtubeId: string | null;
- } = {
- twitch: null,
- twitter: null,
- youtubeId: null,
- };
+ return Promise.all([
+ fetch("https://discord.com/api/users/@me", {
+ headers: [authHeader],
+ }).then(jsonIfOk),
+ fetch("https://discord.com/api/users/@me/connections", {
+ headers: [authHeader],
+ }).then(jsonIfOk),
+ ]);
+ };
- for (const connection of connections) {
- if (connection.visibility !== 1 || !connection.verified) continue;
+ const fetchProfileViaGateway = (token: string) => {
+ const url = `${process.env.AUTH_GATEWAY_PROFILE_URL}?token=${token}`;
- switch (connection.type) {
- case "twitch":
- result.twitch = connection.name;
- break;
- case "twitter":
- result.twitter = connection.name;
- break;
- case "youtube":
- result.youtubeId = connection.id;
+ return fetch(url).then(jsonIfOk);
+ };
+
+ return new OAuth2Strategy(
+ {
+ clientId: envVars.DISCORD_CLIENT_ID,
+ clientSecret: envVars.DISCORD_CLIENT_SECRET,
+
+ authorizationEndpoint: "https://discord.com/api/oauth2/authorize",
+ tokenEndpoint:
+ process.env.AUTH_GATEWAY_TOKEN_URL ||
+ "https://discord.com/api/oauth2/token",
+ redirectURI: new URL("/auth/callback", envVars.BASE_URL).toString(),
+
+ scopes: ["identify", "connections", "email"],
+ },
+ async ({ tokens }) => {
+ try {
+ const discordResponses = authGatewayEnabled()
+ ? await fetchProfileViaGateway(tokens.accessToken())
+ : await fetchProfileViaDiscordApi(tokens.accessToken());
+
+ const [user, connections] =
+ discordUserDetailsSchema.parse(discordResponses);
+
+ const isAlreadyRegistered = Boolean(
+ await UserRepository.identifierToUserId(user.id),
+ );
+
+ if (!isAlreadyRegistered && !user.verified) {
+ logger.info(`User is not verified with id: ${user.id}`);
+ throw new Error("Unverified user");
+ }
+
+ const userFromDb = await UserRepository.upsert({
+ discordAvatar: user.avatar ?? null,
+ discordId: user.id,
+ discordName: user.global_name ?? user.username,
+ discordUniqueName: user.global_name ? user.username : null,
+ ...parseConnections(connections),
+ });
+
+ return userFromDb.id;
+ } catch (e) {
+ console.error("Failed to finish authentication:\n", e);
+ throw new Error("Failed to finish authentication");
}
+ },
+ );
+};
+
+function parseConnections(
+ connections: z.infer,
+) {
+ if (!connections) throw new Error("No connections");
+
+ const result: {
+ twitch: string | null;
+ youtubeId: string | null;
+ bsky: string | null;
+ } = {
+ twitch: null,
+ youtubeId: null,
+ bsky: null,
+ };
+
+ for (const connection of connections) {
+ if (connection.visibility !== 1 || !connection.verified) continue;
+
+ switch (connection.type) {
+ case "twitch":
+ result.twitch = connection.name;
+ break;
+ case "youtube":
+ result.youtubeId = connection.id;
+ break;
+ case "bluesky":
+ result.bsky = connection.name;
}
-
- return result;
}
- protected authorizationParams() {
- const urlSearchParams: Record = {
- scope: this.scope,
- };
-
- return new URLSearchParams(urlSearchParams);
- }
+ return result;
}
function authEnvVars() {
diff --git a/app/features/auth/core/authenticator.server.ts b/app/features/auth/core/authenticator.server.ts
index bf4232c96..9dc341977 100644
--- a/app/features/auth/core/authenticator.server.ts
+++ b/app/features/auth/core/authenticator.server.ts
@@ -1,17 +1,9 @@
import { Authenticator } from "remix-auth";
-import { DiscordStrategy } from "./DiscordStrategy.server";
-import type { LoggedInUser } from "./DiscordStrategy.server";
-import { authSessionStorage } from "./session.server";
+import { DiscordStrategy, type LoggedInUser } from "./DiscordStrategy.server";
-export const DISCORD_AUTH_KEY = "discord";
export const SESSION_KEY = "user";
export const IMPERSONATED_SESSION_KEY = "impersonated_user";
-export const authenticator = new Authenticator(
- authSessionStorage,
- {
- sessionKey: SESSION_KEY,
- },
-);
+export const authenticator = new Authenticator();
-authenticator.use(new DiscordStrategy());
+authenticator.use(DiscordStrategy(), "discord");
diff --git a/app/features/auth/core/routes.server.ts b/app/features/auth/core/routes.server.ts
index 1fb469994..ef24abc54 100644
--- a/app/features/auth/core/routes.server.ts
+++ b/app/features/auth/core/routes.server.ts
@@ -4,13 +4,13 @@ import { isbot } from "isbot";
import { z } from "zod";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { canAccessLohiEndpoint, canPerformAdminActions } from "~/permissions";
+import { logger } from "~/utils/logger";
import { parseSearchParams, validate } from "~/utils/remix.server";
import { ADMIN_PAGE, authErrorUrl } from "~/utils/urls";
import { createLogInLink } from "../queries/createLogInLink.server";
import { deleteLogInLinkByCode } from "../queries/deleteLogInLinkByCode.server";
import { userIdByLogInLinkCode } from "../queries/userIdByLogInLinkCode.server";
import {
- DISCORD_AUTH_KEY,
IMPERSONATED_SESSION_KEY,
SESSION_KEY,
authenticator,
@@ -22,23 +22,42 @@ export const callbackLoader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url);
if (url.searchParams.get("error") === "access_denied") {
// The user denied the authentication request
- // This is part of the oauth2 protocol, but remix-auth-oauth2 doesn't do
- // nice error handling for this case.
// https://www.oauth.com/oauth2-servers/server-side-apps/possible-errors/
throw redirect(authErrorUrl("aborted"));
}
- await authenticator.authenticate(DISCORD_AUTH_KEY, request, {
- successRedirect: "/",
- failureRedirect: authErrorUrl("unknown"),
- });
+ try {
+ const userId = await authenticator.authenticate("discord", request);
- throw new Response("Unknown authentication state", { status: 500 });
+ const session = await authSessionStorage.getSession(
+ request.headers.get(SESSION_KEY),
+ );
+
+ session.set(SESSION_KEY, userId);
+
+ return redirect("/", {
+ headers: {
+ "Set-Cookie": await authSessionStorage.commitSession(session),
+ },
+ });
+ } catch (error) {
+ if (error instanceof Error) {
+ logger.error("Error during authentication:", error);
+ throw redirect(authErrorUrl("unknown"));
+ }
+
+ throw error;
+ }
};
export const logOutAction: ActionFunction = async ({ request }) => {
- await authenticator.logout(request, { redirectTo: "/" });
+ const session = await authSessionStorage.getSession(
+ request.headers.get(SESSION_KEY),
+ );
+ return redirect("/", {
+ headers: { "Set-Cookie": await authSessionStorage.destroySession(session) },
+ });
};
export const logInAction: ActionFunction = async ({ request }) => {
@@ -47,7 +66,7 @@ export const logInAction: ActionFunction = async ({ request }) => {
"Login is temporarily disabled",
);
- return await authenticator.authenticate(DISCORD_AUTH_KEY, request);
+ return await authenticator.authenticate("discord", request);
};
export const impersonateAction: ActionFunction = async ({ request }) => {
diff --git a/app/features/info/routes/contributions.tsx b/app/features/info/routes/contributions.tsx
index 93761d292..299e0f887 100644
--- a/app/features/info/routes/contributions.tsx
+++ b/app/features/info/routes/contributions.tsx
@@ -8,15 +8,9 @@ import { languages } from "~/modules/i18n/config";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { makeTitle } from "~/utils/strings";
import {
- ANTARISKA_TWITTER,
- BORZOIC_TWITTER,
GITHUB_CONTRIBUTORS_URL,
- LEAN_TWITTER,
RHODESMAS_FREESOUND_PROFILE_URL,
- SENDOU_TWITTER_URL,
SPLATOON_3_INK,
- UBERU_TWITTER,
- YAGA_TWITTER,
} from "~/utils/urls";
export const meta: MetaFunction = () => {
@@ -38,7 +32,7 @@ const PROGRAMMERS = [
] as const;
const TRANSLATORS: Array<{
- translators: Array;
+ translators: Array;
language: (typeof languages)[number]["code"];
}> = [
{
@@ -46,11 +40,7 @@ const TRANSLATORS: Array<{
language: "da",
},
{
- translators: [
- { name: "NoAim™bUrn", twitter: "noaim_brn" },
- { name: "Alice", twitter: "Aloschus" },
- "jgiefer",
- ],
+ translators: ["NoAim™bUrn", "Alice", "jgiefer"],
language: "de",
},
{
@@ -74,7 +64,7 @@ const TRANSLATORS: Array<{
language: "he",
},
{
- translators: [{ name: "funyaaa", twitter: "funyaaa1" }, "taqm", "yutarour"],
+ translators: ["funyaaa", "taqm", "yutarour"],
language: "ja",
},
{
@@ -86,15 +76,15 @@ const TRANSLATORS: Array<{
language: "pl",
},
{
- translators: [{ name: "Ant", twitter: "Ant__Spl" }],
+ translators: ["Ant"],
language: "pt-BR",
},
{
- translators: [{ name: "Ferrari", twitter: "Blusling" }],
+ translators: ["Ferrari"],
language: "nl",
},
{
- translators: [{ name: "DoubleCookies", twitter: "DblCookies" }, "yaga"],
+ translators: ["DoubleCookies", "yaga"],
language: "ru",
},
{
@@ -111,11 +101,7 @@ export default function ContributionsPage() {
- Sendou.ink is a project by{" "}
-
- Sendou
- {" "}
- with help from contributors:
+ Sendou.ink is a project by Sendou with help from contributors:
@@ -125,36 +111,11 @@ export default function ContributionsPage() {
{t("contributions:code")}
-
-
- Lean
- {" "}
- - {t("contributions:lean")}
-
-
-
- borzoic
- {" "}
- - {t("contributions:borzoic")}
-
-
-
- uberu
- {" "}
- - {t("contributions:uberu")}
-
-
-
- yaga
- {" "}
- - {t("contributions:yaga")}
-
-
-
- Antariska, yaga & harryXYZ
- {" "}
- - {t("contributions:antariska")}
-
+ Lean - {t("contributions:lean")}
+ borzoic - {t("contributions:borzoic")}
+ uberu - {t("contributions:uberu")}
+ yaga - {t("contributions:yaga")}
+ Antariska, yaga & harryXYZ - {t("contributions:antariska")}
splatoon3.ink
@@ -163,27 +124,12 @@ export default function ContributionsPage() {
{TRANSLATORS.map(({ translators, language }) => (
- {translators
- .map((t) =>
- typeof t === "string" ? (
- t
- ) : (
-
- {t.name}
-
- ),
- )
- .map((element, i, arr) => (
-
- {element}
- {i !== arr.length - 1 ? ", " : null}
-
- ))}{" "}
+ {translators.map((element, i, arr) => (
+
+ {element}
+ {i !== arr.length - 1 ? ", " : null}
+
+ ))}{" "}
- {t("contributions:translation")} (
{languages.find((lang) => lang.code === language)!.name})
diff --git a/app/features/plus-suggestions/routes/plus.suggestions.tsx b/app/features/plus-suggestions/routes/plus.suggestions.tsx
index 3e104e37d..579b0f50a 100644
--- a/app/features/plus-suggestions/routes/plus.suggestions.tsx
+++ b/app/features/plus-suggestions/routes/plus.suggestions.tsx
@@ -335,7 +335,6 @@ function SuggestedUser({
suggested: { id: suggestion.suggested.id },
targetPlusTier: Number(tier),
}) ? (
- // TODO: resetScroll={false} https://twitter.com/ryanflorence/status/1527775882797907969
- Twitter thread
- {" "}
- on a longer explanation.
+ opposite.
Only the whole set's result matters so points gained/lost are the
diff --git a/app/features/sendouq/routes/q.rules.tsx b/app/features/sendouq/routes/q.rules.tsx
index 5456c768f..4c4710ede 100644
--- a/app/features/sendouq/routes/q.rules.tsx
+++ b/app/features/sendouq/routes/q.rules.tsx
@@ -81,7 +81,7 @@ export default function SendouqRules() {
Players banned by{" "}
-
+
Splatoon Competitive Community Safety
{" "}
are not allowed to participate. Playing with banned players is not
diff --git a/app/features/team/TeamRepository.server.ts b/app/features/team/TeamRepository.server.ts
index c289fb944..32af27f8a 100644
--- a/app/features/team/TeamRepository.server.ts
+++ b/app/features/team/TeamRepository.server.ts
@@ -67,7 +67,6 @@ export function findByCustomUrl(customUrl: string) {
.select(({ eb }) => [
"Team.id",
"Team.name",
- "Team.twitter",
"Team.bsky",
"Team.bio",
"Team.customUrl",
@@ -148,12 +147,11 @@ export async function update({
name,
customUrl,
bio,
- twitter,
bsky,
css,
}: Pick<
Insertable
,
- "id" | "name" | "customUrl" | "bio" | "twitter" | "bsky"
+ "id" | "name" | "customUrl" | "bio" | "bsky"
> & { css: string | null }) {
return db
.updateTable("AllTeam")
@@ -161,7 +159,6 @@ export async function update({
name,
customUrl,
bio,
- twitter,
bsky,
css,
})
diff --git a/app/features/team/routes/t.$customUrl.edit.test.ts b/app/features/team/routes/t.$customUrl.edit.test.ts
index 54b5f5188..97bde465d 100644
--- a/app/features/team/routes/t.$customUrl.edit.test.ts
+++ b/app/features/team/routes/t.$customUrl.edit.test.ts
@@ -31,7 +31,6 @@ describe("team creation", () => {
bio: null,
bsky: null,
css: null,
- twitter: null,
},
{ user: "regular", params: { customUrl: "team-1" } },
);
@@ -50,7 +49,6 @@ describe("team creation", () => {
bio: null,
bsky: null,
css: null,
- twitter: null,
},
{ user: "regular", params: { customUrl: "team-1" } },
),
diff --git a/app/features/team/routes/t.$customUrl.edit.tsx b/app/features/team/routes/t.$customUrl.edit.tsx
index 11af6928c..f26a2e267 100644
--- a/app/features/team/routes/t.$customUrl.edit.tsx
+++ b/app/features/team/routes/t.$customUrl.edit.tsx
@@ -25,7 +25,7 @@ import {
parseRequestPayload,
validate,
} from "~/utils/remix.server";
-import { makeTitle, pathnameFromPotentialURL } from "~/utils/strings";
+import { makeTitle } from "~/utils/strings";
import { assertUnreachable } from "~/utils/types";
import {
TEAM_SEARCH_PAGE,
@@ -158,7 +158,6 @@ export default function EditTeamPage() {
) : null}
-
();
- const [value, setValue] = React.useState(team.twitter ?? "");
-
- return (
-
- {t("team:forms.fields.teamTwitter")}
- setValue(pathnameFromPotentialURL(e.target.value))}
- testId="twitter-input"
- />
-
- );
-}
-
function BlueskyInput() {
const { t } = useTranslation(["team"]);
const { team } = useLoaderData();
diff --git a/app/features/team/routes/t.$customUrl.tsx b/app/features/team/routes/t.$customUrl.tsx
index 63031c689..77122f869 100644
--- a/app/features/team/routes/t.$customUrl.tsx
+++ b/app/features/team/routes/t.$customUrl.tsx
@@ -13,7 +13,6 @@ import { SubmitButton } from "~/components/SubmitButton";
import { BskyIcon } from "~/components/icons/Bsky";
import { EditIcon } from "~/components/icons/Edit";
import { StarIcon } from "~/components/icons/Star";
-import { TwitterIcon } from "~/components/icons/Twitter";
import { UsersIcon } from "~/components/icons/Users";
import { useUser } from "~/features/auth/core/user";
import { isAdmin } from "~/permissions";
@@ -27,7 +26,6 @@ import {
manageTeamRosterPage,
navIconUrl,
teamPage,
- twitterUrl,
userPage,
userSubmittedImage,
} from "~/utils/urls";
@@ -128,7 +126,7 @@ function TeamBanner() {
})}