feature/plausible #3

Merged
rui.moraes merged 2 commits from feature/plausible into main 2026-06-13 22:09:53 -03:00
10 changed files with 102 additions and 37 deletions
Showing only changes of commit 1c496e8d8d - Show all commits

2
app/.env Normal file
View File

@@ -0,0 +1,2 @@
VITE_PLAUSIBLE_API=http://localhost/api/event
VITE_PLAUSIBLE_DOMAIN=myapp-dev

2
app/.env.production Normal file
View File

@@ -0,0 +1,2 @@
VITE_PLAUSIBLE_API=https://plausible.mtst.tec.br/api/event
VITE_PLAUSIBLE_DOMAIN=https://decoda.mtst.tec.br

View File

@@ -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

View File

@@ -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",

View File

@@ -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 />

View File

@@ -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;
} }

View File

@@ -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}

View 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,
});
};

View File

@@ -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(),