feature/plausible #3
2
app/.env
Normal file
2
app/.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_PLAUSIBLE_API=http://localhost/api/event
|
||||||
|
VITE_PLAUSIBLE_DOMAIN=myapp-dev
|
||||||
2
app/.env.production
Normal file
2
app/.env.production
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_PLAUSIBLE_API=https://plausible.mtst.tec.br/api/event
|
||||||
|
VITE_PLAUSIBLE_DOMAIN=https://decoda.mtst.tec.br
|
||||||
@@ -3,7 +3,7 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG GIT_COMMIT_HASH=unknown
|
ARG GIT_COMMIT_HASH=unknown
|
||||||
ARG APP_VERSION=1.1.2
|
ARG APP_VERSION=1.2.0
|
||||||
ENV VITE_APP_VERSION=$APP_VERSION
|
ENV VITE_APP_VERSION=$APP_VERSION
|
||||||
ENV VITE_GIT_HASH=$GIT_COMMIT_HASH
|
ENV VITE_GIT_HASH=$GIT_COMMIT_HASH
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "decoda",
|
"name": "decoda",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Aplicação educacional desenvolvida para ensino de programação básica e letramento digital",
|
"description": "Aplicação educacional desenvolvida para ensino de programação básica e letramento digital",
|
||||||
"version": "1.1.2",
|
"version": "1.2.0",
|
||||||
"main": "main.cjs",
|
"main": "main.cjs",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
* @module App
|
* @module App
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { lazy, Suspense } from "react";
|
import { lazy, Suspense, useEffect } from "react";
|
||||||
import { HashRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
import { HashRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import HomePage from "./pages/HomePage/HomePage";
|
import HomePage from "./pages/HomePage/HomePage";
|
||||||
import LabPython from "./pages/LabPython/LabPython";
|
import LabPython from "./pages/LabPython/LabPython";
|
||||||
import ScrollToTop from "./components/ScrollToTop";
|
import ScrollToTop from "./components/ScrollToTop";
|
||||||
|
import { trackPageView } from "./services/plausible";
|
||||||
|
|
||||||
const Playground = lazy(() => import("./pages/Playground/Playground"));
|
const Playground = lazy(() => import("./pages/Playground/Playground"));
|
||||||
const About = lazy(() => import("./pages/About/About"));
|
const About = lazy(() => import("./pages/About/About"));
|
||||||
@@ -68,6 +69,10 @@ function AppRoutes() {
|
|||||||
// keeps rendering behind the modal overlay.
|
// keeps rendering behind the modal overlay.
|
||||||
const backgroundLocation = location.state?.backgroundLocation;
|
const backgroundLocation = location.state?.backgroundLocation;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackPageView(location.pathname);
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
|
|||||||
@@ -653,10 +653,6 @@ video {
|
|||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-0 {
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-10 {
|
.bottom-10 {
|
||||||
bottom: 2.5rem;
|
bottom: 2.5rem;
|
||||||
}
|
}
|
||||||
@@ -984,10 +980,6 @@ video {
|
|||||||
height: 0.25rem;
|
height: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-1\.5 {
|
|
||||||
height: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-10 {
|
.h-10 {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
@@ -2885,10 +2877,6 @@ video {
|
|||||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray-200\/50 {
|
|
||||||
background-color: rgb(229 231 235 / 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-300 {
|
.bg-gray-300 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
||||||
@@ -7125,10 +7113,6 @@ video {
|
|||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration-1000 {
|
|
||||||
transition-duration: 1000ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration-150 {
|
.duration-150 {
|
||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
}
|
}
|
||||||
@@ -7149,10 +7133,6 @@ video {
|
|||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ease-out {
|
|
||||||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.\[-webkit-text-stroke\:2px_black\] {
|
.\[-webkit-text-stroke\:2px_black\] {
|
||||||
-webkit-text-stroke: 2px black;
|
-webkit-text-stroke: 2px black;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import React, {
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { gameEventBus } from "../utils/gameEvents";
|
import { gameEventBus } from "../utils/gameEvents";
|
||||||
import { GameProgressProvider, useGameProgress } from "./GameProgressContext";
|
import { GameProgressProvider, useGameProgress } from "./GameProgressContext";
|
||||||
|
import { trackEvent } from "../services/plausible";
|
||||||
|
|
||||||
export const GAME_STATES = {
|
export const GAME_STATES = {
|
||||||
PARADO: "parado",
|
PARADO: "parado",
|
||||||
@@ -89,6 +90,9 @@ function GameStateInnerProvider({ children, gameConfig }) {
|
|||||||
);
|
);
|
||||||
return urlParams.get(debugKey)?.toLowerCase() === "true";
|
return urlParams.get(debugKey)?.toLowerCase() === "true";
|
||||||
});
|
});
|
||||||
|
const [phaseStartTime, setPhaseStartTime] = useState(null);
|
||||||
|
const [attemptCount, setAttemptCount] = useState(0);
|
||||||
|
const [failureCount, setFailureCount] = useState(0);
|
||||||
|
|
||||||
const getCodeFromWorkspace = useRef(null);
|
const getCodeFromWorkspace = useRef(null);
|
||||||
const getCodeFromEditor = useRef(null);
|
const getCodeFromEditor = useRef(null);
|
||||||
@@ -103,6 +107,8 @@ function GameStateInnerProvider({ children, gameConfig }) {
|
|||||||
* @throws {console.error} Se editor não registrou a função de execução
|
* @throws {console.error} Se editor não registrou a função de execução
|
||||||
*/
|
*/
|
||||||
const execute = () => {
|
const execute = () => {
|
||||||
|
setAttemptCount(prev => prev + 1);
|
||||||
|
|
||||||
if (editorType === "code") {
|
if (editorType === "code") {
|
||||||
if (getCodeFromEditor.current) {
|
if (getCodeFromEditor.current) {
|
||||||
const codigo = getCodeFromEditor.current();
|
const codigo = getCodeFromEditor.current();
|
||||||
@@ -145,6 +151,19 @@ function GameStateInnerProvider({ children, gameConfig }) {
|
|||||||
if (!completedPhases.includes(currentPhase)) {
|
if (!completedPhases.includes(currentPhase)) {
|
||||||
setCompletedPhases([...completedPhases, currentPhase]);
|
setCompletedPhases([...completedPhases, currentPhase]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const durationSeconds = phaseStartTime
|
||||||
|
? Math.round((Date.now() - phaseStartTime) / 1000)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
trackEvent('Activity Success', {
|
||||||
|
activity: gameConfig.gameId,
|
||||||
|
phase: currentPhase,
|
||||||
|
blocks: currentBlockCount,
|
||||||
|
attempts: attemptCount,
|
||||||
|
failures: failureCount,
|
||||||
|
durationSeconds,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,6 +176,14 @@ function GameStateInnerProvider({ children, gameConfig }) {
|
|||||||
*/
|
*/
|
||||||
const finalizeWithFailure = () => {
|
const finalizeWithFailure = () => {
|
||||||
setExecutionState(GAME_STATES.FALHA);
|
setExecutionState(GAME_STATES.FALHA);
|
||||||
|
setFailureCount(prev => prev + 1);
|
||||||
|
|
||||||
|
trackEvent('Activity Failure', {
|
||||||
|
activity: gameConfig.gameId,
|
||||||
|
phase: currentPhase,
|
||||||
|
blocks: currentBlockCount,
|
||||||
|
error: failureMessage,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +225,9 @@ function GameStateInnerProvider({ children, gameConfig }) {
|
|||||||
setGeneratedCode("");
|
setGeneratedCode("");
|
||||||
setCurrentBlockCount(0);
|
setCurrentBlockCount(0);
|
||||||
setCodeEditorContent("");
|
setCodeEditorContent("");
|
||||||
|
setPhaseStartTime(Date.now());
|
||||||
|
setAttemptCount(0);
|
||||||
|
setFailureCount(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -317,6 +347,8 @@ function GameStateInnerProvider({ children, gameConfig }) {
|
|||||||
setFailureMessage,
|
setFailureMessage,
|
||||||
isDebugMode,
|
isDebugMode,
|
||||||
setIsDebugMode,
|
setIsDebugMode,
|
||||||
|
attemptCount,
|
||||||
|
failureCount,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
41
app/src/services/plausible.js
Normal file
41
app/src/services/plausible.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const PLAUSIBLE_API = import.meta.env.VITE_PLAUSIBLE_API || 'http://localhost/api/event';
|
||||||
|
const DOMAIN = import.meta.env.VITE_PLAUSIBLE_DOMAIN || 'myapp-dev';
|
||||||
|
|
||||||
|
const sendEvent = (payload) => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
fetch(PLAUSIBLE_API, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
.catch((e) => console.error('Plausible error:', e))
|
||||||
|
.finally(() => clearTimeout(timeout));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackPageView = (pathname) => {
|
||||||
|
if (!pathname) return;
|
||||||
|
|
||||||
|
sendEvent({
|
||||||
|
domain: DOMAIN,
|
||||||
|
url: `${window.location.origin}${pathname}`,
|
||||||
|
referrer: document.referrer,
|
||||||
|
screenWidth: window.innerWidth,
|
||||||
|
name: 'pageview',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackEvent = (eventName, properties = {}) => {
|
||||||
|
if (!eventName) return;
|
||||||
|
|
||||||
|
sendEvent({
|
||||||
|
domain: DOMAIN,
|
||||||
|
url: window.location.href,
|
||||||
|
referrer: document.referrer,
|
||||||
|
screenWidth: window.innerWidth,
|
||||||
|
name: eventName,
|
||||||
|
props: properties,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -74,6 +74,9 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
allowedHosts: ["localhost", "dev.local", "decoda.mtst.tec.br"],
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
copyLetramentoAtividades(),
|
copyLetramentoAtividades(),
|
||||||
|
|||||||
Reference in New Issue
Block a user