Compare commits
No commits in common. "3e99a7f47d714f0ed24c3031a72414ce556eb409" and "53d053b3b253fe8a635ba991364f678ca8eb52cb" have entirely different histories.
3e99a7f47d
...
53d053b3b2
11 changed files with 11 additions and 19509 deletions
|
|
@ -184,9 +184,8 @@ EXPOSE 3000
|
||||||
|
|
||||||
# Liveness probe hooked into our `/api/health` route (no DB dependency,
|
# Liveness probe hooked into our `/api/health` route (no DB dependency,
|
||||||
# so a transient DB outage does NOT cycle the container).
|
# so a transient DB outage does NOT cycle the container).
|
||||||
#HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
# CMD wget -qO- "http://127.0.0.1:${PORT:-3000}/api/health" >/dev/null 2>&1 || exit 1
|
CMD wget -qO- "http://127.0.0.1:${PORT:-3000}/api/health" >/dev/null 2>&1 || exit 1
|
||||||
# NOTE: HEALTHCHECK IS DISABLED HERE - COOLIFY HEALTHCHECK IS EASIER TO MANAGE
|
|
||||||
|
|
||||||
COPY --chmod=755 docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --chmod=755 docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -595,15 +595,8 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
||||||
</div>
|
</div>
|
||||||
{awards.length > 0 ? (
|
{awards.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
{[...awards]
|
{awards
|
||||||
.sort((a, b) => {
|
.sort((a, b) => (b.experiencePoints ?? 0) - (a.experiencePoints ?? 0))
|
||||||
const typeOrder = { medal: 0, ribbon: 1, commendation: 2 } as const;
|
|
||||||
const categoryOrder =
|
|
||||||
(typeOrder[a.type as keyof typeof typeOrder] ?? typeOrder.commendation) -
|
|
||||||
(typeOrder[b.type as keyof typeof typeOrder] ?? typeOrder.commendation);
|
|
||||||
|
|
||||||
return categoryOrder || (b.experiencePoints ?? 0) - (a.experiencePoints ?? 0);
|
|
||||||
})
|
|
||||||
.map((award) => {
|
.map((award) => {
|
||||||
const awardImage =
|
const awardImage =
|
||||||
award.image && typeof award.image === "object" ? (award.image as Media) : null;
|
award.image && typeof award.image === "object" ? (award.image as Media) : null;
|
||||||
|
|
|
||||||
|
|
@ -376,28 +376,6 @@
|
||||||
animation: pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
animation: pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.award-xp-high {
|
|
||||||
background: linear-gradient(110deg, rgb(245 158 11), rgb(254 249 195), rgb(250 204 21), rgb(245 158 11));
|
|
||||||
background-size: 200% auto;
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
filter: drop-shadow(0 0 0.3rem rgb(250 204 21 / 0.35));
|
|
||||||
animation: award-xp-shimmer 2.5s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes award-xp-shimmer {
|
|
||||||
to {
|
|
||||||
background-position: 200% center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.award-xp-high {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbars — styled to match the dark frontend theme */
|
/* Scrollbars — styled to match the dark frontend theme */
|
||||||
* {
|
* {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8c
|
||||||
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||||
import { LexicalEditorFeatureClient as LexicalEditorFeatureClient_4490b89d4413c1ffaecdacfe72efaf73 } from '@ai-stack/payloadcms/client'
|
import { LexicalEditorFeatureClient as LexicalEditorFeatureClient_4490b89d4413c1ffaecdacfe72efaf73 } from '@ai-stack/payloadcms/client'
|
||||||
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||||
import { default as default_d9c22b951ac6762f436db59b889da382 } from '@/components/admin/awards/CommendationColorField'
|
|
||||||
import { default as default_c3ea68702b5e5f8f2f98055e01c43c8d } from '@/components/admin/awards/RibbonDesigner'
|
import { default as default_c3ea68702b5e5f8f2f98055e01c43c8d } from '@/components/admin/awards/RibbonDesigner'
|
||||||
import { default as default_2f2033acccb3be245f1a85f7b4271dac } from '@/components/admin/awards/MedalDesigner'
|
import { default as default_2f2033acccb3be245f1a85f7b4271dac } from '@/components/admin/awards/MedalDesigner'
|
||||||
import { default as default_0edde40a6ccd30c893b1227360f69360 } from '@/components/static/TickDurationDescription'
|
import { default as default_0edde40a6ccd30c893b1227360f69360 } from '@/components/static/TickDurationDescription'
|
||||||
|
|
@ -29,7 +28,6 @@ export const importMap = {
|
||||||
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||||
"@ai-stack/payloadcms/client#LexicalEditorFeatureClient": LexicalEditorFeatureClient_4490b89d4413c1ffaecdacfe72efaf73,
|
"@ai-stack/payloadcms/client#LexicalEditorFeatureClient": LexicalEditorFeatureClient_4490b89d4413c1ffaecdacfe72efaf73,
|
||||||
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
"@/components/admin/awards/CommendationColorField#default": default_d9c22b951ac6762f436db59b889da382,
|
|
||||||
"@/components/admin/awards/RibbonDesigner#default": default_c3ea68702b5e5f8f2f98055e01c43c8d,
|
"@/components/admin/awards/RibbonDesigner#default": default_c3ea68702b5e5f8f2f98055e01c43c8d,
|
||||||
"@/components/admin/awards/MedalDesigner#default": default_2f2033acccb3be245f1a85f7b4271dac,
|
"@/components/admin/awards/MedalDesigner#default": default_2f2033acccb3be245f1a85f7b4271dac,
|
||||||
"@/components/static/TickDurationDescription#default": default_0edde40a6ccd30c893b1227360f69360,
|
"@/components/static/TickDurationDescription#default": default_0edde40a6ccd30c893b1227360f69360,
|
||||||
|
|
|
||||||
|
|
@ -43,17 +43,6 @@ export const Awards: CollectionConfig = {
|
||||||
],
|
],
|
||||||
defaultValue: "commendation",
|
defaultValue: "commendation",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "commendationColor",
|
|
||||||
type: "text",
|
|
||||||
defaultValue: "#92400e",
|
|
||||||
admin: {
|
|
||||||
components: {
|
|
||||||
Field: "@/components/admin/awards/CommendationColorField",
|
|
||||||
},
|
|
||||||
condition: (data) => data?.type === "commendation",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
type: "upload",
|
type: "upload",
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { useField } from "@payloadcms/ui";
|
|
||||||
|
|
||||||
const DEFAULT_COLOR = "#92400e";
|
|
||||||
|
|
||||||
export default function CommendationColorField() {
|
|
||||||
const { value, setValue } = useField<string>();
|
|
||||||
const color = typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value) ? value : DEFAULT_COLOR;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="field-type text">
|
|
||||||
<label className="field-label" htmlFor="commendation-color">
|
|
||||||
Commendation color
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<input
|
|
||||||
id="commendation-color"
|
|
||||||
type="color"
|
|
||||||
value={color}
|
|
||||||
onChange={(event) => setValue(event.target.value)}
|
|
||||||
aria-label="Choose commendation color"
|
|
||||||
className="h-10 w-14 cursor-pointer rounded border border-border bg-transparent p-1"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={typeof value === "string" ? value : ""}
|
|
||||||
onChange={(event) => setValue(event.target.value)}
|
|
||||||
placeholder={DEFAULT_COLOR}
|
|
||||||
pattern="^#[0-9a-fA-F]{6}$"
|
|
||||||
aria-describedby="commendation-color-description"
|
|
||||||
className="field-type__input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div id="commendation-color-description" className="field-description">
|
|
||||||
Used for the border and background of this commendation on service records.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -5,39 +5,13 @@ import { RibbonViewer } from "@/components/frontend/account/RibbonViewer";
|
||||||
import { RibbonDesign } from "@/components/admin/awards/RibbonDesigner";
|
import { RibbonDesign } from "@/components/admin/awards/RibbonDesigner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const getExperiencePointsClass = (experiencePoints: number) => {
|
|
||||||
if (experiencePoints >= 5000) return "award-xp-high";
|
|
||||||
if (experiencePoints >= 1000) return "text-emerald-400";
|
|
||||||
if (experiencePoints >= 501) return "text-lime-300";
|
|
||||||
if (experiencePoints >= 200) return "text-sky-300";
|
|
||||||
return "text-muted-foreground";
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AwardCard(props: { awardImage: Media | null; award: Award }) {
|
export default function AwardCard(props: { awardImage: Media | null; award: Award }) {
|
||||||
const isCommendation = props.award.type === "commendation" || !props.award.type;
|
|
||||||
const commendationColor =
|
|
||||||
isCommendation &&
|
|
||||||
typeof props.award.commendationColor === "string" &&
|
|
||||||
/^#[0-9a-f]{6}$/i.test(props.award.commendationColor)
|
|
||||||
? props.award.commendationColor
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size={isCommendation ? "sm" : "default"}
|
|
||||||
className={cn("text-center", {
|
className={cn("text-center", {
|
||||||
"medal-card": props.award.type === "medal",
|
"medal-card": props.award.type === "medal",
|
||||||
"commendation-card": isCommendation,
|
|
||||||
})}
|
})}
|
||||||
style={
|
|
||||||
commendationColor
|
|
||||||
? {
|
|
||||||
borderColor: commendationColor,
|
|
||||||
backgroundColor: `${commendationColor}33`,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ItemHeader className="flex flex-col items-center">
|
<ItemHeader className="flex flex-col items-center">
|
||||||
{props.award.ribbonDesign && (
|
{props.award.ribbonDesign && (
|
||||||
|
|
@ -51,40 +25,27 @@ export default function AwardCard(props: { awardImage: Media | null; award: Awar
|
||||||
<img
|
<img
|
||||||
src={props.awardImage.url}
|
src={props.awardImage.url}
|
||||||
alt={props.award.name}
|
alt={props.award.name}
|
||||||
className={cn("object-contain", isCommendation ? "size-6" : "size-8")}
|
className="size-8 object-contain"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<ItemTitle
|
<ItemTitle className={cn({ "text-xl": props.award.type === "medal" })}>
|
||||||
className={cn({
|
|
||||||
"text-xl text-shadow-lg text-shadow-white/15": props.award.type === "medal",
|
|
||||||
"text-xs": isCommendation,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{props.award.name}
|
{props.award.name}
|
||||||
</ItemTitle>
|
</ItemTitle>
|
||||||
<span className="text-[9px] uppercase tracking-wider text-muted-foreground font-mono">
|
<span className="text-[10px] uppercase tracking-wider text-muted-foreground font-mono">
|
||||||
{props.award.type ?? "commendation"}
|
{props.award.type ?? "commendation"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ItemHeader>
|
</ItemHeader>
|
||||||
<ItemContent>
|
<ItemContent>
|
||||||
<ItemDescription
|
<ItemDescription className="line-clamp-3 text-center">
|
||||||
className={cn("text-center", isCommendation ? "line-clamp-none text-xs" : "line-clamp-3")}
|
|
||||||
>
|
|
||||||
{props.award.description
|
{props.award.description
|
||||||
? convertLexicalToPlaintext({ data: props.award.description })
|
? convertLexicalToPlaintext({ data: props.award.description }).slice(0, 120)
|
||||||
: "No description."}
|
: "No description."}
|
||||||
</ItemDescription>
|
</ItemDescription>
|
||||||
{props.award.experiencePoints != null && (
|
{props.award.experiencePoints != null && (
|
||||||
<span
|
<span className="text-xs font-mono text-muted-foreground mt-1">
|
||||||
className={cn(
|
|
||||||
"mt-1 font-mono",
|
|
||||||
getExperiencePointsClass(props.award.experiencePoints),
|
|
||||||
isCommendation ? "text-[10px]" : "text-xs",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
+{props.award.experiencePoints} XP
|
+{props.award.experiencePoints} XP
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +0,0 @@
|
||||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
|
||||||
|
|
||||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "awards" ADD COLUMN "commendation_color" varchar DEFAULT '#92400e';`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "awards" DROP COLUMN "commendation_color";`)
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,6 @@ import * as migration_20260827_022334_add_eod_minesweeper_best_flags from './202
|
||||||
import * as migration_20260827_213857_add_evaluations_collection from './20260827_213857_add_evaluations_collection';
|
import * as migration_20260827_213857_add_evaluations_collection from './20260827_213857_add_evaluations_collection';
|
||||||
import * as migration_20260827_233224_add_mission_completed_at from './20260827_233224_add_mission_completed_at';
|
import * as migration_20260827_233224_add_mission_completed_at from './20260827_233224_add_mission_completed_at';
|
||||||
import * as migration_20260828_041615_add_campaign_mode from './20260828_041615_add_campaign_mode';
|
import * as migration_20260828_041615_add_campaign_mode from './20260828_041615_add_campaign_mode';
|
||||||
import * as migration_20260828_044506 from './20260828_044506';
|
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
|
|
@ -53,11 +52,6 @@ export const migrations = [
|
||||||
{
|
{
|
||||||
up: migration_20260828_041615_add_campaign_mode.up,
|
up: migration_20260828_041615_add_campaign_mode.up,
|
||||||
down: migration_20260828_041615_add_campaign_mode.down,
|
down: migration_20260828_041615_add_campaign_mode.down,
|
||||||
name: '20260828_041615_add_campaign_mode',
|
name: '20260828_041615_add_campaign_mode'
|
||||||
},
|
|
||||||
{
|
|
||||||
up: migration_20260828_044506.up,
|
|
||||||
down: migration_20260828_044506.down,
|
|
||||||
name: '20260828_044506'
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1731,7 +1731,6 @@ export interface Award {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
type?: ('commendation' | 'ribbon' | 'medal') | null;
|
type?: ('commendation' | 'ribbon' | 'medal') | null;
|
||||||
commendationColor?: string | null;
|
|
||||||
image?: (number | null) | Media;
|
image?: (number | null) | Media;
|
||||||
experiencePoints?: number | null;
|
experiencePoints?: number | null;
|
||||||
ribbonDesign?:
|
ribbonDesign?:
|
||||||
|
|
@ -3540,7 +3539,6 @@ export interface AwardsSelect<T extends boolean = true> {
|
||||||
name?: T;
|
name?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
type?: T;
|
type?: T;
|
||||||
commendationColor?: T;
|
|
||||||
image?: T;
|
image?: T;
|
||||||
experiencePoints?: T;
|
experiencePoints?: T;
|
||||||
ribbonDesign?: T;
|
ribbonDesign?: T;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue