Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Api/wwwroot/css/kanban.css
Original file line number Diff line number Diff line change
Expand Up @@ -1039,14 +1039,14 @@ body.sidebar-closed .kanban-head {
.task-card::before {
content: "";
position: absolute;
left: 1px;
top: 1px;
bottom: 1px;
left: 0;
top: 0;
bottom: 0;

width: 6px;

background: var(--task-color, var(--orange));
border-radius: 3px;
border-radius: 12px 0 0 12px;
}

.task-top {
Expand Down
9 changes: 7 additions & 2 deletions Api/wwwroot/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -745,15 +745,16 @@ button.section-head:hover {

.team-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px 20px;
padding-right: calc(var(--sidebar-hover-shift) + 10px);
}

.team-card {
min-width: 0;
min-height: 68px;
border-radius: 24px;
padding: 0 26px;
padding: 14px 20px;
border: 1px solid #3a3a3a;
background: transparent;
color: #f4f4f4;
Expand All @@ -772,8 +773,12 @@ button.section-head:hover {
}

.team-card span:first-child {
min-width: 0;
font-size: 22px;
font-weight: 300;
line-height: 1.2;
overflow-wrap: anywhere;
white-space: normal;
}
.team-card:hover {
background: var(--sidebar-hover-bg);
Expand Down
11 changes: 11 additions & 0 deletions Api/wwwroot/js/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const loginToRegisterBtn = document.getElementById("loginToRegister");
const registerToLoginBtn = document.getElementById("registerToLogin");
const loginForm = document.getElementById("loginForm");
const registerForm = document.getElementById("registerForm");
const TUTORIAL_PENDING_KEY = "boardify.tutorialPending";

showLoginBtn?.addEventListener("click", () => setMode("login"));
showRegisterBtn?.addEventListener("click", () => setMode("register"));
Expand Down Expand Up @@ -60,6 +61,7 @@ registerForm?.addEventListener("submit", async (event) => {
await runAuthAction(registerForm, async () => {
await createUser(email, password, name);
await login(email, password);
markTutorialPending();
setMessage("Аккаунт создан, открываем Boardify...", "success");
redirectToApp();
}, "Не удалось зарегистрироваться. Возможно, email уже занят.");
Expand Down Expand Up @@ -134,5 +136,14 @@ function redirectToApp() {
}, 250);
}

function markTutorialPending() {
try {
localStorage.setItem("boardify.tutorialPending", "true");
localStorage.removeItem("boardify.tutorialSeen");
} catch {
// Registration should continue even if localStorage is unavailable.
}
}

const params = new URLSearchParams(window.location.search);
setMode(params.get("mode") || "start", { keepMessage: true });
34 changes: 23 additions & 11 deletions Api/wwwroot/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
selectProjectFromUrl,
} from "./boardData.js";

import { showFirstLoginTutorial } from "./tutorial.js";

const matrix = document.querySelector("#taskMatrix");

const GROUPS = [
Expand All @@ -28,22 +30,15 @@ let realtimeWorkspaceRefreshTimer = null;
let realtimeWorkspaceRefreshRequest = null;

function row(task) {
const el = document.createElement("a");

const projectId = task.projectId || task.project?.id || "";
const kanbanId = task.kanbanId || task.kanban?.id || "";

const el = document.createElement("article");
el.className = "task-row";
el.href = `./kanban.html?project=${encodeURIComponent(projectId)}&kanban=${encodeURIComponent(kanbanId)}`;

el.innerHTML = `
<div class="task-title">${escapeHtml(task.title)}</div>
<div class="divider"></div>
<div class="task-meta"><span>приоритет</span><span>${escapeHtml(task.priority)}</span></div>
<div class="divider"></div>
<div class="task-meta"><span>Дедлайн через</span><span>${escapeHtml(formatTimeLeft(task.deadline))}</span></div>
`;

return el;
}

Expand All @@ -56,13 +51,16 @@ function renderMatrix(tasks) {
q.dataset.label = group.label;

const groupTasks = tasks.filter((task) =>
isImportant(task) === group.important && isUrgent(task) === group.urgent
isImportant(task) === group.important &&
isUrgent(task) === group.urgent
);

if (groupTasks.length === 0) {
q.appendChild(emptyRow("Нет задач"));
} else {
groupTasks.slice(0, 4).forEach((task) => q.appendChild(row(task)));
groupTasks.slice(0, 4).forEach((task) => {
q.appendChild(row(task));
});
}

matrix.appendChild(q);
Expand All @@ -73,6 +71,7 @@ function emptyRow(text) {
const el = document.createElement("p");
el.className = "empty-state";
el.textContent = text;

return el;
}

Expand All @@ -88,6 +87,7 @@ function isUrgent(task) {
}

const diffMs = deadline.getTime() - Date.now();

return diffMs <= 3 * 24 * 60 * 60 * 1000;
}

Expand All @@ -96,7 +96,9 @@ async function init() {

try {
const workspace = await loadWorkspace();

const activeProject = selectProjectFromUrl(workspace.projects);

const boards = await Promise.all(
workspace.projects.map((project) =>
loadBoard(project).catch(() => null)
Expand All @@ -122,21 +124,31 @@ async function init() {

renderSidebar();
renderMatrix(tasks);

showFirstLoginTutorial();
} catch (error) {
console.error(error);

matrix.innerHTML = "";

GROUPS.forEach((group) => {
const q = document.createElement("section");
q.className = "quadrant";
q.dataset.label = group.label;

q.appendChild(emptyRow("Не удалось загрузить API"));

matrix.appendChild(q);
});
}
}

function renderSidebar() {
renderSidebarNavigation(state.workspace, state.activeProjectId, getTaskStats(state.tasks));
renderSidebarNavigation(
state.workspace,
state.activeProjectId,
getTaskStats(state.tasks)
);
}

function handleRealtimeNotification(notification = {}) {
Expand Down
34 changes: 30 additions & 4 deletions Api/wwwroot/js/tutorial.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const TUTORIAL_STORAGE_KEY = "boardify.tutorialSeen";
const TUTORIAL_PENDING_KEY = "boardify.tutorialPending";
const TUTORIAL_CSS_ID = "boardifyTutorialStyles";
const TUTORIAL_CSS_HREF = "./css/tutorial.css";

export function showFirstLoginTutorial() {
if (wasTutorialSeen() || document.querySelector("[data-tutorial-overlay]")) {
if (!shouldShowTutorial() || document.querySelector("[data-tutorial-overlay]")) {
return;
}

Expand All @@ -21,6 +22,7 @@ export function showFirstLoginTutorial() {

overlay.querySelector("[data-tutorial-close]")?.addEventListener("click", close);
overlay.querySelector("[data-tutorial-done]")?.addEventListener("click", close);

overlay.addEventListener("click", (event) => {
if (event.target === overlay) {
close();
Expand All @@ -34,6 +36,7 @@ export function showFirstLoginTutorial() {
};

document.addEventListener("keydown", onKeyDown);

overlay.cleanupTutorial = () => {
document.removeEventListener("keydown", onKeyDown);
};
Expand All @@ -53,25 +56,30 @@ function createTutorialMarkup() {
</button>

<p class="tutorial-eyebrow">Быстрый старт</p>

<h2 id="tutorialTitle">С чего начать?</h2>

<ol class="tutorial-steps">
<li>
<span class="tutorial-step-index">1</span>
<span>Откройте меню: нажмите на три полоски слева сверху.</span>
</li>

<li>
<span class="tutorial-step-index">2</span>
<span>Перейдите в «Команды» и нажмите «Добавить»: задайте название, цвет и пригласите участников по имени или email.</span>
</li>

<li>
<span class="tutorial-step-index">3</span>
<span>Откройте «Проекты» и создайте первый проект для своей команды.</span>
</li>

<li>
<span class="tutorial-step-index">4</span>
<span>Внутри проекта добавьте kanban-доску.</span>
</li>

<li>
<span class="tutorial-step-index">5</span>
<span>Готово: как админ команды вы можете создавать колонки, задачи и подзадачи.</span>
Expand All @@ -87,8 +95,11 @@ function createTutorialMarkup() {

function closeTutorial(overlay) {
markTutorialSeen();
clearTutorialPending();

overlay.classList.remove("is-open");
document.body.classList.remove("tutorial-locked");

overlay.cleanupTutorial?.();

setTimeout(() => {
Expand All @@ -105,12 +116,19 @@ function ensureTutorialStyles() {
link.id = TUTORIAL_CSS_ID;
link.rel = "stylesheet";
link.href = TUTORIAL_CSS_HREF;

document.head.appendChild(link);
}

function wasTutorialSeen() {
function shouldShowTutorial() {
try {
return localStorage.getItem(TUTORIAL_STORAGE_KEY) === "true";
const isPending = localStorage.getItem(TUTORIAL_PENDING_KEY) === "true";

if (isPending) {
return true;
}

return false;
} catch {
return false;
}
Expand All @@ -120,6 +138,14 @@ function markTutorialSeen() {
try {
localStorage.setItem(TUTORIAL_STORAGE_KEY, "true");
} catch {
// Ignore storage errors: closing the dialog should still work.
// Ignore storage errors.
}
}

function clearTutorialPending() {
try {
localStorage.removeItem(TUTORIAL_PENDING_KEY);
} catch {
// Ignore storage errors.
}
}