├── .env.example ├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── codegen.mjs ├── docker-compose.prod.yml ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── src ├── @types │ └── query.d.ts ├── app │ ├── about │ │ └── page.tsx │ ├── auth │ │ └── page.tsx │ ├── categories │ │ ├── (index) │ │ │ ├── page-client.tsx │ │ │ └── page.tsx │ │ └── [slug] │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── check-updates │ │ ├── page-client.tsx │ │ └── page.tsx │ ├── collections │ │ └── page.tsx │ ├── domains │ │ ├── page-client.tsx │ │ └── page.tsx │ ├── error.tsx │ ├── favicon.ico │ ├── layout.tsx │ ├── not-found.tsx │ ├── page.tsx │ ├── privacy-policy │ │ └── page.tsx │ ├── projects │ │ ├── (index) │ │ │ ├── page-client.tsx │ │ │ └── page.tsx │ │ ├── [slug] │ │ │ ├── edit │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── create │ │ │ ├── page-client.tsx │ │ │ └── page.tsx │ ├── robots.txt │ └── users │ │ └── [username] │ │ ├── layout.tsx │ │ └── page.tsx ├── assets │ ├── animations │ │ ├── emailSent.json │ │ ├── phone.json │ │ └── rocketLunch.json │ ├── css │ │ └── tailwind.css │ └── svg │ │ ├── errors │ │ ├── 403.svg │ │ ├── 404.svg │ │ └── 500.svg │ │ └── logo.svg ├── components │ ├── CookieBanner.tsx │ ├── ReportModal.tsx │ ├── auth │ │ └── LoginWithGithub.tsx │ ├── categories │ │ └── show │ │ │ └── Breadcrumb.tsx │ ├── domains │ │ ├── Actions.tsx │ │ ├── Create.tsx │ │ └── UnverifiedAlert.tsx │ ├── errors │ │ └── 404.tsx │ ├── footer │ │ └── Footer.tsx │ ├── home │ │ ├── CountDown.tsx │ │ └── TopCategories.tsx │ ├── navbar │ │ ├── Bottom.tsx │ │ ├── Links.tsx │ │ ├── MobileDropdown.tsx │ │ ├── Navbar.tsx │ │ ├── Profile.tsx │ │ └── ThemeSwitcher.tsx │ ├── projects │ │ ├── create │ │ │ └── NewProjectCard.tsx │ │ ├── edit │ │ │ ├── Information.tsx │ │ │ └── Releases.tsx │ │ ├── form │ │ │ ├── Alerts.tsx │ │ │ ├── CategoriesSelect.tsx │ │ │ ├── Delete.tsx │ │ │ ├── DomainsSelect.tsx │ │ │ ├── Form.tsx │ │ │ ├── MaintainersSelect.tsx │ │ │ ├── Publish.tsx │ │ │ └── Submit.tsx │ │ └── show │ │ │ ├── Breadcrumb.tsx │ │ │ ├── Card.tsx │ │ │ ├── Information.tsx │ │ │ ├── LatestRelease.tsx │ │ │ ├── Links.tsx │ │ │ ├── Maintainers.tsx │ │ │ ├── ResourcesModal.tsx │ │ │ ├── Reviews.tsx │ │ │ ├── ReviewsPerStars.tsx │ │ │ ├── Screenshots.tsx │ │ │ └── Stats.tsx │ ├── providers │ │ └── ReactQueryProvider.tsx │ ├── releases │ │ ├── ReleaseFormModal.tsx │ │ └── ReleasesModal.tsx │ ├── reviews │ │ ├── AuthUserReview.tsx │ │ ├── Form.tsx │ │ ├── Options.tsx │ │ └── ReviewsModal.tsx │ ├── updates │ │ └── OpenFlorisBoard.tsx │ └── users │ │ └── show │ │ ├── Collections.tsx │ │ ├── DeleteAccount.tsx │ │ ├── ProfileUpdate.tsx │ │ ├── Projects.tsx │ │ ├── Reviews.tsx │ │ └── Tabs.tsx ├── docs │ ├── about.md │ └── privacy-policy.md ├── fixtures │ ├── config.ts │ ├── footer.ts │ ├── forms │ │ ├── errorMessages.ts │ │ └── validations.ts │ └── navbar.ts ├── generated.ts ├── hooks │ ├── index.ts │ ├── useCanEditProject.ts │ ├── useSearchParams.ts │ └── useStore.ts ├── interfaces │ └── index.ts ├── libs │ ├── api.ts │ ├── axios.ts │ ├── query.ts │ └── yup.ts ├── services │ ├── auth │ │ ├── github.ts │ │ ├── logout.ts │ │ └── sanctum.ts │ ├── categories │ │ ├── index.ts │ │ └── show.ts │ ├── collections │ │ ├── index.ts │ │ └── show.ts │ ├── domains │ │ ├── create.ts │ │ ├── delete.ts │ │ ├── index.ts │ │ └── verify.ts │ ├── home │ │ └── index.ts │ ├── index.ts │ ├── projects │ │ ├── create.ts │ │ ├── delete.ts │ │ ├── edit.ts │ │ ├── image │ │ │ └── create.ts │ │ ├── index.ts │ │ ├── publish.ts │ │ ├── screenshots │ │ │ ├── create.ts │ │ │ └── delete.ts │ │ └── show.ts │ ├── releases │ │ ├── create.ts │ │ ├── download.ts │ │ └── index.ts │ ├── reviews │ │ ├── create.ts │ │ ├── delete.ts │ │ ├── edit.ts │ │ └── index.ts │ ├── updates │ │ └── check.ts │ └── users │ │ ├── delete.ts │ │ ├── edit.ts │ │ ├── index.ts │ │ ├── me.ts │ │ └── show.ts ├── shared │ ├── AuthMiddleware.tsx │ ├── BlurImage.tsx │ ├── CenterSpinner.tsx │ ├── Collapse.tsx │ ├── DynamicIcon.tsx │ ├── EmptyList.tsx │ ├── badges │ │ └── StatusBadge.tsx │ ├── cards │ │ ├── category │ │ │ ├── CategoryCard.tsx │ │ │ └── CategoryCardSkeleton.tsx │ │ ├── project │ │ │ ├── ProjectCard.tsx │ │ │ └── ProjectCardSkeleton.tsx │ │ ├── release │ │ │ ├── ReleaseCard.tsx │ │ │ └── ReleaseCardSkeleton.tsx │ │ └── review │ │ │ ├── ReviewCard.tsx │ │ │ └── ReviewCardSkeleton.tsx │ ├── forms │ │ ├── Button.tsx │ │ ├── ErrorMessage.tsx │ │ ├── FieldWrapper.tsx │ │ ├── FileUpload.tsx │ │ ├── Input.tsx │ │ ├── Label.tsx │ │ ├── LoadMore.tsx │ │ ├── Markdown.tsx │ │ ├── MarkdownInput.tsx │ │ ├── PasswordField.tsx │ │ ├── ReactSelect.tsx │ │ ├── Search.tsx │ │ ├── Select.tsx │ │ └── Textarea.tsx │ ├── home │ │ ├── ProjectHorizontalList.tsx │ │ └── Section.tsx │ ├── layouts │ │ └── Html.tsx │ ├── modals │ │ ├── DialogModal.tsx │ │ └── Modal.tsx │ ├── projects │ │ └── ProjectInfiniteGridList.tsx │ └── releases │ │ └── Download.tsx ├── states │ └── themeState.ts ├── types │ └── index.ts └── utils │ ├── index.ts │ └── updates.ts ├── tailwind.config.ts ├── tsconfig.json └── ui.dockerfile /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_BACKEND_URL=http://localhost -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run ts:check 2 | npm run lint 3 | npm run format && git add -A . 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | package-lock.json 5 | pnpm-lock.yaml 6 | .yarn -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["codegen", "Floris"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlorisBoard Addons Frontend Documentation 2 | 3 | Welcome to the official documentation for FlorisBoard Addons Frontend. This guide provides comprehensive information on setting up and running the Next Js application for the frontend. Follow the steps below to get started with the FlorisBoard Addons Frontend. 4 | 5 | ## Table of Contents 6 | 7 | - [How to Run the Frontend](#how-to-run-the-frontend) 8 | 9 | ## How to Run the Frontend 10 | 11 | Before proceeding, ensure that Node.js (LTS version recommended) and npm (Node Package Manager) is installed on your system. 12 | 13 | 1. **Copy the .env file:** 14 | 15 | ```bash 16 | cp .env.example .env.local 17 | ``` 18 | 19 | 2. **Install Packages** 20 | 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | 3. **Run Development Server** 26 | 27 | ```bash 28 | npm run dev 29 | ``` 30 | -------------------------------------------------------------------------------- /codegen.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { generateApi } from 'swagger-typescript-api'; 3 | 4 | generateApi({ 5 | name: 'generated.ts', 6 | output: path.resolve(process.cwd(), './src'), 7 | url: 'http://localhost/docs/api.json', 8 | toJS: false, 9 | httpClientType: 'axios', 10 | cleanOutput: false, 11 | sortTypes: true, 12 | sortRoutes: true, 13 | extractRequestBody: true, 14 | extractRequestParams: true, 15 | singleHttpClient: true, 16 | }); 17 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | services: 2 | ui: 3 | build: 4 | context: . 5 | dockerfile: ui.dockerfile 6 | restart: unless-stopped 7 | networks: 8 | - addons 9 | ports: 10 | - '3000:3000' 11 | 12 | networks: 13 | addons: 14 | name: addons 15 | external: true 16 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | // compiler: { removeConsole: process.env.NODE_ENV === 'production' }, 4 | async redirects() { 5 | return [ 6 | { source: '/login', destination: '/auth', permanent: false }, 7 | { source: '/register', destination: '/auth', permanent: false }, 8 | ]; 9 | }, 10 | images: { 11 | remotePatterns: [ 12 | { hostname: 'picsum.photos' }, 13 | { hostname: 'localhost' }, 14 | { hostname: 's3.addons.florisboard.org' }, 15 | ], 16 | }, 17 | }; 18 | 19 | module.exports = nextConfig; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "addons-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbo", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "format": "prettier --write .", 11 | "ts:check": "tsc --project tsconfig.json --noEmit", 12 | "codegen": "node codegen.mjs", 13 | "prepare": "husky install" 14 | }, 15 | "dependencies": { 16 | "@lottiefiles/react-lottie-player": "^3.5.4", 17 | "@tanstack/react-query": "^5.51.23", 18 | "@tanstack/react-query-devtools": "^5.51.23", 19 | "axios": "^1.7.3", 20 | "clsx": "^2.1.1", 21 | "date-fns": "^3.6.0", 22 | "filepond": "^4.31.2", 23 | "filepond-plugin-file-poster": "^2.5.1", 24 | "filepond-plugin-file-validate-size": "^2.2.8", 25 | "filepond-plugin-file-validate-type": "^1.2.9", 26 | "filepond-plugin-image-exif-orientation": "^1.0.11", 27 | "filepond-plugin-image-preview": "^4.6.12", 28 | "formik": "^2.4.6", 29 | "install": "^0.13.0", 30 | "isbot": "^5.1.14", 31 | "lodash": "^4.17.21", 32 | "next": "14.2.5", 33 | "nextjs-toploader": "^1.6.12", 34 | "react": "^18", 35 | "react-avatar": "^5.0.3", 36 | "react-dom": "^18", 37 | "react-filepond": "^7.1.2", 38 | "react-icons": "^5.3.0", 39 | "react-markdown": "^9.0.1", 40 | "react-select": "^5.8.0", 41 | "react-toastify": "^10.0.5", 42 | "react-use-cookie": "^1.6.1", 43 | "sharp": "^0.33.4", 44 | "swiper": "^11.1.9", 45 | "tailwind-merge": "^2.5.2", 46 | "use-debounce": "^10.0.2", 47 | "yup": "^1.4.0", 48 | "zustand": "^4.5.4" 49 | }, 50 | "devDependencies": { 51 | "@tailwindcss/typography": "^0.5.14", 52 | "@trivago/prettier-plugin-sort-imports": "^4.3.0", 53 | "@types/lodash": "^4.17.7", 54 | "@types/node": "^22", 55 | "@types/react": "^18", 56 | "@types/react-dom": "^18", 57 | "autoprefixer": "^10.4.20", 58 | "daisyui": "^4.12.10", 59 | "eslint": "^8.0.0", 60 | "eslint-config-next": "14.2.5", 61 | "husky": "^9.1.4", 62 | "postcss": "^8", 63 | "prettier-plugin-tailwindcss": "^0.6.6", 64 | "swagger-typescript-api": "^13.0.21", 65 | "tailwind-scrollbar": "^3.1.0", 66 | "tailwindcss": "^3.4.9", 67 | "typescript": "^5" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | printWidth: 100, 7 | jsxSingleQuote: false, 8 | bracketSpacing: true, 9 | bracketSameLine: false, 10 | arrowParens: 'always', 11 | tailwindFunctions: ['cn'], 12 | importOrder: [ 13 | '^react', 14 | '^next', 15 | '', 16 | '^@heroicons', 17 | '^@tanstack', 18 | '^@(.*)$', 19 | '^[./]', 20 | ], 21 | importOrderSeparation: false, 22 | importOrderSortSpecifiers: true, 23 | plugins: ['@trivago/prettier-plugin-sort-imports', 'prettier-plugin-tailwindcss'], 24 | }; 25 | -------------------------------------------------------------------------------- /src/@types/query.d.ts: -------------------------------------------------------------------------------- 1 | import '@tanstack/react-query'; 2 | import { TReactQueryErrorMeta, TReactQuerySuccessMeta } from '@/types'; 3 | 4 | interface MyMeta extends Record { 5 | success?: TReactQuerySuccessMeta; 6 | error?: TReactQueryErrorMeta; 7 | } 8 | 9 | declare module '@tanstack/react-query' { 10 | interface Register { 11 | queryMeta: MyMeta; 12 | mutationMeta: MyMeta; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Metadata } from 'next'; 3 | import { promises as fs } from 'fs'; 4 | import Markdown from '@/shared/forms/Markdown'; 5 | 6 | export const metadata: Metadata = { 7 | title: 'About', 8 | description: 'About', 9 | }; 10 | 11 | export default async function PrivacyPolicy() { 12 | const markdown = await fs.readFile(process.cwd() + '/src/docs/about.md', 'utf8'); 13 | 14 | return ( 15 |
16 | {markdown} 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/auth/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Metadata } from 'next'; 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | import logo from '@/assets/svg/logo.svg'; 6 | import LoginWithGithub from '@/components/auth/LoginWithGithub'; 7 | import AuthMiddleware from '@/shared/AuthMiddleware'; 8 | 9 | export const metadata: Metadata = { 10 | title: 'Authentication', 11 | description: 12 | 'Authenticate to unlock project creation, review capabilities, and more with FlorisBoard Addons.', 13 | }; 14 | 15 | export default function Auth() { 16 | return ( 17 | 18 |
19 |
20 |
21 | FlorisBoard Logo 22 |

Authentication

23 |

24 | Please proceed to authenticate for your FlorisBoard account using one of the providers 25 | listed below. By doing so, you acknowledge and agree to our{' '} 26 | 27 | Privacy and Policy 28 | 29 | . 30 |

31 | 32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/app/categories/(index)/page-client.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { Fragment } from 'react'; 4 | import config from '@/fixtures/config'; 5 | import { useSearchParams } from '@/hooks'; 6 | import useCategories from '@/services/categories'; 7 | import CategoryCard from '@/shared/cards/category/CategoryCard'; 8 | import CategoryCardSkeleton from '@/shared/cards/category/CategoryCardSkeleton'; 9 | import LoadMore from '@/shared/forms/LoadMore'; 10 | 11 | export default function CategoriesList() { 12 | const [searchParams] = useSearchParams(); 13 | const query = searchParams.get(config.searchKey) ?? ''; 14 | const { 15 | data: categories, 16 | isLoading, 17 | hasNextPage, 18 | isFetchingNextPage, 19 | fetchNextPage, 20 | } = useCategories({ filter: { title: query } }); 21 | 22 | return ( 23 |
24 | {categories?.pages.map((page) => ( 25 | 26 | {page.data.map((category) => ( 27 | 28 | ))} 29 | 30 | ))} 31 | {isLoading && Array.from({ length: 16 }).map((_, i) => )} 32 | {hasNextPage && } 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/app/categories/(index)/page.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { Metadata } from 'next'; 3 | import Search from '@/shared/forms/Search'; 4 | import CategoriesList from './page-client'; 5 | 6 | export const metadata: Metadata = { 7 | title: 'Categories', 8 | description: 'Categories', 9 | }; 10 | 11 | export default function Categories() { 12 | return ( 13 |
14 |
15 |

Categories

16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/categories/[slug]/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Metadata } from 'next'; 3 | import { notFound } from 'next/navigation'; 4 | import { getCategoryServerCache } from '@/services/categories/show'; 5 | import { THasChildren } from '@/types'; 6 | import { extractIdFromSlug } from '@/utils'; 7 | 8 | type Props = { 9 | params: { slug: string }; 10 | }; 11 | 12 | export async function generateMetadata({ params }: Props): Promise { 13 | const id = extractIdFromSlug(params.slug); 14 | if (!id) notFound(); 15 | const category = await getCategoryServerCache(id); 16 | 17 | return { 18 | title: `${category?.title} Projects`, 19 | description: `Find useful projects from category ${category?.title} for your FlorisBoard Keyboard`, 20 | }; 21 | } 22 | 23 | export default function Layout({ children }: THasChildren) { 24 | return children; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/categories/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useEffect } from 'react'; 4 | import { useParams } from 'next/navigation'; 5 | import Breadcrumb from '@/components/categories/show/Breadcrumb'; 6 | import config from '@/fixtures/config'; 7 | import { useSearchParams } from '@/hooks'; 8 | import useCategory from '@/services/categories/show'; 9 | import useProjects from '@/services/projects'; 10 | import Search from '@/shared/forms/Search'; 11 | import ProjectInfiniteGridList from '@/shared/projects/ProjectInfiniteGridList'; 12 | import { extractIdFromSlug } from '@/utils'; 13 | 14 | export default function Category() { 15 | const { slug } = useParams<{ slug: string }>(); 16 | const { data: category } = useCategory(extractIdFromSlug(slug)!); 17 | const [searchParams] = useSearchParams(); 18 | const query = searchParams.get(config.searchKey) ?? ''; 19 | const queryResult = useProjects({ filter: { category_id: category.id, title: query } }); 20 | 21 | return ( 22 |
23 | 24 |
25 |

26 | {category.title} Projects 27 |

28 | 29 |
30 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/app/check-updates/page-client.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import OpenFlorisBoard from '@/components/updates/OpenFlorisBoard'; 5 | import useCheckUpdates from '@/services/updates/check'; 6 | import CenterSpinner from '@/shared/CenterSpinner'; 7 | import Download from '@/shared/releases/Download'; 8 | import { hasNewVersion } from '@/utils/updates'; 9 | 10 | function getProjectsFromHash(): { [packageName: string]: string } { 11 | try { 12 | const hash = window.location.hash; 13 | if (!hash || !hash.startsWith('#data=')) return {}; 14 | return JSON.parse(decodeURIComponent(hash.substring(6))); 15 | } catch (error) { 16 | return {}; 17 | } 18 | } 19 | 20 | const tableHeads = [ 21 | '#', 22 | 'Package Name', 23 | 'Title', 24 | 'Current Version', 25 | 'Available Version', 26 | 'Actions', 27 | ]; 28 | 29 | export default function CheckUpdatesClient() { 30 | const parsedProjects = Object.entries(getProjectsFromHash()).map(([packageName, version]) => ({ 31 | package_name: packageName, 32 | version_name: version, 33 | })); 34 | 35 | const { data, isLoading } = useCheckUpdates({ projects: parsedProjects }); 36 | if (isLoading) return ; 37 | if ((data?.data.length ?? 0) < 1) return ; 38 | 39 | return ( 40 |
41 | 42 | 43 | 44 | {tableHeads.map((text) => ( 45 | 46 | ))} 47 | 48 | 49 | 50 | {data?.data.map((value, i) => { 51 | const parsedProject = parsedProjects.find( 52 | (project) => project.package_name === value.project.package_name, 53 | ); 54 | 55 | return ( 56 | 57 | 58 | 59 | 60 | 61 | 62 | 77 | 78 | ); 79 | })} 80 | 81 |
{text}
{i + 1}{value.project.package_name}{value.project.title}{parsedProject?.version_name}{value.latest_release?.version_name ?? '-'} 63 | {hasNewVersion( 64 | parsedProject?.version_name, 65 | value.latest_release?.version_name, 66 | ) && ( 67 | 75 | )} 76 |
82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/app/check-updates/page.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { Metadata } from 'next'; 3 | import CheckUpdatesClient from './page-client'; 4 | 5 | export const metadata: Metadata = { 6 | title: 'Check Updates', 7 | description: "Check if the projects that you're using on FlorisBoard is up to date.", 8 | }; 9 | 10 | export default function CheckUpdates() { 11 | return ( 12 |
13 |

Check Updates

14 | 15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/collections/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HiInformationCircle } from 'react-icons/hi2'; 3 | import { Metadata } from 'next'; 4 | 5 | export const metadata: Metadata = { 6 | title: 'Collections', 7 | description: 'Collections', 8 | }; 9 | 10 | export default function Collections() { 11 | return ( 12 |
13 |

Collections

14 |
15 | 16 | This feature is not ready yet. 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/domains/page-client.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import { formatDistanceToNow } from 'date-fns'; 5 | import Actions from '@/components/domains/Actions'; 6 | import useDomains from '@/services/domains'; 7 | 8 | export default function DomainsList() { 9 | const { data } = useDomains(); 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {data.data.map((domain, i) => { 24 | return ( 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | ); 36 | })} 37 | 38 |
#DomainVerification TextVerified At
{i + 1}{domain.name}{domain.verification_text} 30 | {domain.verified_at && 31 | formatDistanceToNow(new Date(domain.verified_at), { addSuffix: true })} 32 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/app/domains/page.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { Metadata } from 'next'; 3 | import Create from '@/components/domains/Create'; 4 | import UnverifiedAlert from '@/components/domains/UnverifiedAlert'; 5 | import AuthMiddleware from '@/shared/AuthMiddleware'; 6 | import CenterSpinner from '@/shared/CenterSpinner'; 7 | import Collapse from '@/shared/Collapse'; 8 | import DomainsList from './page-client'; 9 | 10 | export const metadata: Metadata = { 11 | title: 'Domains', 12 | description: 'Domains', 13 | }; 14 | 15 | export default function Domains() { 16 | return ( 17 | 18 |
19 |
20 |

Domains

21 | 22 |
23 | 24 | }> 25 | 26 | 27 |
28 | 29 |
    30 |
  1. Copy the verification code of the domain you want to verify.
  2. 31 |
  3. Go to your domain name provider management and go to DNS record management.
  4. 32 |
  5. Create a new TXT record with host @ and paste the verification code there.
  6. 33 |
  7. Wait couple of seconds (or sometimes minutes) and click verify domain.
  8. 34 |
35 |
36 |
37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/app/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import Error404 from '@/components/errors/404'; 5 | import { isAxiosError } from '@/utils'; 6 | 7 | type ErrorBoundaryProps = { 8 | error: Error & { digest?: string }; 9 | reset: () => void; 10 | }; 11 | 12 | export default function ErrorBoundary({ error, reset }: ErrorBoundaryProps) { 13 | if (isAxiosError(error, 404)) return ; 14 | return
Error
; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florisboard/addons-frontend/37aa050c1f4a45bbdf8f50499178873cd4314386/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import NextTopLoader from 'nextjs-toploader'; 3 | import 'swiper/css'; 4 | import 'swiper/css/a11y'; 5 | import 'swiper/css/navigation'; 6 | import Footer from '@/components/footer/Footer'; 7 | import Bottom from '@/components/navbar/Bottom'; 8 | import Navbar from '@/components/navbar/Navbar'; 9 | import ReactQueryProvider from '@/components/providers/ReactQueryProvider'; 10 | import Html from '@/shared/layouts/Html'; 11 | import { THasChildren } from '@/types'; 12 | 13 | export const metadata: Metadata = { 14 | title: { 15 | template: `%s - FlorisBoard Addons`, 16 | default: 'FlorisBoard Addons', 17 | }, 18 | description: 'FlorisBoard addons', 19 | }; 20 | 21 | type RootLayoutProps = THasChildren; 22 | 23 | export default function RootLayout({ children }: RootLayoutProps) { 24 | return ( 25 | 26 | 27 | 28 | 29 |
{children}
30 | 31 |