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

Move components to TypeScript #1928

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@
"eslint --fix",
"git add"
]
}
},
"packageManager": "[email protected]+sha1.216899f511c8dfde183c7cb50b69009c779534a8"
}
57 changes: 37 additions & 20 deletions scripts/utils.js → scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const exec = require('@actions/exec');
const core = require('@actions/core');
const github = require('@actions/github');
const Joi = require('joi');
const http = require('http');
const https = require('https');
const flags = require('./flags.js');
import exec from '@actions/exec';
import core from '@actions/core';
import github from '@actions/github';
import Joi from 'joi';
import http from 'http';
import https from 'https';
import flags from './flags.js';

async function getCurrentBranchName() {
let myOutput = '';
Expand All @@ -13,8 +13,8 @@ async function getCurrentBranchName() {
const options = {
silent: true,
listeners: {
stdout: (data) => (myOutput += data.toString()),
stderr: (data) => (myError += data.toString()),
stdout: (data: Buffer) => (myOutput += data.toString()),
stderr: (data: Buffer) => (myError += data.toString()),
},
};

Expand All @@ -23,7 +23,7 @@ async function getCurrentBranchName() {
}

/** on master branch will return an empty array */
module.exports.getMasterData = async function () {
export const getMasterData = async function () {
const options = { silent: true };
const curentBranchName = await getCurrentBranchName();
// when on a branch/PR different from master
Expand All @@ -40,7 +40,7 @@ module.exports.getMasterData = async function () {
}

// eslint-disable-next-line global-require
const masterData = require('./masterData.js');
const masterData = (await import('./masterData.js')).default as Person[];

// restore `scripts/masterData.js` after was loaded
if (curentBranchName !== 'master') {
Expand All @@ -50,7 +50,7 @@ module.exports.getMasterData = async function () {
return masterData;
};

module.exports.Schema = Joi.object({
export const Schema = Joi.object({
name: Joi.string().required(),
description: Joi.string().required(),
url: Joi.string()
Expand All @@ -60,16 +60,33 @@ module.exports.Schema = Joi.object({
country: Joi.string()
.valid(...flags)
.required(),
twitter: Joi.string().pattern(new RegExp(/^@?(\w){1,15}$/)),
mastodon: Joi.string().pattern(new RegExp(/^@(\w){1,30}@(\w)+\.(.?\w)+$/)),
bluesky: Joi.string().pattern(new RegExp(/^[\w-]+\.(?:[\w-]+\.)?[\w-]+$/)),
twitter: Joi.string().pattern(/^@?(\w){1,15}$/),
mastodon: Joi.string().pattern(/^@(\w){1,30}@(\w)+\.(.?\w)+$/),
bluesky: Joi.string().pattern(/^[\w-]+\.(?:[\w-]+\.)?[\w-]+$/),
emoji: Joi.string().allow(''),
computer: Joi.string().valid('apple', 'windows', 'linux', 'bsd'),
phone: Joi.string().valid('iphone', 'android', 'windowsphone', 'flipphone'),
tags: Joi.array().items(Joi.string()),
});

module.exports.getStatusCode = function (url) {
/*
TODO: This should be inferred but then I want to move to Valibot. If you give a moose a muffin.
*/
export type Person = {
name: string;
description: string;
url: string;
country: string;
twitter?: `@${string}`;
mastodon?: string;
bluesky?: string;
emoji?: string;
computer?: 'apple' | 'windows' | 'linux' | 'bsd';
phone?: 'iphone' | 'android' | 'windowsphone' | 'flipphone';
tags?: string[];
};

export const getStatusCode = function (url: string) {
const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
const REQUEST_TIMEOUT = 10000;
Expand All @@ -90,10 +107,10 @@ module.exports.getStatusCode = function (url) {

// If there are errors, will fail the action & add a comment detailing the issues
// If there are no errors, will leave an "all-clear" comment with relevant URLs (to ease a potential manual check)
module.exports.communicateValidationOutcome = async function (
errors,
failedUrls,
changedData
export const communicateValidationOutcome = function (
errors: { message: string }[],
failedUrls: string[],
changedData: { name: string; url: string }[]
) {
let comment = '';
if (errors.length || failedUrls.length) {
Expand Down
67 changes: 17 additions & 50 deletions src/components/Person.js → src/components/Person.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import PropTypes from 'prop-types';
import { name } from 'country-emoji';
import { useParams } from '@remix-run/react';
import * as icons from '../util/icons';
import { Person } from '../../scripts/utils';

export default function Person({ person }) {
export default function PersonComponent({ person }: { person: Person }) {
const url = new URL(person.url);
const twitter = person.twitter
? `https://unavatar.io/x/${person.twitter.replace('@', '')}`
: null;
: '';
const website = `https://unavatar.io/${url.host}`;
const unavatar = person.twitter
? `${twitter}?fallback=${website}&ttl=28d`
Expand All @@ -18,7 +19,7 @@ export default function Person({ person }) {
return (
<div
className="PersonWrapper"
style={{ contentVisibility: "auto", containIntrinsicHeight: "560px" }}
style={{ contentVisibility: 'auto', containIntrinsicHeight: '560px' }}
>
<div className="PersonInner">
<header>
Expand All @@ -29,14 +30,14 @@ export default function Person({ person }) {
alt={person.name}
onError={({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src = "/default.png";
currentTarget.src = '/default.png';
}}
loading="lazy"
/>
<h3>
<a href={person.url} target="_blank" rel="noopener noreferrer">
{person.name}
</a>{" "}
</a>{' '}
{person.emoji}
</h3>
<a
Expand All @@ -46,14 +47,14 @@ export default function Person({ person }) {
href={person.url}
>
{url.host}
{url.pathname.replace(/\/$/, "")}
{url.pathname.replace(/\/$/, '')}
</a>
</header>
<p>{person.description}</p>
<ul className="Tags">
{person.tags.map((tag) => (
{person.tags?.map((tag) => (
<li
className={`Tag small ${tag === currentTag ? "currentTag" : ""}`}
className={`Tag small ${tag === currentTag ? 'currentTag' : ''}`}
key={tag}
>
{tag}
Expand Down Expand Up @@ -83,12 +84,12 @@ export default function Person({ person }) {
{person.twitter && (
<div className="SocialHandle">
<a
href={`https://twitter.com/${person.twitter.replace("@", "")}`}
href={`https://twitter.com/${person.twitter.replace('@', '')}`}
target="_blank"
rel="noopener noreferrer"
>
<span className="at">@</span>
{person.twitter.replace("@", "")}
{person.twitter.replace('@', '')}
</a>
</div>
)}
Expand All @@ -97,7 +98,10 @@ export default function Person({ person }) {
{person.bluesky && !person.twitter && (
<div className="SocialHandle">
<a
href={`https://bsky.app/profile/${person.bluesky.replace("@", "")}`}
href={`https://bsky.app/profile/${person.bluesky.replace(
'@',
''
)}`}
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -124,7 +128,8 @@ export default function Person({ person }) {
{/* If they have a bluesky, and no mastodon and no twitter, show that */}
{person.bluesky && !person.mastodon && !person.twitter && (
<div className="SocialHandle">
<a href={`https://bsky.app/profile/${person.bluesky}`}
<a
href={`https://bsky.app/profile/${person.bluesky}`}
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -137,41 +142,3 @@ export default function Person({ person }) {
</div>
);
}

Person.propTypes = {
person: PropTypes.shape({
github: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
emoji: PropTypes.string,
description: PropTypes.string,
tags: PropTypes.arrayOf(PropTypes.string),
country: PropTypes.string,
computer: PropTypes.oneOf(['apple', 'windows', 'linux']),
phone: PropTypes.oneOf(['iphone', 'android', 'windowsphone', 'flipphone']),
twitter(props, propName, componentName) {
if (!/^@?(\w){1,15}$/.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to` +
` \`${componentName}\`. This isn't a legit Twitter handle.`
);
}
},
mastodon(props, propName, componentName) {
if (!/^@(\w){1,30}@(\w)+\.(\w)+$/.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to` +
` \`${componentName}\`. This isn't a legit Mastodon handle.`
);
}
},
bluesky(props, propName, componentName) {
if (!/^(\w)+\.(\w)+\.(\w)+$/.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to` +
` \`${componentName}\`. This isn't a legit Bluesky handle.`
);
}
},
}),
};
22 changes: 16 additions & 6 deletions src/components/Topics.js → src/components/Topics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ import { Link, useParams, useRouteLoaderData } from '@remix-run/react';
import * as icons from '../util/icons';

export default function Topics() {
const { tags, countries, devices } = useRouteLoaderData("root");
const params = useParams();
const currentTag = params.tag || 'all';
const res = useRouteLoaderData<{
tags: { name: string; count: number }[];
countries: { emoji: string; name: string; count: number }[];
devices: { name: string; count: number }[];
}>('root');

if (!res) return;
const { tags, countries, devices } = res;
return (
<div className="Tags">
{tags.map((tag) => (
<Link
prefetch="intent"
key={`tag-${tag.name}`}
to={
tag.name === "all" ? "/" : `/like/${encodeURIComponent(tag.name)}`
tag.name === 'all' ? '/' : `/like/${encodeURIComponent(tag.name)}`
}
className={`Tag ${currentTag === tag.name ? "currentTag" : ""}`}
className={`Tag ${currentTag === tag.name ? 'currentTag' : ''}`}
>
{tag.name}
<span className="TagCount">{tag.count}</span>
Expand All @@ -26,7 +32,7 @@ export default function Topics() {
<Link
to={`/like/${tag.emoji}`}
prefetch="intent"
className={`Tag ${currentTag === tag.emoji ? "currentTag" : ""}`}
className={`Tag ${currentTag === tag.emoji ? 'currentTag' : ''}`}
key={`filter-${tag.name}`}
title={tag.name}
>
Expand All @@ -38,12 +44,16 @@ export default function Topics() {
{devices.map((tag) => (
<Link
to={`/like/${tag.name}`}
className={`Tag ${currentTag === tag.name ? "currentTag" : ""}`}
className={`Tag ${currentTag === tag.name ? 'currentTag' : ''}`}
prefetch="intent"
key={`filter-${tag.name}`}
title={tag.name}
>
<img height="20px" src={icons[tag.name]} alt={tag.name} />
<img
height="20px"
src={icons[tag.name as keyof typeof icons]}
alt={tag.name}
/>
<span className="TagCount">{tag.count}</span>
</Link>
))}
Expand Down
17 changes: 6 additions & 11 deletions src/components/layout.js → src/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import Header from './header';
import 'normalize.css';

export default function Layout({ children }) {
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<main className="Main">
<Header />
{children}
<footer>
{/* eslint-disable-next-line */}
<center ya-i-used-a-center-tag="sue me">
<p>
Made by <a href="https://wesbos.com">Wes Bos</a> with{" "}
<a href="https://www.remix.run">Remix</a> ©{" "}
Made by <a href="https://wesbos.com">Wes Bos</a> with{' '}
<a href="https://www.remix.run">Remix</a> ©{' '}
{new Date().getFullYear()}
</p>
<p>
Source on{" "}
Source on{' '}
<a href="https://github.com/wesbos/awesome-uses/">GitHub</a>. Add
yourself!
</p>
Expand All @@ -34,8 +33,4 @@ export default function Layout({ children }) {
</footer>
</main>
);
};

Layout.propTypes = {
children: PropTypes.node.isRequired,
};
}
6 changes: 3 additions & 3 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useLoaderData, useParams } from '@remix-run/react';
import { json, LoaderArgs } from '@remix-run/server-runtime';
import React, { useContext } from 'react';
import { getPeople } from 'src/util/stats';
import Topics from '../components/Topics';
import BackToTop from '../components/BackToTop';
import Person from '../components/Person';
import { getPeople } from 'src/util/stats';

export async function loader({ params }: LoaderArgs) {
const people = getPeople(params.tag);
return {people};
return { people };
}

export default function Index() {
Expand All @@ -17,7 +17,7 @@ export default function Index() {
<>
<Topics />
<div className="People">
{people.map(person => (
{people.map((person) => (
<Person key={person.name} person={person} />
))}
</div>
Expand Down
File renamed without changes.