@useaward/embed-predictions 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web.js +870 -0
- package/package.json +49 -0
- package/src/api/client.ts +107 -0
- package/src/assets/index.css +15 -0
- package/src/components/ButtonBar.tsx +52 -0
- package/src/components/CorrectValueStep.tsx +80 -0
- package/src/components/EmailInput.tsx +38 -0
- package/src/components/Game.tsx +365 -0
- package/src/components/GameLayout.tsx +29 -0
- package/src/components/GamePlay.tsx +77 -0
- package/src/components/LanguageSwitcher.tsx +40 -0
- package/src/components/ScoreGameStep.tsx +113 -0
- package/src/components/StepRenderer.tsx +41 -0
- package/src/components/SuccessScreen.tsx +24 -0
- package/src/components/WelcomeScreen.tsx +43 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/constants.ts +27 -0
- package/src/context/GameContext.tsx +43 -0
- package/src/context/locale-context.tsx +32 -0
- package/src/features/standard/StandardGameEmbed.tsx +57 -0
- package/src/global.d.ts +1 -0
- package/src/locale/en.ts +24 -0
- package/src/locale/lv.ts +24 -0
- package/src/register.ts +13 -0
- package/src/stores/gameStore.ts +101 -0
- package/src/types/style.ts +9 -0
- package/src/types/translations.ts +8 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils/fetch-locale.ts +8 -0
- package/src/utils/inject-font.ts +23 -0
- package/src/utils/set-css-variables.ts +83 -0
- package/src/web.ts +12 -0
- package/src/window.ts +33 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
import { Show } from "solid-js";
|
|
3
|
+
|
|
4
|
+
import { useGameContext } from "../context/GameContext";
|
|
5
|
+
|
|
6
|
+
interface GameLayoutProps {
|
|
7
|
+
children: JSX.Element;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const GameLayout = (props: GameLayoutProps) => {
|
|
11
|
+
const { gameData } = useGameContext();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<Show when={gameData.image?.publicUrl}>
|
|
16
|
+
{(publicUrl) => (
|
|
17
|
+
<div class="flex justify-center">
|
|
18
|
+
<img
|
|
19
|
+
src={publicUrl()}
|
|
20
|
+
alt={gameData.name}
|
|
21
|
+
class="h-12 w-auto object-contain"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
)}
|
|
25
|
+
</Show>
|
|
26
|
+
{props.children}
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { format } from "date-fns";
|
|
2
|
+
import { Show } from "solid-js";
|
|
3
|
+
|
|
4
|
+
import { useGameContext } from "../context/GameContext";
|
|
5
|
+
import { ButtonBar } from "./ButtonBar";
|
|
6
|
+
import { StepRenderer } from "./StepRenderer";
|
|
7
|
+
|
|
8
|
+
export const GamePlay = () => {
|
|
9
|
+
const {
|
|
10
|
+
gameData,
|
|
11
|
+
store,
|
|
12
|
+
currentStep,
|
|
13
|
+
handleScorePredictionChange,
|
|
14
|
+
handleCorrectValueChange,
|
|
15
|
+
} = useGameContext();
|
|
16
|
+
return (
|
|
17
|
+
<div class="flex w-full flex-col justify-start gap-4">
|
|
18
|
+
<Show when={currentStep()} keyed>
|
|
19
|
+
{(step) => (
|
|
20
|
+
<h3
|
|
21
|
+
class="text-left text-2xl font-semibold"
|
|
22
|
+
style={{
|
|
23
|
+
color: gameData.settings?.questionTextColor || "#000000",
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
{step.title}
|
|
27
|
+
</h3>
|
|
28
|
+
)}
|
|
29
|
+
</Show>
|
|
30
|
+
|
|
31
|
+
<div class="rounded-lg border border-solid border-gray-200 bg-white p-6">
|
|
32
|
+
<Show when={currentStep()} keyed>
|
|
33
|
+
{(step) => (
|
|
34
|
+
<div class="mb-2 flex items-center justify-between">
|
|
35
|
+
<Show when={step.leagueIndicator}>
|
|
36
|
+
<span class="rounded-lg bg-gray-100 px-2 py-1 text-lg font-medium">
|
|
37
|
+
{step.leagueIndicator}
|
|
38
|
+
</span>
|
|
39
|
+
</Show>
|
|
40
|
+
|
|
41
|
+
<Show when={step.dateTime}>
|
|
42
|
+
{step.dateTime ? (
|
|
43
|
+
<span class="text-xl text-gray-600">
|
|
44
|
+
{format(step.dateTime, "dd.LL.yyyy '·' k:mm")}
|
|
45
|
+
</span>
|
|
46
|
+
) : undefined}
|
|
47
|
+
</Show>
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
</Show>
|
|
51
|
+
|
|
52
|
+
<Show when={currentStep()} keyed>
|
|
53
|
+
{(step) => (
|
|
54
|
+
<StepRenderer
|
|
55
|
+
step={step}
|
|
56
|
+
prediction={store.state.predictions[step.id] || {}}
|
|
57
|
+
onScorePredictionChange={(stepValueId, score) =>
|
|
58
|
+
handleScorePredictionChange(step.id, stepValueId, score)
|
|
59
|
+
}
|
|
60
|
+
onCorrectValueChange={(stepValueId) =>
|
|
61
|
+
handleCorrectValueChange(step.id, stepValueId)
|
|
62
|
+
}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
</Show>
|
|
66
|
+
|
|
67
|
+
<Show when={store.state.submitError}>
|
|
68
|
+
<div class="mt-4 rounded border border-red-200 bg-red-50 p-3">
|
|
69
|
+
<p class="text-sm text-red-700">{store.state.submitError}</p>
|
|
70
|
+
</div>
|
|
71
|
+
</Show>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<ButtonBar />
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useLocaleContext } from "@/context/locale-context";
|
|
2
|
+
import { For } from "solid-js";
|
|
3
|
+
|
|
4
|
+
import type { Locale } from "../types/translations";
|
|
5
|
+
|
|
6
|
+
const LOCALE_LABELS: Record<Locale, string> = {
|
|
7
|
+
lv: "LV",
|
|
8
|
+
en: "EN",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const AVAILABLE_LOCALES: Locale[] = ["lv", "en"];
|
|
12
|
+
|
|
13
|
+
export const LanguageSwitcher = () => {
|
|
14
|
+
const { locale, setLocale } = useLocaleContext();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div class="flex items-center gap-1">
|
|
18
|
+
<For each={AVAILABLE_LOCALES}>
|
|
19
|
+
{(loc, index) => (
|
|
20
|
+
<>
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
class={`cursor-pointer rounded px-1.5 py-0.5 text-xs font-medium transition-colors ${
|
|
24
|
+
locale() === loc
|
|
25
|
+
? "bg-gray-200 text-gray-900"
|
|
26
|
+
: "text-gray-400 hover:text-gray-600"
|
|
27
|
+
}`}
|
|
28
|
+
onClick={() => setLocale(loc)}
|
|
29
|
+
>
|
|
30
|
+
{LOCALE_LABELS[loc]}
|
|
31
|
+
</button>
|
|
32
|
+
{index() < AVAILABLE_LOCALES.length - 1 && (
|
|
33
|
+
<span class="text-xs text-gray-300">|</span>
|
|
34
|
+
)}
|
|
35
|
+
</>
|
|
36
|
+
)}
|
|
37
|
+
</For>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
type StepValue = {
|
|
2
|
+
id: string;
|
|
3
|
+
order: number;
|
|
4
|
+
value: {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
shortName: string | null;
|
|
8
|
+
image: {
|
|
9
|
+
publicUrl: string;
|
|
10
|
+
} | null;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type Step = {
|
|
15
|
+
id: string;
|
|
16
|
+
type: "scoreGame" | "correctValue";
|
|
17
|
+
title: string;
|
|
18
|
+
dateTime: Date | null;
|
|
19
|
+
order: number;
|
|
20
|
+
leagueIndicator: string | null;
|
|
21
|
+
stepValues: StepValue[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type Prediction = {
|
|
25
|
+
[stepValueId: string]: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ScoreGameStepProps = {
|
|
29
|
+
step: Step;
|
|
30
|
+
prediction: Prediction;
|
|
31
|
+
onPredictionChange: (stepValueId: string, score: number) => void;
|
|
32
|
+
questionTextColor?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const ScoreGameStep = (props: ScoreGameStepProps) => {
|
|
36
|
+
const incrementScore = (stepValueId: string) => {
|
|
37
|
+
const currentScore = props.prediction[stepValueId] || 0;
|
|
38
|
+
props.onPredictionChange(stepValueId, currentScore + 1);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const decrementScore = (stepValueId: string) => {
|
|
42
|
+
const currentScore = props.prediction[stepValueId] || 0;
|
|
43
|
+
if (currentScore > 0) {
|
|
44
|
+
props.onPredictionChange(stepValueId, currentScore - 1);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const sortedValues = () =>
|
|
49
|
+
[...props.step.stepValues].sort((a, b) => a.order - b.order);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<div class="flex items-start justify-center gap-2">
|
|
54
|
+
{sortedValues().map((stepValue, index) => {
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<div
|
|
58
|
+
class={`flex items-center gap-6 ${index === 1 ? "flex-row-reverse" : "flex-row"}`}
|
|
59
|
+
>
|
|
60
|
+
<div class="flex flex-col items-center">
|
|
61
|
+
{/* Team Logo or Fallback Circle */}
|
|
62
|
+
{stepValue.value.image?.publicUrl ? (
|
|
63
|
+
<img
|
|
64
|
+
src={stepValue.value.image.publicUrl}
|
|
65
|
+
alt={stepValue.value.name}
|
|
66
|
+
class="mb-3 h-20 w-20 object-contain"
|
|
67
|
+
/>
|
|
68
|
+
) : (
|
|
69
|
+
<div class="mb-3 flex h-20 w-20 items-center justify-center rounded-full bg-gray-300 text-2xl font-bold text-gray-600">
|
|
70
|
+
{stepValue.value.name.charAt(0).toUpperCase()}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{/* Team Name */}
|
|
75
|
+
<div
|
|
76
|
+
class="mb-3 max-w-[120px] text-center text-sm leading-tight font-semibold"
|
|
77
|
+
style={{
|
|
78
|
+
color: props.questionTextColor || "#000000",
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{stepValue.value.name}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* Score Controls */}
|
|
86
|
+
<div class="flex flex-col items-center gap-2">
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onClick={() => incrementScore(stepValue.id)}
|
|
90
|
+
class="flex h-10 w-10 items-center justify-center rounded-md bg-gray-100 text-2xl leading-none font-bold hover:bg-gray-200 active:bg-gray-300"
|
|
91
|
+
>
|
|
92
|
+
+
|
|
93
|
+
</button>
|
|
94
|
+
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-blue-100 text-2xl font-bold">
|
|
95
|
+
{props.prediction[stepValue.id] || 0}
|
|
96
|
+
</div>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={() => decrementScore(stepValue.id)}
|
|
100
|
+
class="flex h-10 w-10 items-center justify-center rounded-md bg-gray-100 text-2xl leading-none font-bold hover:bg-gray-200 active:bg-gray-300 disabled:cursor-not-allowed disabled:opacity-30"
|
|
101
|
+
disabled={(props.prediction[stepValue.id] || 0) === 0}
|
|
102
|
+
>
|
|
103
|
+
−
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
})}
|
|
110
|
+
</div>
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Step } from "../api/client";
|
|
2
|
+
import { useGameContext } from "../context/GameContext";
|
|
3
|
+
import { CorrectValueStep } from "./CorrectValueStep";
|
|
4
|
+
import { ScoreGameStep } from "./ScoreGameStep";
|
|
5
|
+
|
|
6
|
+
interface StepRendererProps {
|
|
7
|
+
step: Step;
|
|
8
|
+
prediction: Record<string, any>;
|
|
9
|
+
onScorePredictionChange: (stepValueId: string, score: number) => void;
|
|
10
|
+
onCorrectValueChange: (stepValueId: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const StepRenderer = (props: StepRendererProps) => {
|
|
14
|
+
const { gameData } = useGameContext();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
{props.step.type === "scoreGame" ? (
|
|
19
|
+
<ScoreGameStep
|
|
20
|
+
step={props.step}
|
|
21
|
+
prediction={props.prediction}
|
|
22
|
+
onPredictionChange={(stepValueId, score) =>
|
|
23
|
+
props.onScorePredictionChange(stepValueId, score)
|
|
24
|
+
}
|
|
25
|
+
questionTextColor={gameData.settings?.questionTextColor}
|
|
26
|
+
/>
|
|
27
|
+
) : (
|
|
28
|
+
<CorrectValueStep
|
|
29
|
+
step={props.step}
|
|
30
|
+
prediction={props.prediction}
|
|
31
|
+
onPredictionChange={(stepValueId) =>
|
|
32
|
+
props.onCorrectValueChange(stepValueId)
|
|
33
|
+
}
|
|
34
|
+
questionTextColor={gameData.settings?.questionTextColor}
|
|
35
|
+
selectionBackgroundColor={gameData.settings?.selectionBackground}
|
|
36
|
+
selectionTextColor={gameData.settings?.selectionTextColor}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useGameContext } from "../context/GameContext";
|
|
2
|
+
|
|
3
|
+
export const SuccessScreen = () => {
|
|
4
|
+
const { gameData } = useGameContext();
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div class="flex flex-col p-4">
|
|
8
|
+
<div class="p-8 text-center text-white">
|
|
9
|
+
<h3 class="mb-4 text-2xl font-bold uppercase">
|
|
10
|
+
{gameData.endDescription || "Thank you for playing!"}
|
|
11
|
+
</h3>
|
|
12
|
+
<p class="mb-6 text-sm opacity-90">
|
|
13
|
+
Points will be added to your profile after the game ends
|
|
14
|
+
</p>
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
class="rounded-lg bg-white px-8 py-3 font-medium text-gray-900 uppercase hover:bg-gray-100"
|
|
18
|
+
>
|
|
19
|
+
View Profile
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useLocaleContext } from "@/context/locale-context";
|
|
2
|
+
import { Show } from "solid-js";
|
|
3
|
+
|
|
4
|
+
import { useGameContext } from "../context/GameContext";
|
|
5
|
+
import { Button } from "./ui/button";
|
|
6
|
+
|
|
7
|
+
export const WelcomeScreen = () => {
|
|
8
|
+
const { gameData, store } = useGameContext();
|
|
9
|
+
const { t } = useLocaleContext();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<div class="flex w-full flex-col justify-start">
|
|
14
|
+
<h2 class="text-left text-2xl font-bold uppercase">
|
|
15
|
+
{gameData.name ?? t("predictionGame.predictionGame")}
|
|
16
|
+
</h2>
|
|
17
|
+
|
|
18
|
+
<p class="text-base text-gray-400">
|
|
19
|
+
{gameData.description ?? t("predictionGame.welcomeText")}
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
<Button class="w-full" onClick={() => store.setHasStarted(true)}>
|
|
23
|
+
{t("predictionGame.playNow")}
|
|
24
|
+
</Button>
|
|
25
|
+
|
|
26
|
+
<Show when={gameData.settings?.termsUrl}>
|
|
27
|
+
{(termsUrl) => (
|
|
28
|
+
<a
|
|
29
|
+
href={termsUrl()}
|
|
30
|
+
target="_blank"
|
|
31
|
+
rel="noopener noreferrer"
|
|
32
|
+
class="text-xs underline opacity-75 hover:opacity-100"
|
|
33
|
+
style={{
|
|
34
|
+
color: gameData.settings?.questionTextColor || "#666666",
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{t("predictionGame.contestRules")}
|
|
38
|
+
</a>
|
|
39
|
+
)}
|
|
40
|
+
</Show>
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority";
|
|
2
|
+
import type { JSX } from "solid-js";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { Show, splitProps } from "solid-js";
|
|
6
|
+
|
|
7
|
+
import { Spinner } from "./spinner";
|
|
8
|
+
|
|
9
|
+
const buttonVariants = cva(
|
|
10
|
+
"group/button z-button inline-flex shrink-0 cursor-pointer items-center justify-center rounded-lg bg-black font-bold whitespace-nowrap uppercase transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default:
|
|
15
|
+
"bg-(--useaward-button-color) text-(--useaward-button-text-color)",
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: "px-4 py-3",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "default",
|
|
23
|
+
size: "default",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
interface ButtonProps
|
|
29
|
+
extends
|
|
30
|
+
JSX.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
31
|
+
VariantProps<typeof buttonVariants> {
|
|
32
|
+
fullWidth?: boolean;
|
|
33
|
+
loading?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const Button = (props: ButtonProps) => {
|
|
37
|
+
const [local, others] = splitProps(props, [
|
|
38
|
+
"variant",
|
|
39
|
+
"size",
|
|
40
|
+
"class",
|
|
41
|
+
"fullWidth",
|
|
42
|
+
"loading",
|
|
43
|
+
"children",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<button
|
|
48
|
+
class={cn(
|
|
49
|
+
buttonVariants({ variant: local.variant, size: local.size }),
|
|
50
|
+
local.class,
|
|
51
|
+
local.fullWidth && "w-full",
|
|
52
|
+
)}
|
|
53
|
+
data-slot="button"
|
|
54
|
+
{...others}
|
|
55
|
+
disabled={local.loading || others.disabled}
|
|
56
|
+
>
|
|
57
|
+
<Show when={local.loading} fallback={local.children}>
|
|
58
|
+
<Spinner />
|
|
59
|
+
</Show>
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export { Button, type ButtonProps, buttonVariants };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentProps } from "solid-js";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
import { LoaderCircle } from "lucide-solid";
|
|
4
|
+
|
|
5
|
+
function Spinner({
|
|
6
|
+
class: className,
|
|
7
|
+
...props
|
|
8
|
+
}: ComponentProps<typeof LoaderCircle>) {
|
|
9
|
+
return (
|
|
10
|
+
<LoaderCircle
|
|
11
|
+
role="status"
|
|
12
|
+
aria-label="Loading"
|
|
13
|
+
class={cn("size-4 animate-spin", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
export { Spinner };
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GameProps } from "./components/Game";
|
|
2
|
+
import * as en from "./locale/en";
|
|
3
|
+
import * as lv from "./locale/lv";
|
|
4
|
+
|
|
5
|
+
export const defaultGameProps: GameProps = {
|
|
6
|
+
predictionGameId: undefined,
|
|
7
|
+
apiBaseUrl: "http://localhost:3000",
|
|
8
|
+
email: undefined,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const defaultFontFamily = "Inter";
|
|
12
|
+
|
|
13
|
+
export const locales = { en, lv };
|
|
14
|
+
|
|
15
|
+
export const embedCssVariableNames = {
|
|
16
|
+
general: {
|
|
17
|
+
fontFamily: "--useaward-font-family",
|
|
18
|
+
backgroundColor: "--useaward-background-color",
|
|
19
|
+
},
|
|
20
|
+
embed: {
|
|
21
|
+
questionTextColor: "--useaward-question-color",
|
|
22
|
+
buttonBackground: "--useaward-button-color",
|
|
23
|
+
buttonTextColor: "--useaward-button-text-color",
|
|
24
|
+
selectionBackground: "--useaward-selection-background-color",
|
|
25
|
+
selectionTextColor: "--useaward-selection-text-color",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
import { createContext, useContext } from "solid-js";
|
|
3
|
+
|
|
4
|
+
import type { GameData, Step } from "../api/client";
|
|
5
|
+
import type { GameStore } from "../stores/gameStore";
|
|
6
|
+
|
|
7
|
+
export interface GameContextType {
|
|
8
|
+
gameData: GameData;
|
|
9
|
+
store: GameStore;
|
|
10
|
+
sortedSteps: () => Step[];
|
|
11
|
+
currentStep: () => Step | undefined;
|
|
12
|
+
totalSteps: () => number;
|
|
13
|
+
useStepByStep: () => boolean;
|
|
14
|
+
handleNext: () => void;
|
|
15
|
+
handleSubmit: () => void;
|
|
16
|
+
handleScorePredictionChange: (
|
|
17
|
+
stepId: string,
|
|
18
|
+
stepValueId: string,
|
|
19
|
+
score: number,
|
|
20
|
+
) => void;
|
|
21
|
+
handleCorrectValueChange: (stepId: string, stepValueId: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const GameContext = createContext<GameContextType>();
|
|
25
|
+
|
|
26
|
+
export function GameProvider(props: {
|
|
27
|
+
value: GameContextType;
|
|
28
|
+
children: JSX.Element;
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<GameContext.Provider value={props.value}>
|
|
32
|
+
{props.children}
|
|
33
|
+
</GameContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useGameContext(): GameContextType {
|
|
38
|
+
const context = useContext(GameContext);
|
|
39
|
+
if (!context) {
|
|
40
|
+
throw new Error("useGameContext must be used within a GameProvider");
|
|
41
|
+
}
|
|
42
|
+
return context;
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Accessor, Setter } from "solid-js";
|
|
2
|
+
import * as i18n from "@solid-primitives/i18n";
|
|
3
|
+
import { createContext, useContext } from "solid-js";
|
|
4
|
+
|
|
5
|
+
import type { Dictionary, Locale } from "../types/translations";
|
|
6
|
+
|
|
7
|
+
export interface LocaleContextType {
|
|
8
|
+
t: i18n.Translator<Dictionary, string>;
|
|
9
|
+
locale: Accessor<Locale>;
|
|
10
|
+
setLocale: Setter<Locale>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LocaleContext = createContext<LocaleContextType>();
|
|
14
|
+
|
|
15
|
+
export function LocaleProvider(props: {
|
|
16
|
+
value: LocaleContextType;
|
|
17
|
+
children: any;
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<LocaleContext.Provider value={props.value}>
|
|
21
|
+
{props.children}
|
|
22
|
+
</LocaleContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useLocaleContext(): LocaleContextType {
|
|
27
|
+
const context = useContext(LocaleContext);
|
|
28
|
+
if (!context) {
|
|
29
|
+
throw new Error("useLocaleContext must be used within a LocaleProvider");
|
|
30
|
+
}
|
|
31
|
+
return context;
|
|
32
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { EnvironmentProvider } from "@ark-ui/solid";
|
|
2
|
+
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
|
3
|
+
|
|
4
|
+
import type { GameProps } from "../../components/Game";
|
|
5
|
+
import styles from "../../assets/index.css";
|
|
6
|
+
import { Game } from "../../components/Game";
|
|
7
|
+
|
|
8
|
+
const hostElementCss = `
|
|
9
|
+
:host {
|
|
10
|
+
display: inline-block;
|
|
11
|
+
max-width: 100%;
|
|
12
|
+
width: 100%;
|
|
13
|
+
min-height: 220px;
|
|
14
|
+
overflow-y: hidden;
|
|
15
|
+
border: 2px solid black;
|
|
16
|
+
border-radius: 1rem;
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export const StandardGameEmbed = (
|
|
21
|
+
props: GameProps,
|
|
22
|
+
{ element }: { element: any },
|
|
23
|
+
) => {
|
|
24
|
+
const [isEmbedDisplayed, setIsEmbedDisplayed] = createSignal(false);
|
|
25
|
+
const launchEmbed = () => {
|
|
26
|
+
setIsEmbedDisplayed(true);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const embedLauncherObserver = new IntersectionObserver((intersections) => {
|
|
30
|
+
if (intersections.some((intersection) => intersection.isIntersecting))
|
|
31
|
+
launchEmbed();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
onMount(() => {
|
|
35
|
+
embedLauncherObserver.observe(element);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
onCleanup(() => {
|
|
39
|
+
embedLauncherObserver.disconnect();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<EnvironmentProvider
|
|
44
|
+
value={
|
|
45
|
+
document.querySelector("useaward-prediction-game")?.shadowRoot as Node
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
<style>
|
|
49
|
+
{styles}
|
|
50
|
+
{hostElementCss}
|
|
51
|
+
</style>
|
|
52
|
+
<Show when={isEmbedDisplayed()}>
|
|
53
|
+
<Game {...props} />
|
|
54
|
+
</Show>
|
|
55
|
+
</EnvironmentProvider>
|
|
56
|
+
);
|
|
57
|
+
};
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css";
|
package/src/locale/en.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const dict = {
|
|
2
|
+
predictionGame: {
|
|
3
|
+
predictionGame: "Prediction Game",
|
|
4
|
+
welcomeText: "Predict and win prizes",
|
|
5
|
+
playNow: "Play now",
|
|
6
|
+
confirmResult: "Confirm result",
|
|
7
|
+
submit: "Submit prediction",
|
|
8
|
+
predictionsSubmitted: "Thank you for playing!",
|
|
9
|
+
predictionsSubmittedDescription:
|
|
10
|
+
"Points will be credited to your profile immediately after the game ends.",
|
|
11
|
+
contestRules: "Contest rules",
|
|
12
|
+
predictionsNotOpenYet: "Prediction game is not open yet",
|
|
13
|
+
predictionsNotOpenYetDescription:
|
|
14
|
+
"This prediction game will open soon. Please check back later.",
|
|
15
|
+
predictionsClosed: "Prediction game is closed",
|
|
16
|
+
predictionsClosedDescription:
|
|
17
|
+
"This prediction game is no longer accepting submissions. Please check back later.",
|
|
18
|
+
error: {
|
|
19
|
+
prediction: {
|
|
20
|
+
required: "Please select a prediction",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
package/src/locale/lv.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const dict = {
|
|
2
|
+
predictionGame: {
|
|
3
|
+
predictionGame: "Prognožu spēle",
|
|
4
|
+
welcomeText: "Prognozē un laimē balvas",
|
|
5
|
+
playNow: "Spēlēt tagad",
|
|
6
|
+
confirmResult: "Apstiprināt rezultātu",
|
|
7
|
+
submit: "Iesniegt prognozi",
|
|
8
|
+
predictionsSubmitted: "Paldies par spēli!",
|
|
9
|
+
predictionsSubmittedDescription:
|
|
10
|
+
"Punkti tiks ieskaitīti Jūsu profilā uzreiz pēc spēles beigām.",
|
|
11
|
+
contestRules: "Konkursa noteikumi",
|
|
12
|
+
predictionsNotOpenYet: "Prognožu spēle vēl nav pieejama",
|
|
13
|
+
predictionsNotOpenYetDescription:
|
|
14
|
+
"Šī spēle būs pieejama drīzumā. Lūdzu, pārbaudiet vēlāk.",
|
|
15
|
+
predictionsClosed: "Prognožu spēle ir slēgta",
|
|
16
|
+
predictionsClosedDescription:
|
|
17
|
+
"Šī prognožu spēle ir beigusies un vairs nepieņem jaunas prognozes.",
|
|
18
|
+
error: {
|
|
19
|
+
prediction: {
|
|
20
|
+
required: "Lūdzu, izvēlieties prognozi",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { customElement } from "solid-element";
|
|
2
|
+
|
|
3
|
+
import { defaultGameProps } from "./constants";
|
|
4
|
+
import { StandardGameEmbed } from "./features/standard/StandardGameEmbed";
|
|
5
|
+
|
|
6
|
+
export const registerWebComponents = () => {
|
|
7
|
+
if (typeof window === "undefined") return;
|
|
8
|
+
customElement(
|
|
9
|
+
"useaward-prediction-game",
|
|
10
|
+
defaultGameProps,
|
|
11
|
+
StandardGameEmbed,
|
|
12
|
+
);
|
|
13
|
+
};
|