diff --git a/app/[lang]/download/page.tsx b/app/[lang]/download/page.tsx index 7454363..9b9a309 100644 --- a/app/[lang]/download/page.tsx +++ b/app/[lang]/download/page.tsx @@ -117,15 +117,25 @@ export default async function DownloadPage({params: {lang}, searchParams}: Downl return options; } + const platformSelect = ( + : null} + platforms={getPlatformOptions()} + /> + ) + + const noRelease = ( + <> +

{dict.downloadPage.noReleaseFound}

+ + ); + return (
- : null} - platforms={getPlatformOptions()} - /> + {!release ? noRelease : platformSelect}
) } \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index fb8d437..f94c30c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import React from "react"; import {Inter as FontSans} from "next/font/google" import {cn} from "@/lib/utils"; import {headers} from "next/headers"; +import {getDictionary} from "@/i18n/dictionaries"; export const runtime = 'edge'; @@ -12,8 +13,11 @@ type RootLayoutProps = { } export async function generateMetadata(): Promise { + const dict = await getDictionary("en"); return { metadataBase: new URL(`https://${headers().get("host")}`), + title: dict.common.title, + description: dict.common.description, }; } diff --git a/lib/gh-utils.ts b/lib/gh-utils.ts index a8dd0cd..752405b 100644 --- a/lib/gh-utils.ts +++ b/lib/gh-utils.ts @@ -28,26 +28,26 @@ export type Release = { } const getReadableName = (name: string): string => { - if(name.includes('aarch64')) { + if (name.includes('aarch64')) { return 'Linux ARM'; } - if(name.includes('x86_64') && name.includes('linux')) { + if (name.includes('x86_64') && name.includes('linux')) { return 'Linux x86/x64'; } if (name.includes('stub') && name.includes('exe')) { return 'Windows 64bit Online Installer'; } - if (name.includes('exe') && name.includes('win32')){ + if (name.includes('exe') && name.includes('win32')) { return 'Windows 32-bit'; } - if (name.includes('exe') && name.includes('win64')){ + if (name.includes('exe') && name.includes('win64')) { return 'Windows 64-bit'; } - if (name.includes('portable') && name.includes('zip') && name.includes('win')){ + if (name.includes('portable') && name.includes('zip') && name.includes('win')) { return 'Windows Portable'; } if (name.includes('dmg')) { - return 'MacOS'; + return 'macOS'; } return name; @@ -75,61 +75,86 @@ const getPlatformTypByAssetName = (name: string): Platform => { return Platform.Linux; } -export async function getRelease(): Promise { - const octokit = new Octokit(); - const response = await octokit.rest.repos.getLatestRelease({ - owner: 'Floorp-Projects', - repo: 'Floorp', - }); +const accessToken = process.env.GITHUB_TOKEN; +const options = accessToken ? {auth: accessToken} : {}; - if (!response.data.assets || response.data.published_at === null) { - console.error('Failed to fetch release data'); - return null; - } - const date = new Date(response.data.published_at); +const hasQuota = async (octokit: Octokit): Promise => { + const rateLimitResponse = await octokit.rest.rateLimit.get(); - let hashes = ''; - const assets: Record = {} - for (let i = 0; i < response.data.assets.length; i++) { - const asset = response.data.assets[i]; + if (rateLimitResponse.data.resources.core.remaining === 0) { + console.log('Request quota exhausted before making request'); + return false; + } - if (asset.name === "hashes.txt") { - hashes = asset.browser_download_url; - } + return true; +} - if (asset.name.includes('.mar') || asset.name.includes('.txt')) { - continue; +export async function getRelease(): Promise { + const octokit = new Octokit(options); + if (!await hasQuota(octokit)) { + return null; + } + try { + const response = await octokit.rest.repos.getLatestRelease({ + owner: 'Floorp-Projects', + repo: 'Floorp', + }); + + if (!response?.data.assets || response?.data.published_at === null) { + console.error('Failed to fetch release data'); + return null; } - const platform = getPlatformTypByAssetName(asset.name); - if (!assets[platform]) { - assets[platform] = []; + const date = new Date(response.data.published_at); + + let hashes = ''; + const assets: Record = {} + for (let i = 0; i < response.data.assets.length; i++) { + const asset = response.data.assets[i]; + + if (asset.name === "hashes.txt") { + hashes = asset.browser_download_url; + } + + if (asset.name.includes('.mar') || asset.name.includes('.txt')) { + continue; + } + const platform = getPlatformTypByAssetName(asset.name); + if (!assets[platform]) { + assets[platform] = []; + } + assets[platform].push(getAssetInfo(asset)); } - assets[platform].push(getAssetInfo(asset)); - } - const portableResponse = await octokit.rest.repos.getLatestRelease({ - owner: 'Floorp-Projects', - repo: 'Floorp-Portable', - }); - let targetAsset = portableResponse.data.assets.find((asset) => asset.name.includes('windows')); - if (targetAsset) { - if (!assets[Platform.Windows]) { - assets[Platform.Windows] = []; + const portableResponse = await octokit.rest.repos.getLatestRelease({ + owner: 'Floorp-Projects', + repo: 'Floorp-Portable', + }); + let targetAsset = portableResponse.data.assets.find((asset) => asset.name.includes('windows')); + if (targetAsset) { + if (!assets[Platform.Windows]) { + assets[Platform.Windows] = []; + } + assets[Platform.Windows].push(getAssetInfo(targetAsset)); } - assets[Platform.Windows].push(getAssetInfo(targetAsset)); - } - return { - version: response.data.tag_name, - name: response.data.name || response.data.tag_name, - publishedAt: date, - downloads: assets, - hashes + return { + version: response.data.tag_name, + name: response.data.name || response.data.tag_name, + publishedAt: date, + downloads: assets, + hashes + } + } catch (e) { + console.error('Failed to fetch release data', e); + return null; } } export async function getTags(): Promise { - const octokit = new Octokit(); + const octokit = new Octokit(options); + if (!await hasQuota(octokit)) { + return []; + } const response = await octokit.rest.repos.listTags({ owner: 'Floorp-Projects', repo: 'Floorp', diff --git a/lib/utils.ts b/lib/utils.ts index e77a894..73d7c88 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -24,17 +24,17 @@ export const getComponent = (node: React.ReactNode, returnIfProduction: boolean export const platformOptions = { "windows": "Windows", "linux": "Linux", - "macos": "MacOS", + "macos": "macOS", "android": "Android", - "ios": "IOS", + "ios": "iOS", } as Record; export enum Platform { Linux = 'Linux', Windows = 'Windows', - MacOS = 'MacOS', + MacOS = 'macOS', Android = 'Android', - IOS = 'IOS', + IOS = 'iOS', } export const convertOptionToPlatform = (option: string): Platform => { diff --git a/package-lock.json b/package-lock.json index 7f646f8..0eb8d7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@formatjs/intl-localematcher": "^0.5.4", + "@octokit/rest": "^20.1.1", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", @@ -2414,6 +2415,146 @@ "node": ">= 18" } }, + "node_modules/@octokit/rest": { + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.1.tgz", + "integrity": "sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==", + "dependencies": { + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.3.1", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.2.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", + "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/graphql": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", + "dependencies": { + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", + "dependencies": { + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest/node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/@octokit/rest/node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + }, "node_modules/@octokit/types": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", @@ -4452,6 +4593,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -6826,7 +6972,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -8677,7 +8822,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/yaml": { diff --git a/package.json b/package.json index 69bdd87..5f2e93e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@formatjs/intl-localematcher": "^0.5.4", + "@octokit/rest": "^20.1.1", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6",