mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-22 09:13:19 +01:00
Migrate to Biome (#1322)
* migrate to Biome * package updates * ci fix * typing fix
This commit is contained in:
parent
4eff043f17
commit
7eb0075e6f
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "next/core-web-vitals"
|
|
||||||
}
|
|
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@ -15,6 +15,23 @@ env:
|
|||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run prebuild
|
||||||
|
- run: npm run lint
|
||||||
|
- run: npm run typecheck
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@ -28,6 +45,7 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
- run: npm run prebuild
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
19
.prettierrc
19
.prettierrc
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
|
||||||
"importOrder": [
|
|
||||||
"^(react/(.*)$)|^(react$)",
|
|
||||||
"^(next/(.*)$)|^(next$)",
|
|
||||||
"<THIRD_PARTY_MODULES>",
|
|
||||||
"^(@/(.*)$)|^(@$)",
|
|
||||||
"^(@content/(.*)$)|^(@content$)",
|
|
||||||
"^(@generated/(.*)$)|^(@generated$)",
|
|
||||||
"^[./]"
|
|
||||||
],
|
|
||||||
"importOrderSeparation": false,
|
|
||||||
"importOrderSortSpecifiers": true
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"stylelint-prettier/recommended"
|
|
||||||
]
|
|
||||||
}
|
|
12
README.md
12
README.md
@ -104,8 +104,8 @@ If you want to know how, have a look at the respective component:
|
|||||||
All SVG assets will be converted to React components with the help of [@svgr/webpack](https://react-svgr.com). Makes use of [SVGR](https://github.com/smooth-code/svgr) so SVG assets can be imported like so:
|
All SVG assets will be converted to React components with the help of [@svgr/webpack](https://react-svgr.com). Makes use of [SVGR](https://github.com/smooth-code/svgr) so SVG assets can be imported like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import Logo from './images/logo.svg'
|
import Logo from "./images/logo.svg";
|
||||||
return <Logo />
|
return <Logo />;
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🤓 Scripts
|
## 🤓 Scripts
|
||||||
@ -159,18 +159,12 @@ npm run dev
|
|||||||
|
|
||||||
### 🔮 Linting
|
### 🔮 Linting
|
||||||
|
|
||||||
ESLint, Prettier, and Stylelint are setup for all linting purposes:
|
[Biome](https://biomejs.dev) is setup for all linting and formatting purposes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
To automatically format all code files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run format
|
|
||||||
```
|
|
||||||
|
|
||||||
### 👩🔬 Testing
|
### 👩🔬 Testing
|
||||||
|
|
||||||
Test suite is setup with [Jest](https://jestjs.io) and [react-testing-library](https://github.com/kentcdodds/react-testing-library).
|
Test suite is setup with [Jest](https://jestjs.io) and [react-testing-library](https://github.com/kentcdodds/react-testing-library).
|
||||||
|
23
biome.json
Normal file
23
biome.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2
|
||||||
|
},
|
||||||
|
"linter": { "enabled": true, "rules": { "recommended": true } },
|
||||||
|
"organizeImports": { "enabled": true },
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"jsxQuoteStyle": "double",
|
||||||
|
"quoteProperties": "asNeeded",
|
||||||
|
"trailingCommas": "none",
|
||||||
|
"semicolons": "asNeeded",
|
||||||
|
"arrowParentheses": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"attributePosition": "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,13 @@ const next = (phase, { defaultConfig }) => {
|
|||||||
return typeof defaultConfig.webpack === 'function'
|
return typeof defaultConfig.webpack === 'function'
|
||||||
? defaultConfig.webpack(config, options)
|
? defaultConfig.webpack(config, options)
|
||||||
: config
|
: config
|
||||||
|
},
|
||||||
|
|
||||||
|
eslint: {
|
||||||
|
// Using Biome instead of ESLint,
|
||||||
|
// sadly Next.js doesn't have a way to disable ESLint
|
||||||
|
// see https://github.com/vercel/next.js/discussions/59347
|
||||||
|
ignoreDuringBuilds: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4208
package-lock.json
generated
4208
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@ -13,12 +13,9 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"export": "npm run prebuild && next export",
|
"export": "npm run prebuild && next export",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"lint:js": "next lint",
|
"lint": "biome check --write src/**/*",
|
||||||
"lint:css": "stylelint ./src/**/*.css",
|
|
||||||
"lint": "npm run lint:js && npm run lint:css",
|
|
||||||
"format": "prettier --write 'src/**/*.{ts,tsx,css}'",
|
|
||||||
"jest": "jest --coverage -c tests/jest.config.js",
|
"jest": "jest --coverage -c tests/jest.config.js",
|
||||||
"test": "NODE_ENV=test npm run prebuild && npm run lint && npm run typecheck && npm run jest",
|
"test": "NODE_ENV=test npm run jest",
|
||||||
"new": "node --import tsx/esm ./scripts/new.ts",
|
"new": "node --import tsx/esm ./scripts/new.ts",
|
||||||
"favicon": "node --import tsx/esm ./scripts/favicon.ts",
|
"favicon": "node --import tsx/esm ./scripts/favicon.ts",
|
||||||
"prebuild": "node --import tsx/esm ./scripts/prebuild.ts"
|
"prebuild": "node --import tsx/esm ./scripts/prebuild.ts"
|
||||||
@ -28,9 +25,9 @@
|
|||||||
"@radix-ui/react-select": "^2.1.1",
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
"@yaireo/relative-time": "^1.0.4",
|
"@yaireo/relative-time": "^1.0.4",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.2.11",
|
"framer-motion": "^11.3.17",
|
||||||
"lucide-react": "^0.399.0",
|
"lucide-react": "^0.416.0",
|
||||||
"next": "14.2.4",
|
"next": "14.2.5",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -39,37 +36,27 @@
|
|||||||
"remark-html": "^16.0.1"
|
"remark-html": "^16.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "1.8.3",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@testing-library/jest-dom": "^6.4.6",
|
"@testing-library/jest-dom": "^6.4.8",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-config-next": "^14.2.4",
|
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"ora": "^8.0.1",
|
"ora": "^8.0.1",
|
||||||
"prettier": "^3.3.2",
|
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"sharp-ico": "^0.1.5",
|
"sharp-ico": "^0.1.5",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"stylelint": "^16.6.1",
|
"tsx": "^4.16.2",
|
||||||
"stylelint-prettier": "^5.0.0",
|
"typescript": "^5.5.4"
|
||||||
"tsx": "^4.16.0",
|
|
||||||
"typescript": "^5.5.2"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.6.0"
|
"node": "^20.6.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": ["> 0.2%", "last 3 versions", "Firefox ESR", "not dead"]
|
||||||
"> 0.2%",
|
|
||||||
"last 3 versions",
|
|
||||||
"Firefox ESR",
|
|
||||||
"not dead"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -1 +1,18 @@
|
|||||||
{"name":"matthias kretschmann","short_name":"mk","display":"standalone","start_url":"/?homescreen=1","icons":[{"src":"/manifest/favicon-192.png","type":"image/png","sizes":"192x192"},{"src":"/manifest/favicon-512.png","type":"image/png","sizes":"512x512"}]}
|
{
|
||||||
|
"name": "matthias kretschmann",
|
||||||
|
"short_name": "mk",
|
||||||
|
"display": "standalone",
|
||||||
|
"start_url": "/?homescreen=1",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/manifest/favicon-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/manifest/favicon-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
10
public/sw.js
10
public/sw.js
@ -1,15 +1,13 @@
|
|||||||
// https://github.com/NekR/self-destroying-sw
|
// https://github.com/NekR/self-destroying-sw
|
||||||
self.addEventListener('install', function (e) {
|
self.addEventListener('install', (e) => {
|
||||||
self.skipWaiting()
|
self.skipWaiting()
|
||||||
})
|
})
|
||||||
|
|
||||||
self.addEventListener('activate', function (e) {
|
self.addEventListener('activate', (e) => {
|
||||||
self.registration
|
self.registration
|
||||||
.unregister()
|
.unregister()
|
||||||
.then(function () {
|
.then(() => self.clients.matchAll())
|
||||||
return self.clients.matchAll()
|
.then((clients) => {
|
||||||
})
|
|
||||||
.then(function (clients) {
|
|
||||||
clients.forEach((client) => client.navigate(client.url))
|
clients.forEach((client) => client.navigate(client.url))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import fs from 'fs'
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import type { ProjectType } from '@/types/project'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
import ora from 'ora'
|
import ora from 'ora'
|
||||||
import path from 'path'
|
|
||||||
import type ProjectType from '@/types/project'
|
|
||||||
import { transformProject } from './transformProject'
|
import { transformProject } from './transformProject'
|
||||||
|
|
||||||
const contentDirectory = path.join(process.cwd(), '_content')
|
const contentDirectory = path.join(process.cwd(), '_content')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import { join } from 'path'
|
import { join } from 'node:path'
|
||||||
|
import type { ImageType } from '@/types/image'
|
||||||
import sharp from 'sharp'
|
import sharp from 'sharp'
|
||||||
import type ImageType from '@/types/image'
|
|
||||||
import { rgbDataURL } from './rgbDataURL'
|
import { rgbDataURL } from './rgbDataURL'
|
||||||
|
|
||||||
const imagesDirectory = join(process.cwd(), 'public', 'images')
|
const imagesDirectory = join(process.cwd(), 'public', 'images')
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ProjectType from '@/types/project'
|
import type { ProjectType } from '@/types/project'
|
||||||
import { getProjectImages } from './images'
|
import { getProjectImages } from './images'
|
||||||
import { markdownToHtml } from './markdown'
|
import { markdownToHtml } from './markdown'
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env ts-node
|
#!/usr/bin/env ts-node
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import ora from 'ora'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import ora from 'ora'
|
||||||
import slugify from 'slugify'
|
import slugify from 'slugify'
|
||||||
|
|
||||||
const templatePath = path.join(process.cwd(), 'scripts', 'new.yml')
|
const templatePath = path.join(process.cwd(), 'scripts', 'new.yml')
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { Metadata } from 'next'
|
|
||||||
import { notFound } from 'next/navigation'
|
|
||||||
import Header from '@/components/Header/Header'
|
import Header from '@/components/Header/Header'
|
||||||
import Project from '@/components/Project'
|
import Project from '@/components/Project'
|
||||||
import ProjectNav from '@/components/ProjectNav'
|
import ProjectNav from '@/components/ProjectNav'
|
||||||
@ -7,6 +5,8 @@ import { getAllSlugs } from '@/lib/getAllSlugs'
|
|||||||
import { getProjectBySlug } from '@/lib/getProjectBySlug'
|
import { getProjectBySlug } from '@/lib/getProjectBySlug'
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
import projects from '@generated/projects.json'
|
import projects from '@generated/projects.json'
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
import { notFound } from 'next/navigation'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: { slug: string }
|
params: { slug: string }
|
||||||
@ -21,10 +21,10 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|||||||
description: `${project.description.slice(0, 157)}...`,
|
description: `${project.description.slice(0, 157)}...`,
|
||||||
metadataBase: new URL(meta.url),
|
metadataBase: new URL(meta.url),
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: '/' + project.slug
|
canonical: `/${project.slug}`
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
url: '/' + project.slug,
|
url: `/${project.slug}`,
|
||||||
images: [{ url: project.images[0].src }]
|
images: [{ url: project.images[0].src }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { render } from '@testing-library/react'
|
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
import projectMock from '../../../tests/__fixtures__/project.json'
|
import projectMock from '../../../tests/__fixtures__/project.json'
|
||||||
import Page, { generateMetadata, generateStaticParams } from '../[slug]/page'
|
import Page, { generateMetadata, generateStaticParams } from '../[slug]/page'
|
||||||
|
|
||||||
@ -31,10 +31,10 @@ describe('app: [slug]/page', () => {
|
|||||||
description: `${projectMock.description.slice(0, 157)}...`,
|
description: `${projectMock.description.slice(0, 157)}...`,
|
||||||
metadataBase: new URL(meta.url),
|
metadataBase: new URL(meta.url),
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: '/' + projectMock.slug
|
canonical: `/${projectMock.slug}`
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
url: '/' + projectMock.slug,
|
url: `/${projectMock.slug}`,
|
||||||
images: [{ url: projectMock.images[0].src }]
|
images: [{ url: projectMock.images[0].src }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -5,10 +5,10 @@ describe('app: /layout', () => {
|
|||||||
// suppress error "Warning: validateDOMNesting(...): <html> cannot appear as a child of <div>"
|
// suppress error "Warning: validateDOMNesting(...): <html> cannot appear as a child of <div>"
|
||||||
// https://github.com/testing-library/react-testing-library/issues/1250
|
// https://github.com/testing-library/react-testing-library/issues/1250
|
||||||
let originalError: {
|
let originalError: {
|
||||||
(...data: any[]): void
|
(...data: unknown[]): void
|
||||||
(message?: any, ...optionalParams: any[]): void
|
(message?: unknown, ...optionalParams: unknown[]): void
|
||||||
(...data: any[]): void
|
(...data: unknown[]): void
|
||||||
(message?: any, ...optionalParams: any[]): void
|
(message?: unknown, ...optionalParams: unknown[]): void
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ReactNode } from 'react'
|
|
||||||
import { Metadata, Viewport } from 'next'
|
|
||||||
import Script from 'next/script'
|
|
||||||
import Footer from '@/components/Footer'
|
import Footer from '@/components/Footer'
|
||||||
import HostnameCheck from '@/components/HostnameCheck'
|
import HostnameCheck from '@/components/HostnameCheck'
|
||||||
import ThemeSwitch from '@/components/ThemeSwitch'
|
import ThemeSwitch from '@/components/ThemeSwitch'
|
||||||
import { UMAMI_SCRIPT_URL, UMAMI_WEBSITE_ID } from '@/lib/umami'
|
import { UMAMI_SCRIPT_URL, UMAMI_WEBSITE_ID } from '@/lib/umami'
|
||||||
|
import type { Metadata, Viewport } from 'next'
|
||||||
|
import Script from 'next/script'
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
import '@/styles/global.css'
|
import '@/styles/global.css'
|
||||||
import styles from '@/styles/layout.module.css'
|
import styles from '@/styles/layout.module.css'
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
@ -50,7 +50,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
<link rel="manifest" href="/manifest/manifest.webmanifest"></link>
|
<link rel="manifest" href="/manifest/manifest.webmanifest" />
|
||||||
|
|
||||||
{isProduction && (
|
{isProduction && (
|
||||||
<Script
|
<Script
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Metadata } from 'next'
|
|
||||||
import NotFound from '@/components/404'
|
import NotFound from '@/components/404'
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: `Shenanigans`,
|
title: 'Shenanigans',
|
||||||
description: 'Page not found.'
|
description: 'Page not found.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Suspense } from 'react'
|
|
||||||
import Hero from '@/components/Hero'
|
import Hero from '@/components/Hero'
|
||||||
import Projects from '@/components/Projects'
|
import Projects from '@/components/Projects'
|
||||||
import Repositories from '@/components/Repositories/Repositories'
|
import Repositories from '@/components/Repositories/Repositories'
|
||||||
import { preloadLocation } from '@/lib/getLocation'
|
import { preloadLocation } from '@/lib/getLocation'
|
||||||
import { getRepos } from '@/lib/getRepos'
|
import { getRepos } from '@/lib/getRepos'
|
||||||
import projects from '@generated/projects.json'
|
import projects from '@generated/projects.json'
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
|
||||||
export default async function IndexPage() {
|
export default async function IndexPage() {
|
||||||
const repos = await getRepos()
|
const repos = await getRepos()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
|
||||||
import NotFoundPage from '@/components/404'
|
import NotFoundPage from '@/components/404'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import mockData from '../../../tests/__fixtures__/giphy.json'
|
import mockData from '../../../tests/__fixtures__/giphy.json'
|
||||||
|
|
||||||
describe('NotFoundPage', () => {
|
describe('NotFoundPage', () => {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { MouseEvent, useEffect, useState } from 'react'
|
import { getRandomGif } from '@/lib/getRandomGif'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { getRandomGif } from '@/lib/getRandomGif'
|
import { type MouseEvent, useEffect, useState } from 'react'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
@ -36,13 +36,9 @@ export default function NotFound() {
|
|||||||
{tag} gifs, entirely your choice.
|
{tag} gifs, entirely your choice.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<video
|
<video className="gif" src={gif} data-testid={gif || null} autoPlay loop>
|
||||||
className="gif"
|
<track kind="captions" srcLang="en" label="English" />
|
||||||
src={gif}
|
</video>
|
||||||
data-testid={gif || null}
|
|
||||||
autoPlay
|
|
||||||
loop
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleClick}>{`Get another '${tag}' gif`}</Button>
|
<Button onClick={handleClick}>{`Get another '${tag}' gif`}</Button>
|
||||||
|
@ -10,6 +10,7 @@ export default function Availability() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={className}>
|
<section className={className}>
|
||||||
|
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: nothing injected on run time */}
|
||||||
<p dangerouslySetInnerHTML={{ __html: html }} />
|
<p dangerouslySetInnerHTML={{ __html: html }} />
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import Farcaster from '@/images/farcaster.svg'
|
||||||
|
import Mastodon from '@/images/mastodon.svg'
|
||||||
// https://lucide.dev
|
// https://lucide.dev
|
||||||
import {
|
import {
|
||||||
ArrowDownCircle,
|
ArrowDownCircle,
|
||||||
@ -16,8 +18,6 @@ import {
|
|||||||
Star,
|
Star,
|
||||||
Sun
|
Sun
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import Farcaster from '@/images/farcaster.svg'
|
|
||||||
import Mastodon from '@/images/mastodon.svg'
|
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = React.SVGAttributes<{
|
type Props = React.SVGAttributes<{
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState, useTransition } from 'react'
|
import { getLocation } from '@/lib/getLocation'
|
||||||
import RelativeTime from '@yaireo/relative-time'
|
import RelativeTime from '@yaireo/relative-time'
|
||||||
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
||||||
import { getLocation } from '@/lib/getLocation'
|
import { useEffect, useState, useTransition } from 'react'
|
||||||
import { fadeIn, getAnimationProps } from '../Transitions'
|
import { fadeIn, getAnimationProps } from '../Transitions'
|
||||||
import { Flag } from './Flag'
|
import { Flag } from './Flag'
|
||||||
import styles from './Location.module.css'
|
import styles from './Location.module.css'
|
||||||
import { UseLocation } from './types'
|
import type { UseLocation } from './types'
|
||||||
|
|
||||||
function Animation({ children }: { children: React.ReactNode }) {
|
function Animation({ children }: { children: React.ReactNode }) {
|
||||||
const shouldReduceMotion = useReducedMotion()
|
const shouldReduceMotion = useReducedMotion()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Link from 'next/link'
|
|
||||||
import Logo from '@/images/logo.svg'
|
import Logo from '@/images/logo.svg'
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
|
import Link from 'next/link'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
|
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
||||||
import { getAnimationProps } from '../Transitions'
|
import { getAnimationProps } from '../Transitions'
|
||||||
import { NetworkLink } from './NetworkLink'
|
import { NetworkLink } from './NetworkLink'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import Button from '@/components/Button'
|
import Button from '@/components/Button'
|
||||||
import Icon from '@/components/Icon'
|
import Icon from '@/components/Icon'
|
||||||
|
import type { ProjectLink } from '@/types'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
export default function ProjectLinks({
|
export default function ProjectLinks({
|
||||||
links
|
links
|
||||||
}: {
|
}: {
|
||||||
links: { title: string; url: string; icon: string }[]
|
links: ProjectLink[]
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.projectLinks}>
|
<div className={styles.projectLinks}>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import type { ImageType, ProjectType } from '@/types'
|
||||||
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
||||||
import type ImageType from '@/types/image'
|
|
||||||
import type ProjectType from '@/types/project'
|
|
||||||
import ProjectImage from '../ProjectImage'
|
import ProjectImage from '../ProjectImage'
|
||||||
import { getAnimationProps, moveInBottom } from '../Transitions'
|
import { getAnimationProps, moveInBottom } from '../Transitions'
|
||||||
import ProjectLinks from './Links'
|
import ProjectLinks from './Links'
|
||||||
@ -41,6 +40,7 @@ export default function Project({
|
|||||||
<m.div
|
<m.div
|
||||||
variants={moveInBottom}
|
variants={moveInBottom}
|
||||||
className={styles.description}
|
className={styles.description}
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
|
||||||
dangerouslySetInnerHTML={{ __html: descriptionHtml ?? '' }}
|
dangerouslySetInnerHTML={{ __html: descriptionHtml ?? '' }}
|
||||||
/>
|
/>
|
||||||
</m.header>
|
</m.header>
|
||||||
@ -51,7 +51,7 @@ export default function Project({
|
|||||||
className={styles.fullContainer}
|
className={styles.fullContainer}
|
||||||
image={image}
|
image={image}
|
||||||
alt={`Showcase image no. ${i + 1} for ${title}`}
|
alt={`Showcase image no. ${i + 1} for ${title}`}
|
||||||
key={i}
|
key={image.src}
|
||||||
sizes="100vw"
|
sizes="100vw"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import type { ImageType } from '@/types'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import project from '@tests/__fixtures__/project.json'
|
||||||
import ProjectImage from '.'
|
import ProjectImage from '.'
|
||||||
import project from '../../../tests/__fixtures__/project.json'
|
|
||||||
|
|
||||||
describe('ProjectImage', () => {
|
describe('ProjectImage', () => {
|
||||||
it('renders correctly', async () => {
|
it('renders correctly', async () => {
|
||||||
@ -17,7 +18,11 @@ describe('ProjectImage', () => {
|
|||||||
|
|
||||||
it('returns without errors without image', async () => {
|
it('returns without errors without image', async () => {
|
||||||
render(
|
render(
|
||||||
<ProjectImage image={null as any} alt={project.title} sizes="100vw" />
|
<ProjectImage
|
||||||
|
image={null as unknown as ImageType}
|
||||||
|
alt={project.title}
|
||||||
|
sizes="100vw"
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import type { ImageType } from '@/types'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import ImageType from '@/types/image'
|
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
export default function ProjectImage({
|
export default function ProjectImage({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ForwardedRef, forwardRef } from 'react'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import ProjectType from '../../types/project'
|
import { type ForwardedRef, forwardRef } from 'react'
|
||||||
|
import type { ProjectType } from '../../types/project'
|
||||||
import ProjectImage from '../ProjectImage'
|
import ProjectImage from '../ProjectImage'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import type { ProjectType } from '@/types'
|
||||||
import { createRef, useEffect } from 'react'
|
import { createRef, useEffect } from 'react'
|
||||||
import ProjectType from '@/types/project'
|
|
||||||
import { Project } from './Project'
|
import { Project } from './Project'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export default function ProjectNav({ projects, currentSlug }: Props) {
|
|||||||
|
|
||||||
const activeItem = currentItem.current
|
const activeItem = currentItem.current
|
||||||
const scrollRect = scrollContainer.current.getBoundingClientRect()
|
const scrollRect = scrollContainer.current.getBoundingClientRect()
|
||||||
const activeRect = activeItem && activeItem.getBoundingClientRect()
|
const activeRect = activeItem?.getBoundingClientRect()
|
||||||
if (!activeItem || !scrollRect || !activeRect) return
|
if (!activeItem || !scrollRect || !activeRect) return
|
||||||
|
|
||||||
const newScrollLeftPosition =
|
const newScrollLeftPosition =
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import type { ImageType } from '@/types'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import ImageType from '../../types/image'
|
|
||||||
import ProjectImage from '../ProjectImage'
|
import ProjectImage from '../ProjectImage'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ProjectType from '@/types/project'
|
import type { ProjectType } from '@/types/project'
|
||||||
import ProjectPreview from '../ProjectPreview'
|
import ProjectPreview from '../ProjectPreview'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import type { Repo } from '@/types/repo'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Repo from '@/types/repo'
|
import repos from '@tests/__fixtures__/repos.json'
|
||||||
import Repositories from '.'
|
import Repositories from '.'
|
||||||
import repos from '../../../tests/__fixtures__/repos.json'
|
|
||||||
|
|
||||||
describe('Repositories', () => {
|
describe('Repositories', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Repo from '@/types/repo'
|
import type { Repo } from '@/types'
|
||||||
import Repository from '../Repository'
|
import Repository from '../Repository'
|
||||||
import styles from './Repositories.module.css'
|
import styles from './Repositories.module.css'
|
||||||
|
|
||||||
@ -7,7 +7,9 @@ export default function Repositories({ repos }: { repos: Repo[] | undefined }) {
|
|||||||
<>
|
<>
|
||||||
<h2 className={styles.sectionTitle}>Open Source Projects</h2>
|
<h2 className={styles.sectionTitle}>Open Source Projects</h2>
|
||||||
<div className={styles.repos}>
|
<div className={styles.repos}>
|
||||||
{repos?.map((repo) => <Repository key={repo.name} repo={repo} />)}
|
{repos?.map((repo) => (
|
||||||
|
<Repository key={repo.name} repo={repo} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import type { Repo } from '@/types/repo'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Repo from '@/types/repo'
|
import repos from '@tests/__fixtures__/repos.json'
|
||||||
import Repository from '.'
|
import Repository from '.'
|
||||||
import repos from '../../../tests/__fixtures__/repos.json'
|
|
||||||
|
|
||||||
describe('Repository', () => {
|
describe('Repository', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Repo from '@/types/repo'
|
import type { Repo } from '@/types'
|
||||||
import Icon from '../Icon'
|
import Icon from '../Icon'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import Icon from '@/components/Icon'
|
||||||
import * as Select from '@radix-ui/react-select'
|
import * as Select from '@radix-ui/react-select'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import Icon from '../Icon'
|
import { useEffect, useState } from 'react'
|
||||||
import { Item } from './Item'
|
import { Item } from './Item'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ export default function ThemeSwitch() {
|
|||||||
<Select.Arrow className={styles.arrow} width={14} height={7} />
|
<Select.Arrow className={styles.arrow} width={14} height={7} />
|
||||||
<Select.Viewport className={styles.viewport}>
|
<Select.Viewport className={styles.viewport}>
|
||||||
{themes
|
{themes
|
||||||
.map((theme) => <Item key={theme} theme={theme}></Item>)
|
.map((theme) => <Item key={theme} theme={theme} />)
|
||||||
.reverse()}
|
.reverse()}
|
||||||
</Select.Viewport>
|
</Select.Viewport>
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import saveAs from 'file-saver'
|
|
||||||
import avatar from '@/images/avatar.jpg'
|
import avatar from '@/images/avatar.jpg'
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
|
import saveAs from 'file-saver'
|
||||||
import { imageToDataUrl } from './imageToDataUrl'
|
import { imageToDataUrl } from './imageToDataUrl'
|
||||||
|
|
||||||
export function constructVcard(dataUrl: string) {
|
export function constructVcard(dataUrl: string) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import fetch, { FetchMock } from 'jest-fetch-mock'
|
import fetch, { type FetchMock } from 'jest-fetch-mock'
|
||||||
import { imageToDataUrl } from './imageToDataUrl'
|
import { imageToDataUrl } from './imageToDataUrl'
|
||||||
|
|
||||||
const dummyPath = 'http://example.com/image.png'
|
const dummyPath = 'http://example.com/image.png'
|
||||||
@ -23,6 +23,7 @@ describe('imageToDataUrl', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should convert image to data URL', async () => {
|
it('should convert image to data URL', async () => {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: not worth it for mocking
|
||||||
function MockFileReader(this: any) {
|
function MockFileReader(this: any) {
|
||||||
this.readAsDataURL = function () {
|
this.readAsDataURL = function () {
|
||||||
this.result = 'data:image/png;base64,...'
|
this.result = 'data:image/png;base64,...'
|
||||||
@ -30,7 +31,7 @@ describe('imageToDataUrl', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.FileReader = MockFileReader as any
|
window.FileReader = MockFileReader as unknown as typeof FileReader
|
||||||
|
|
||||||
const dataUrl = await imageToDataUrl(dummyPath)
|
const dataUrl = await imageToDataUrl(dummyPath)
|
||||||
|
|
||||||
@ -39,12 +40,12 @@ describe('imageToDataUrl', () => {
|
|||||||
|
|
||||||
it('should handle errors in readAsDataURL', async () => {
|
it('should handle errors in readAsDataURL', async () => {
|
||||||
function MockFileReader(this: FileReader) {
|
function MockFileReader(this: FileReader) {
|
||||||
this.readAsDataURL = function () {
|
this.readAsDataURL = () => {
|
||||||
throw new Error('Mock error')
|
throw new Error('Mock error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.FileReader = MockFileReader as any
|
window.FileReader = MockFileReader as unknown as typeof FileReader
|
||||||
|
|
||||||
// Expect imageToDataUrl to reject with the mock error
|
// Expect imageToDataUrl to reject with the mock error
|
||||||
await expect(imageToDataUrl(dummyPath)).rejects.toThrow('Mock error')
|
await expect(imageToDataUrl(dummyPath)).rejects.toThrow('Mock error')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { MouseEvent } from 'react'
|
|
||||||
import meta from '@content/meta.json'
|
import meta from '@content/meta.json'
|
||||||
|
import type { MouseEvent } from 'react'
|
||||||
|
|
||||||
export default function Vcard() {
|
export default function Vcard() {
|
||||||
const handleAddressbookClick = (e: MouseEvent) => {
|
const handleAddressbookClick = (e: MouseEvent) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import { revalidatePath } from 'next/cache'
|
|
||||||
import { GiphyFetch } from '@giphy/js-fetch-api'
|
import { GiphyFetch } from '@giphy/js-fetch-api'
|
||||||
|
import { revalidatePath } from 'next/cache'
|
||||||
|
|
||||||
export async function getRandomGif(tag: string, pathname?: string) {
|
export async function getRandomGif(tag: string, pathname?: string) {
|
||||||
try {
|
try {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import fetch, { FetchMock } from 'jest-fetch-mock'
|
|
||||||
import repoFilter from '@content/repos.json'
|
import repoFilter from '@content/repos.json'
|
||||||
|
import fetch, { type FetchMock } from 'jest-fetch-mock'
|
||||||
import { getRepos } from './getRepos'
|
import { getRepos } from './getRepos'
|
||||||
|
|
||||||
jest.mock('react', () => ({
|
jest.mock('react', () => ({
|
||||||
...jest.requireActual('react'),
|
...jest.requireActual('react'),
|
||||||
cache: (fn: any) => fn
|
cache: (fn: () => void) => fn
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('getRepos', () => {
|
describe('getRepos', () => {
|
||||||
@ -33,8 +32,9 @@ describe('getRepos', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle network errors', async () => {
|
it('should handle network errors', async () => {
|
||||||
let consoleErrorMock: jest.SpyInstance
|
const consoleErrorMock: jest.SpyInstance = jest
|
||||||
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {})
|
.spyOn(console, 'error')
|
||||||
|
.mockImplementation(() => {})
|
||||||
;(fetch as FetchMock).mockRejectOnce(new Error('Network error'))
|
;(fetch as FetchMock).mockRejectOnce(new Error('Network error'))
|
||||||
|
|
||||||
const data = await getRepos()
|
const data = await getRepos()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import { cache } from 'react'
|
import type { Repo } from '@/types'
|
||||||
import type Repo from '@/types/repo'
|
|
||||||
import filter from '@content/repos.json'
|
import filter from '@content/repos.json'
|
||||||
|
import { cache } from 'react'
|
||||||
|
|
||||||
//
|
//
|
||||||
// Get GitHub repos
|
// Get GitHub repos
|
||||||
@ -22,7 +22,7 @@ export const getRepos = cache(async () => {
|
|||||||
try {
|
try {
|
||||||
let repos: Repo[] = []
|
let repos: Repo[] = []
|
||||||
|
|
||||||
for (let item of filter) {
|
for (const item of filter) {
|
||||||
const user = item.split('/')[0]
|
const user = item.split('/')[0]
|
||||||
const repoName = item.split('/')[1]
|
const repoName = item.split('/')[1]
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
declare type ImageType = {
|
export declare type ImageType = {
|
||||||
src: string
|
src: string
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
format?: string
|
format?: string
|
||||||
blurDataURL?: string
|
blurDataURL?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageType
|
|
||||||
|
3
src/types/index.ts
Normal file
3
src/types/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './image'
|
||||||
|
export * from './project'
|
||||||
|
export * from './repo'
|
@ -1,13 +1,17 @@
|
|||||||
import ImageType from './image'
|
import type { ImageType } from './image'
|
||||||
|
|
||||||
declare type ProjectType = {
|
export declare type ProjectLink = {
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
icon?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type ProjectType = {
|
||||||
images: ImageType[]
|
images: ImageType[]
|
||||||
slug: string
|
slug: string
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
descriptionHtml: string
|
descriptionHtml: string
|
||||||
techstack: string[]
|
techstack: string[]
|
||||||
links?: any
|
links?: ProjectLink[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectType
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
declare type Repo = {
|
export declare type Repo = {
|
||||||
name: string
|
name: string
|
||||||
full_name: string
|
full_name: string
|
||||||
description: string
|
description: string
|
||||||
@ -7,5 +7,3 @@ declare type Repo = {
|
|||||||
stargazers_count: number
|
stargazers_count: number
|
||||||
pushed_at: string
|
pushed_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Repo
|
|
||||||
|
12
src/types/umami.d.ts
vendored
12
src/types/umami.d.ts
vendored
@ -1,17 +1,23 @@
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
umami?: (eventName: string) => void | {
|
umami?: (eventName: string) =>
|
||||||
|
| undefined
|
||||||
|
| {
|
||||||
trackEvent: (
|
trackEvent: (
|
||||||
event_name: string,
|
event_name: string,
|
||||||
event_data?: { [key: string]: string },
|
event_data?: { [key: string]: string },
|
||||||
url?: string,
|
url?: string,
|
||||||
website_id?: string
|
website_id?: string
|
||||||
) => void
|
) => void
|
||||||
trackView: (url: string, referrer?: string, website_id?: string) => void
|
trackView: (
|
||||||
|
url: string,
|
||||||
|
referrer?: string,
|
||||||
|
website_id?: string
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.umami = window.umami || {}
|
window.umami = window.umami || {}
|
||||||
|
|
||||||
export {}
|
export type {}
|
||||||
|
1
src/types/yaml.d.ts
vendored
1
src/types/yaml.d.ts
vendored
@ -1,4 +1,5 @@
|
|||||||
declare module '*.yml' {
|
declare module '*.yml' {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: could be any data
|
||||||
const data: any
|
const data: any
|
||||||
export default data
|
export default data
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SVGProps, forwardRef } from 'react'
|
import { type SVGProps, forwardRef } from 'react'
|
||||||
|
|
||||||
const SvgrMock = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
|
const SvgrMock = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
|
||||||
(props, ref) => <svg ref={ref} {...props} />
|
(props, ref) => <svg ref={ref} {...props} />
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"plugins": [{ "name": "next" }],
|
"plugins": [{ "name": "next" }],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
|
"@tests/*": ["./tests/*"],
|
||||||
"@content/*": ["./_content/*"],
|
"@content/*": ["./_content/*"],
|
||||||
"@generated/*": ["./generated/*"]
|
"@generated/*": ["./generated/*"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user