From 84b1a800c7b3e2e262f5e3ef1f8a80f2546afefb Mon Sep 17 00:00:00 2001 From: Techno Tim Date: Wed, 25 Aug 2021 19:16:09 -0500 Subject: [PATCH] =?UTF-8?q?feat(rewrite):=20Rewrote=20the=20whole=20thing?= =?UTF-8?q?=20in=20ReactJS=20=F0=9F=92=85=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(rewrite): Rewrote the whole thing in ReactJS ๐Ÿ’… * chore(docs): Updated --- .babelrc | 6 + .env.example | 23 + .eslintignore | 13 + .eslintrc | 42 + .github/workflows/main.yml | 2 + .github/workflows/pull-request.yml | 2 + .gitignore | 30 +- .vscode/launch.json | 13 + Dockerfile | 33 +- README.md | 11 +- app.js | 31 - docker-compose.yml | 3 +- entrypoint.sh | 6 - env.js | 390 - package.json | 53 +- {www => public}/css/brands.css | 8 - .../skeleton-dark.css => public/css/dark.css | 0 .../css/light.css | 12 +- {www => public}/css/normalize.css | 0 public/robots.txt | 2 + sandbox.config.json | 5 + src/client.js | 15 + src/components/App/App.js | 13 + src/components/App/App.test.js | 16 + src/components/Avatar/Avatar.css | 6 + src/components/Avatar/Avatar.js | 20 + src/components/Button/Button.js | 33 + src/components/Button/Button.test.js | 10 + src/components/Home/Home.js | 288 + src/components/Home/Home.test.js | 10 + src/config.js | 162 + {www => src}/icons/discord.svg | 0 {www => src}/icons/email.svg | 0 {www => src}/icons/email_alt.svg | 0 {www => src}/icons/facebook.svg | 0 {www => src}/icons/figma.svg | 0 {www => src}/icons/github.svg | 0 {www => src}/icons/goodreads.svg | 0 {www => src}/icons/instagram.svg | 0 {www => src}/icons/kit.svg | 0 {www => src}/icons/letterboxd.svg | 0 {www => src}/icons/linkedin.svg | 0 {www => src}/icons/mastodon.svg | 0 {www => src}/icons/medium.svg | 0 {www => src}/icons/messenger.svg | 0 {www => src}/icons/microblog.svg | 0 {www => src}/icons/pinterest.svg | 0 {www => src}/icons/producthunt.svg | 0 {www => src}/icons/reddit.svg | 0 {www => src}/icons/skoob.svg | 0 {www => src}/icons/snapchat.svg | 0 {www => src}/icons/soundcloud.svg | 0 {www => src}/icons/spotify.svg | 0 {www => src}/icons/steam.svg | 0 {www => src}/icons/telegram.svg | 0 {www => src}/icons/tiktok.svg | 0 {www => src}/icons/tumblr.svg | 0 {www => src}/icons/twitch.svg | 0 {www => src}/icons/twitter.svg | 0 {www => src}/icons/vimeo.svg | 0 {www => src}/icons/wordpress.svg | 0 {www => src}/icons/youtube.svg | 0 src/index.js | 28 + src/server.js | 92 + template/index.html | 172 - www/icons/whatsapp.svg | 1 - www/images/avatar.png | Bin 1742 -> 0 bytes www/images/avatar@2x.png | Bin 3425 -> 0 bytes www/index.html | 165 - yarn.lock | 9373 ++++++++++++++++- 70 files changed, 10190 insertions(+), 899 deletions(-) create mode 100644 .babelrc create mode 100644 .env.example create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .vscode/launch.json delete mode 100644 app.js delete mode 100755 entrypoint.sh delete mode 100644 env.js rename {www => public}/css/brands.css (97%) rename www/css/skeleton-dark.css => public/css/dark.css (100%) rename www/css/skeleton-light.css => public/css/light.css (97%) rename {www => public}/css/normalize.css (100%) create mode 100644 public/robots.txt create mode 100644 sandbox.config.json create mode 100644 src/client.js create mode 100644 src/components/App/App.js create mode 100644 src/components/App/App.test.js create mode 100644 src/components/Avatar/Avatar.css create mode 100644 src/components/Avatar/Avatar.js create mode 100644 src/components/Button/Button.js create mode 100644 src/components/Button/Button.test.js create mode 100644 src/components/Home/Home.js create mode 100644 src/components/Home/Home.test.js create mode 100644 src/config.js rename {www => src}/icons/discord.svg (100%) rename {www => src}/icons/email.svg (100%) rename {www => src}/icons/email_alt.svg (100%) rename {www => src}/icons/facebook.svg (100%) rename {www => src}/icons/figma.svg (100%) rename {www => src}/icons/github.svg (100%) rename {www => src}/icons/goodreads.svg (100%) rename {www => src}/icons/instagram.svg (100%) rename {www => src}/icons/kit.svg (100%) rename {www => src}/icons/letterboxd.svg (100%) rename {www => src}/icons/linkedin.svg (100%) rename {www => src}/icons/mastodon.svg (100%) rename {www => src}/icons/medium.svg (100%) rename {www => src}/icons/messenger.svg (100%) rename {www => src}/icons/microblog.svg (100%) rename {www => src}/icons/pinterest.svg (100%) rename {www => src}/icons/producthunt.svg (100%) rename {www => src}/icons/reddit.svg (100%) rename {www => src}/icons/skoob.svg (100%) rename {www => src}/icons/snapchat.svg (100%) rename {www => src}/icons/soundcloud.svg (100%) rename {www => src}/icons/spotify.svg (100%) rename {www => src}/icons/steam.svg (100%) rename {www => src}/icons/telegram.svg (100%) rename {www => src}/icons/tiktok.svg (100%) rename {www => src}/icons/tumblr.svg (100%) rename {www => src}/icons/twitch.svg (100%) rename {www => src}/icons/twitter.svg (100%) rename {www => src}/icons/vimeo.svg (100%) rename {www => src}/icons/wordpress.svg (100%) rename {www => src}/icons/youtube.svg (100%) create mode 100644 src/index.js create mode 100644 src/server.js delete mode 100644 template/index.html delete mode 100644 www/icons/whatsapp.svg delete mode 100644 www/images/avatar.png delete mode 100644 www/images/avatar@2x.png delete mode 100644 www/index.html diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..4f06b0c --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ] +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..640f118 --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# this is used for local debugging +# RAZZLE_ variable is passed to the server +# copy this to .env for testing + +RAZZLE_META_TITLE=Techno Tim +RAZZLE_META_DESCRIPTION=Techno Tim Link page +RAZZLE_META_AUTHOR=Techno Tim +RAZZLE_THEME=Dark +RAZZLE_FAVICON_URL=https://pbs.twimg.com/profile_images/1286144221217316864/qIAsKOpB_200x200.jpg +RAZZLE_AVATAR_URL=https://pbs.twimg.com/profile_images/1286144221217316864/qIAsKOpB_200x200.jpg +RAZZLE_AVATAR_2X_URL=https://pbs.twimg.com/profile_images/1286144221217316864/qIAsKOpB_400x400.jpg +RAZZLE_AVATAR_ALT=Techno Tim Profile Pic +RAZZLE_NAME=TechnoTim +RAZZLE_BIO=Hey! Just a place where you can connect with me! +RAZZLE_GITHUB=https://github.com/timothystewart6 +RAZZLE_TWITTER=https://twitter.com/TechnoTimLive +RAZZLE_INSTAGRAM=https://www.instagram.com/techno.tim +RAZZLE_YOUTUBE=https://www.youtube.com/channel/UCOk-gHyjcWZNj3Br4oxwh0A +RAZZLE_TWITCH=https://www.twitch.tv/technotim/ +RAZZLE_DISCORD=https://discord.gg/DJKexrJ +RAZZLE_TIKTOK=https://www.tiktok.com/@technotim +RAZZLE_KIT=https://kit.co/TechnoTim +RAZZLE_FOOTER=Thanks for stopping by! \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..918ef1e --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +logs +*.log +npm-debug.log* +.DS_Store + +coverage +node_modules +build +.env.local +.env.development.local +.env.test.local +.env.production.local +cache \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..de7b631 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,42 @@ +{ + "parser": "@babel/eslint-parser", + "settings": { + "react": { + "pragma": "React", + "version": "detect" + } + }, + "plugins": ["jsx","prettier"], + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "prettier" + ], + "env": { + "jest": true, + "jasmine": true, + "node": true, + "browser": true, + "es6": true + + }, + "rules": { + "prettier/prettier": + ["error", { + "singleQuote": true, + "jsxSingleQuote": false, + "arrowParens": "avoid", + "semi": true, + "trailingComma": "all", + "spaceAfterFunction": true + }], + "no-console": ["error", { "allow": ["warn", "error"] }], + "no-alert": "error", + "no-debugger": "error", + "prefer-const": "error", + "prefer-arrow-callback": "error", + "no-unused-vars": ["error", {"args": "none"}], + "react/prop-types": 0, + "react/no-unescaped-entities": 0 + } +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88770a5..1f13b9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,6 +20,8 @@ jobs: - name: Install Dependencies, Test, and Build run: | yarn install --frozen-lockfile --check-files + yarn lint + yarn test env: CI: true diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index de7fffa..c229da6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,5 +23,7 @@ jobs: - name: Install Dependencies, Test, and Build run: | yarn install --frozen-lockfile --check-files + yarn lint + yarn test env: CI: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 763d5bc..ed1b450 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,14 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc +logs +*.log +npm-debug.log* .DS_Store + +coverage +node_modules +build .env.local .env.development.local .env.test.local .env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# this is is the output -www/index.html \ No newline at end of file +.env +cache \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ae6f27f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to dev server", + "type": "node", + "request": "attach", + "protocol": "inspector", + "address": "localhost", + "port": 9229 + } + ] + } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 81409fc..5245c1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,23 @@ -FROM node:14.17.4-alpine +FROM node:14.17.4-alpine AS node-build RUN apk --no-cache add \ gettext \ bash -ENV NODE_ENV=production -WORKDIR /usr/src/app -COPY package.json yarn.lock ./ -RUN yarn install --frozen-lockfile --check-files --production=true -COPY www ./www -COPY template ./template -COPY env.js ./ -COPY app.js ./ -COPY ./entrypoint.sh / -RUN chmod +x /entrypoint.sh -EXPOSE 3000 -ENTRYPOINT ["/entrypoint.sh"] -CMD [ "node", "app.js" ] + +WORKDIR /usr/src/app +COPY package.json ./ +COPY yarn.lock ./ +COPY src ./src +COPY public ./public +RUN yarn install --frozen-lockfile --check-files +RUN yarn build --noninteractive +RUN yarn install --frozen-lockfile --check-files --production --modules-folder node_modules_prod + +FROM node:14.17.4-alpine +WORKDIR /usr/src/app +ENV NODE_ENV production +RUN mkdir -p /node_modules +COPY --from=node-build /usr/src/app/build ./build +COPY --from=node-build /usr/src/app/node_modules_prod ./node_modules +EXPOSE 3000 +CMD [ "node", "build/server.js" ] diff --git a/README.md b/README.md index b877f6b..2f01cca 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ๐Ÿ”— LittleLink-Server This project is based on the great work from [littlelink](https://github.com/sethcottle/littlelink) -It takes the same simple approach to a link page and hosts it within a nodeJS server containerized for you to use. Now, customizing `LittleLink` with `littlelink-server` is as easy as passing in some environment variables. If you need help configuring this, please see [this video that explains everything](https://www.youtube.com/watch?v=42SqfI_AjXU). +It takes the same simple approach to a link page and hosts it within a NodeJS server with React Server Side Rendering, containerized for you to use. Now, customizing `LittleLink` with `littlelink-server` is as easy as passing in some environment variables. If you need help configuring this, please see [this video that explains everything](https://www.youtube.com/watch?v=42SqfI_AjXU). # ๐Ÿš€ Getting Started @@ -45,7 +45,9 @@ services: # - MEDIUM=https://medium.com # - PINTEREST=https://www.pinterest.com/ # - EMAIL=you@example.com + # - EMAIL_TEXT=Email Me! # - EMAIL_ALT=you@example.com + # - EMAIL_ALT_TEXT=Email me! # - SOUND_CLOUD=https://souncloud.com # - FIGMA=https://figma.com # - TELEGRAM=https://telegram.org/ @@ -87,20 +89,23 @@ docker run -d \ -e DISCORD='https://discord.gg/DJKexrJ' \ -e TIKTOK='https://www.tiktok.com/@technotim' \ -e KIT='https://kit.co/TechnoTim' \ + -e EMAIL='someone@example.com' \ + -e EMAIL_TEXT='Email me!' \ + -e FOOTER=Thanks for stopping by! \ --restart unless-stopped \ ghcr.io/techno-tim/littlelink-server:latest ``` ## Kubernetes -[unoffical helm chart provided by k8s-at-home](https://github.com/k8s-at-home/charts/tree/master/charts/stable/littlelink-server) +[Unofficial helm chart provided by k8s-at-home](https://github.com/k8s-at-home/charts/tree/master/charts/stable/littlelink-server) ``` helm repo add k8s-at-home https://k8s-at-home.com/charts/ helm repo update helm install littlelink-server \ --set env.TZ="America/New York" \ - --set env.META_TITLE="Technotim" + --set env.META_TITLE="TechnoTim" k8s-at-home/littlelink-server ``` Or use a values.yaml files diff --git a/app.js b/app.js deleted file mode 100644 index c8121d3..0000000 --- a/app.js +++ /dev/null @@ -1,31 +0,0 @@ -const express = require('express') -const morgan = require('morgan') -const compression = require('compression') -const fs = require('fs') -const jsdom = require('jsdom') -const useEnv = require('./env') - -fs.readFile('./template/index.html', 'utf8', (err, file) => { - if (err) { - throw err; - } - const { JSDOM } = jsdom - const dom = new JSDOM(file); - const html = "\n" + useEnv(dom.window.document).documentElement.outerHTML; - fs.writeFile('./www/index.html', html, 'utf8', (err) => { - if (err) { - throw err; - } - }); -}); - -const app = express(); -app.use(morgan('combined')); -app.use(express.static('www')); -app.use(compression()) - -const server = app.listen(process.env.PORT || 3000, () => { - const host = server.address().address - const port = server.address().port - console.log('Express app listening at http://%s:%s', host, port) -}) diff --git a/docker-compose.yml b/docker-compose.yml index 46dc449..f9e71dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,13 +19,12 @@ services: - BIO=Hey! Just a place where you can connect with me! - GITHUB=https://github.com/timothystewart6 - TWITTER=https://twitter.com/TechnoTimLive - - MASTODON=https://mastodon.social/TechnoTimLive - INSTAGRAM=https://www.instagram.com/techno.tim - YOUTUBE=https://www.youtube.com/channel/UCOk-gHyjcWZNj3Br4oxwh0A/ - TWITCH=https://www.twitch.tv/technotim/ - DISCORD=https://discord.gg/DJKexrJ - KIT=https://kit.co/TechnoTim - - FOOTER=Thanks for stopping by! + - FOOTER=Thanks for stopping by!!! ports: - 8080:3000 diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index f83e2d5..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -originalfile="/usr/src/app/env.js" -tmpfile=$(mktemp) -cp --attributes-only --preserve $originalfile $tmpfile -cat $originalfile | envsubst | tee $tmpfile && mv $tmpfile $originalfile -exec "$@" diff --git a/env.js b/env.js deleted file mode 100644 index ee5cfcc..0000000 --- a/env.js +++ /dev/null @@ -1,390 +0,0 @@ -module.exports = useEnv; - -var env = { - META_TITLE: '$META_TITLE', - META_DESCRIPTION: '$META_DESCRIPTION', - META_AUTHOR: '$META_AUTHOR', - THEME: '$THEME', - FAVICON_URL: '$FAVICON_URL', - AVATAR_URL: '$AVATAR_URL', - AVATAR_ALT: '$AVATAR_ALT', - AVATAR_2X_URL: '$AVATAR_2X_URL', - NAME: '$NAME', - BIO: '$BIO', - GITHUB: '$GITHUB', - TWITTER: '$TWITTER', - MASTODON: '$MASTODON', - MICRO_BLOG: '$MICRO_BLOG', - INSTAGRAM: '$INSTAGRAM', - FACEBOOK: '$FACEBOOK', - FACEBOOK_MESSENGER: '$FACEBOOK_MESSENGER', - LINKED_IN: '$LINKED_IN', - YOUTUBE: '$YOUTUBE', - DISCORD: '$DISCORD', - TWITCH: '$TWITCH', - PRODUCT_HUNT: '$PRODUCT_HUNT', - SNAPCHAT: '$SNAPCHAT', - SPOTIFY: '$SPOTIFY', - REDDIT: '$REDDIT', - MEDIUM: '$MEDIUM', - PINTEREST: '$PINTEREST', - TIKTOK: '$TIKTOK', - EMAIL: '$EMAIL', - EMAIL_TEXT: '$EMAIL_TEXT', - EMAIL_ALT: '$EMAIL_ALT', - EMAIL_ALT_TEXT: '$EMAIL_ALT_TEXT', - SOUND_CLOUD: '$SOUND_CLOUD', - FIGMA: '$FIGMA', - KIT: '$KIT', - TELEGRAM: '$TELEGRAM', - TUMBLR: '$TUMBLR', - STEAM: '$STEAM', - VIMEO: '$VIMEO', - WORDPRESS: '$WORDPRESS', - GOODREADS: '$GOODREADS', - SKOOB: '$SKOOB', - WHATSAPP: '$WHATSAPP', - LETTERBOXD: '$LETTERBOXD', - FOOTER: '$FOOTER', - -} - -function useEnv(document) { - var metalTitleEl = document.getElementById('meta-title'); - if (env.META_TITLE) { - metalTitleEl.textContent = env.META_TITLE; - } else { - metalTitleEl.remove() - } - - var metalDescriptionEl = document.getElementById('meta-description'); - if (env.META_DESCRIPTION) { - metalDescriptionEl.content = env.META_DESCRIPTION; - } else { - metalDescriptionEl.remove() - } - - var metaAuthorEl = document.getElementById('meta-author'); - if (env.META_AUTHOR) { - metaAuthorEl.content = env.META_AUTHOR; - } else { - metaAuthorEl.remove() - } - - var themeEl = document.getElementById('theme'); - if (env.THEME && env.THEME.toLocaleLowerCase() === 'dark') { - themeEl.href = 'css/skeleton-dark.css'; - } else { - themeEl.href = 'css/skeleton-light.css'; - } - - var faviconEl = document.getElementById('favicon'); - if (env.FAVICON_URL) { - faviconEl.href = env.FAVICON_URL; - } - - var avatarEl = document.getElementById('avatar'); - if (env.AVATAR_URL) { - avatarEl.src = env.AVATAR_URL; - avatarEl.alt = env.AVATAR_ALT; - avatarEl.srcset = env.AVATAR_2X_URL + ' 2x'; - } else { - avatarEl.remove() - } - - var nameEl = document.getElementById('name'); - if (env.NAME) { - nameEl.innerHTML = env.NAME; - } else { - nameEl.remove() - } - - var bioEl = document.getElementById('bio'); - if (env.BIO) { - bioEl.innerHTML = env.BIO; - } else { - bioEl.remove() - } - - var githubEl = document.getElementById('github'); - if (env.GITHUB) { - githubEl.href = env.GITHUB; - } else { - githubEl.nextElementSibling.remove() - githubEl.remove() - } - - var twitterEl = document.getElementById('twitter'); - if (env.TWITTER) { - twitterEl.href = env.TWITTER; - } else { - twitterEl.nextElementSibling.remove() - twitterEl.remove() - } - - var mastodonEl = document.getElementById('mastodon'); - if (env.MASTODON) { - mastodonEl.href = env.MASTODON; - } else { - mastodonEl.nextElementSibling.remove() - mastodonEl.remove() - } - - var microblogEl = document.getElementById('microblog'); - if (env.MICRO_BLOG) { - microblogEl.href = env.MICRO_BLOG; - } else { - microblogEl.nextElementSibling.remove() - microblogEl.remove() - } - - var instagramEl = document.getElementById('instagram'); - if (env.INSTAGRAM) { - instagramEl.href = env.INSTAGRAM; - } else { - instagramEl.nextElementSibling.remove() - instagramEl.remove() - } - - var facebookEl = document.getElementById('facebook'); - if (env.FACEBOOK) { - facebookEl.href = env.FACEBOOK; - } else { - facebookEl.nextElementSibling.remove() - facebookEl.remove() - } - - var facebookMessengerEl = document.getElementById('facebook-messenger'); - if (env.FACEBOOK_MESSENGER) { - facebookMessengerEl.href = env.FACEBOOK_MESSENGER; - } else { - facebookMessengerEl.nextElementSibling.remove() - facebookMessengerEl.remove() - } - - - var linkedInEl = document.getElementById('linkedin'); - if (env.LINKED_IN) { - linkedInEl.href = env.LINKED_IN; - } else { - linkedInEl.nextElementSibling.remove() - linkedInEl.remove() - } - - - var youTubeEl = document.getElementById('youtube'); - if (env.YOUTUBE) { - youTubeEl.href = env.YOUTUBE; - } else { - youTubeEl.nextElementSibling.remove() - youTubeEl.remove() - } - - - var discordEl = document.getElementById('discord'); - if (env.DISCORD) { - discordEl.href = env.DISCORD; - } else { - discordEl.nextElementSibling.remove() - discordEl.remove() - } - - - var twitchEl = document.getElementById('twitch'); - if (env.TWITCH) { - twitchEl.href = env.TWITCH; - } else { - twitchEl.nextElementSibling.remove() - twitchEl.remove() - } - - var productHunEl = document.getElementById('producthunt'); - if (env.PRODUCT_HUNT) { - productHunEl.href = env.PRODUCT_HUNT; - } else { - productHunEl.nextElementSibling.remove() - productHunEl.remove() - } - - var snapchatEl = document.getElementById('snapchat'); - if (env.SNAPCHAT) { - snapchatEl.href = env.SNAPCHAT; - } else { - snapchatEl.nextElementSibling.remove() - snapchatEl.remove() - } - - var spotifyEl = document.getElementById('spotify'); - if (env.SPOTIFY) { - spotifyEl.href = env.SPOTIFY; - } else { - spotifyEl.nextElementSibling.remove() - spotifyEl.remove() - } - - var redditEl = document.getElementById('reddit'); - if (env.REDDIT) { - redditEl.href = env.REDDIT; - } else { - redditEl.nextElementSibling.remove() - redditEl.remove() - } - - - var mediumEl = document.getElementById('medium'); - if (env.MEDIUM) { - mediumEl.href = env.MEDIUM; - } else { - mediumEl.nextElementSibling.remove() - mediumEl.remove() - } - - - var pinterestEl = document.getElementById('pinterest'); - if (env.PINTEREST) { - pinterestEl.href = env.PINTEREST; - } else { - pinterestEl.nextElementSibling.remove() - pinterestEl.remove() - } - - var tiktokEl = document.getElementById('tiktok'); - if (env.TIKTOK) { - tiktokEl.href = env.TIKTOK; - } else { - tiktokEl.nextElementSibling.remove() - tiktokEl.remove() - } - - var emailEl = document.getElementById('email'); - if (env.EMAIL) { - emailEl.innerHTML = env.EMAIL_TEXT || env.EMAIL - emailEl.href = 'mailto:' + env.EMAIL; - } else { - emailEl.nextElementSibling.remove() - emailEl.remove() - } - - - var emailAltEl = document.getElementById('email-alt'); - if (env.EMAIL_ALT) { - emailAltEl.innerHTML = env.EMAIL_ALT_TEXT || env.EMAIL_ALT - emailAltEl.href = 'mailto:' + env.EMAIL_ALT; - } else { - emailAltEl.nextElementSibling.remove() - emailAltEl.remove() - } - - var soundCloudEl = document.getElementById('soundcloud'); - if (env.SOUND_CLOUD) { - soundCloudEl.href = env.SOUND_CLOUD; - } else { - soundCloudEl.nextElementSibling.remove() - soundCloudEl.remove() - } - - - var figmaEl = document.getElementById('figma'); - if (env.FIGMA) { - figmaEl.href = env.FIGMA; - } else { - figmaEl.nextElementSibling.remove() - figmaEl.remove() - } - - - var kitEl = document.getElementById('kit'); - if (env.KIT) { - kitEl.href = env.KIT; - } else { - kitEl.nextElementSibling.remove() - kitEl.remove() - } - - - var telegramEl = document.getElementById('telegram'); - if (env.TELEGRAM) { - telegramEl.href = env.TELEGRAM; - } else { - telegramEl.nextElementSibling.remove() - telegramEl.remove() - } - - var temblrEl = document.getElementById('tumblr'); - if (env.TUMBLR) { - temblrEl.href = env.TUMBLR; - } else { - temblrEl.nextElementSibling.remove() - temblrEl.remove() - } - - - var steamEl = document.getElementById('steam'); - if (env.STEAM) { - steamEl.href = env.STEAM; - } else { - steamEl.nextElementSibling.remove() - steamEl.remove() - } - - - var vimeoEl = document.getElementById('vimeo'); - if (env.VIMEO) { - vimeoEl.href = env.VIMEO; - } else { - vimeoEl.nextElementSibling.remove() - vimeoEl.remove() - } - - - var wordpressEl = document.getElementById('wordpress'); - if (env.WORDPRESS) { - wordpressEl.href = env.WORDPRESS; - } else { - wordpressEl.nextElementSibling.remove() - wordpressEl.remove() - } - - var goodreadsEl = document.getElementById('goodreads'); - if (env.GOODREADS) { - goodreadsEl.href = env.GOODREADS; - } else { - goodreadsEl.nextElementSibling.remove() - goodreadsEl.remove() - } - - var skoobEl = document.getElementById('skoob'); - if (env.SKOOB) { - skoobEl.href = env.SKOOB; - } else { - skoobEl.nextElementSibling.remove() - skoobEl.remove() - } - - var whatsappEl = document.getElementById('whatsapp'); - if (env.WHATSAPP) { - whatsappEl.href = env.WHATSAPP; - } else { - whatsappEl.nextElementSibling.remove() - whatsappEl.remove() - } - - var letterboxdEl = document.getElementById('letterboxd'); - if (env.LETTERBOXD) { - letterboxdEl.href = env.LETTERBOXD; - } else { - letterboxdEl.nextElementSibling.remove() - letterboxdEl.remove(); - } - - var footerEl = document.getElementById('footer'); - if (env.FOOTER) { - footerEl.innerHTML = env.FOOTER; - } else { - footerEl.remove() - } - - return document; -} - - diff --git a/package.json b/package.json index ef13305..6d41ae8 100644 --- a/package.json +++ b/package.json @@ -6,33 +6,40 @@ "license": "MIT", "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node app.js" + "start": "razzle start", + "build": "razzle build", + "debug": "yarn start --inspect --inspect-port 9230", + "test": "razzle test --env=jsdom", + "start:prod": "NODE_ENV=production node build/server.js", + "lint": "eslint ." }, - "repository": { - "type": "git", - "url": "git+https://github.com/timothystewart6/littlelink.git" - }, - "keywords": [ - "littlelink", - "linktree", - "linktr.ee", - "javascript", - "open", - "source", - "html", - "css", - "docker" - ], - "author": "Timothy Stewart", - "bugs": { - "url": "https://github.com/timothystewart6/littlelink/issues" - }, - "homepage": "https://github.com/timothystewart6/littlelink#readme", "dependencies": { "compression": "^1.7.4", "express": "^4.17.1", "morgan": "^1.10.0", - "jsdom": "^17.0.0" + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router-dom": "^5.2.0", + "serialize-javascript": "^6.0.0" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.15.0", + "@babel/preset-react": "^7.14.5", + "babel-preset-razzle": "4.0.6", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.24.1", + "eslint-plugin-jsx": "^0.1.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-react": "^7.24.0", + "html-webpack-plugin": "^4.5.2", + "mini-css-extract-plugin": "^0.9.0", + "prettier": "^2.3.2", + "razzle": "^4.0.6", + "razzle-dev-utils": "^4.0.6", + "webpack": "^4.44.1", + "webpack-dev-server": "^3.11.2" } } diff --git a/www/css/brands.css b/public/css/brands.css similarity index 97% rename from www/css/brands.css rename to public/css/brands.css index 0372266..0f83531 100644 --- a/www/css/brands.css +++ b/public/css/brands.css @@ -317,11 +317,3 @@ button:hover, .button.button-wordpress:hover, .button.button-wordpress:focus { filter: brightness(90%) } - -/* WhatsApp */ -.button.button-whatsapp { - color: #FFFFFF; - background-color: #2DB842 } -.button.button-whatsapp:hover, -.button.button-whatsapp:focus { - filter: brightness(90%) } \ No newline at end of file diff --git a/www/css/skeleton-dark.css b/public/css/dark.css similarity index 100% rename from www/css/skeleton-dark.css rename to public/css/dark.css diff --git a/www/css/skeleton-light.css b/public/css/light.css similarity index 97% rename from www/css/skeleton-light.css rename to public/css/light.css index a161193..a930f4d 100644 --- a/www/css/skeleton-light.css +++ b/public/css/light.css @@ -166,9 +166,9 @@ hr { border-top: 1px solid #E1E1E1; } - .avatar { - vertical-align: middle; - width: 128px; - height: 128px; - border-radius: 50%; - } \ No newline at end of file +.avatar { + vertical-align: middle; + width: 128px; + height: 128px; + border-radius: 50%; +} \ No newline at end of file diff --git a/www/css/normalize.css b/public/css/normalize.css similarity index 100% rename from www/css/normalize.css rename to public/css/normalize.css diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..54fb989 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * + diff --git a/sandbox.config.json b/sandbox.config.json new file mode 100644 index 0000000..59d7c1e --- /dev/null +++ b/sandbox.config.json @@ -0,0 +1,5 @@ +{ + "container": { + "port": 3000 + } +} diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..94fa436 --- /dev/null +++ b/src/client.js @@ -0,0 +1,15 @@ +import App from './components/App/App'; +import { BrowserRouter } from 'react-router-dom'; +import React from 'react'; +import { hydrate } from 'react-dom'; + +hydrate( + + + , + document.getElementById('root'), +); + +if (module.hot) { + module.hot.accept(); +} diff --git a/src/components/App/App.js b/src/components/App/App.js new file mode 100644 index 0000000..058d44e --- /dev/null +++ b/src/components/App/App.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; +import Home from '../Home/Home'; + +const App = () => ( +
+ + + +
+); + +export default App; diff --git a/src/components/App/App.test.js b/src/components/App/App.test.js new file mode 100644 index 0000000..ca97c5e --- /dev/null +++ b/src/components/App/App.test.js @@ -0,0 +1,16 @@ +import App from './App'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { MemoryRouter } from 'react-router-dom'; + +describe('', () => { + test('renders without exploding', () => { + const div = document.createElement('div'); + ReactDOM.render( + + + , + div, + ); + }); +}); diff --git a/src/components/Avatar/Avatar.css b/src/components/Avatar/Avatar.css new file mode 100644 index 0000000..13bfca7 --- /dev/null +++ b/src/components/Avatar/Avatar.css @@ -0,0 +1,6 @@ +.avatar { + vertical-align: middle; + width: 128px; + height: 128px; + border-radius: 50%; + } \ No newline at end of file diff --git a/src/components/Avatar/Avatar.js b/src/components/Avatar/Avatar.js new file mode 100644 index 0000000..99959d1 --- /dev/null +++ b/src/components/Avatar/Avatar.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { string } from 'prop-types'; + +import './Avatar.css'; + +function Avatar(props) { + const { src, srcSet, alt } = props; + + return ( + {alt} + ); +} + +export default Avatar; + +Avatar.propType = { + src: string, + srcSet: string, + alt: string, +}; diff --git a/src/components/Button/Button.js b/src/components/Button/Button.js new file mode 100644 index 0000000..0132dfa --- /dev/null +++ b/src/components/Button/Button.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { string } from 'prop-types'; + +function Button(props) { + const { name, href, displayName, logo } = props; + + return ( + <> + + {logo && ( + {`${displayName} + )} + + {displayName} + + + ); +} + +export default Button; + +Button.propType = { + src: string.isRequired, + srcSet: string, + alt: string.isRequired, + href: string, +}; diff --git a/src/components/Button/Button.test.js b/src/components/Button/Button.test.js new file mode 100644 index 0000000..893e441 --- /dev/null +++ b/src/components/Button/Button.test.js @@ -0,0 +1,10 @@ +import Button from './Button'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +describe('