Refine admin app insights data
This commit is contained in:
parent
556fa62b08
commit
89c985497c
@ -1,11 +1,11 @@
|
|||||||
import { getGitHubMetaWithFallback, getGitHubPublicFork } from '@/admin/github';
|
import { getGitHubMetaWithFallback, getGitHubPublicFork } from '@/admin/github';
|
||||||
import { TEMPLATE_BASE_OWNER, TEMPLATE_BASE_REPO } from '@/app-core/config';
|
import { TEMPLATE_REPO_OWNER, TEMPLATE_REPO_NAME } from '@/app-core/config';
|
||||||
|
|
||||||
describe('GitHub', () => {
|
describe('GitHub', () => {
|
||||||
it('fetches base repo meta', async () => {
|
it('fetches base repo meta', async () => {
|
||||||
const meta = await getGitHubMetaWithFallback({
|
const meta = await getGitHubMetaWithFallback({
|
||||||
owner: TEMPLATE_BASE_OWNER,
|
owner: TEMPLATE_REPO_OWNER,
|
||||||
repo: TEMPLATE_BASE_REPO,
|
repo: TEMPLATE_REPO_NAME,
|
||||||
});
|
});
|
||||||
expect(meta).toBeDefined();
|
expect(meta).toBeDefined();
|
||||||
expect(meta.url).toBeDefined();
|
expect(meta.url).toBeDefined();
|
||||||
|
|||||||
@ -6,17 +6,32 @@ import {
|
|||||||
getUniqueTags,
|
getUniqueTags,
|
||||||
} from '@/photo/db/query';
|
} from '@/photo/db/query';
|
||||||
import AdminAppInsightsClient from './AdminAppInsightsClient';
|
import AdminAppInsightsClient from './AdminAppInsightsClient';
|
||||||
import { APP_CONFIGURATION } from '@/app-core/config';
|
import {
|
||||||
|
APP_CONFIGURATION,
|
||||||
|
IS_VERCEL_GIT_PROVIDER_GITHUB,
|
||||||
|
VERCEL_GIT_BRANCH,
|
||||||
|
VERCEL_GIT_COMMIT_SHA,
|
||||||
|
VERCEL_GIT_REPO_OWNER,
|
||||||
|
VERCEL_GIT_REPO_SLUG,
|
||||||
|
} from '@/app-core/config';
|
||||||
|
import { getGitHubMetaWithFallback } from './github';
|
||||||
|
|
||||||
|
const owner = VERCEL_GIT_REPO_OWNER;
|
||||||
|
const repo = VERCEL_GIT_REPO_SLUG;
|
||||||
|
const branch = VERCEL_GIT_BRANCH;
|
||||||
|
const commit = VERCEL_GIT_COMMIT_SHA;
|
||||||
|
|
||||||
export default async function AdminAppInsights() {
|
export default async function AdminAppInsights() {
|
||||||
const [
|
const [
|
||||||
{ count, dateRange },
|
{ count, dateRange },
|
||||||
|
{ count: countHidden },
|
||||||
tags,
|
tags,
|
||||||
cameras,
|
cameras,
|
||||||
filmSimulations,
|
filmSimulations,
|
||||||
lenses,
|
lenses,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getPhotosMeta({ hidden: 'include' }),
|
getPhotosMeta({ hidden: 'include' }),
|
||||||
|
getPhotosMeta({ hidden: 'only' }),
|
||||||
getUniqueTags(),
|
getUniqueTags(),
|
||||||
getUniqueCameras(),
|
getUniqueCameras(),
|
||||||
getUniqueFilmSimulations(),
|
getUniqueFilmSimulations(),
|
||||||
@ -28,16 +43,29 @@ export default async function AdminAppInsights() {
|
|||||||
hasVercelBlobStorage,
|
hasVercelBlobStorage,
|
||||||
} = APP_CONFIGURATION;
|
} = APP_CONFIGURATION;
|
||||||
|
|
||||||
|
const codeMeta = IS_VERCEL_GIT_PROVIDER_GITHUB
|
||||||
|
? await getGitHubMetaWithFallback({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
branch,
|
||||||
|
commit,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminAppInsightsClient
|
<AdminAppInsightsClient
|
||||||
|
codeMeta={codeMeta}
|
||||||
recommendations={{
|
recommendations={{
|
||||||
fork: true,
|
fork: true,
|
||||||
forkBehind: true,
|
forkBehind: true,
|
||||||
ai: isAiTextGenerationEnabled,
|
ai: isAiTextGenerationEnabled,
|
||||||
aiRateLimiting: isAiTextGenerationEnabled && !hasVercelBlobStorage,
|
aiRateLimiting: isAiTextGenerationEnabled && !hasVercelBlobStorage,
|
||||||
|
photoMatting: true,
|
||||||
|
gridFirst: true,
|
||||||
}}
|
}}
|
||||||
photoStats={{
|
photoStats={{
|
||||||
photosCount: count,
|
photosCount: count,
|
||||||
|
photosCountHidden: countHidden,
|
||||||
tagsCount: tags.length,
|
tagsCount: tags.length,
|
||||||
camerasCount: cameras.length,
|
camerasCount: cameras.length,
|
||||||
filmSimulationsCount: filmSimulations.length,
|
filmSimulationsCount: filmSimulations.length,
|
||||||
|
|||||||
@ -1,31 +1,47 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import IconGrSync from '@/app-core/IconGrSync';
|
import IconGrSync from '@/app-core/IconGrSync';
|
||||||
import Note from '@/components/Note';
|
|
||||||
import ScoreCard from '@/components/ScoreCard';
|
import ScoreCard from '@/components/ScoreCard';
|
||||||
import ScoreCardRow from '@/components/ScoreCardRow';
|
import ScoreCardRow from '@/components/ScoreCardRow';
|
||||||
import WarningNote from '@/components/WarningNote';
|
|
||||||
import { dateRangeForPhotos, PhotoDateRange } from '@/photo';
|
import { dateRangeForPhotos, PhotoDateRange } from '@/photo';
|
||||||
|
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
|
||||||
import clsx from 'clsx/lite';
|
import clsx from 'clsx/lite';
|
||||||
import { HiSparkles } from 'react-icons/hi';
|
import { FaCamera } from 'react-icons/fa';
|
||||||
|
import { FaTag } from 'react-icons/fa';
|
||||||
|
import { FaRegCalendar } from 'react-icons/fa6';
|
||||||
|
import {
|
||||||
|
HiOutlinePhotograph,
|
||||||
|
HiOutlineRefresh,
|
||||||
|
HiSparkles,
|
||||||
|
} from 'react-icons/hi';
|
||||||
import { MdLightbulbOutline } from 'react-icons/md';
|
import { MdLightbulbOutline } from 'react-icons/md';
|
||||||
import { PiWarningBold } from 'react-icons/pi';
|
import { PiWarningBold } from 'react-icons/pi';
|
||||||
|
import { TbCone } from 'react-icons/tb';
|
||||||
|
import { getGitHubMetaWithFallback } from './github';
|
||||||
|
import { BiGitBranch, BiGitCommit, BiLogoGithub } from 'react-icons/bi';
|
||||||
|
import {
|
||||||
|
TEMPLATE_REPO_BRANCH,
|
||||||
|
TEMPLATE_REPO_OWNER,
|
||||||
|
TEMPLATE_REPO_NAME,
|
||||||
|
VERCEL_GIT_COMMIT_SHA_SHORT,
|
||||||
|
} from '@/app-core/config';
|
||||||
|
|
||||||
|
const DEBUG_COMMIT_SHA = '4cd29ed';
|
||||||
|
const DEBUG_COMMIT_MESSAGE = 'Long commit message for debugging purposes';
|
||||||
|
|
||||||
type Recommendation =
|
type Recommendation =
|
||||||
'fork' |
|
'fork' |
|
||||||
'forkBehind' |
|
'forkBehind' |
|
||||||
'ai' |
|
'ai' |
|
||||||
'aiRateLimiting';
|
'aiRateLimiting' |
|
||||||
|
'photoMatting' |
|
||||||
|
'gridFirst';
|
||||||
|
|
||||||
export default function AdminAppInsightsClient({
|
export default function AdminAppInsightsClient({
|
||||||
recommendations: {
|
codeMeta,
|
||||||
fork,
|
|
||||||
forkBehind,
|
|
||||||
ai,
|
|
||||||
aiRateLimiting,
|
|
||||||
},
|
|
||||||
photoStats: {
|
photoStats: {
|
||||||
photosCount,
|
photosCount,
|
||||||
|
photosCountHidden,
|
||||||
tagsCount,
|
tagsCount,
|
||||||
camerasCount,
|
camerasCount,
|
||||||
filmSimulationsCount,
|
filmSimulationsCount,
|
||||||
@ -34,23 +50,25 @@ export default function AdminAppInsightsClient({
|
|||||||
},
|
},
|
||||||
debug,
|
debug,
|
||||||
}: {
|
}: {
|
||||||
recommendations: Record<Recommendation, boolean>,
|
codeMeta?: Awaited<ReturnType<typeof getGitHubMetaWithFallback>>
|
||||||
|
recommendations: Record<Recommendation, boolean>
|
||||||
photoStats: {
|
photoStats: {
|
||||||
photosCount: number
|
photosCount: number
|
||||||
|
photosCountHidden: number
|
||||||
tagsCount: number
|
tagsCount: number
|
||||||
camerasCount: number
|
camerasCount: number
|
||||||
filmSimulationsCount: number
|
filmSimulationsCount: number
|
||||||
lensesCount: number
|
lensesCount: number
|
||||||
dateRange?: PhotoDateRange
|
dateRange?: PhotoDateRange
|
||||||
},
|
},
|
||||||
debug?: boolean,
|
debug?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const { descriptionWithSpaces } = dateRangeForPhotos(undefined, dateRange);
|
const { descriptionWithSpaces } = dateRangeForPhotos(undefined, dateRange);
|
||||||
|
|
||||||
const renderTitle = (title: string) =>
|
const renderTitle = (title: string) =>
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'text-center uppercase font-bold tracking-wide',
|
'uppercase font-medium tracking-wider text-[0.8rem]',
|
||||||
'text-medium',
|
'text-medium',
|
||||||
)}>
|
)}>
|
||||||
{title}
|
{title}
|
||||||
@ -64,19 +82,53 @@ export default function AdminAppInsightsClient({
|
|||||||
'w-full sm:w-[80%] lg:w-[60%]',
|
'w-full sm:w-[80%] lg:w-[60%]',
|
||||||
'space-y-4 md:space-y-6',
|
'space-y-4 md:space-y-6',
|
||||||
)}>
|
)}>
|
||||||
|
{(codeMeta?.isBaseRepo || codeMeta?.isForkedFromBase || debug) && <>
|
||||||
|
{renderTitle('Build details')}
|
||||||
<ScoreCard>
|
<ScoreCard>
|
||||||
<ScoreCardRow
|
<ScoreCardRow
|
||||||
icon={
|
icon={<BiLogoGithub size={17} />}
|
||||||
<PiWarningBold
|
content={<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div>{codeMeta?.owner ?? TEMPLATE_REPO_OWNER}</div>
|
||||||
|
<div>/</div>
|
||||||
|
<div>{codeMeta?.repo ?? TEMPLATE_REPO_NAME}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 min-w-0">
|
||||||
|
<div><BiGitBranch size={17} /></div>
|
||||||
|
<div className="truncate">
|
||||||
|
{codeMeta?.branch ?? TEMPLATE_REPO_BRANCH}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
{(codeMeta?.behindBy || debug) &&
|
||||||
|
<ScoreCardRow
|
||||||
|
icon={<HiOutlineRefresh
|
||||||
size={17}
|
size={17}
|
||||||
className="translate-x-[0.5px] text-amber-600"
|
className="translate-x-[0.5px] text-amber-600"
|
||||||
/>
|
/>}
|
||||||
}
|
// eslint-disable-next-line max-len
|
||||||
content="This fork is 9 commits behind"
|
content={`This fork is ${codeMeta?.behindBy ?? 9} commits behind`}
|
||||||
additionalContent={<>
|
additionalContent={<>
|
||||||
Sync your fork to receive new features and fixes
|
Sync your fork to receive new features and fixes
|
||||||
</>}
|
</>}
|
||||||
|
/>}
|
||||||
|
<ScoreCardRow
|
||||||
|
// icon={<BiLogoGithub size={17} />}
|
||||||
|
icon={<BiGitCommit size={18} className="translate-y-[0px]" />}
|
||||||
|
content={<div className="flex items-center gap-2">
|
||||||
|
<div className="text-medium">
|
||||||
|
{VERCEL_GIT_COMMIT_SHA_SHORT ?? DEBUG_COMMIT_SHA}
|
||||||
|
</div>
|
||||||
|
<div className="truncate">
|
||||||
|
{codeMeta?.commit ?? DEBUG_COMMIT_MESSAGE}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
/>
|
/>
|
||||||
|
</ScoreCard>
|
||||||
|
</>}
|
||||||
|
{renderTitle('Template recommendations')}
|
||||||
|
<ScoreCard>
|
||||||
<ScoreCardRow
|
<ScoreCardRow
|
||||||
icon={<PiWarningBold
|
icon={<PiWarningBold
|
||||||
size={17}
|
size={17}
|
||||||
@ -105,42 +157,55 @@ export default function AdminAppInsightsClient({
|
|||||||
content="Enable AI text generation in the app configuration"
|
content="Enable AI text generation in the app configuration"
|
||||||
/>
|
/>
|
||||||
</ScoreCard>
|
</ScoreCard>
|
||||||
{renderTitle('Code Observability')}
|
|
||||||
{(fork || debug) &&
|
|
||||||
<Note icon={<IconGrSync />}>
|
|
||||||
Consider forking this repository to receive new features and fixes
|
|
||||||
</Note>}
|
|
||||||
{(forkBehind || debug) &&
|
|
||||||
<WarningNote>
|
|
||||||
This fork is 9 commits behind
|
|
||||||
</WarningNote>}
|
|
||||||
{renderTitle('Template Recommendations')}
|
|
||||||
{(ai || debug) && <Note icon={<HiSparkles />}>
|
|
||||||
Enable AI text generation in the app configuration
|
|
||||||
</Note>}
|
|
||||||
{(aiRateLimiting || debug) && <WarningNote>
|
|
||||||
Consider enabling rate limiting to mitigate AI abuse
|
|
||||||
</WarningNote>}
|
|
||||||
{renderTitle('Library Stats')}
|
{renderTitle('Library Stats')}
|
||||||
<div className={clsx(
|
<ScoreCard className="uppercase">
|
||||||
'grid grid-cols-2 gap-3 w-full',
|
<ScoreCardRow
|
||||||
'border border-main rounded-md p-6 bg-main shadow-xs',
|
icon={<HiOutlinePhotograph
|
||||||
'uppercase',
|
size={17}
|
||||||
)}>
|
className="translate-y-[0.5px]"
|
||||||
<div className="tracking-wide">Photos</div>
|
/>}
|
||||||
<div className="eright">{photosCount}</div>
|
content={<>
|
||||||
<div className="tracking-wide">Tags</div>
|
{photosCount} photos
|
||||||
<div className="text-right">{tagsCount}</div>
|
{photosCountHidden > 0 &&
|
||||||
<div className="tracking-wide">Cameras</div>
|
` (${photosCountHidden} hidden)`}
|
||||||
<div className="text-right">{camerasCount}</div>
|
</>}
|
||||||
<div className="tracking-wide">Films</div>
|
/>
|
||||||
<div className="text-right">{filmSimulationsCount}</div>
|
<ScoreCardRow
|
||||||
<div className="tracking-wide">Lenses</div>
|
icon={<FaTag
|
||||||
<div className="text-right">{lensesCount}</div>
|
size={12}
|
||||||
<span className="text-center col-span-2">
|
className="translate-y-[3px]"
|
||||||
{descriptionWithSpaces}
|
/>}
|
||||||
</span>
|
content={`${tagsCount} tags`}
|
||||||
</div>
|
/>
|
||||||
|
<ScoreCardRow
|
||||||
|
icon={<FaCamera
|
||||||
|
size={13}
|
||||||
|
className="translate-y-[2px]"
|
||||||
|
/>}
|
||||||
|
content={`${camerasCount} cameras`}
|
||||||
|
/>
|
||||||
|
{filmSimulationsCount &&
|
||||||
|
<ScoreCardRow
|
||||||
|
icon={<span className="inline-flex w-3">
|
||||||
|
<PhotoFilmSimulationIcon
|
||||||
|
className="shrink-0 translate-x-[-1px] translate-y-[-0.5px]"
|
||||||
|
height={18}
|
||||||
|
/>
|
||||||
|
</span>}
|
||||||
|
content={`${filmSimulationsCount} film simulations`}
|
||||||
|
/>}
|
||||||
|
<ScoreCardRow
|
||||||
|
icon={<TbCone className="rotate-[270deg] translate-x-[-2px]" />}
|
||||||
|
content={`${lensesCount} lenses`}
|
||||||
|
/>
|
||||||
|
<ScoreCardRow
|
||||||
|
icon={<FaRegCalendar
|
||||||
|
size={13}
|
||||||
|
className="translate-y-[1.5px] translate-x-[-2px]"
|
||||||
|
/>}
|
||||||
|
content={descriptionWithSpaces}
|
||||||
|
/>
|
||||||
|
</ScoreCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
TEMPLATE_BASE_OWNER,
|
TEMPLATE_REPO_OWNER,
|
||||||
TEMPLATE_BASE_REPO,
|
TEMPLATE_REPO_NAME,
|
||||||
TEMPLATE_BASE_BRANCH,
|
TEMPLATE_REPO_BRANCH,
|
||||||
} from '@/app-core/config';
|
} from '@/app-core/config';
|
||||||
|
|
||||||
const DEFAULT_BRANCH = 'main';
|
const DEFAULT_BRANCH = 'main';
|
||||||
@ -23,8 +23,8 @@ interface RepoParams {
|
|||||||
// Website urls
|
// Website urls
|
||||||
|
|
||||||
export const getGitHubRepoUrl = ({
|
export const getGitHubRepoUrl = ({
|
||||||
owner = TEMPLATE_BASE_OWNER,
|
owner = TEMPLATE_REPO_OWNER,
|
||||||
repo = TEMPLATE_BASE_REPO,
|
repo = TEMPLATE_REPO_NAME,
|
||||||
}: RepoParams = {}) =>
|
}: RepoParams = {}) =>
|
||||||
`https://github.com/${owner}/${repo}`;
|
`https://github.com/${owner}/${repo}`;
|
||||||
|
|
||||||
@ -34,13 +34,13 @@ export const getGitHubCompareUrl = ({
|
|||||||
branch = DEFAULT_BRANCH,
|
branch = DEFAULT_BRANCH,
|
||||||
}: RepoParams = {}) =>
|
}: RepoParams = {}) =>
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
`${getGitHubRepoUrl({ owner, repo })}/compare/${branch}...${TEMPLATE_BASE_OWNER}:${TEMPLATE_BASE_REPO}:${TEMPLATE_BASE_BRANCH}`;
|
`${getGitHubRepoUrl({ owner, repo })}/compare/${branch}...${TEMPLATE_REPO_OWNER}:${TEMPLATE_REPO_NAME}:${TEMPLATE_REPO_BRANCH}`;
|
||||||
|
|
||||||
// API urls
|
// API urls
|
||||||
|
|
||||||
const getGitHubApiRepoUrl = ({
|
const getGitHubApiRepoUrl = ({
|
||||||
owner = TEMPLATE_BASE_OWNER,
|
owner = TEMPLATE_REPO_OWNER,
|
||||||
repo = TEMPLATE_BASE_REPO,
|
repo = TEMPLATE_REPO_NAME,
|
||||||
}: RepoParams = {}) =>
|
}: RepoParams = {}) =>
|
||||||
`https://api.github.com/repos/${owner}/${repo}`;
|
`https://api.github.com/repos/${owner}/${repo}`;
|
||||||
|
|
||||||
@ -56,10 +56,10 @@ const getGitHubApiCompareToRepoUrl = ({
|
|||||||
branch = DEFAULT_BRANCH,
|
branch = DEFAULT_BRANCH,
|
||||||
}: RepoParams = {}) =>
|
}: RepoParams = {}) =>
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
`${getGitHubApiRepoUrl()}/compare/${TEMPLATE_BASE_BRANCH}...${owner}:${repo}:${branch}`;
|
`${getGitHubApiRepoUrl()}/compare/${TEMPLATE_REPO_BRANCH}...${owner}:${repo}:${branch}`;
|
||||||
|
|
||||||
const getGitHubApiCompareToCommitUrl = ({ commit }: RepoParams = {}) =>
|
const getGitHubApiCompareToCommitUrl = ({ commit }: RepoParams = {}) =>
|
||||||
`${getGitHubApiRepoUrl()}/compare/${TEMPLATE_BASE_BRANCH}...${commit}`;
|
`${getGitHubApiRepoUrl()}/compare/${TEMPLATE_REPO_BRANCH}...${commit}`;
|
||||||
|
|
||||||
// Requests
|
// Requests
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ const getIsRepoForkedFromBase = async (params: RepoParams) => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return (
|
return (
|
||||||
Boolean(data.fork) &&
|
Boolean(data.fork) &&
|
||||||
data.source?.full_name === `${TEMPLATE_BASE_OWNER}/${TEMPLATE_BASE_REPO}`
|
data.source?.full_name === `${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,8 +97,8 @@ const getGitHubCommitsBehindFromCommit = async (params?: RepoParams) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isRepoBaseRepo = ({ owner, repo }: RepoParams) =>
|
const isRepoBaseRepo = ({ owner, repo }: RepoParams) =>
|
||||||
owner?.toLowerCase() === TEMPLATE_BASE_OWNER &&
|
owner?.toLowerCase() === TEMPLATE_REPO_OWNER &&
|
||||||
repo?.toLowerCase() === TEMPLATE_BASE_REPO;
|
repo?.toLowerCase() === TEMPLATE_REPO_NAME;
|
||||||
|
|
||||||
export const getGitHubPublicFork = async (
|
export const getGitHubPublicFork = async (
|
||||||
params?: RepoParams,
|
params?: RepoParams,
|
||||||
@ -144,6 +144,7 @@ const getGitHubMeta = async (params: RepoParams) => {
|
|||||||
: 'This fork is up to date.';
|
: 'This fork is up to date.';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...params,
|
||||||
url,
|
url,
|
||||||
isForkedFromBase,
|
isForkedFromBase,
|
||||||
isBaseRepo,
|
isBaseRepo,
|
||||||
@ -160,6 +161,7 @@ export const getGitHubMetaWithFallback = (params: RepoParams) =>
|
|||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error('Error retrieving GitHub meta', { params, error: e });
|
console.error('Error retrieving GitHub meta', { params, error: e });
|
||||||
return {
|
return {
|
||||||
|
...params,
|
||||||
url: undefined,
|
url: undefined,
|
||||||
isForkedFromBase: false,
|
isForkedFromBase: false,
|
||||||
isBaseRepo: undefined,
|
isBaseRepo: undefined,
|
||||||
|
|||||||
@ -6,17 +6,21 @@ import type { StorageType } from '@/services/storage';
|
|||||||
import { makeUrlAbsolute, shortenUrl } from '@/utility/url';
|
import { makeUrlAbsolute, shortenUrl } from '@/utility/url';
|
||||||
|
|
||||||
// HARD-CODED GLOBAL CONFIGURATION
|
// HARD-CODED GLOBAL CONFIGURATION
|
||||||
|
|
||||||
export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined;
|
export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined;
|
||||||
|
|
||||||
// META / SOURCE / DOMAINS
|
// TEMPLATE META
|
||||||
export const SITE_TITLE =
|
|
||||||
process.env.NEXT_PUBLIC_SITE_TITLE ||
|
|
||||||
'Photo Blog';
|
|
||||||
|
|
||||||
// SOURCE
|
export const TEMPLATE_TITLE = 'Photo Blog';
|
||||||
export const TEMPLATE_BASE_OWNER = 'sambecker';
|
export const TEMPLATE_DESCRIPTION = 'Store photos with original camera data';
|
||||||
export const TEMPLATE_BASE_REPO = 'exif-photo-blog';
|
|
||||||
export const TEMPLATE_BASE_BRANCH = 'main';
|
// SOURCE CODE
|
||||||
|
|
||||||
|
export const TEMPLATE_REPO_OWNER = 'sambecker';
|
||||||
|
export const TEMPLATE_REPO_NAME = 'exif-photo-blog';
|
||||||
|
export const TEMPLATE_REPO_BRANCH = 'main';
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
export const TEMPLATE_REPO_URL = `https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`;
|
||||||
|
|
||||||
export const VERCEL_GIT_PROVIDER =
|
export const VERCEL_GIT_PROVIDER =
|
||||||
process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER;
|
process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER;
|
||||||
@ -59,6 +63,12 @@ export const IS_PREVIEW = VERCEL_ENV === 'preview';
|
|||||||
export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass';
|
export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass';
|
||||||
export const VERCEL_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
|
export const VERCEL_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
|
||||||
|
|
||||||
|
// SITE META
|
||||||
|
|
||||||
|
export const SITE_TITLE =
|
||||||
|
process.env.NEXT_PUBLIC_SITE_TITLE ||
|
||||||
|
TEMPLATE_TITLE;
|
||||||
|
|
||||||
// User-facing domain, potential site title
|
// User-facing domain, potential site title
|
||||||
const SITE_DOMAIN =
|
const SITE_DOMAIN =
|
||||||
process.env.NEXT_PUBLIC_SITE_DOMAIN ||
|
process.env.NEXT_PUBLIC_SITE_DOMAIN ||
|
||||||
@ -90,11 +100,14 @@ export const SITE_ABOUT = process.env.NEXT_PUBLIC_SITE_ABOUT;
|
|||||||
export const HAS_DEFINED_SITE_DESCRIPTION =
|
export const HAS_DEFINED_SITE_DESCRIPTION =
|
||||||
Boolean(process.env.NEXT_PUBLIC_SITE_DESCRIPTION);
|
Boolean(process.env.NEXT_PUBLIC_SITE_DESCRIPTION);
|
||||||
|
|
||||||
|
// STORAGE
|
||||||
|
|
||||||
// STORAGE: DATABASE
|
// STORAGE: DATABASE
|
||||||
export const HAS_DATABASE =
|
export const HAS_DATABASE =
|
||||||
Boolean(process.env.POSTGRES_URL);
|
Boolean(process.env.POSTGRES_URL);
|
||||||
export const POSTGRES_SSL_ENABLED =
|
export const POSTGRES_SSL_ENABLED =
|
||||||
process.env.DISABLE_POSTGRES_SSL === '1' ? false : true;
|
process.env.DISABLE_POSTGRES_SSL === '1' ? false : true;
|
||||||
|
|
||||||
// STORAGE: VERCEL KV
|
// STORAGE: VERCEL KV
|
||||||
export const HAS_VERCEL_KV =
|
export const HAS_VERCEL_KV =
|
||||||
Boolean(process.env.KV_URL);
|
Boolean(process.env.KV_URL);
|
||||||
|
|||||||
@ -1,24 +1,25 @@
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
|
import {
|
||||||
|
TEMPLATE_REPO_OWNER,
|
||||||
|
TEMPLATE_REPO_NAME,
|
||||||
|
TEMPLATE_DESCRIPTION,
|
||||||
|
TEMPLATE_TITLE,
|
||||||
|
} from '@/app-core/config';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
const REQUIRE_ENV_VARS = false;
|
const REQUIRE_ENV_VARS = false;
|
||||||
|
|
||||||
const TITLE = 'Photo Blog';
|
|
||||||
const DESCRIPTION = 'Store photos with original camera data';
|
|
||||||
const REPO_TEAM = 'sambecker';
|
|
||||||
const REPO_NAME = 'exif-photo-blog';
|
|
||||||
|
|
||||||
export function GET() {
|
export function GET() {
|
||||||
const url = new URL('https://vercel.com/new/clone');
|
const url = new URL('https://vercel.com/new/clone');
|
||||||
|
|
||||||
url.searchParams.set('demo-title', TITLE);
|
url.searchParams.set('demo-title', TEMPLATE_TITLE);
|
||||||
url.searchParams.set('demo-description', DESCRIPTION);
|
url.searchParams.set('demo-description', TEMPLATE_DESCRIPTION);
|
||||||
url.searchParams.set('demo-url', 'https://photos.sambecker.com');
|
url.searchParams.set('demo-url', 'https://photos.sambecker.com');
|
||||||
url.searchParams.set('demo-description', DESCRIPTION);
|
url.searchParams.set('demo-description', TEMPLATE_DESCRIPTION);
|
||||||
url.searchParams.set('demo-image', 'https://photos.sambecker.com/template-image-tight');
|
url.searchParams.set('demo-image', 'https://photos.sambecker.com/template-image-tight');
|
||||||
url.searchParams.set('project-name', TITLE);
|
url.searchParams.set('project-name', TEMPLATE_TITLE);
|
||||||
url.searchParams.set('repository-name', REPO_NAME);
|
url.searchParams.set('repository-name', TEMPLATE_REPO_NAME);
|
||||||
url.searchParams.set('repository-url', `https://github.com/${REPO_TEAM}/${REPO_NAME}`);
|
url.searchParams.set('repository-url', `https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`);
|
||||||
url.searchParams.set('from', 'templates');
|
url.searchParams.set('from', 'templates');
|
||||||
url.searchParams.set('skippable-integrations', '1');
|
url.searchParams.set('skippable-integrations', '1');
|
||||||
if (REQUIRE_ENV_VARS) {
|
if (REQUIRE_ENV_VARS) {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { TEMPLATE_REPO_NAME, TEMPLATE_REPO_URL } from '@/app-core/config';
|
||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { BiLogoGithub } from 'react-icons/bi';
|
import { BiLogoGithub } from 'react-icons/bi';
|
||||||
@ -9,7 +10,7 @@ export default function RepoLink() {
|
|||||||
Made with
|
Made with
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href="http://github.com/sambecker/exif-photo-blog"
|
href={TEMPLATE_REPO_URL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex items-center gap-0.5',
|
'flex items-center gap-0.5',
|
||||||
@ -21,7 +22,7 @@ export default function RepoLink() {
|
|||||||
size={16}
|
size={16}
|
||||||
className="translate-y-[0.5px] hidden xs:inline-block"
|
className="translate-y-[0.5px] hidden xs:inline-block"
|
||||||
/>
|
/>
|
||||||
exif-photo-blog
|
{TEMPLATE_REPO_NAME}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
|
import clsx from 'clsx/lite';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export default function ScoreCard({
|
export default function ScoreCard({
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode,
|
children: ReactNode,
|
||||||
|
className?: string,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="component-surface shadow-xs divide-y divide-main">
|
<div className={clsx(
|
||||||
|
'component-surface shadow-xs divide-y divide-main',
|
||||||
|
className,
|
||||||
|
)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
import { FaMinus, FaPlus } from 'react-icons/fa6';
|
import { FaMinus, FaPlus } from 'react-icons/fa6';
|
||||||
|
|
||||||
export default function ScoreCardRow({
|
export default function ScoreCardRow({
|
||||||
icon,
|
icon,
|
||||||
content,
|
content,
|
||||||
@ -11,16 +12,20 @@ export default function ScoreCardRow({
|
|||||||
additionalContent?: ReactNode
|
additionalContent?: ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'flex gap-4',
|
'flex',
|
||||||
'px-4 py-2',
|
'py-2 pr-2',
|
||||||
|
)}>
|
||||||
|
<div className={clsx(
|
||||||
|
'flex justify-center pt-[8px] w-11 sm:w-14',
|
||||||
|
'shrink-0 text-icon',
|
||||||
)}>
|
)}>
|
||||||
<div className="pt-[8px] shrink-0 text-main">
|
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
<div className="grow space-y-2 py-1.5 w-full overflow-auto">
|
<div className="grow space-y-2 py-1.5 w-full overflow-auto">
|
||||||
<div className="text-main">
|
<div className="text-main pr-2">
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
{isExpanded &&
|
{isExpanded &&
|
||||||
@ -31,7 +36,7 @@ export default function ScoreCardRow({
|
|||||||
{additionalContent && <button
|
{additionalContent && <button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
className="px-[9px] self-start -mr-1"
|
className="px-[9px] self-start"
|
||||||
>
|
>
|
||||||
{isExpanded
|
{isExpanded
|
||||||
? <FaMinus />
|
? <FaMinus />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user