mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Fix lint
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Test / docker (push) Has been cancelled
Runs E2E API Tests / build (22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E API Tests with external source of truth / build (22.x) (push) Has been cancelled
Lint & build / lint_and_build (latest) (push) Has been cancelled
Dev Environment Test With Custom Base Port / restart-dev-and-test-with-custom-base-port (push) Has been cancelled
Dev Environment Test / restart-dev-and-test (push) Has been cancelled
Run setup tests / setup-tests (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Test / docker (push) Has been cancelled
Runs E2E API Tests / build (22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E API Tests with external source of truth / build (22.x) (push) Has been cancelled
Lint & build / lint_and_build (latest) (push) Has been cancelled
Dev Environment Test With Custom Base Port / restart-dev-and-test-with-custom-base-port (push) Has been cancelled
Dev Environment Test / restart-dev-and-test (push) Has been cancelled
Run setup tests / setup-tests (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
This commit is contained in:
parent
92ef462d13
commit
62b9fa3881
@ -79,190 +79,190 @@ export function AppStoreEntry({
|
||||
<div className="flex flex-col bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-950">
|
||||
{/* Hero Section */}
|
||||
<div className="relative px-6 py-8 border-b border-gray-200 dark:border-gray-800">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row gap-6 items-start">
|
||||
{/* App Icon */}
|
||||
<div className="flex-shrink-0">
|
||||
<AppIcon
|
||||
appId={appId}
|
||||
className="shadow-xl ring-1 ring-black/5 dark:ring-white/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* App Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start gap-2 mb-2">
|
||||
<TitleComponent className="text-3xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{app.displayName}
|
||||
</TitleComponent>
|
||||
{app.stage !== "stable" && (
|
||||
<Badge
|
||||
variant={app.stage === "alpha" ? "destructive" : "secondary"}
|
||||
className="text-xs px-2 py-0.5"
|
||||
>
|
||||
{app.stage === "alpha" ? "Alpha" : "Beta"}
|
||||
</Badge>
|
||||
)}
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row gap-6 items-start">
|
||||
{/* App Icon */}
|
||||
<div className="flex-shrink-0">
|
||||
<AppIcon
|
||||
appId={appId}
|
||||
className="shadow-xl ring-1 ring-black/5 dark:ring-white/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-4">
|
||||
{app.subtitle}
|
||||
</p>
|
||||
{/* App Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start gap-2 mb-2">
|
||||
<TitleComponent className="text-3xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{app.displayName}
|
||||
</TitleComponent>
|
||||
{app.stage !== "stable" && (
|
||||
<Badge
|
||||
variant={app.stage === "alpha" ? "destructive" : "secondary"}
|
||||
className="text-xs px-2 py-0.5"
|
||||
>
|
||||
{app.stage === "alpha" ? "Alpha" : "Beta"}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{(app.tags as Array<keyof typeof ALL_APP_TAGS>).map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
className={cn(
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-4">
|
||||
{app.subtitle}
|
||||
</p>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{(app.tags as Array<keyof typeof ALL_APP_TAGS>).map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
className={cn(
|
||||
"px-3 py-1 rounded-full text-xs font-medium",
|
||||
tag === "expert"
|
||||
? "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300"
|
||||
: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||
)}
|
||||
>
|
||||
{ALL_APP_TAGS[tag].displayName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
>
|
||||
{ALL_APP_TAGS[tag].displayName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-6">
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center gap-1.5 p-3 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
<feature.icon className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400 text-center">
|
||||
{feature.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Features Grid */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-6">
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center gap-1.5 p-3 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
<feature.icon className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400 text-center">
|
||||
{feature.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<div className="flex items-center gap-4">
|
||||
{isEnabled ? (
|
||||
<>
|
||||
{/* CTA Button */}
|
||||
<div className="flex items-center gap-4">
|
||||
{isEnabled ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={onOpen}
|
||||
size="lg"
|
||||
className="px-8 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium shadow-lg shadow-blue-500/20"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
Open App
|
||||
</Button>
|
||||
{onDisable && (
|
||||
<Button
|
||||
onClick={onDisable}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
Disable
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
onClick={onOpen}
|
||||
onClick={onEnable}
|
||||
size="lg"
|
||||
className="px-8 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium shadow-lg shadow-blue-500/20"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
Open App
|
||||
Enable App
|
||||
</Button>
|
||||
{onDisable && (
|
||||
<Button
|
||||
onClick={onDisable}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
Disable
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
onClick={onEnable}
|
||||
size="lg"
|
||||
className="px-8 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium shadow-lg shadow-blue-500/20"
|
||||
>
|
||||
Enable App
|
||||
</Button>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stage Warning */}
|
||||
{app.stage !== "stable" && (
|
||||
<div className="max-w-4xl mx-auto mt-6">
|
||||
<div
|
||||
className={cn(
|
||||
{/* Stage Warning */}
|
||||
{app.stage !== "stable" && (
|
||||
<div className="max-w-4xl mx-auto mt-6">
|
||||
<div
|
||||
className={cn(
|
||||
"p-4 rounded-xl border-l-4",
|
||||
app.stage === "alpha"
|
||||
? "bg-red-50 dark:bg-red-950/20 border-red-500 dark:border-red-600"
|
||||
: "bg-amber-50 dark:bg-amber-950/20 border-amber-500 dark:border-amber-600"
|
||||
)}
|
||||
>
|
||||
<p
|
||||
className={cn(
|
||||
>
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
app.stage === "alpha"
|
||||
? "text-red-800 dark:text-red-300"
|
||||
: "text-amber-800 dark:text-amber-300"
|
||||
)}
|
||||
>
|
||||
{app.stage === "alpha" && (
|
||||
<>
|
||||
<strong>Alpha Release:</strong> This app is in early development and may have bugs or unexpected behavior. Use with caution in production environments.
|
||||
</>
|
||||
)}
|
||||
{app.stage === "beta" && (
|
||||
<>
|
||||
<strong>Beta Release:</strong> This app is being actively tested. You may encounter some issues, but it is generally stable for production use.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
>
|
||||
{app.stage === "alpha" && (
|
||||
<>
|
||||
<strong>Alpha Release:</strong> This app is in early development and may have bugs or unexpected behavior. Use with caution in production environments.
|
||||
</>
|
||||
)}
|
||||
{app.stage === "beta" && (
|
||||
<>
|
||||
<strong>Beta Release:</strong> This app is being actively tested. You may encounter some issues, but it is generally stable for production use.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Screenshots Section */}
|
||||
{appFrontend.screenshots.length > 0 && (
|
||||
<div className="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950">
|
||||
<div className="max-w-4xl mx-auto px-6 py-8">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
Preview
|
||||
</h2>
|
||||
<div className="relative group/screenshots">
|
||||
{/* Left scroll button */}
|
||||
<button
|
||||
onClick={() => scrollScreenshots('left')}
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-white/90 dark:bg-gray-900/90 hover:bg-white dark:hover:bg-gray-800 p-2 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 opacity-0 group-hover/screenshots:opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Scroll left"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5 text-gray-700 dark:text-gray-300" />
|
||||
</button>
|
||||
{/* Screenshots Section */}
|
||||
{appFrontend.screenshots.length > 0 && (
|
||||
<div className="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950">
|
||||
<div className="max-w-4xl mx-auto px-6 py-8">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
Preview
|
||||
</h2>
|
||||
<div className="relative group/screenshots">
|
||||
{/* Left scroll button */}
|
||||
<button
|
||||
onClick={() => scrollScreenshots('left')}
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-white/90 dark:bg-gray-900/90 hover:bg-white dark:hover:bg-gray-800 p-2 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 opacity-0 group-hover/screenshots:opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Scroll left"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5 text-gray-700 dark:text-gray-300" />
|
||||
</button>
|
||||
|
||||
{/* Right scroll button */}
|
||||
<button
|
||||
onClick={() => scrollScreenshots('right')}
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-white/90 dark:bg-gray-900/90 hover:bg-white dark:hover:bg-gray-800 p-2 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 opacity-0 group-hover/screenshots:opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Scroll right"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5 text-gray-700 dark:text-gray-300" />
|
||||
</button>
|
||||
{/* Right scroll button */}
|
||||
<button
|
||||
onClick={() => scrollScreenshots('right')}
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-white/90 dark:bg-gray-900/90 hover:bg-white dark:hover:bg-gray-800 p-2 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 opacity-0 group-hover/screenshots:opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Scroll right"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5 text-gray-700 dark:text-gray-300" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
ref={screenshotContainerRef}
|
||||
className="flex gap-4 pb-4 overflow-x-auto scrollbar-hide scroll-smooth"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
{appFrontend.screenshots.map((screenshot: string, index: number) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setPreviewIndex(index)}
|
||||
className="relative h-64 w-96 rounded-xl shadow-lg flex-shrink-0 overflow-hidden border border-gray-200 dark:border-gray-800 cursor-pointer hover:ring-2 hover:ring-blue-500/50 transition-shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<Image
|
||||
src={screenshot}
|
||||
alt={`${app.displayName} screenshot ${index + 1}`}
|
||||
fill
|
||||
className="object-cover select-none"
|
||||
draggable={false}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
<div
|
||||
ref={screenshotContainerRef}
|
||||
className="flex gap-4 pb-4 overflow-x-auto scrollbar-hide scroll-smooth"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
{appFrontend.screenshots.map((screenshot: string, index: number) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setPreviewIndex(index)}
|
||||
className="relative h-64 w-96 rounded-xl shadow-lg flex-shrink-0 overflow-hidden border border-gray-200 dark:border-gray-800 cursor-pointer hover:ring-2 hover:ring-blue-500/50 transition-shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<Image
|
||||
src={screenshot}
|
||||
alt={`${app.displayName} screenshot ${index + 1}`}
|
||||
fill
|
||||
className="object-cover select-none"
|
||||
draggable={false}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Description Section */}
|
||||
<div className="max-w-4xl mx-auto px-6 py-8">
|
||||
@ -274,68 +274,68 @@ export function AppStoreEntry({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Screenshot Preview Modal */}
|
||||
<Dialog open={previewIndex !== null} onOpenChange={(open) => !open && setPreviewIndex(null)}>
|
||||
<DialogContent className="max-w-7xl max-h-[95vh] p-0 bg-black/95 border-0" noCloseButton>
|
||||
<DialogTitle className="sr-only">
|
||||
{previewIndex !== null
|
||||
? `${app.displayName} screenshot ${previewIndex + 1} of ${appFrontend.screenshots.length}`
|
||||
: 'Screenshot preview'}
|
||||
</DialogTitle>
|
||||
<div className="relative w-full h-full flex items-center justify-center p-4">
|
||||
{previewIndex !== null && (
|
||||
<>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={() => setPreviewIndex(null)}
|
||||
className="absolute top-4 right-4 z-50 bg-white/10 hover:bg-white/20 p-2 rounded-full transition-colors"
|
||||
aria-label="Close preview"
|
||||
>
|
||||
<X className="w-6 h-6 text-white" />
|
||||
</button>
|
||||
|
||||
{/* Image counter */}
|
||||
<div className="absolute top-4 left-4 z-50 bg-white/10 px-3 py-1 rounded-full text-white text-sm">
|
||||
{previewIndex + 1} / {appFrontend.screenshots.length}
|
||||
</div>
|
||||
|
||||
{/* Previous button */}
|
||||
{previewIndex > 0 && (
|
||||
{/* Screenshot Preview Modal */}
|
||||
<Dialog open={previewIndex !== null} onOpenChange={(open) => !open && setPreviewIndex(null)}>
|
||||
<DialogContent className="max-w-7xl max-h-[95vh] p-0 bg-black/95 border-0" noCloseButton>
|
||||
<DialogTitle className="sr-only">
|
||||
{previewIndex !== null
|
||||
? `${app.displayName} screenshot ${previewIndex + 1} of ${appFrontend.screenshots.length}`
|
||||
: 'Screenshot preview'}
|
||||
</DialogTitle>
|
||||
<div className="relative w-full h-full flex items-center justify-center p-4">
|
||||
{previewIndex !== null && (
|
||||
<>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={() => navigatePreview('prev')}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 z-50 bg-white/10 hover:bg-white/20 p-3 rounded-full transition-colors"
|
||||
aria-label="Previous screenshot"
|
||||
onClick={() => setPreviewIndex(null)}
|
||||
className="absolute top-4 right-4 z-50 bg-white/10 hover:bg-white/20 p-2 rounded-full transition-colors"
|
||||
aria-label="Close preview"
|
||||
>
|
||||
<ChevronLeft className="w-8 h-8 text-white" />
|
||||
<X className="w-6 h-6 text-white" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Next button */}
|
||||
{previewIndex < appFrontend.screenshots.length - 1 && (
|
||||
<button
|
||||
onClick={() => navigatePreview('next')}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 z-50 bg-white/10 hover:bg-white/20 p-3 rounded-full transition-colors"
|
||||
aria-label="Next screenshot"
|
||||
>
|
||||
<ChevronRight className="w-8 h-8 text-white" />
|
||||
</button>
|
||||
)}
|
||||
{/* Image counter */}
|
||||
<div className="absolute top-4 left-4 z-50 bg-white/10 px-3 py-1 rounded-full text-white text-sm">
|
||||
{previewIndex + 1} / {appFrontend.screenshots.length}
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="relative w-full h-[85vh] flex items-center justify-center">
|
||||
<Image
|
||||
src={appFrontend.screenshots[previewIndex]}
|
||||
alt={`${app.displayName} screenshot ${previewIndex + 1}`}
|
||||
fill
|
||||
className="object-contain"
|
||||
sizes="100vw"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Previous button */}
|
||||
{previewIndex > 0 && (
|
||||
<button
|
||||
onClick={() => navigatePreview('prev')}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 z-50 bg-white/10 hover:bg-white/20 p-3 rounded-full transition-colors"
|
||||
aria-label="Previous screenshot"
|
||||
>
|
||||
<ChevronLeft className="w-8 h-8 text-white" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Next button */}
|
||||
{previewIndex < appFrontend.screenshots.length - 1 && (
|
||||
<button
|
||||
onClick={() => navigatePreview('next')}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 z-50 bg-white/10 hover:bg-white/20 p-3 rounded-full transition-colors"
|
||||
aria-label="Next screenshot"
|
||||
>
|
||||
<ChevronRight className="w-8 h-8 text-white" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Image */}
|
||||
<div className="relative w-full h-[85vh] flex items-center justify-center">
|
||||
<Image
|
||||
src={appFrontend.screenshots[previewIndex]}
|
||||
alt={`${app.displayName} screenshot ${previewIndex + 1}`}
|
||||
fill
|
||||
className="object-contain"
|
||||
sizes="100vw"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user