Skip to content

Commit

Permalink
feat: Is sexism over in tech?
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcosNASA committed Jun 7, 2024
0 parents commit c7201b9
Show file tree
Hide file tree
Showing 51 changed files with 9,694 additions and 0 deletions.
83 changes: 83 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* This is intended to be a basic starting point for linting in your app.
* It relies on recommended configs out of the box for simplicity, but you can
* and should modify this configuration to best suit your team's needs.
*/

/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},

// Base config
extends: ["eslint:recommended"],

overrides: [
// React
{
files: ["**/*.{js,jsx,ts,tsx}"],
plugins: ["react", "jsx-a11y"],
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
],
settings: {
react: {
version: "detect",
},
formComponents: ["Form"],
linkComponents: [
{ name: "Link", linkAttribute: "to" },
{ name: "NavLink", linkAttribute: "to" },
],
"import/resolver": {
typescript: {},
},
},
},

// Typescript
{
files: ["**/*.{ts,tsx}"],
plugins: ["@typescript-eslint", "import"],
parser: "@typescript-eslint/parser",
settings: {
"import/internal-regex": "^~/",
"import/resolver": {
node: {
extensions: [".ts", ".tsx"],
},
typescript: {
alwaysTryTypes: true,
},
},
},
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
],
},

// Node
{
files: [".eslintrc.cjs"],
env: {
node: true,
},
},
],
};
33 changes: 33 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Deploy

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
run_install: false

- name: Install dependencies
run: pnpm install

- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules

/.wrangler
/build
.env
.dev.vars
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Welcome to Remix + Cloudflare Workers!

- 📖 [Remix docs](https://remix.run/docs)
- 📖 [Remix Cloudflare docs](https://remix.run/guides/vite#cloudflare)

## Development

Run the dev server:

```sh
npm run dev
```

To run Wrangler:

```sh
npm run build
npm start
```

## Typegen

Generate types for your Cloudflare bindings in `wrangler.toml`:

```sh
npm run typegen
```

You will need to rerun typegen whenever you make changes to `wrangler.toml`.

## Deployment

If you don't already have an account, then [create a cloudflare account here](https://dash.cloudflare.com/sign-up) and after verifying your email address with Cloudflare, go to your dashboard and set up your free custom Cloudflare Workers subdomain.

Once that's done, you should be able to deploy your app:

```sh
npm run deploy
```
20 changes: 20 additions & 0 deletions app/api/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Octokit } from '@octokit/core'
import * as process from 'node:process'

export const client = (url: string, options: RequestInit) => {
return fetch(url, options)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText, { cause: response })
}
return response.json()
})
.catch((error: Error) => {
if (process.env.NODE_ENV !== 'production') console.error(error)
return Promise.reject(error)
})
}

export const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
})
16 changes: 16 additions & 0 deletions app/api/issues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { octokit } from './client'

export const IssuesAPI = {
get: () =>
octokit.request('GET /repos/{owner}/{repo}/issues', {
owner: 'MarcosNASA',
repo: 'is-sexism-over.tech',
state: 'closed',
labels: 'testimonial',
sort: 'created',
direction: 'desc',
headers: {
'X-GitHub-Api-Version': '2022-11-28',
},
}),
}
18 changes: 18 additions & 0 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* By default, Remix will handle hydrating your app on the client for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/file-conventions/entry.client
*/

import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
43 changes: 43 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* By default, Remix will handle generating the HTTP Response for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/file-conventions/entry.server
*/

import type { AppLoadContext, EntryContext } from "@remix-run/cloudflare";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToReadableStream } from "react-dom/server";

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
// This is ignored so we can keep it in the template for visibility. Feel
// free to delete this parameter in your app if you're not using it!
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadContext: AppLoadContext
) {
const body = await renderToReadableStream(
<RemixServer context={remixContext} url={request.url} />,
{
signal: request.signal,
onError(error: unknown) {
// Log streaming rendering errors from inside the shell
console.error(error);
responseStatusCode = 500;
},
}
);

if (isbot(request.headers.get("user-agent") || "")) {
await body.allReady;
}

responseHeaders.set("Content-Type", "text/html");
return new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
});
}
116 changes: 116 additions & 0 deletions app/features/countdown/components/Countdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react'
import { type Experience } from '~/features/experience/domain'
import { Countdown as CountdownDomain } from '~/features/countdown/domain'
import { Text } from '~/features/text/domain'
import { Styles } from '~/features/ui/domain'
import { Anchor } from '~/features/ui/components/Anchor'

export const Countdown = ({
lastExperience,
}: {
lastExperience?: Experience
}) => {
const [countdown, setCountdown] = React.useState(() =>
lastExperience
? CountdownDomain.countdown(lastExperience.createdAt)
: { days: 0, hours: 0, minutes: 0, seconds: 0 },
)

const isOver = lastExperience
? CountdownDomain.isOver(lastExperience.createdAt)
: true

React.useEffect(() => {
if (!lastExperience) return
const interval = setInterval(() => {
setCountdown(CountdownDomain.countdown(lastExperience.createdAt))
}, 1000)
return () => clearInterval(interval)
}, [lastExperience])

if (isOver) {
return (
<div className="group flex flex-col items-start md:items-center justify-center gap-1 lg:gap-4">
<div className="flex flex-col gap-0 text-start md:text-center text-4xl md:text-9xl font-yeseva font-bold">
<span>Sexism is</span>
<span
className={Styles.cn(
'flex md:justify-center items-center',
'text-pink-400',
'h-[0px] group-hover:h-[1.5ch] opacity-0 group-hover:opacity-100',
'[transition:height_0.6s_0s_ease-in,opacity_0.3s_0.3s_ease-out]',
)}
>
not
</span>
<span>over!</span>
</div>

<div className={Styles.cn('text-2xl lg:text-2xl text-zinc-400')}>
<Anchor href="https://github.com/MarcosNASA/is-sexism-over.tech/issues/new">
Share your experience
</Anchor>{' '}
to reset the countdown
</div>
</div>
)
}

return (
<div className="flex flex-col justify-center items-center gap-6">
<div className="flex flex-col justify-center items-center gap-2 font-yeseva">
<div className="flex justify-center items-center gap-4 tabular-nums">
<div className="flex flex-col lg:flex-row justify-center items-center gap-2">
<span className="text-2xl lg:text-5xl font-bold">
{String(countdown.days).padStart(2, '0')}{' '}
</span>
<span className="text-3xl lg:text-4xl font-semibold">
{Text.pluralize(countdown.days, { singular: 'day' })}
</span>
</div>
<div className="flex flex-col lg:flex-row justify-center items-center gap-2">
<span className="text-2xl lg:text-5xl font-bold">
{String(countdown.hours).padStart(2, '0')}{' '}
</span>
<span className="text-3xl lg:text-4xl font-semibold">
{Text.pluralize(countdown.hours, { singular: 'hour' })}
</span>
</div>
<div className="flex flex-col lg:flex-row justify-center items-center gap-2">
<span className="text-2xl lg:text-5xl font-bold">
{String(countdown.minutes).padStart(2, '0')}{' '}
</span>
<span className="text-3xl lg:text-4xl font-semibold">
{Text.pluralize(countdown.minutes, { singular: 'minute' })}
</span>
</div>
<div className="flex flex-col lg:flex-row justify-center items-center gap-2">
<span className="text-2xl lg:text-5xl font-bold">
{String(countdown.seconds).padStart(2, '0')}{' '}
</span>
<span className="text-3xl lg:text-4xl font-semibold">
{Text.pluralize(countdown.seconds, { singular: 'second' })}
</span>
</div>
</div>

<span className="text-2xl lg:text-5xl font-yeseva font-bold">
left for sexism to be over
</span>
</div>

<div className="text-2xl text-zinc-400">
<Anchor href="https://github.com/MarcosNASA/is-sexism-over.tech/issues/new">
Share your experience
</Anchor>{' '}
to reset the countdown
</div>
</div>
)
}

const CountdownSkeleton = () => {
return <div className="w-full" />
}

Countdown.Skeleton = CountdownSkeleton
Loading

0 comments on commit c7201b9

Please sign in to comment.