diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index e2b2377..0000000 --- a/.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -max_line_length = 80 -trim_trailing_whitespace = true - -[*.md] -max_line_length = 0 -trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 282f21f..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.js linguist-language=TypeScript diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e3493eb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: openfurs diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9ba63bf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 + +updates: +- package-ecosystem: "npm" + directory: '/' + schedule: + interval: weekly + time: '00:00' + open-pull-requests-limit: 2 + assignees: + - skepfusky + commit-message: + prefix: fix + prefix-development: chore + include: scope + +- package-ecosystem: "npm" + directory: '/app' + schedule: + interval: weekly + time: '00:00' + open-pull-requests-limit: 10 + assignees: + - skepfusky + commit-message: + prefix: fix + prefix-development: chore + include: scope diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 4ff03de..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,68 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - pull_request: - branches: [ main ] - - schedule: - - cron: '15 10 * * 4' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript', 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/typescript-check.yml b/.github/workflows/typescript-check.yml new file mode 100644 index 0000000..384ab89 --- /dev/null +++ b/.github/workflows/typescript-check.yml @@ -0,0 +1,29 @@ +name: TS Type Checking + +on: + push: + paths: + - "**.ts" + - "**.tsx" + +jobs: + typescript-check: + name: TypeScript Type Checks + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Use yarn cache keyed by hashed yarn.lock + uses: actions/cache@v2 + with: + path: ~/.yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Checking types + run: yarn tsc:lint diff --git a/.gitignore b/.gitignore index e58ffe8..8ff4345 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies node_modules /.pnp @@ -37,12 +35,24 @@ node_modules # python and database venv -# ssl certs -*.key -*.crt - # Ignore local Housepets json file housepets_db.json +redis_config.json # Ignore __pycache__ stuff -server/__pycache__ +**/__pycache__ + +# Ignore lock files +package-lock.json +yarn.lock +pnpm-lock.yaml + +# PWA service workers +**/sw.js +**/sw.js.map +**/workbox-*.js +**/workbox-*.js.map +**/worker-*.js +**/worker-*.js.map +**/fallback-development.js +**/fallback-*.js diff --git a/.husky/common.sh b/.husky/common.sh new file mode 100644 index 0000000..7bf8076 --- /dev/null +++ b/.husky/common.sh @@ -0,0 +1,8 @@ +command_exists () { + command -v "$1" >/dev/null 2>&1 +} + +# Workaround for Windows 10, Git Bash and Yarn +if command_exists winpty && test -t 1; then + exec < /dev/tty +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..ab79fb2 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" +. "$(dirname -- "$0")/common.sh" + +git fetch +git pull diff --git a/.vscode/searchpets.code-snippets b/.vscode/searchpets.code-snippets new file mode 100644 index 0000000..f39aab2 --- /dev/null +++ b/.vscode/searchpets.code-snippets @@ -0,0 +1,9 @@ +{ + "Import styles": { + "scope": "typescriptreact", + "prefix": "importstyles", + "body": [ + "import styles from \"@/styles/$0.module.scss\"" + ], + } +} diff --git a/README.md b/README.md index 7564efc..eda27ae 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,21 @@

searchpets-peanut-transparent
- Searchpets + Searchpets!

- GPL 2.0 License + GPL 2.0 License Searchpets' open issues + + +

-Searchpets is an open source search engine to find characters and texts from comics (coming soon) from Housepets! written in Next.js and FastAPI. +Searchpets! is comic search engine for searching characters, texts from comics, and chapter arcs from the entire Housepets! catalog! Written in Python and TypeScript - it was taken inspiration from this forum post.

-This project was taken inspiration from this forum post. + Searchpets Dev Demo

-

- Searchpets Dev Demo -

- -## Tech stack - -![Searchpets Stack](https://skillicons.dev/icons?i=react,nextjs,ts,js,sass,tailwind,py,flask,cloudflare) - -The front-end is written in a React framework, Next.js + TypeScript; -with Tailwind CSS and Sass as CSS painkillers. For the back-end, it's powered -with Python with the lightweight web framework, Flask. - -The website is currently being hosted from a custom Linode server and -delivered through the interwebs with CloudFlare. ## Project structure @@ -36,6 +25,16 @@ delivered through the interwebs with CloudFlare. - `scripts` - Automated Bash scripts to bulk install Python and Node packages and for deployment from the server +## Infrastructure + +The front-end is written in Next.js + TypeScript + Lit Web Components; +with Tailwind CSS. While the back-end is written +in Python with a lightweight and fast web framework, FastAPI - the +database is powered by Redis. + +The infrastructure is being hosted from Linode and Netlify then +delivered through the interwebs with Cloudflare. + ## Running the app locally ### Prerequisites @@ -53,7 +52,7 @@ To install all the required Node and Python libraries, execute the `setup.sh` file. ```console -sh scripts/setup.sh +sh scripts/init.sh ``` Once all the libraries have been installed, it will execute `gen.py` to @@ -64,9 +63,10 @@ concurrently start both Node and Python servers. ## Disclaimer -The use of third-party content is heavily transformative, and therefore, subject -of Fair Use. _Housepets!_ and its story and characters is a trademark of Rick Griffin. +_Searchpets!_ is an open source fan project. SP does not own any of the +contents used on this website and has no direct affiliation with the entire +_Housepets!_ web comic or all of Rick Griffin's intellectual property. ## License -GPT-2.0 +GPL-2.0 diff --git a/app/.env.local.example b/app/.env.local.example index b6cd685..073a04e 100644 --- a/app/.env.local.example +++ b/app/.env.local.example @@ -1 +1,2 @@ +REDIS_URL= GA= diff --git a/app/.eslintrc.json b/app/.eslintrc.json index 3b8ecbf..eb8dfd0 100644 --- a/app/.eslintrc.json +++ b/app/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": "next/core-web-vitals", + "extends": ["next/core-web-vitals"], "rules": { "@next/next/no-img-element": "off", "react/no-unescaped-entities": "off" diff --git a/app/.prettierignore b/app/.prettierignore index 3073754..dae6e31 100644 --- a/app/.prettierignore +++ b/app/.prettierignore @@ -1,4 +1,3 @@ -next -out -build -coverage +app/.next +app/out +app/build diff --git a/app/.prettierrc b/app/.prettierrc new file mode 100644 index 0000000..64c15df --- /dev/null +++ b/app/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "none", + "useTabs": false, + "tabWidth": 2, + "semi": false, + "printWidth": 80 +} diff --git a/app/.prettierrc.js b/app/.prettierrc.js deleted file mode 100644 index fc5c4e1..0000000 --- a/app/.prettierrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - trailingComma: "es5", - tabWidth: 2, - semi: false, - printWidth: 80 -} diff --git a/app/@types/app.d.ts b/app/@types/app.d.ts new file mode 100644 index 0000000..d0e7d42 --- /dev/null +++ b/app/@types/app.d.ts @@ -0,0 +1,61 @@ +declare type ThemeOverrides = "light" | "dark" | "unset" + +declare type OptionsCtxTypes = { + theme: ThemeOverrides + highContrast: boolean + expandComics?: boolean + animations: boolean | undefined + setTheme: (theme: ThemeOverrides) => void + setExpandComics?: (expandComics: boolean) => void + setHighContrast: (highContrast: boolean) => void + setAnimations: (animations: boolean) => void +} + +declare type SidebarCtxTypes = { + expanded: boolean + marginSize: string + setExpanded: (expanded: boolean) => void + setMarginSize: (marginSize: string) => void +} + +declare type ModalCtxTypes = { + modalOpen: boolean + setModalOpen: (modalOpen: boolean) => void +} + +declare interface LayoutProps { + children?: React.ReactNode +} + +declare interface ContainerProps extends LayoutProps { + title?: string + description?: string + wrap?: boolean | undefined +} + +declare interface NavLinkProps { + link?: string + name?: string + icon: IconProp +} + +declare interface ComicItemProps { + title?: string + img: string + link: string + characters?: string[] + guestItem?: boolean + favoriteItem?: boolean +} + +declare interface NavbarRootProps extends LayoutProps {} + +declare interface SidebarItemProps { + link?: string + name?: string + icon?: any + disabled?: boolean + header?: string + kofi?: boolean + hideOnCollapse?: boolean +} diff --git a/app/@types/backend.d.ts b/app/@types/backend.d.ts new file mode 100644 index 0000000..3e4ceb7 --- /dev/null +++ b/app/@types/backend.d.ts @@ -0,0 +1,24 @@ +import type { SetStateAction } from "react" + +declare global { + type CharacterArrayType = { + characters: string[] + } + + type SearchResponseType = { + years: string + comics: string[] + } & CharacterArrayType + + type ComicItemType = { + title: string + comic_link: string + image: string + guest: string + } & CharacterArrayType + + type DataResponseType = { + comicCount: number + charCount: number + } +} diff --git a/app/@types/index.d.ts b/app/@types/index.d.ts new file mode 100644 index 0000000..23e0db6 --- /dev/null +++ b/app/@types/index.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/app/cypress.config.ts b/app/cypress.config.ts new file mode 100644 index 0000000..17161e3 --- /dev/null +++ b/app/cypress.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, +}); diff --git a/app/cypress/fixtures/example.json b/app/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/app/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/app/cypress/support/commands.ts b/app/cypress/support/commands.ts new file mode 100644 index 0000000..2e02825 --- /dev/null +++ b/app/cypress/support/commands.ts @@ -0,0 +1,38 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } +export {} diff --git a/app/cypress/support/e2e.ts b/app/cypress/support/e2e.ts new file mode 100644 index 0000000..ed5730d --- /dev/null +++ b/app/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/app/lib/ga.ts b/app/lib/ga.ts index e5c8f78..9c2fc85 100644 --- a/app/lib/ga.ts +++ b/app/lib/ga.ts @@ -4,12 +4,14 @@ declare global { } } -export const pageview = (url: any) => { - window.gtag("config", process.env.GA, { - page_path: url, - }) +export const pageView = (url: string) => { + if (typeof window.gtag !== "undefined") { + window.gtag("config", process.env.GA, { + page_path: url + }) + } } -export const event = ({ action, params }) => { +export const event = ({ action, params }: any) => { window.gtag("event", action, params) } diff --git a/app/lib/redis.ts b/app/lib/redis.ts new file mode 100644 index 0000000..ba5df66 --- /dev/null +++ b/app/lib/redis.ts @@ -0,0 +1,85 @@ +import { createClient } from "redis" + +const client = createClient({ + url: process.env.REDIS_URL +}) + +export async function searchComics(years: string[], characters: string[]) { + client.connect() + + try { + years = Array.isArray(years) ? years : [years] + characters = Array.isArray(characters) ? characters : [characters] + + let comicsOutput: ComicItemType[] = [] + + console.log(years) + console.log(characters) + + const character_query = characters + .map((character) => { + return `@characters:{${character}}` + }) + .join(" ") + + console.log(character_query) + + for (const year of years) { + console.log(year) + console.log("this needs to run after the above") + + await client.ft + .search(year, character_query, { LIMIT: { from: 0, size: 500 } }) + .then((result) => { + result.documents.forEach((doc) => { + const comic: ComicItemType = { + title: doc.value.title as string, + characters: (doc.value.characters as string).split(","), + comic_link: doc.value.comic_link as string, + guest: doc.value.guest as string, + image: doc.value.image as string + } + comicsOutput.push(comic) + }) + }) + } + + client.quit() + return { comics: comicsOutput } + } catch { + client.quit() + return { comics: "ERROR: Search failed!" } + } +} + +export async function grabData() { + client.connect() + let comicCount = 0 + let charCount = 0 + + await client.DBSIZE().then((result) => { + console.log(result - 1) + comicCount = result - 1 + }) + + await client.LLEN("characters_db").then((result) => { + console.log(result) + charCount = result + }) + + client.quit() + return { comicCount: comicCount, charCount: charCount } +} + +export async function grabCharacters() { + client.connect() + + let characters: string[] | undefined + + await client.LRANGE("characters_db", 0, -1).then((result) => { + client.quit() + characters = result + }) + + return { characters_db: characters } +} diff --git a/app/next.config.js b/app/next.config.js index cfbf807..2366057 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -1,13 +1,50 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - compress: true, - compiler: { - removeConsole: true, - }, - images: { - domains: ['www.housepetscomic.com'] - }, -} +// @ts-check + +/** + * @type {import('next').NextConfig} + **/ +module.exports = async (phase) => { + const withPlugins = require("next-compose-plugins") + + const runtimeCaching = require('next-pwa/cache') + runtimeCaching[0].handler = 'StaleWhileRevalidate' + + const withPWA = require('next-pwa')({ + dest: "public", + disable: process.env.NODE_ENV === "development", + register: true, + runtimeCaching + }); -module.exports = nextConfig + const withMDX = require("@next/mdx")({ + extension: /\.mdx?$/, + options: { + providerImportSource: "@mdx-js/react" + } + }) + + const nextConfig = { + reactStrictMode: true, + swcMinify: true, + compress: true, + compiler: { + removeConsole: process.env.NODE_ENV !== "development" + }, + images: { + domains: ["www.housepetscomic.com"], + formats: ["image/webp"] + }, + } + + const defaultConfig = {} + + return withPlugins( + [ + withMDX({ + pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"] + }), + withPWA + ], + nextConfig + )(phase, { defaultConfig }) +} diff --git a/app/package.json b/app/package.json index c4bc712..76a0b11 100644 --- a/app/package.json +++ b/app/package.json @@ -1,35 +1,44 @@ { - "name": "housepets-search-engine", + "name": "searchpets-v2-next", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "cypress:open": "cypress open" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.1.1", - "@fortawesome/free-brands-svg-icons": "^6.1.1", - "@fortawesome/free-solid-svg-icons": "^6.1.1", - "@fortawesome/react-fontawesome": "^0.1.18", - "next": "12.1.2", - "react": "^18.1.0", - "react-dom": "^18.1.0", - "sharp": "^0.30.5" + "@fortawesome/fontawesome-svg-core": "^6.1.2", + "@fortawesome/free-brands-svg-icons": "^6.1.2", + "@fortawesome/free-solid-svg-icons": "^6.1.2", + "@fortawesome/react-fontawesome": "^0.2.0", + "@mdx-js/loader": "^2.1.2", + "@mdx-js/react": "^2.1.2", + "@next/mdx": "^12.2.5", + "cypress": "^10.3.1", + "next": "12.2.3", + "next-pwa": "^5.5.5", + "postcss": "^8.4.14", + "react": "18.2.0", + "react-device-detect": "^2.2.2", + "react-dom": "18.2.0", + "react-intersection-observer": "^9.4.0", + "react-markdown": "^8.0.3", + "redis": "^4.2.0", + "sass": "^1.54.0", + "tailwindcss": "^3.1.7", + "typescript": "4.7.4" }, "devDependencies": { - "@types/node": "17.0.23", - "@types/react": "17.0.43", - "@types/react-dom": "17.0.14", - "autoprefixer": "^10.4.4", - "cssnano": "^5.1.7", - "eslint": "8.12.0", - "eslint-config-next": "12.1.2", - "postcss": "^8.4.12", - "prettier": "2.6.2", - "sass": "^1.49.9", - "tailwindcss": "^3.0.23", - "typescript": "4.6.3" + "@types/node": "18.6.3", + "@types/react": "18.0.15", + "@types/react-dom": "18.0.6", + "autoprefixer": "^10.4.8", + "cssnano": "^5.1.12", + "eslint": "8.20.0", + "eslint-config-next": "12.2.3", + "next-compose-plugins": "^2.2.1" } } diff --git a/app/public/favicon.ico b/app/public/favicon.ico deleted file mode 100644 index 4bb84e5..0000000 Binary files a/app/public/favicon.ico and /dev/null differ diff --git a/app/public/manifest.json b/app/public/manifest.json new file mode 100644 index 0000000..d5c3419 --- /dev/null +++ b/app/public/manifest.json @@ -0,0 +1,30 @@ +{ + "name": "Searchpets!", + "short_name": "Searchpets!", + "theme_color": "#ffffff", + "background_color": "#7f5bd5", + "start_url": "/", + "scope": "/", + "dir": "auto", + "display": "standalone", + "icons": [ + { + "src": "/static/apple-touch-icon-120x120.png", + "sizes": "120x120", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/static/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/static/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + } + ] +} diff --git a/app/public/static/Searchpets-Peanut.png b/app/public/static/Searchpets-Peanut.png deleted file mode 100644 index 089f8d1..0000000 Binary files a/app/public/static/Searchpets-Peanut.png and /dev/null differ diff --git a/app/public/static/android-chrome-192x192.png b/app/public/static/android-chrome-192x192.png new file mode 100644 index 0000000..4050eac Binary files /dev/null and b/app/public/static/android-chrome-192x192.png differ diff --git a/app/public/static/android-chrome-512x512.png b/app/public/static/android-chrome-512x512.png new file mode 100644 index 0000000..96762b1 Binary files /dev/null and b/app/public/static/android-chrome-512x512.png differ diff --git a/app/public/static/apple-touch-icon-120x120.png b/app/public/static/apple-touch-icon-120x120.png new file mode 100644 index 0000000..2810d49 Binary files /dev/null and b/app/public/static/apple-touch-icon-120x120.png differ diff --git a/app/public/static/apple-touch-icon-144x144.png b/app/public/static/apple-touch-icon-144x144.png new file mode 100644 index 0000000..f3b3528 Binary files /dev/null and b/app/public/static/apple-touch-icon-144x144.png differ diff --git a/app/public/static/apple-touch-icon-152x152.png b/app/public/static/apple-touch-icon-152x152.png new file mode 100644 index 0000000..326fc83 Binary files /dev/null and b/app/public/static/apple-touch-icon-152x152.png differ diff --git a/app/public/static/apple-touch-icon-180x180.png b/app/public/static/apple-touch-icon-180x180.png new file mode 100644 index 0000000..88de2bd Binary files /dev/null and b/app/public/static/apple-touch-icon-180x180.png differ diff --git a/app/public/static/apple-touch-icon.png b/app/public/static/apple-touch-icon.png new file mode 100644 index 0000000..88de2bd Binary files /dev/null and b/app/public/static/apple-touch-icon.png differ diff --git a/app/public/static/favicon-96x96.png b/app/public/static/favicon-96x96.png new file mode 100644 index 0000000..6c5ba2c Binary files /dev/null and b/app/public/static/favicon-96x96.png differ diff --git a/app/public/static/foxplushie_loading.png b/app/public/static/foxplushie_loading.png new file mode 100644 index 0000000..ba0db21 Binary files /dev/null and b/app/public/static/foxplushie_loading.png differ diff --git a/app/public/static/kofilogo.png b/app/public/static/kofilogo.png new file mode 100644 index 0000000..89e65cd Binary files /dev/null and b/app/public/static/kofilogo.png differ diff --git a/app/public/static/safari-pinned-tab.svg b/app/public/static/safari-pinned-tab.svg new file mode 100644 index 0000000..f7d5afb --- /dev/null +++ b/app/public/static/safari-pinned-tab.svg @@ -0,0 +1,530 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/components/BackToTop.module.scss b/app/src/components/BackToTop.module.scss new file mode 100644 index 0000000..d5d3fb8 --- /dev/null +++ b/app/src/components/BackToTop.module.scss @@ -0,0 +1,17 @@ +.btt { + @apply fixed bottom-8 right-7 z-50 px-7 md:px-6 py-7 md:py-3 flex gap-2 items-center rounded-full md:rounded-md bg-purple-500 dark:bg-purple-600 shadow-md text-white transition-all duration-300; + @apply opacity-100 translate-y-0 pointer-events-auto; + + &:hover { + @apply bg-purple-600 dark:bg-purple-800 + } + + strong { + @apply hidden md:block; + } + + &-hidden { + @extend .btt; + @apply opacity-0 translate-y-6 pointer-events-none; + } +} diff --git a/app/src/components/BackToTop.tsx b/app/src/components/BackToTop.tsx deleted file mode 100644 index 151109a..0000000 --- a/app/src/components/BackToTop.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect, useState } from "react" -import { FontAwesomeIcon as FaIcon } from "@fortawesome/react-fontawesome" -import { faAngleUp } from "@fortawesome/free-solid-svg-icons" - -export default function BackToTopButton() { - const [show, setShow] = useState("back-to-top-btn") - - const handleScroll = () => { - window.scrollY > 450 - ? setShow("back-to-top-btn show") - : setShow("back-to-top-btn") - } - - useEffect(() => { - window.addEventListener("scroll", handleScroll) - return () => window.removeEventListener("scroll", handleScroll) - }, []) - - return ( - - ) -} diff --git a/app/src/components/BackToTopBtn.tsx b/app/src/components/BackToTopBtn.tsx new file mode 100644 index 0000000..16471d7 --- /dev/null +++ b/app/src/components/BackToTopBtn.tsx @@ -0,0 +1,32 @@ +import { useState, useEffect } from "react" +import { faAngleUp } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import styles from "./BackToTop.module.scss" + +export default function BackToTopBtn() { + const [show, setShow] = useState(styles["btt-hidden"].toString()) + + useEffect(() => { + const handleScroll = () => { + window.scrollY > 350 + ? setShow(styles.btt.toString()) + : setShow(styles["btt-hidden"].toString()) + } + + window.addEventListener("scroll", handleScroll) + return () => window.removeEventListener("scroll", handleScroll) + }, []) + + return ( + + ) +} diff --git a/app/src/components/BaseHead.tsx b/app/src/components/BaseHead.tsx deleted file mode 100644 index a93a4a0..0000000 --- a/app/src/components/BaseHead.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import Head from "next/head" -import { useRouter } from "next/router" - -export default function BaseHead({ - title, - description, -}: { - title: string - description: string -}) { - const SITE_TITLE = "Searchpets!" - const router = useRouter() - - return ( - <> - - - {SITE_TITLE} | {title} - - - - - - - - - - - - - - - - - ) -} diff --git a/app/src/components/CharacterItem.tsx b/app/src/components/CharacterItem.tsx index 3735fbe..3c19e82 100644 --- a/app/src/components/CharacterItem.tsx +++ b/app/src/components/CharacterItem.tsx @@ -1,17 +1,9 @@ -interface ICharacterItemProps { - character: string; - appearance?: number; - color?: string; -} +import ParseRegexId from "@/utils/ParseRegexId" -export default function CharacterItem({ character, appearance, ...props }: ICharacterItemProps) { - const characterName = character - .replace(/(\s)|(\')/g, "-") - .replace(/(\()|(\))|(\.)/g, "") - .toLowerCase() +export default function CharacterItem({ name }: { name?: string }) { return ( -
- {character} +
+ {name}
- ); -}; + ) +} diff --git a/app/src/components/ComicItem.tsx b/app/src/components/ComicItem.tsx deleted file mode 100644 index e182874..0000000 --- a/app/src/components/ComicItem.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons" -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import Image from "next/image" -import Link from "next/link" -interface IComicItemProps { - title: string - characters: string - link: string - image: string -} - -export default function ComicItem({ - title, - characters, - link, - image, -}: IComicItemProps) { - return ( -
-

"{title}"

-
- {title} -
-
-
-
- - Characters - -
- {characters.split(", ").map((character, i) => { - const characterName = character - .replace(/(\s)|(\')/g, "-") - .replace(/(\()|(\))|(\.)/g, "") - .toLowerCase() - return ( - - {character} - - ) - })} -
-
- - - Original Link - - -
-
- ) -} - -export function ComicItemLoading() { - return ( -
-
-
-
- Loading... -
-
-
- ) -} diff --git a/app/src/components/ComicItem/ComicCharacterItem.tsx b/app/src/components/ComicItem/ComicCharacterItem.tsx new file mode 100644 index 0000000..331868b --- /dev/null +++ b/app/src/components/ComicItem/ComicCharacterItem.tsx @@ -0,0 +1,23 @@ +import styles from "./ComicItem.module.scss" +import { faPlus } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import ParseRegexId from "@/utils/ParseRegexId" + +export default function CharacterItem({ name }: { name?: string }) { + const characterName = ParseRegexId(name) + + return ( +
  • + {name} + +
  • + ) +} diff --git a/app/src/components/ComicItem/ComicItem.module.scss b/app/src/components/ComicItem/ComicItem.module.scss new file mode 100644 index 0000000..88e1110 --- /dev/null +++ b/app/src/components/ComicItem/ComicItem.module.scss @@ -0,0 +1,175 @@ +.wrapper { + @apply gap-2 px-5 py-4 dark:bg-neutral-800 bg-neutral-50 shadow-lg rounded-md flex flex-col border-2 border-transparent; + + @media (min-width: 1024px) { + height: 680px; + } + + &:nth-child(odd) { + // @apply bg-gray-900; + } + + &:target { + @apply dark:bg-violet-900 dark:bg-opacity-30 border-purple-400 transition-colors duration-300; + } +} + +.wrapper-guest { + @extend .wrapper; + @apply relative dark:bg-[#25253b] bg-indigo-100 dark:border-indigo-900; +} + +[theme-override="dark"] .wrapper { + @apply bg-neutral-800; +} + +.heading-container { + @apply flex items-center justify-between; +} + +.heading-actions { + @apply flex gap-x-2; +} + +.heading { + @apply font-bold text-xl italic px-2 py-1 -translate-x-1.5 rounded-md transition-colors duration-300; + + svg { + @apply opacity-0 transition-all duration-300; + } + + &:hover { + @apply dark:bg-purple-700 bg-purple-500 text-white; + + svg { + @apply opacity-100 text-white; + } + } +} + +.buttons { + @apply px-4 py-3.5 rounded-md flex; +} + +.bookmark-btn { + @extend .buttons; + @apply dark:bg-yellow-900 dark:hover:bg-yellow-800 bg-yellow-200 hover:bg-yellow-300; +} + +.link-btn { + @extend .buttons; + @apply dark:bg-neutral-900 dark:hover:bg-neutral-700 bg-neutral-200 hover:bg-neutral-300; +} + +.image-container { + @apply relative w-full; + + --comic-rel-size: 1; + + @media (min-width: 413px) { + --comic-rel-size: 1.4; + } + + @media (min-width: 700px) { + --comic-rel-size: 2.1 !important; + } + + @media (min-width: 500px) { + --comic-rel-size: 1.75; + } + + height: calc(var(--comic-height-column) * var(--comic-rel-size)); + + @media (min-width: 1024px) { + height: 690px; + } + + img { + @apply select-none; + } +} + +.subheading { + @apply text-xl flex items-center gap-x-1; +} + +.count { + @apply bg-opacity-40 dark:bg-opacity-40 px-2 py-0.5 rounded-md text-sm bg-purple-500; +} + +.info-container { + @apply flex flex-col justify-between gap-y-1; + + ul { + @apply flex gap-1 flex-wrap; + } + + :is(.char-item, .spacing) { + @apply transition-all duration-150; + } + + .char-item { + @apply relative py-1 px-2.5 bg-opacity-25 rounded-md select-none text-sm cursor-pointer; + + &:hover { + @apply dark:bg-purple-700 bg-purple-300 #{!important}; + @apply rounded-tr-none rounded-br-none; + + .spacing { + @apply opacity-100 translate-x-0; + } + } + } + + .spacing { + @apply opacity-0 -translate-x-3 z-10 rounded-tr-md rounded-br-md p-[0.44rem] -right-6 top-0 absolute dark:bg-purple-800 bg-purple-400 pointer-events-none; + } +} + +.loading-container { + @apply rounded-md h-full w-full; + background: linear-gradient( + 128deg, + theme("colors.neutral.200") 0%, + theme("colors.neutral.200") 35%, + theme("colors.neutral.400") 50%, + theme("colors.neutral.200") 65%, + theme("colors.neutral.200") 100% + ); + + @media (prefers-color-scheme: dark) { + background: linear-gradient( + 128deg, + theme("colors.neutral.700") 0%, + theme("colors.neutral.700") 35%, + theme("colors.neutral.900") 50%, + theme("colors.neutral.700") 65%, + theme("colors.neutral.700") 100% + ); + background-size: 750% 750%; + } + + animation: gradientScroll 2s infinite linear; + background-size: 750% 750%; + + &:is(:first-child, :last-child) { + @apply h-[4.5rem]; + } + + &:first-child { + animation-delay: 150ms; + } + + &:last-child { + animation-delay: -150ms; + } +} + +@keyframes gradientScroll { + 0% { + background-position: 0% 50%; + } + 100% { + background-position: 100% 50%; + } +} diff --git a/app/src/components/ComicItem/ComicItem.tsx b/app/src/components/ComicItem/ComicItem.tsx new file mode 100644 index 0000000..0a4abc4 --- /dev/null +++ b/app/src/components/ComicItem/ComicItem.tsx @@ -0,0 +1,128 @@ +import { useState, useEffect } from "react" +import Image from "next/image" +import styles from "./ComicItem.module.scss" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { + faExternalLink, + faLink, + faStar +} from "@fortawesome/free-solid-svg-icons" +import Link from "next/link" +import FoxSpin from "../FoxSpin" +import CharacterItem from "./ComicCharacterItem" +import ParseRegexId from "@/utils/ParseRegexId" + +export default function ComicItem({ + title, + img, + characters, + link, + guestItem, + favoriteItem +}: ComicItemProps) { + const [isLoaded, setIsLoaded] = useState(false) + const [nWidth, setNWidth] = useState() + const [nHeight, setNHeight] = useState() + + // console.log({ title: title , nw: nWidth, nH: nHeight }) + + const comicStyleWrapper = !guestItem + ? styles.wrapper.toString() + : styles["wrapper-guest"].toString() + + const regexTitle = ParseRegexId(title) + + useEffect(() => { + if (nHeight == undefined || nHeight === 1) { + setNHeight(350) + } + }, [nHeight]) + + return ( +
    +
    +

    + + + {title} + + + +

    +
    + + + + +
    +
    +
    +
    +
    +
    + Characters + {characters?.length} +
    +
      + {characters!.map((character: string) => ( + + ))} +
    +
    +
    + ) +} + +export function ComicItemLoading() { + return ( +
    +
    +
    +
    +
    + ) +} diff --git a/app/src/components/ComicItem/index.tsx b/app/src/components/ComicItem/index.tsx new file mode 100644 index 0000000..11ac488 --- /dev/null +++ b/app/src/components/ComicItem/index.tsx @@ -0,0 +1 @@ +export { default } from "./ComicItem" diff --git a/app/src/components/Container.tsx b/app/src/components/Container.tsx deleted file mode 100644 index bfb2f15..0000000 --- a/app/src/components/Container.tsx +++ /dev/null @@ -1,17 +0,0 @@ -interface IContainerProps { - children: any; - classNames?: string; - mainClassName?: string; -} - -export default function Container({ - children, - mainClassName = "flex flex-col justify-between", - classNames = "page_searchComic-wrapper", -}: IContainerProps) { - return ( -
    -
    {children}
    -
    - ); -}; diff --git a/app/src/components/Footer.tsx b/app/src/components/Footer.tsx deleted file mode 100644 index 9a65670..0000000 --- a/app/src/components/Footer.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import Link from "next/link"; -import { FontAwesomeIcon as FaIcon } from "@fortawesome/react-fontawesome"; -import { faGithub, faTwitter } from "@fortawesome/free-brands-svg-icons"; -import { faLink } from "@fortawesome/free-solid-svg-icons"; - -export default function Footer() { - return ( - - ) -} diff --git a/app/src/components/FoxSpin.module.scss b/app/src/components/FoxSpin.module.scss new file mode 100644 index 0000000..f664983 --- /dev/null +++ b/app/src/components/FoxSpin.module.scss @@ -0,0 +1,20 @@ +#wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 5; + row-gap: 0.5rem; + height: 100%; + width: 100%; +} + +#fox-speen { + animation: speen 7s linear infinite; +} + +@keyframes speen { + 100% { + transform: rotate(-360deg); + } +} diff --git a/app/src/components/FoxSpin.tsx b/app/src/components/FoxSpin.tsx new file mode 100644 index 0000000..5671f67 --- /dev/null +++ b/app/src/components/FoxSpin.tsx @@ -0,0 +1,18 @@ +import Image from "next/image" +import styles from "./FoxSpin.module.scss" + +export default function FoxSpin({ hidden }: { hidden: boolean }) { + return ( + + ) +} diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx deleted file mode 100644 index ad80676..0000000 --- a/app/src/components/Header.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useState, useEffect } from "react" -import Link from "next/link" -import { useRouter } from "next/router" -import { FontAwesomeIcon as FaIcon } from "@fortawesome/react-fontawesome" -import { - faInfoCircle, - faList, - faSearch, -} from "@fortawesome/free-solid-svg-icons" - -export default function Header() { - const router = useRouter() - - const [navIdn, setNavIdn] = useState("translate-x-9") - - useEffect(() => { - if (router.pathname) { - switch (router.pathname) { - case "/characters": - setNavIdn("translate-x-[10.75rem]") - break - case "/about": - setNavIdn("translate-x-[19rem]") - break - default: - setNavIdn("translate-x-9") - } - } - }, [router]) - - return ( -
    -
    -
    - - Searchpets - -
    - -
    -
    - ) -} diff --git a/app/src/components/Layout.tsx b/app/src/components/Layout.tsx deleted file mode 100644 index a20fd7f..0000000 --- a/app/src/components/Layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Header from './Header' -import Footer from './Footer' -import Head from 'next/head' - -export default function Layout({ children }) { - return ( - <> - - - - - -
    - {children} -