OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
82
app/Domain/Help/Composers/Helpermodal.php
Normal file
82
app/Domain/Help/Composers/Helpermodal.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Composers;
|
||||
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Leantime\Core\Controller\Composer;
|
||||
use Leantime\Core\Controller\Frontcontroller as FrontcontrollerCore;
|
||||
use Leantime\Domain\Auth\Services\Auth;
|
||||
use Leantime\Domain\Help\Services\Helper;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
|
||||
class Helpermodal extends Composer
|
||||
{
|
||||
private Setting $settingsRepo;
|
||||
|
||||
private Helper $helperService;
|
||||
|
||||
private Auth $authService;
|
||||
|
||||
public static array $views = [
|
||||
'help::helpermodal',
|
||||
];
|
||||
|
||||
public function init(
|
||||
Setting $settingsRepo,
|
||||
Helper $helperService,
|
||||
Auth $authService
|
||||
): void {
|
||||
$this->settingsRepo = $settingsRepo;
|
||||
$this->helperService = $helperService;
|
||||
$this->authService = $authService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function with(): array
|
||||
{
|
||||
$action = FrontcontrollerCore::getCurrentRoute();
|
||||
|
||||
// Don't show modals in test environment
|
||||
if (app()->environment('testing')) {
|
||||
return ['showHelperModal' => false, 'currentModal' => [], 'isFirstLogin' => false];
|
||||
}
|
||||
|
||||
$showHelperModal = false;
|
||||
$completedOnboarding = $this->settingsRepo->getSetting('companysettings.completedOnboarding');
|
||||
$isFirstLogin = $this->helperService->isFirstLogin($this->authService->getUserId());
|
||||
|
||||
// Backwards compatibilty
|
||||
if ($isFirstLogin && $completedOnboarding) {
|
||||
$isFirstLogin = false;
|
||||
}
|
||||
|
||||
$currentModal = $this->helperService->getHelperModalByRoute($action);
|
||||
|
||||
if (
|
||||
$isFirstLogin === false
|
||||
&& $currentModal['template'] !== 'notfound'
|
||||
&& (
|
||||
session()->exists('usersettings.modals.'.$currentModal['template']) === false
|
||||
|| session('usersettings.modals.'.$currentModal['template']) === false)
|
||||
) {
|
||||
if (! session()->exists('usersettings.modals')) {
|
||||
session(['usersettings.modals' => []]);
|
||||
}
|
||||
|
||||
if (! session()->exists('usersettings.modals.'.$currentModal['template'])) {
|
||||
session(['usersettings.modals.'.$currentModal['template'] => 1]);
|
||||
$showHelperModal = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For development purposes, always show the modal
|
||||
return [
|
||||
'completedOnboarding' => $completedOnboarding,
|
||||
'showHelperModal' => $showHelperModal,
|
||||
'currentModal' => $currentModal['template'],
|
||||
'isFirstLogin' => $isFirstLogin,
|
||||
];
|
||||
}
|
||||
}
|
||||
14
app/Domain/Help/Contracts/OnboardingSteps.php
Normal file
14
app/Domain/Help/Contracts/OnboardingSteps.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Contracts;
|
||||
|
||||
interface OnboardingSteps
|
||||
{
|
||||
public function getTitle(): string;
|
||||
|
||||
public function getAction(): string;
|
||||
|
||||
public function getTemplate(): string;
|
||||
|
||||
public function handle($params): bool;
|
||||
}
|
||||
61
app/Domain/Help/Controllers/FirstLogin.php
Normal file
61
app/Domain/Help/Controllers/FirstLogin.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Controllers;
|
||||
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Domain\Help\Services\Helper;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FirstLogin extends Controller
|
||||
{
|
||||
private Helper $helperService;
|
||||
|
||||
/**
|
||||
* Injects the Helper service.
|
||||
*/
|
||||
public function init(Helper $helperService): void
|
||||
{
|
||||
$this->helperService = $helperService;
|
||||
}
|
||||
|
||||
/**
|
||||
* get - handle get requests
|
||||
*
|
||||
* Renders the appropriate first-login onboarding step partial.
|
||||
*
|
||||
* @param array $params Request parameters.
|
||||
*/
|
||||
public function get($params): Response
|
||||
{
|
||||
$step = $this->helperService->resolveFirstLoginStep($_GET['step'] ?? null);
|
||||
|
||||
if ($step['isEnd']) {
|
||||
return $this->tpl->displayPartial($step['template']);
|
||||
}
|
||||
|
||||
$this->tpl->assign('currentStep', $step['key']);
|
||||
$this->tpl->assign('nextStep', $step['next']);
|
||||
|
||||
return $this->tpl->displayPartial($step['template']);
|
||||
}
|
||||
|
||||
/**
|
||||
* post - handle post requests
|
||||
*
|
||||
* Delegates onboarding step handling to the Helper service and redirects
|
||||
* to the resolved next step.
|
||||
*
|
||||
* @param array $params Request parameters.
|
||||
*/
|
||||
public function post($params): Response
|
||||
{
|
||||
$result = $this->helperService->handleFirstLoginStep($params);
|
||||
|
||||
if (! $result['valid']) {
|
||||
return Frontcontroller::redirect(BASE_URL.'/help/firstLogin');
|
||||
}
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/help/firstLogin?step='.$result['next']);
|
||||
}
|
||||
}
|
||||
42
app/Domain/Help/Controllers/ShowOnboardingDialog.php
Normal file
42
app/Domain/Help/Controllers/ShowOnboardingDialog.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Controllers;
|
||||
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Domain\Help\Services\Helper;
|
||||
|
||||
class ShowOnboardingDialog extends Controller
|
||||
{
|
||||
protected Helper $helpService;
|
||||
|
||||
/**
|
||||
* Injects the Helper service.
|
||||
*/
|
||||
public function init(Helper $helpService): void
|
||||
{
|
||||
$this->helpService = $helpService;
|
||||
}
|
||||
|
||||
/**
|
||||
* get - handle get requests
|
||||
*
|
||||
* Renders the onboarding modal partial for the requested module or route.
|
||||
* The "show once per session" bookkeeping and sanitization live in the service.
|
||||
*
|
||||
* @param array $params Request parameters.
|
||||
*/
|
||||
public function get($params)
|
||||
{
|
||||
if (isset($params['module']) && $params['module'] != '') {
|
||||
$template = $this->helpService->markModalSeenForModule($params['module']);
|
||||
|
||||
return $this->tpl->displayPartial('help.'.$template);
|
||||
}
|
||||
|
||||
if (isset($params['route']) && $params['route'] != '') {
|
||||
$template = $this->helpService->markModalSeenForRoute($params['route']);
|
||||
|
||||
return $this->tpl->displayPartial('help.'.$template);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
app/Domain/Help/Controllers/Support.php
Normal file
27
app/Domain/Help/Controllers/Support.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Controllers;
|
||||
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Domain\Help\Services\Helper;
|
||||
|
||||
class Support extends Controller
|
||||
{
|
||||
protected Helper $helpService;
|
||||
|
||||
public function init(Helper $helpService)
|
||||
{
|
||||
$this->helpService = $helpService;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* get - handle get requests
|
||||
*/
|
||||
public function get($params)
|
||||
{
|
||||
|
||||
return $this->tpl->display('help.support');
|
||||
|
||||
}
|
||||
}
|
||||
28
app/Domain/Help/Controllers/Updates.php
Normal file
28
app/Domain/Help/Controllers/Updates.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Controllers;
|
||||
|
||||
use Leantime\Core\Controller\Controller;
|
||||
|
||||
class Updates extends Controller
|
||||
{
|
||||
/**
|
||||
* get - handle get requests
|
||||
*/
|
||||
public function get($params) {}
|
||||
|
||||
/**
|
||||
* post - handle post requests
|
||||
*/
|
||||
public function post($params) {}
|
||||
|
||||
/**
|
||||
* put - handle put requests
|
||||
*/
|
||||
public function put($params) {}
|
||||
|
||||
/**
|
||||
* delete - handle delete requests
|
||||
*/
|
||||
public function delete($params) {}
|
||||
}
|
||||
46
app/Domain/Help/Hxcontrollers/HelperModal.php
Normal file
46
app/Domain/Help/Hxcontrollers/HelperModal.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Hxcontrollers;
|
||||
|
||||
use Leantime\Core\Controller\HtmxController;
|
||||
use Leantime\Domain\Help\Services\Helper;
|
||||
use Leantime\Domain\Users\Services\Users as UserService;
|
||||
|
||||
class HelperModal extends HtmxController
|
||||
{
|
||||
protected static string $view = '';
|
||||
|
||||
protected Helper $helperService;
|
||||
|
||||
protected UserService $userService;
|
||||
|
||||
/**
|
||||
* Controller constructor
|
||||
*
|
||||
* @param Helper $helperService The help domain service.
|
||||
* @param UserService $userService The users domain service.
|
||||
* @return void
|
||||
*/
|
||||
public function init(
|
||||
Helper $helperService,
|
||||
UserService $userService
|
||||
) {
|
||||
$this->helperService = $helperService;
|
||||
$this->userService = $userService;
|
||||
}
|
||||
|
||||
public function get() {}
|
||||
|
||||
public function dontShowAgain($params)
|
||||
{
|
||||
|
||||
$modal = $params['modalId'] ?? '';
|
||||
$hidePermanently = ($params['hidePermanently'] ?? false) === 'on' ? true : false;
|
||||
|
||||
if ($modal !== '') {
|
||||
$this->userService->updateUserSettings('modals', $modal, $hidePermanently);
|
||||
}
|
||||
|
||||
return $this->tpl->emptyResponse();
|
||||
}
|
||||
}
|
||||
52
app/Domain/Help/Js/confettiHelper.js
Normal file
52
app/Domain/Help/Js/confettiHelper.js
Normal file
@@ -0,0 +1,52 @@
|
||||
leantime.confettiHelper = (function () {
|
||||
|
||||
/**
|
||||
* Trigger a confetti animation
|
||||
* @param {Object} options - Confetti options
|
||||
*/
|
||||
var triggerConfetti = function(options) {
|
||||
const defaultOptions = {
|
||||
particleCount: 100,
|
||||
spread: 70,
|
||||
origin: { y: 0.6 },
|
||||
disableForReducedMotion: true
|
||||
};
|
||||
|
||||
// Merge default options with provided options
|
||||
const confettiOptions = {...defaultOptions, ...options};
|
||||
|
||||
// Check if confetti library is loaded
|
||||
if (typeof confetti === 'function') {
|
||||
confetti(confettiOptions);
|
||||
} else {
|
||||
console.error('Confetti library not loaded');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger a success celebration with confetti
|
||||
*/
|
||||
var celebrateSuccess = function() {
|
||||
triggerConfetti({
|
||||
particleCount: 150,
|
||||
spread: 90,
|
||||
origin: { y: 0.8 },
|
||||
colors: ['#26a69a', '#00bcd4', '#4caf50', '#8bc34a', '#cddc39']
|
||||
});
|
||||
|
||||
// Add a second burst for more effect
|
||||
setTimeout(function() {
|
||||
triggerConfetti({
|
||||
particleCount: 80,
|
||||
spread: 120,
|
||||
origin: { y: 0.7 },
|
||||
colors: ['#ff9800', '#ff5722', '#f44336', '#e91e63', '#9c27b0']
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return {
|
||||
triggerConfetti: triggerConfetti,
|
||||
celebrateSuccess: celebrateSuccess
|
||||
};
|
||||
})();
|
||||
51
app/Domain/Help/Js/firstTaskController.js
Normal file
51
app/Domain/Help/Js/firstTaskController.js
Normal file
@@ -0,0 +1,51 @@
|
||||
leantime.firstTaskController = (function () {
|
||||
|
||||
/**
|
||||
* Initialize the first task form
|
||||
*/
|
||||
var initFirstTaskForm = function() {
|
||||
jQuery(document).ready(function() {
|
||||
const firstTaskForm = jQuery('#firstTaskOnboarding');
|
||||
|
||||
if (firstTaskForm.length > 0) {
|
||||
firstTaskForm.on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const taskInput = jQuery('#firstTask');
|
||||
const taskText = taskInput.val().trim();
|
||||
|
||||
if (taskText === '') {
|
||||
// Show validation error
|
||||
taskInput.addClass('error');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove any error styling
|
||||
taskInput.removeClass('error');
|
||||
|
||||
// Show confetti animation
|
||||
leantime.confettiHelper.celebrateSuccess();
|
||||
|
||||
// Wait for confetti animation before submitting
|
||||
setTimeout(function() {
|
||||
firstTaskForm[0].submit();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Remove error styling when user starts typing
|
||||
jQuery('#firstTask').on('input', function() {
|
||||
jQuery(this).removeClass('error');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
initFirstTaskForm: initFirstTaskForm
|
||||
};
|
||||
})();
|
||||
|
||||
// Initialize when document is ready
|
||||
jQuery(document).ready(function() {
|
||||
leantime.firstTaskController.initFirstTaskForm();
|
||||
});
|
||||
137
app/Domain/Help/Js/helperController.js
Normal file
137
app/Domain/Help/Js/helperController.js
Normal file
@@ -0,0 +1,137 @@
|
||||
leantime.helperController = (function () {
|
||||
|
||||
var setDontShowAgain = function(module, dontShow) {
|
||||
if (dontShow) {
|
||||
leantime.helperRepository.updateUserModalSettings(module, true);
|
||||
} else {
|
||||
leantime.helperRepository.updateUserModalSettings(module, false);
|
||||
}
|
||||
};
|
||||
|
||||
var startMyWorkDashboardTour = function () {
|
||||
leantime.modals.setCustomModalCallback(function(){});
|
||||
leantime.modals.closeModal();
|
||||
|
||||
// Use the tour factory instead
|
||||
return leantime.tourFactory.startTour('myWorkDashboard');
|
||||
};
|
||||
|
||||
var closeModal = function() {
|
||||
leantime.modals.setCustomModalCallback(function(){});
|
||||
leantime.modals.closeModal();
|
||||
|
||||
}
|
||||
|
||||
var startProjectDashboardTour = function () {
|
||||
leantime.modals.setCustomModalCallback(function(){});
|
||||
leantime.modals.closeModal();
|
||||
|
||||
leantime.helperRepository.updateUserModalSettings("projectDashboard");
|
||||
return leantime.tourFactory.startTour('projectDashboard');
|
||||
};
|
||||
|
||||
var startKanbanTour = function () {
|
||||
if(jQuery.nmTop()) {
|
||||
jQuery.nmTop().close();
|
||||
}
|
||||
|
||||
leantime.helperRepository.updateUserModalSettings("kanbanBoard");
|
||||
return leantime.tourFactory.startTour('kanbanBoard');
|
||||
};
|
||||
|
||||
var startMilestoneTour = function () {
|
||||
if(jQuery.nmTop()) {
|
||||
jQuery.nmTop().close();
|
||||
}
|
||||
|
||||
leantime.helperRepository.updateUserModalSettings("milestoneView");
|
||||
return leantime.tourFactory.startTour('milestoneView');
|
||||
};
|
||||
|
||||
var startGoalTour = function () {
|
||||
leantime.modals.setCustomModalCallback(function(){});
|
||||
leantime.modals.closeModal();
|
||||
|
||||
leantime.helperRepository.updateUserModalSettings("goals");
|
||||
return leantime.tourFactory.startTour('goalsView');
|
||||
};
|
||||
|
||||
var firstLoginModal = function () {
|
||||
|
||||
jQuery(document).ready(function () {
|
||||
|
||||
var onboardingModal = {
|
||||
sizes: {
|
||||
minW: 700,
|
||||
minH: 250
|
||||
},
|
||||
resizable: true,
|
||||
autoSizable: true,
|
||||
callbacks: {
|
||||
afterShowCont: function () {
|
||||
jQuery(".showDialogOnLoad").show();
|
||||
jQuery(".onboardingModal").nyroModal(onboardingModal);
|
||||
},
|
||||
beforeClose: function () {
|
||||
|
||||
location.reload();
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
jQuery(".onboardingModal").nyroModal(onboardingModal);
|
||||
|
||||
jQuery.nmManual(
|
||||
leantime.appUrl + "/help/firstLogin",
|
||||
onboardingModal
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
//Functions
|
||||
var showHelperModal = function (module, minW, minH) {
|
||||
|
||||
jQuery(document).ready(function () {
|
||||
jQuery.nmManual(
|
||||
leantime.appUrl + "/help/showOnboardingDialog?module=" + module,
|
||||
{sizes: {
|
||||
minW: minW || 200,
|
||||
minH: minH || 500,
|
||||
},
|
||||
resizable: true,
|
||||
autoSizable: true,
|
||||
callbacks: {
|
||||
beforeShowCont: function () {
|
||||
leantime.replaceSVGColors();
|
||||
},
|
||||
afterShowCont: function() {
|
||||
htmx.process(".nyroModalCont");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
var hideAndKeepHidden = function (module) {
|
||||
|
||||
leantime.helperRepository.updateUserModalSettings(module);
|
||||
leantime.modals.setCustomModalCallback(function(){});
|
||||
leantime.modals.closeModal();
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
showHelperModal: showHelperModal,
|
||||
hideAndKeepHidden: hideAndKeepHidden,
|
||||
setDontShowAgain: setDontShowAgain,
|
||||
closeModal:closeModal,
|
||||
startMyWorkDashboardTour: startMyWorkDashboardTour,
|
||||
startProjectDashboardTour:startProjectDashboardTour,
|
||||
startKanbanTour: startKanbanTour,
|
||||
startMilestoneTour: startMilestoneTour,
|
||||
startGoalTour:startGoalTour,
|
||||
firstLoginModal:firstLoginModal
|
||||
};
|
||||
})();
|
||||
36
app/Domain/Help/Js/helperRepository.js
Normal file
36
app/Domain/Help/Js/helperRepository.js
Normal file
@@ -0,0 +1,36 @@
|
||||
var leantime = leantime || {};
|
||||
|
||||
leantime.helperRepository = (function () {
|
||||
|
||||
//Functions
|
||||
|
||||
var updateUserModalSettings = function (module, permanent = false) {
|
||||
|
||||
leantime.rpc('Users.Users.saveModalDismissal', { modalKey: module, permanent: !!permanent })
|
||||
.catch(function (e) { console.error('Could not save modal setting', e); });
|
||||
|
||||
};
|
||||
|
||||
var startingTour = function () {
|
||||
|
||||
leantime.rpc('Api.Api.setTourActive', { tourActive: 1 })
|
||||
.catch(function (e) { console.error('Could not start tour', e); });
|
||||
|
||||
};
|
||||
|
||||
var stopTour = function () {
|
||||
|
||||
leantime.rpc('Api.Api.setTourActive', { tourActive: 0 })
|
||||
.catch(function (e) { console.error('Could not stop tour', e); });
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Make public what you want to have public, everything else is private
|
||||
return {
|
||||
updateUserModalSettings: updateUserModalSettings,
|
||||
startingTour:startingTour,
|
||||
stopTour:stopTour
|
||||
};
|
||||
})();
|
||||
358
app/Domain/Help/Js/tourFactory.js
Normal file
358
app/Domain/Help/Js/tourFactory.js
Normal file
@@ -0,0 +1,358 @@
|
||||
leantime.tourFactory = (function () {
|
||||
|
||||
/**
|
||||
* Create a new tour with default settings
|
||||
* @param {string} tourName - The name of the tour
|
||||
* @returns {Object} - Shepherd tour object
|
||||
*/
|
||||
var createTour = function(tourName) {
|
||||
return new Shepherd.Tour({
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
classes: 'shepherd-theme-arrows',
|
||||
scrollTo: false,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
tourName: tourName
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a tour completion
|
||||
* @param {string} tourName - The name of the tour that was completed
|
||||
*/
|
||||
var registerTourCompletion = function(tourName) {
|
||||
leantime.helperRepository.updateUserModalSettings(tourName);
|
||||
|
||||
// Track tour completion for analytics
|
||||
if (typeof _paq !== 'undefined') {
|
||||
_paq.push(['trackEvent', 'Tour', 'Completed', tourName]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get tour definitions for a specific tour
|
||||
* @param {string} tourName - The name of the tour
|
||||
* @returns {Array} - Array of tour step definitions
|
||||
*/
|
||||
var getTourDefinition = function(tourName) {
|
||||
const tourDefinitions = {
|
||||
'myWorkDashboard': [
|
||||
{
|
||||
id: "welcome-step",
|
||||
title: "👋 Welcome to the Dashboard Tour",
|
||||
text: "Let's start with a few basics to get you up to speed on the navigation and different elements inside Leantime.",
|
||||
},
|
||||
{
|
||||
id: "top-nav-step",
|
||||
title: "Work Modes",
|
||||
text: "The top navigation shows you the current 'work mode' you're in. You can be inside a project, your personal work area, or in the company mode.",
|
||||
attachTo: { element: '.work-modes', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "menu-step",
|
||||
title: "Left Navigation",
|
||||
text: "The left navigation breaks down the current work mode and gives you access to different areas. Everything presented here is part of the currently selected work mode above.",
|
||||
attachTo: { element: '.leftpanel', on: 'right' }
|
||||
},
|
||||
{
|
||||
id: "my-menu",
|
||||
title: "My Profile Bar",
|
||||
text: "Here you'll find links to your account as well as notifications and latest news about Leantime.",
|
||||
attachTo: { element: '.headmenu.pull-right', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "dashboard-widgets",
|
||||
title: "Your Dashboard Widgets",
|
||||
text: "Your dashboard is broken up into various widgets showing you focused information about your work.",
|
||||
attachTo: { element: '.primaryContent', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: "dashboard-widgets-dandd",
|
||||
title: "Customize Your Dashboard",
|
||||
text: "Widgets can be resized and moved around using the drag and drop functionality.",
|
||||
attachTo: { element: '#widget_wrapper_todos .grid-handler-top', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "my-todo-widget",
|
||||
title: "My To-Dos",
|
||||
text: "The My To-Do widget shows you all the tasks that are currently assigned to you.",
|
||||
attachTo: { element: '#widget_wrapper_todos', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: "my-todo-widget2",
|
||||
title: "Group To-Dos",
|
||||
text: "You can use the dropdowns here to filter your tasks by project or group them by priority, status, project, or dates.",
|
||||
attachTo: { element: '#yourToDoContainer > .clear', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "my-todo-widget4",
|
||||
title: "Sort To-Dos",
|
||||
text: "Each To-Do can be dragged and dropped to change its order. You can also drag and drop a task to the calendar to schedule it.",
|
||||
attachTo: { element: '#yourToDoContainer .sortable-item:first-child', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "my-todo-widget-timer",
|
||||
title: "Start the Timer",
|
||||
text: "When you're ready to start working on a task, just click the start timer button.",
|
||||
attachTo: { element: '#yourToDoContainer .sortable-item:first-child .timerContainer', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "my-todo-widget-complete",
|
||||
title: "Complete a Task",
|
||||
text: "Once a task is complete, you can mark it as done by clicking the status dropdown.",
|
||||
attachTo: { element: '#yourToDoContainer .sortable-item:first-child .statusDropdown', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "my-todo-widget-add",
|
||||
title: "Add More Tasks",
|
||||
text: "You can add more tasks by clicking the plus button in each section, or by using the 3-dot menu next to each task.",
|
||||
attachTo: { element: '#yourToDoContainer .fa-circle-plus', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: "finish",
|
||||
title: "Congratulations!",
|
||||
text: "You've completed the My Work dashboard tour. Head to <a href='"+leantime.appUrl+"/dashboard/show'>your project</a> to learn more about project management in Leantime.",
|
||||
},
|
||||
],
|
||||
'projectDashboard': [
|
||||
{
|
||||
id: "left-nav",
|
||||
title: "Project Menu",
|
||||
text: "The project menu organizes your project into different sections: <strong>Data Room</strong> - to host all your files and information, <strong>Think</strong> - to strategize, ideate and define, <strong>Make</strong> - to manage goals, milestones and tasks.",
|
||||
attachTo: { element: '.leftmenu ul', on: 'left' }
|
||||
},
|
||||
{
|
||||
id: 'project-selector',
|
||||
title: "Project Selector",
|
||||
text: "Use the project selector to jump between projects. The left menu shows everything related to your current project.",
|
||||
attachTo: { element: '.bigProjectSelector', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'project-checklist',
|
||||
title: "Checklist",
|
||||
text: "The project checklist is a quick reference to see if your project contains all the necessary information for successful execution.",
|
||||
attachTo: { element: '#progressForm', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'project-status',
|
||||
title: "Quick Status Updates",
|
||||
text: "You can use status updates to quickly share the progress of your project with your team using red, yellow and green colors. The status will be visible across the application.",
|
||||
attachTo: { element: '.project-updates', on: 'left' }
|
||||
},
|
||||
{
|
||||
id: 'project-progress',
|
||||
title: "Progress",
|
||||
text: "The project progress indicator shows you how much work you have completed. It accounts for various task sizes, team velocity, and project milestones.",
|
||||
attachTo: { element: '.project-progress', on: 'left' }
|
||||
},
|
||||
{
|
||||
id: 'latest-tasks',
|
||||
title: "Latest Tasks",
|
||||
text: "The list of latest tasks shows you the most recent tasks added to your project and can be used as a project inbox.",
|
||||
attachTo: { element: '.latest-todos', on: 'left' }
|
||||
},
|
||||
{
|
||||
id: 'teams',
|
||||
title: "Team",
|
||||
text: "The team box shows you the members of this project.",
|
||||
attachTo: { element: '.team-container', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: 'finished',
|
||||
title: "Congratulations!",
|
||||
text: "You've completed the project dashboard tour. Head to the <a href='"+leantime.appUrl+"/tickets/showKanban'>To-Dos</a> to learn more about the various ways to manage your tasks in Leantime.",
|
||||
}
|
||||
],
|
||||
'kanbanBoard': [
|
||||
{
|
||||
id: 'kanban-overview',
|
||||
title: "Your Kanban Board",
|
||||
text: "This is your Kanban board. It helps you visualize your work and limit work-in-progress.",
|
||||
attachTo: { element: '.kanban-board-wrapper', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: 'kanban-columns',
|
||||
title: "Work Flow",
|
||||
text: "Tasks move from left to right as they progress. Drag and drop cards to update their status.",
|
||||
attachTo: { element: '.column', on: 'right' }
|
||||
},
|
||||
{
|
||||
id: 'kanban-columns2',
|
||||
title: "Flexible Columns",
|
||||
text: "Using the 3-dot menu, you can add or remove columns and rename them.",
|
||||
attachTo: { element: '.column .widgettitle .inlineDropDownContainer', on: 'right' }
|
||||
},
|
||||
{
|
||||
id: 'kanban-filter',
|
||||
title: "Filter Tasks",
|
||||
text: "You can filter your tasks by various fields like priority, status, project, or dates.",
|
||||
attachTo: { element: '.filterWrapper > .btn', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'kanban-group',
|
||||
title: "Swimlanes",
|
||||
text: "Additionally, you can group your tasks to create Kanban swimlanes. This helps you visualize your work by team members, priority, or milestones.",
|
||||
attachTo: { element: '.filterWrapper > .btn-group', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'kanban-sprints',
|
||||
title: "Sprints",
|
||||
text: "If you manage your work in sprints, you can use the dropdown here to select, create, and update sprints.",
|
||||
attachTo: { element: '.pageheader .dropdown', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'kanban-congrats',
|
||||
title: "Congratulations!",
|
||||
text: "This concludes the Kanban tour. Head to the <a href='"+leantime.appUrl+"/tickets/showKanban'>Milestones</a> to learn how to create and manage milestones in Leantime.",
|
||||
}
|
||||
],
|
||||
'milestoneView': [
|
||||
{
|
||||
id: 'milestone-overview',
|
||||
title: "Milestones",
|
||||
text: "Milestones help you track major outcomes and project phases.",
|
||||
attachTo: { element: '.gantt-wrapper', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: 'milestone-drag',
|
||||
title: "Drag & Sort",
|
||||
text: "Each bar represents one milestone. You can drag them along the timeline, reorder, and resize them. Everything you do on this screen updates the timing of your milestones.",
|
||||
attachTo: { element: '.gantt-wrapper', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: 'milestone-filter',
|
||||
title: "Filter",
|
||||
text: "You can filter your milestones and also view tasks that are part of them.",
|
||||
attachTo: { element: '.filterWrapper > .btn', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'milestone-timeframes',
|
||||
title: "Timeframes",
|
||||
text: "You can change the timeframe of the timeline view to see more of the year or dive deep into a daily breakdown.",
|
||||
attachTo: { element: '.col-md-4 .pull-right', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'milestone-congrats',
|
||||
title: "Congratulations!",
|
||||
text: "This concludes the milestone tour. Head to the <a href='"+leantime.appUrl+"/goalcanvas/dashboard'>Goals</a> to learn how to create and manage goals in Leantime.",
|
||||
},
|
||||
],
|
||||
'goalsView': [
|
||||
{
|
||||
id: 'goals-overview',
|
||||
title: "Goals",
|
||||
text: "Goals help you track measurable impact on your projects.",
|
||||
},
|
||||
{
|
||||
id: 'goal-parts',
|
||||
title: "Objectives & Metrics",
|
||||
text: "Each goal is made up of an Objective (what you're trying to accomplish) and a Metric (how you'll measure it).",
|
||||
attachTo: { element: '.ticketBox', on: 'top' }
|
||||
},
|
||||
{
|
||||
id: 'goal-progress',
|
||||
title: "Goal Progress",
|
||||
text: "As you update your goal metrics, the progress bar will show you how far along you are.",
|
||||
attachTo: { element: '.ticketBox > .row > .col-md-12 .progress', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'milestone-connection',
|
||||
title: "Milestones & Goals",
|
||||
text: "You can connect goals to milestones to track the task-level progress of your goals. This helps identify gaps in your project plan.",
|
||||
attachTo: { element: '.ticketBox.fixed', on: 'bottom' }
|
||||
},
|
||||
{
|
||||
id: 'milestone-congrats',
|
||||
title: "Congratulations!",
|
||||
text: "This concludes the goals tour. Milestones, Goals, and To-Dos are the basic building blocks in Leantime. Use them to break down your work into manageable chunks. Head to the <a href='"+leantime.appUrl+"/tickets/showKanban'>Kanban Board</a> to review your tasks.",
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
return tourDefinitions[tourName] || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a tour from a definition
|
||||
* @param {string} tourName - The name of the tour to build
|
||||
* @returns {Object} - Configured Shepherd tour object
|
||||
*/
|
||||
var buildTour = function(tourName) {
|
||||
const tour = createTour(tourName);
|
||||
const steps = getTourDefinition(tourName);
|
||||
|
||||
steps.forEach((step, index) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === steps.length - 1;
|
||||
|
||||
// Configure buttons based on position in tour
|
||||
const buttons = [];
|
||||
|
||||
if (!isFirst) {
|
||||
buttons.push({
|
||||
text: leantime.i18n.__("tour.back"),
|
||||
classes: 'shepherd-button-secondary',
|
||||
action: tour.back
|
||||
});
|
||||
}
|
||||
|
||||
if (isLast) {
|
||||
buttons.push({
|
||||
text: leantime.i18n.__("tour.finish"),
|
||||
action: function() {
|
||||
registerTourCompletion(tourName);
|
||||
confetti();
|
||||
tour.complete();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
buttons.push({
|
||||
text: leantime.i18n.__("tour.next"),
|
||||
action: tour.next
|
||||
});
|
||||
}
|
||||
|
||||
// Add cancel button for all steps
|
||||
if (!isLast) {
|
||||
buttons.unshift({
|
||||
text: leantime.i18n.__("tour.cancel"),
|
||||
classes: 'shepherd-button-secondary',
|
||||
action: tour.cancel
|
||||
});
|
||||
}
|
||||
|
||||
// Add the step to the tour
|
||||
tour.addStep({
|
||||
...step,
|
||||
buttons: buttons
|
||||
});
|
||||
});
|
||||
|
||||
// Add event handlers
|
||||
tour.on('complete', function() {
|
||||
registerTourCompletion(tourName);
|
||||
});
|
||||
|
||||
return tour;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a specific tour
|
||||
* @param {string} tourName - The name of the tour to start
|
||||
*/
|
||||
var startTour = function(tourName) {
|
||||
const tour = buildTour(tourName);
|
||||
tour.start();
|
||||
return tour;
|
||||
};
|
||||
|
||||
return {
|
||||
createTour: createTour,
|
||||
buildTour: buildTour,
|
||||
startTour: startTour,
|
||||
getTourDefinition: getTourDefinition
|
||||
};
|
||||
})();
|
||||
89
app/Domain/Help/Services/FirstTaskStep.php
Normal file
89
app/Domain/Help/Services/FirstTaskStep.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Services;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use Leantime\Core\Exceptions\AuthorizationException;
|
||||
use Leantime\Domain\Help\Contracts\OnboardingSteps;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
use Leantime\Domain\Tickets\Services\Tickets;
|
||||
use Throwable;
|
||||
|
||||
class FirstTaskStep implements OnboardingSteps
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
public function __construct(
|
||||
private Setting $settingsRepo,
|
||||
private Tickets $ticketService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the title of the current project.
|
||||
*
|
||||
* @return string The title of the current project.
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Name your first task';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action for the current request.
|
||||
*
|
||||
* @return string The action for the current request.
|
||||
*/
|
||||
public function getAction(): string
|
||||
{
|
||||
// TODO: Implement getAction() method.
|
||||
return 'ProjectIntro';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the template for the project introduction step.
|
||||
*
|
||||
* @return string The template name for the project introduction step.
|
||||
*/
|
||||
public function getTemplate(): string
|
||||
{
|
||||
return 'help.firstTaskStep';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given parameters.
|
||||
*
|
||||
* Persisting the firstLoginCompleted flag MUST NOT depend on the optional
|
||||
* first-task creation succeeding. Users without TicketsPermissions::CREATE
|
||||
* (e.g. the readonly role) would otherwise have quickAddTicket() throw a
|
||||
* permission error before the flag was ever written, trapping them in an
|
||||
* infinite onboarding modal loop (see GH #3683). The ticket creation is a
|
||||
* best-effort convenience; the completion flag is the load-bearing write.
|
||||
*
|
||||
* @param array $params The parameters passed to the handle method.
|
||||
* @return bool Returns true on success.
|
||||
*/
|
||||
public function handle($params): bool
|
||||
{
|
||||
$headline = isset($params['headline']) && is_string($params['headline'])
|
||||
? trim($params['headline'])
|
||||
: '';
|
||||
|
||||
if ($headline !== '') {
|
||||
try {
|
||||
$this->ticketService->quickAddTicket(['headline' => $headline]);
|
||||
} catch (AuthorizationException $e) {
|
||||
// Expected: the readonly role has no TicketsPermissions::CREATE. This is
|
||||
// a normal outcome, not an incident, so it is not reported — otherwise
|
||||
// every readonly first login would look like a recurring error to ops.
|
||||
} catch (Throwable $e) {
|
||||
// Anything else is genuinely unexpected and worth surfacing, but it
|
||||
// still must not block the completion flag write below.
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->settingsRepo->saveSetting('user.'.session()->get('userdata.id', -1).'.firstLoginCompleted', true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
538
app/Domain/Help/Services/Helper.php
Normal file
538
app/Domain/Help/Services/Helper.php
Normal file
@@ -0,0 +1,538 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Services;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
|
||||
class Helper
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
private $availableModals = [
|
||||
'dashboard.show' => [
|
||||
'id' => 'dashboard.show',
|
||||
'template' => 'projectDashboard',
|
||||
'tour' => 'dashboard',
|
||||
'autoLoad' => true,
|
||||
],
|
||||
'dashboard.home' => [
|
||||
'id' => 'dashboard.home',
|
||||
'template' => 'home',
|
||||
'tour' => 'myWorkDashboard',
|
||||
'autoLoad' => true,
|
||||
],
|
||||
'tickets.showKanban' => [
|
||||
'id' => 'tickets.showKanban',
|
||||
'template' => 'kanban',
|
||||
'tour' => '',
|
||||
'autoLoad' => true,
|
||||
],
|
||||
'tickets.roadmap' => [
|
||||
'id' => 'tickets.roadmap',
|
||||
'template' => 'roadmap',
|
||||
'tour' => '',
|
||||
'autoLoad' => true,
|
||||
],
|
||||
'goalcanvas.dashboard' => [
|
||||
'id' => 'goalcanvas.dashboard',
|
||||
'template' => 'goals',
|
||||
'tour' => '',
|
||||
'autoLoad' => true,
|
||||
],
|
||||
'leancanvas.showCanvas' => [
|
||||
'id' => 'leancanvas.showCanvas',
|
||||
'template' => 'fullLeanCanvas',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'leancanvas.simpleCanvas' => [
|
||||
'id' => 'leancanvas.simpleCanvas',
|
||||
'template' => 'simpleLeanCanvas',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'ideas.showBoards' => [
|
||||
'id' => 'ideas.showBoards',
|
||||
'template' => 'ideaBoard',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'ideas.advancedBoards' => [
|
||||
'id' => 'ideas.advancedBoards',
|
||||
'template' => 'advancedBoards',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'retroscanvas.showBoards' => [
|
||||
'id' => 'retroscanvas.showBoards',
|
||||
'template' => 'retroscanvas',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'timesheets.showMy' => [
|
||||
'id' => 'timesheets.showMy',
|
||||
'template' => 'mytimesheets',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'projects.newProject' => [
|
||||
'id' => 'projects.newProject',
|
||||
'template' => 'newProject',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'projects.showAll' => [
|
||||
'id' => 'projects.showAll',
|
||||
'template' => 'showProjects',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'clients.showAll' => [
|
||||
'id' => 'clients.showAll',
|
||||
'template' => 'showClients',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'blueprints.showBoards' => [
|
||||
'id' => 'blueprints.showBoards',
|
||||
'template' => 'blueprints',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
'wiki.show' => [
|
||||
'id' => 'wiki.show',
|
||||
'template' => 'wiki',
|
||||
'tour' => '',
|
||||
'autoLoad' => false,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
* Initializes the availableModals property by dispatching the "addHelperModal" event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(private Setting $settingsRepo)
|
||||
{
|
||||
|
||||
$this->availableModals = self::dispatch_filter('addHelperModal', $this->availableModals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all available helper modals.
|
||||
*
|
||||
* @return array The array of available helper modals.
|
||||
*/
|
||||
public function getAllHelperModals(): array
|
||||
{
|
||||
return $this->availableModals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the corresponding helper modal for a given route.
|
||||
*
|
||||
* @param string $route The route for which to retrieve the helper modal.
|
||||
* @return array The helper modal associated with the given route. If not found, a 'notfound' template array is returned.
|
||||
*/
|
||||
public function getHelperModalByRoute(string $route): array
|
||||
{
|
||||
return $this->availableModals[$route] ?? ['template' => 'notfound'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first login steps.
|
||||
*
|
||||
* This method returns an array of steps that a user needs to follow during the first login.
|
||||
*
|
||||
* Each step consists of a template and a button label.
|
||||
*
|
||||
* @return array The first login steps.
|
||||
*/
|
||||
public function getFirstLoginSteps(): array
|
||||
{
|
||||
$steps = [
|
||||
0 => ['class' => "Leantime\Domain\Help\Services\FirstTaskStep", 'next' => 'end'],
|
||||
];
|
||||
|
||||
// make array of onboarding steps.
|
||||
$steps = self::dispatch_filter('filterSteps', $steps);
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves which first-login onboarding step should be displayed.
|
||||
*
|
||||
* Given an optional requested step key (from the request), this returns
|
||||
* the resolved step including its template and whether the onboarding flow
|
||||
* has reached the final "end" step. When no valid step key is provided the
|
||||
* first available step is used.
|
||||
*
|
||||
* @param string|null $requestedStep The requested step key (e.g. a numeric index or "end").
|
||||
* @return array{key: int|string, next: string|int|null, template: string|null, isEnd: bool} The resolved step information.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function resolveFirstLoginStep(?string $requestedStep): array
|
||||
{
|
||||
if ($requestedStep === 'end') {
|
||||
return [
|
||||
'key' => 'end',
|
||||
'next' => null,
|
||||
'template' => 'help.firstLoginEnd',
|
||||
'isEnd' => true,
|
||||
];
|
||||
}
|
||||
|
||||
$allSteps = $this->getFirstLoginSteps();
|
||||
|
||||
$currentStepKey = collect($allSteps)->keys()->first();
|
||||
|
||||
if ($requestedStep !== null && isset($allSteps[$requestedStep])) {
|
||||
$currentStepKey = (int) $requestedStep;
|
||||
}
|
||||
|
||||
$currentStep = $allSteps[$currentStepKey];
|
||||
|
||||
/** @var \Leantime\Domain\Help\Contracts\OnboardingSteps $stepObject */
|
||||
$stepObject = app()->make($currentStep['class']);
|
||||
|
||||
return [
|
||||
'key' => $currentStepKey,
|
||||
'next' => $currentStep['next'],
|
||||
'template' => $stepObject->getTemplate(),
|
||||
'isEnd' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submission of a first-login onboarding step.
|
||||
*
|
||||
* Resolves the current step from the submitted parameters, delegates handling
|
||||
* to the step's class, and returns whether the step was valid along with the
|
||||
* key of the step the user should be redirected to next.
|
||||
*
|
||||
* @param array $params The submitted request parameters. Must contain a numeric "currentStep".
|
||||
* @return array{valid: bool, next: string|int} Result of the step handling: "valid" indicates whether the
|
||||
* submitted step was recognized, "next" is the step key to navigate to.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function handleFirstLoginStep(array $params): array
|
||||
{
|
||||
$allSteps = $this->getFirstLoginSteps();
|
||||
|
||||
if (
|
||||
! isset($params['currentStep'])
|
||||
|| ! is_numeric($params['currentStep'])
|
||||
|| ! isset($allSteps[$params['currentStep']])
|
||||
) {
|
||||
return ['valid' => false, 'next' => ''];
|
||||
}
|
||||
|
||||
$currentStep = $allSteps[$params['currentStep']];
|
||||
|
||||
/** @var \Leantime\Domain\Help\Contracts\OnboardingSteps $stepObject */
|
||||
$stepObject = app()->make($currentStep['class']);
|
||||
|
||||
$result = $stepObject->handle($params);
|
||||
|
||||
if ($result) {
|
||||
return ['valid' => true, 'next' => $currentStep['next']];
|
||||
}
|
||||
|
||||
return ['valid' => true, 'next' => $params['currentStep']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an onboarding modal as seen for a given module and returns its template name.
|
||||
*
|
||||
* Ensures the per-session modal tracking store exists, sanitizes the module
|
||||
* identifier, records that the modal has been shown once for this session, and
|
||||
* returns the (sanitized) template name to render.
|
||||
*
|
||||
* @param string $module The module identifier whose modal should be marked as seen.
|
||||
* @return string The sanitized template name to render (without the "help." prefix).
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function markModalSeenForModule(string $module): string
|
||||
{
|
||||
$this->ensureModalSessionStore();
|
||||
|
||||
$template = htmlspecialchars($module);
|
||||
|
||||
if (! session()->exists('usersettings.modals.'.$template)) {
|
||||
session(['usersettings.modals.'.$template => 1]);
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an onboarding modal as seen for a given route and returns its template name.
|
||||
*
|
||||
* Sanitizes the route, resolves the matching helper modal, ensures the per-session
|
||||
* modal tracking store exists, records that the modal has been shown once for this
|
||||
* session, and returns the template name to render.
|
||||
*
|
||||
* @param string $route The route identifier whose helper modal should be marked as seen.
|
||||
* @return string The template name to render (without the "help." prefix).
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function markModalSeenForRoute(string $route): string
|
||||
{
|
||||
$this->ensureModalSessionStore();
|
||||
|
||||
$filteredRoute = htmlspecialchars($route);
|
||||
|
||||
$modal = $this->getHelperModalByRoute($filteredRoute);
|
||||
|
||||
if (! session()->exists('usersettings.modals.'.$modal['template'])) {
|
||||
session(['usersettings.modals.'.$modal['template'] => 1]);
|
||||
}
|
||||
|
||||
return $modal['template'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the per-session modal tracking store exists.
|
||||
*
|
||||
* Initializes "usersettings.modals" to an empty array when it has not yet
|
||||
* been set so that modals are only shown once per session.
|
||||
*/
|
||||
private function ensureModalSessionStore(): void
|
||||
{
|
||||
if (! session()->exists('usersettings.modals')) {
|
||||
session(['usersettings.modals' => []]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is the user's first login.
|
||||
*
|
||||
* NOTE: This is now a pure check with no side effects.
|
||||
* Default project creation has been moved to ensureDefaultProject()
|
||||
* which is called from the CurrentProject middleware, not from view composers.
|
||||
*
|
||||
* @param int $userId The user ID to check
|
||||
* @return bool True if this is the first login, false otherwise
|
||||
*/
|
||||
public function isFirstLogin(int $userId): bool
|
||||
{
|
||||
$onboardingComplete = $this->settingsRepo->getSetting('user.'.$userId.'.firstLoginCompleted');
|
||||
|
||||
return ! isset($onboardingComplete) || $onboardingComplete === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the user has a default project.
|
||||
* Creates one if the user has no current project set.
|
||||
* Called from middleware (not from view composers) to avoid
|
||||
* write operations during template rendering.
|
||||
*
|
||||
* @param int $userId The user ID to check.
|
||||
* @param string $role The user's role for determining task content.
|
||||
*/
|
||||
public function ensureDefaultProject(int $userId, string $role = 'editor'): void
|
||||
{
|
||||
$currentProject = session('currentProject');
|
||||
if ($currentProject === null || $currentProject === 0 || $currentProject === '' || $currentProject === false) {
|
||||
$this->createDefaultProject($userId, $role);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the first login as completed for a user
|
||||
*
|
||||
* @param int $userId The user ID to update
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function markFirstLoginComplete(int $userId): bool
|
||||
{
|
||||
|
||||
return $this->settingsRepo->saveSetting('user.'.$userId.'.firstLoginCompleted', true);
|
||||
|
||||
}
|
||||
|
||||
public function getOnboardingChecklist(int $userId): array|false
|
||||
{
|
||||
|
||||
$checklist = $this->settingsRepo->getSetting('user.'.$userId.'.onboardingChecklist');
|
||||
$checklist = json_decode($checklist, true);
|
||||
|
||||
// if(!$checklist) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// Checklist Debug
|
||||
$checklist = [
|
||||
'step1' => [
|
||||
'completed' => true,
|
||||
'label' => 'Create your first task',
|
||||
],
|
||||
'step2' => [
|
||||
'completed' => false,
|
||||
'label' => 'Complete the My Work Dashboard Tour',
|
||||
],
|
||||
'step3' => [
|
||||
'completed' => false,
|
||||
'label' => 'Review your personal project',
|
||||
'url' => '',
|
||||
],
|
||||
'step4' => [
|
||||
'completed' => false,
|
||||
'label' => 'Review your project',
|
||||
'url' => '',
|
||||
],
|
||||
'step5' => [
|
||||
'completed' => false,
|
||||
'label' => 'Create a milestone',
|
||||
'url' => '',
|
||||
],
|
||||
'step6' => [
|
||||
'completed' => false,
|
||||
'label' => 'Create a goal',
|
||||
'url' => '',
|
||||
],
|
||||
'step7' => [
|
||||
'completed' => false,
|
||||
'label' => 'Comment on a task',
|
||||
'url' => '',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
return $checklist;
|
||||
|
||||
}
|
||||
|
||||
public function createDefaultProject(int $userId, string $role = 'editor')
|
||||
{
|
||||
|
||||
// Create Project
|
||||
$projectService = app()->make(\Leantime\Domain\Projects\Services\Projects::class);
|
||||
|
||||
$values = [
|
||||
'name' => 'My Project',
|
||||
'details' => 'Welcome to your first project in Leantime!<br />This is your space to organize tasks, track goals, and plan your work. Feel free to modify anything here or create additional projects as you grow. This project is just for you to get started',
|
||||
'clientId' => 0,
|
||||
'hourBudget' => 0,
|
||||
'assignedUsers' => [['id' => $userId, 'projectRole' => '']],
|
||||
'dollarBudget' => 0,
|
||||
'psettings' => 'restricted',
|
||||
'type' => 'project',
|
||||
'start' => null,
|
||||
'end' => null,
|
||||
];
|
||||
|
||||
$projectId = $projectService->addProject($values);
|
||||
|
||||
// Create Milestone
|
||||
$ticketService = app()->make(\Leantime\Domain\Tickets\Services\Tickets::class);
|
||||
$values = [
|
||||
'headline' => '🚀 Getting Started',
|
||||
'projectId' => $projectId,
|
||||
'editorId' => $userId,
|
||||
'userId' => $userId,
|
||||
'date' => dtHelper()->userNow()->formatDateTimeForDb(),
|
||||
'editFrom' => dtHelper()->userNow()->formatDateTimeForDb(),
|
||||
'editTo' => dtHelper()->userNow()->addDays(14)->formatDateTimeForDb(),
|
||||
'tags' => '#124F7D',
|
||||
];
|
||||
$milestoneId = $ticketService->quickAddMilestone($values);
|
||||
|
||||
// Create Tasks
|
||||
$values = [
|
||||
'headline' => '',
|
||||
'description' => '',
|
||||
'projectId' => $projectId,
|
||||
'editorId' => $userId,
|
||||
'userId' => $userId,
|
||||
'dateToFinish' => dtHelper()->userNow()->addDays(3)->formatDateTimeForDb(),
|
||||
'milestone' => $milestoneId,
|
||||
];
|
||||
|
||||
$values['headline'] = '💬 Join our community chat';
|
||||
$values['description'] = 'Our community chat is a great resource to ask questions and get feedback on project set up. <a href="https://discord.gg/4zMzJtAq9z" target="_blank">Community Chat</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
if (in_array($role, ['admin', 'owner', 'manager'])) {
|
||||
|
||||
$values['headline'] = '👥 Invite your team mates';
|
||||
$values['description'] = 'Whether you are working with someone or just need an accountability buddy. Using Leantime as a group helps to stay on track and motivated <a href="'.BASE_URL.'/users/showAll">User Management</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
}
|
||||
|
||||
$values['headline'] = '🎯 Learn More about Leantime\'s Project Structure';
|
||||
$values['description'] = 'We have a lot of additional resources on our help documentation. To learn more about project structure in Leantime and best practices visit: <a href="https://support.leantime.io/en/article/getting-started-in-leantime-an-introduction-to-setting-structure-to-the-work-14t1qip/" target="_blank">https://help.leantime.io</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
$values['headline'] = '🎯 Create a Goal';
|
||||
$values['description'] = 'Goals are used to track and measure long term objectives. They should be measurable using metrics you can update on a regular basis. Goals and Milestones can be connected to view the execution progress while viewing the metric progress <a href="'.BASE_URL.'/goalcanvas/dashboard">Project Goals</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
$values['headline'] = '🚩 Create a Milestone';
|
||||
$values['description'] = 'Milestones allow you to categorize phases of your projects into discrete outcomes. Each milestone has a start and end date and should deliver some output <a href="'.BASE_URL.'/tickets/roadmap/">Project Milestone</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
$values['headline'] = '🗺️ Explore your Personal Project';
|
||||
$values['description'] = 'Your personal project is a space where you can organize your tasks, goals and work. You can access it via the project selector on the top or by clicking this link here: <a href="'.BASE_URL.'/projects/changeCurrentProject/'.$projectId.'/">My Project</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
$values['headline'] = '🖼️ Complete my Leantime profile';
|
||||
$values['description'] = 'Update profile picture and complete work preferences to personalize my experience. <a href="'.BASE_URL.'/users/editOwn/">My Profile</a>';
|
||||
$values['dateToFinish'] = dtHelper()->userNow()->addDays(1)->formatDateForUser();
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
$values['headline'] = '📌 Create your first task';
|
||||
$values['description'] = '';
|
||||
$values['dateToFinish'] = dtHelper()->userNow();
|
||||
$values['status'] = 0;
|
||||
$ticketService->quickAddTicket($values);
|
||||
|
||||
// Create Goal. This is a SYSTEM-orchestrated onboarding write (running in the
|
||||
// userSignUpSuccess listener while the user's project membership is still being set
|
||||
// up), so it goes through the REPOSITORY directly — bypassing the CREATE-authorized
|
||||
// Goalcanvas service methods, which would otherwise 403 the brand-new user. (Same
|
||||
// landmine pattern as Wiki's default-notebook / Ideas' default-board bootstrap.)
|
||||
$goalRepo = app()->make(\Leantime\Domain\Goalcanvas\Repositories\Goalcanvas::class);
|
||||
$values = [
|
||||
'title' => 'My Goals',
|
||||
'author' => $userId,
|
||||
'projectId' => $projectId,
|
||||
];
|
||||
$currentCanvasId = $goalRepo->addCanvas($values);
|
||||
|
||||
$values = [
|
||||
'description' => 'Tasks completed on time', // Metric
|
||||
'title' => 'Build My Productivity System', // Objective
|
||||
'box' => 'goal',
|
||||
'author' => $userId,
|
||||
'canvasId' => $currentCanvasId,
|
||||
'milestoneId' => $milestoneId,
|
||||
'startDate' => dtHelper()->userNow()->formatDateForUser(),
|
||||
'endDate' => dtHelper()->userNow()->addMonths(2)->formatDateForUser(),
|
||||
'metricType' => 'percent',
|
||||
'assignedTo' => $userId,
|
||||
'startValue' => '0',
|
||||
'currentValue' => '0',
|
||||
'endValue' => '80',
|
||||
];
|
||||
|
||||
$goalRepo->createGoal($values);
|
||||
|
||||
$projectService->changeCurrentSessionProject($projectId);
|
||||
|
||||
}
|
||||
}
|
||||
97
app/Domain/Help/Services/InviteTeamStep.php
Normal file
97
app/Domain/Help/Services/InviteTeamStep.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Services;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use Leantime\Core\UI\Template;
|
||||
use Leantime\Domain\Help\Contracts\OnboardingSteps;
|
||||
use Leantime\Domain\Projects\Services\Projects;
|
||||
use Leantime\Domain\Users\Services\Users;
|
||||
|
||||
class InviteTeamStep implements OnboardingSteps
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
public function __construct(
|
||||
private Projects $projectService,
|
||||
private Users $userService,
|
||||
private Template $tplService
|
||||
) {}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Invite your team';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the action for the current request.
|
||||
*
|
||||
* This method is responsible for returning the action to be performed based on the current request.
|
||||
* The action is returned as a string.
|
||||
*
|
||||
* @return string The action to be performed.
|
||||
*/
|
||||
public function getAction(): string
|
||||
{
|
||||
// TODO: Implement getAction() method.
|
||||
return 'InviteTeam';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the template for the current request.
|
||||
*
|
||||
* This method is responsible for returning the template to be used for rendering the content based on the current request.
|
||||
* The template is returned as a string.
|
||||
*
|
||||
* @return string The template to be used for rendering the content.
|
||||
*/
|
||||
public function getTemplate(): string
|
||||
{
|
||||
return 'help.inviteTeamStep';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the given parameters for performing a specific action.
|
||||
*
|
||||
* This method is responsible for processing and handling the given parameters for performing a specific action.
|
||||
* It iterates over the parameters and checks if the corresponding email is set and not empty.
|
||||
* If the email is valid and does not exist as a username, it creates a new user invite and then establishes a relation
|
||||
* between the new user and the current project.
|
||||
* In the end, a success notification is set.
|
||||
*
|
||||
* @param array $params The parameters to be handled.
|
||||
* @return bool True if the handling was successful, false otherwise.
|
||||
*/
|
||||
public function handle($params): bool
|
||||
{
|
||||
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
if (isset($params['email'.$i]) && $params['email'.$i] != '') {
|
||||
$values = [
|
||||
'firstname' => '',
|
||||
'lastname' => '',
|
||||
'user' => ($params['email'.$i]),
|
||||
'phone' => '',
|
||||
'role' => '20',
|
||||
'password' => '',
|
||||
'pwReset' => '',
|
||||
'status' => '',
|
||||
'clientId' => '',
|
||||
];
|
||||
|
||||
if (filter_var($params['email'.$i], FILTER_VALIDATE_EMAIL)) {
|
||||
if ($this->userService->usernameExist($params['email'.$i]) === false) {
|
||||
$userId = $this->userService->createUserInvite($values);
|
||||
if ($userId !== false) {
|
||||
$this->projectService->editUserProjectRelations((int) $userId, [session('currentProject')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->tplService->setNotification(__('notification.invitation_sent'), 'success', 'user_invited');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
79
app/Domain/Help/Services/ProjectDefinitionStep.php
Normal file
79
app/Domain/Help/Services/ProjectDefinitionStep.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Services;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use Leantime\Domain\Help\Contracts\OnboardingSteps;
|
||||
use Leantime\Domain\Projects\Services\Projects;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
|
||||
class ProjectDefinitionStep implements OnboardingSteps
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
public function __construct(
|
||||
private Setting $settingsRepo,
|
||||
private Projects $projectService
|
||||
) {}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Describe your project';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the action of the current object.
|
||||
*
|
||||
* @return string The action of the current object.
|
||||
*/
|
||||
public function getAction(): string
|
||||
{
|
||||
// TODO: Implement getAction() method.
|
||||
return 'ProjectDefinition';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the template to render for the current object.
|
||||
*
|
||||
* @return string The template to render for the current object.
|
||||
*/
|
||||
public function getTemplate(): string
|
||||
{
|
||||
return 'help.projectDefinitionStep';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the given parameters and performs necessary operations.
|
||||
*
|
||||
* @param array $params The parameters passed to the method.
|
||||
* @return bool Returns true.
|
||||
*/
|
||||
public function handle($params): bool
|
||||
{
|
||||
|
||||
$description = '';
|
||||
|
||||
if (isset($params['accomplish'])) {
|
||||
$description .= '<h3>'.__('label.what_are_you_trying_to_accomplish').'</h3>';
|
||||
$description .= ''.$params['accomplish'];
|
||||
}
|
||||
|
||||
if (isset($params['worldview'])) {
|
||||
$description .= '<br /><h3>'.__('label.how_does_the_world_look_like').'</h3>';
|
||||
$description .= ''.$params['worldview'];
|
||||
}
|
||||
|
||||
if (isset($params['whyImportant'])) {
|
||||
$description .= '<br /><h3>'.__('label.why_is_this_important').'</h3>';
|
||||
$description .= ''.$params['whyImportant'];
|
||||
}
|
||||
|
||||
$this->projectService->patch(session('currentProject'), ['details' => $description]);
|
||||
$this->projectService->changeCurrentSessionProject(session('currentProject'));
|
||||
|
||||
$this->settingsRepo->saveSetting('companysettings.completedOnboarding', true);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
68
app/Domain/Help/Services/ProjectIntroStep.php
Normal file
68
app/Domain/Help/Services/ProjectIntroStep.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Help\Services;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use Leantime\Domain\Help\Contracts\OnboardingSteps;
|
||||
use Leantime\Domain\Projects\Services\Projects;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
|
||||
class ProjectIntroStep implements OnboardingSteps
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
public function __construct(
|
||||
private Setting $settingsRepo,
|
||||
private Projects $projectService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the title of the current project.
|
||||
*
|
||||
* @return string The title of the current project.
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Name your project';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action for the current request.
|
||||
*
|
||||
* @return string The action for the current request.
|
||||
*/
|
||||
public function getAction(): string
|
||||
{
|
||||
// TODO: Implement getAction() method.
|
||||
return 'ProjectIntro';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the template for the project introduction step.
|
||||
*
|
||||
* @return string The template name for the project introduction step.
|
||||
*/
|
||||
public function getTemplate(): string
|
||||
{
|
||||
return 'help.projectIntroStep';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given parameters.
|
||||
*
|
||||
* @param array $params The parameters passed to the handle method.
|
||||
* @return bool Returns true on success.
|
||||
*/
|
||||
public function handle($params): bool
|
||||
{
|
||||
|
||||
if (isset($params['projectname'])) {
|
||||
$this->projectService->patch(session('currentProject'), ['name' => $_POST['projectname']]);
|
||||
$this->projectService->changeCurrentSessionProject(session('currentProject'));
|
||||
}
|
||||
|
||||
$this->settingsRepo->saveSetting('companysettings.completedOnboarding', true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
25
app/Domain/Help/Templates/advancedBoards.blade.php
Normal file
25
app/Domain/Help/Templates/advancedBoards.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_new_ideas_jdea.svg') !!}
|
||||
</div>
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_organized_idea_board') !!}</h3><br />
|
||||
<p>{!! __('text.advanced_boards_helper_content') !!}
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="leantime.helperController.hideAndKeepHidden('advancedIdeaBoards')" contentRole="tertiary">{!! __('links.close_dont_show_again') !!}</x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
22
app/Domain/Help/Templates/backlog.blade.php
Normal file
22
app/Domain/Help/Templates/backlog.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_schedule_pnbk.svg') !!}
|
||||
</div><br />
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_backlog') !!}</h3><br />
|
||||
<p>{!! __('text.backlog_helper_content') !!}</p>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
31
app/Domain/Help/Templates/blueprints.blade.php
Normal file
31
app/Domain/Help/Templates/blueprints.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_design_data_khdb.svg') !!}
|
||||
</div>
|
||||
<h1>Define your projects with ease</h1><br />
|
||||
<p>Blueprints are your chance to make sense of all the data. Leantime has a variety of tools and canvases to define your project background via Business Model Canvases, SWOT Analysis or Empathy Maps.<br /><br />
|
||||
If you don't know where to start we suggest you create a "Project Value Canvas". This canvas will answer the most important questions of your project:
|
||||
|
||||
Who is your customer? <br />
|
||||
What problem are you solving?<br />
|
||||
What is your solution?<br />
|
||||
What benefit does your solution offer over your competitors<br />
|
||||
</p>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<x-global::forms.button tag="a" link="{{ BASE_URL }}/valuecanvas/showCanvas" contentRole="primary">Create a Project Value Canvas</x-global::forms.button><br />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/cpCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/cpCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'cp'])
|
||||
22
app/Domain/Help/Templates/dashboard.blade.php
Normal file
22
app/Domain/Help/Templates/dashboard.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="center padding-lg" style="width:800px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<x-global::undrawSvg image="undraw_social_serenity_vhix.svg" maxWidth="auto" maxheight="auto" height="250px" headline="{{ __('headlines.welcome') }}"></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row onboarding">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<br />
|
||||
Leantime is built to empower your super powers and help you see your progress towards your goals.<br />
|
||||
<br />
|
||||
Most of us are used to creating task lists — but we’re going to ask you to think about what exactly you are trying to accomplish.<br />
|
||||
<br />
|
||||
1. Set a vision (strategy) and set your goals<br />
|
||||
2. Define the work that will get you to those goals<br />
|
||||
3. And then reach them, planning your day to day with your My Work Dashboard.<br />
|
||||
<br /><br />
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="primary" onclick="leantime.helperController.hideAndKeepHidden('dashboard'); leantime.helperController.startProjectDashboardTour();">{{ __("buttons.lets_go") }} <i class="fa-solid fa-arrow-right"></i></x-global::forms.button>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/dbmCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/dbmCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'dbm'])
|
||||
1
app/Domain/Help/Templates/eaCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/eaCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'ea'])
|
||||
1
app/Domain/Help/Templates/emCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/emCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'em'])
|
||||
4
app/Domain/Help/Templates/firstLoginEnd.blade.php
Normal file
4
app/Domain/Help/Templates/firstLoginEnd.blade.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<script>
|
||||
confetti();
|
||||
jQuery.nmTop().close(2000);
|
||||
</script>
|
||||
27
app/Domain/Help/Templates/firstTaskStep.blade.php
Normal file
27
app/Domain/Help/Templates/firstTaskStep.blade.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<div style="max-width:700px;">
|
||||
<form class="onboardingModal" method="post" id="firstTaskOnboarding" action="{{ BASE_URL }}/help/firstLogin?step={{ $nextStep }}">
|
||||
<input type="hidden" name="currentStep" value="{{ $currentStep }}" />
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>{{ __('headlines.welcome_to_leantime') }}</h1>
|
||||
<p>{{ __('text.lets_start_with_first_task') }}</p>
|
||||
<br />
|
||||
<label><strong>{{ __('label.whats_one_thing_to_do_today') }}</strong></label>
|
||||
<x-global::forms.text-input id="firstTask" name="headline" value="" placeholder="{{ __('input.placeholder.finish_slide_deck') }}" style="width:100%;" required />
|
||||
<br />
|
||||
<p class="text-muted">{{ __('text.first_task_help') }}</p>
|
||||
<br />
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.lets_go')" />
|
||||
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class='svgContainer' style="width:300px; margin-top:40px;">
|
||||
{!! file_get_contents(ROOT . "/dist/images/svg/undraw_happy_news_re_tsbd.svg") !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
25
app/Domain/Help/Templates/fullLeanCanvas.blade.php
Normal file
25
app/Domain/Help/Templates/fullLeanCanvas.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_design_data_khdb.svg') !!}
|
||||
</div>
|
||||
<h1>{!! __('headlines.welcome_to_research_board') !!}</h1><br />
|
||||
<p>{!! __('text.full_lean_canvas_helper_content') !!}</p>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
47
app/Domain/Help/Templates/goals.blade.php
Normal file
47
app/Domain/Help/Templates/goals.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<x-global::undrawSvg
|
||||
image="undraw_goals_re_lu76.svg"
|
||||
maxWidth="auto"
|
||||
headlineSize="var(--font-size-xxxl)"
|
||||
maxheight="auto"
|
||||
height="250px"
|
||||
headline="Goals to keep you focused"
|
||||
></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<br />
|
||||
<div id="firstLoginContent">
|
||||
<p><br />Goals allow you to break down your project into achievable, measurable and actionable chunks.<br />While milestones focus on execution, goals are about the metrics you want to achieve.<br/>
|
||||
Each goal should have a clear objective (the thing you want to achieve) and should be easily measurable using a single metric.<br /><br />
|
||||
Once you have a goal you can assign milestones to it to break it down into the executable tasks.<br />
|
||||
</p><br />
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="tertiary" onclick="leantime.helperController.closeModal()">I'll explore on my own</x-global::forms.button>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="primary" onclick="leantime.helperController.closeModal(); leantime.helperController.startGoalTour();">{{ __("buttons.start_tour") }} <i class="fa-solid fa-arrow-right"></i></x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<form hx-post="{{ BASE_URL }}/help/helperModal/dontShowAgain" hx-trigger="change" hx-swap="none">
|
||||
<label class="tw-text-sm tw-mt-sm" >
|
||||
<input type="hidden" name="modalId" value="goals" />
|
||||
<input type="checkbox" id="dontShowAgain" name="hidePermanently" style="margin-top:-2px;">
|
||||
Don't show this again
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
22
app/Domain/Help/Templates/helpermodal.blade.php
Normal file
22
app/Domain/Help/Templates/helpermodal.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
jQuery(document).ready(function () {
|
||||
|
||||
// First login flow
|
||||
@if($isFirstLogin === true || $isFirstLogin === "true")
|
||||
leantime.helperController.firstLoginModal();
|
||||
@else
|
||||
|
||||
// Returning user flow
|
||||
@if(($isFirstLogin === false || $isFirstLogin === "false") && $showHelperModal === true)
|
||||
|
||||
// Show the appropriate helper modal for the current page
|
||||
@if(is_array($currentModal) && isset($currentModal['autoLoad']) && ($currentModal['autoLoad'] === true || $currentModal['autoLoad'] === "true"))
|
||||
leantime.helperController.showHelperModal('{{ $currentModal['template'] }}', 500, 700);
|
||||
@elseif(is_string($currentModal))
|
||||
leantime.helperController.showHelperModal('{{ $currentModal}}', 500, 700);
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
|
||||
});
|
||||
</script>
|
||||
43
app/Domain/Help/Templates/home.blade.php
Normal file
43
app/Domain/Help/Templates/home.blade.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<div class="center padding-lg" style="width:800px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<x-global::undrawSvg
|
||||
image="undraw_social_serenity_vhix.svg"
|
||||
maxWidth="auto"
|
||||
headlineSize="var(--font-size-xxxl)"
|
||||
maxheight="auto"
|
||||
height="250px"
|
||||
headline="{{ __('headlines.your_personal_dashboard') }}"
|
||||
></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row onboarding">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<br />
|
||||
<div id="firstLoginContent">
|
||||
<p>Your My Work dashboard brings everything that matters into focus. <br />
|
||||
Your most important work is now front and center, organized just for you and how your brain works best.<br /><br />
|
||||
From quick tasks to ambitious goals, everything you need is right here. This is your space to capture ideas, track progress, and celebrate wins along the way.<br />
|
||||
</p><br />
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="tertiary" onclick="leantime.helperController.closeModal()">I'll explore on my own</x-global::forms.button>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="primary" onclick="leantime.helperController.closeModal(); leantime.helperController.startMyWorkDashboardTour();">{{ __("buttons.start_tour") }} <i class="fa-solid fa-arrow-right"></i></x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<form hx-post="{{ BASE_URL }}/help/helperModal/dontShowAgain" hx-trigger="change" hx-swap="none">
|
||||
<label class="tw-text-sm tw-mt-sm" >
|
||||
<input type="hidden" name="modalId" value="home" />
|
||||
<input type="checkbox" id="dontShowAgain" name="hidePermanently" style="margin-top:-2px;">
|
||||
Don't show this again
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
24
app/Domain/Help/Templates/ideaBoard.blade.php
Normal file
24
app/Domain/Help/Templates/ideaBoard.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_new_ideas_jdea.svg') !!}
|
||||
</div>
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_idea_board') !!}</h3><br />
|
||||
<p>{!! __('text.idea_board_helper_content') !!}<br /></p>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
24
app/Domain/Help/Templates/ideationBoard.blade.php
Normal file
24
app/Domain/Help/Templates/ideationBoard.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_new_ideas_jdea.svg') !!}
|
||||
</div>
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_idea_board') !!}</h3><br />
|
||||
<p>{!! __('text.idea_board_helper_content') !!}<br /></p>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/insightsCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/insightsCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'insights'])
|
||||
26
app/Domain/Help/Templates/inviteTeamStep.blade.php
Normal file
26
app/Domain/Help/Templates/inviteTeamStep.blade.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<form class="onboardingModal" method="post" action="{{ BASE_URL }}/help/firstLogin?step={{ $nextStep }}">
|
||||
<input type="hidden" name="currentStep" value="{{ $currentStep }}" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h1>{{ __('headlines.invite_crew') }}</h1>
|
||||
<p>{{ __('text.invite_team') }}</p>
|
||||
<br />
|
||||
<x-global::forms.text-input type="email" name="email1" value="" placeholder="{{ __('input.placeholder.email_invite') }}" style="width: 100%;" /><br />
|
||||
<x-global::forms.text-input type="email" name="email2" value="" placeholder="{{ __('input.placeholder.email_invite') }}" style="width: 100%;" /><br />
|
||||
<x-global::forms.text-input type="email" name="email3" value="" placeholder="{{ __('input.placeholder.email_invite') }}" style="width: 100%;" /><br />
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class='svgContainer' style="width:300px; margin-top:60px;">
|
||||
{!! file_get_contents(ROOT . "/dist/images/svg/undraw_children_re_c37f.svg"); !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-right">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" contentRole="tertiary" onclick="jQuery.nmTop().close();">{{ __('links.skip_for_now') }}</x-global::forms.button>
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.lets_go')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
44
app/Domain/Help/Templates/kanban.blade.php
Normal file
44
app/Domain/Help/Templates/kanban.blade.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<x-global::undrawSvg
|
||||
image="undraw_scrum-board_uqku.svg"
|
||||
maxWidth="auto"
|
||||
headlineSize="var(--font-size-xxxl)"
|
||||
maxheight="auto"
|
||||
height="250px"
|
||||
headline="{{ __('headlines.the_kanban_board') }}"
|
||||
></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<br />
|
||||
<div id="firstLoginContent">
|
||||
<p><br />{!! __('text.kanban_helper_content') !!}
|
||||
</p><br />
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="tertiary" onclick="leantime.helperController.closeModal()">I'll explore on my own</x-global::forms.button>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="primary" onclick="leantime.helperController.closeModal(); leantime.helperController.startKanbanTour();">{{ __("buttons.start_tour") }} <i class="fa-solid fa-arrow-right"></i></x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<form hx-post="{{ BASE_URL }}/help/helperModal/dontShowAgain" hx-trigger="change" hx-swap="none">
|
||||
<label class="tw-text-sm tw-mt-sm" >
|
||||
<input type="hidden" name="modalId" value="kanban" />
|
||||
<input type="checkbox" id="dontShowAgain" name="hidePermanently" style="margin-top:-2px;">
|
||||
Don't show this again
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/lbmCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/lbmCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'lbm'])
|
||||
1
app/Domain/Help/Templates/leanCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/leanCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'lean'])
|
||||
1
app/Domain/Help/Templates/minempathyCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/minempathyCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'minempathy'])
|
||||
24
app/Domain/Help/Templates/mytimesheets.blade.php
Normal file
24
app/Domain/Help/Templates/mytimesheets.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_time_management_30iu.svg') !!}
|
||||
</div><br />
|
||||
<h3 class="primaryColor">{!! __('headlines.the_timesheets') !!}</h3>
|
||||
<p>{!! __('text.my_timesheets_helper_content') !!}</p>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
21
app/Domain/Help/Templates/newProject.blade.php
Normal file
21
app/Domain/Help/Templates/newProject.blade.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_Organizing_projects_0p9a.svg') !!}
|
||||
</div><br />
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_your_project') !!}</h3><br />
|
||||
{!! __('text.new_project_helper_content') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 align-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
19
app/Domain/Help/Templates/notfound.blade.php
Normal file
19
app/Domain/Help/Templates/notfound.blade.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3 class="primaryColor">{!! __('headlines.help') !!}</h3><br /><br />
|
||||
{!! __('text.not_found_helper_content_simple') !!}
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/obmCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/obmCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'obm'])
|
||||
17
app/Domain/Help/Templates/partials/gettingstarted.blade.php
Normal file
17
app/Domain/Help/Templates/partials/gettingstarted.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
<div clas="clear"></div>
|
||||
|
||||
<div class="pull-left">
|
||||
<x-global::undrawSvg
|
||||
image="undraw_game_day_ucx9.svg"
|
||||
maxWidth="auto"
|
||||
maxheight="auto"
|
||||
height="100px"
|
||||
align="left"
|
||||
></x-global::undrawSvg>
|
||||
</div>
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
44
app/Domain/Help/Templates/projectDashboard.blade.php
Normal file
44
app/Domain/Help/Templates/projectDashboard.blade.php
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
<div class="center padding-lg" style="width:800px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<x-global::undrawSvg
|
||||
image="undraw_joyride_re_968t.svg"
|
||||
maxWidth="auto"
|
||||
headlineSize="var(--font-size-xxxl)"
|
||||
maxheight="auto"
|
||||
height="250px"
|
||||
headline="Managing Projects"
|
||||
></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row onboarding">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<br />
|
||||
<div id="firstLoginContent">
|
||||
<p><br />Projects in Leantime are collaborative workspaces where you and your team organize, track, and deliver work efficiently. Each project serves as a container for related goals, tasks, milestones, and allows you to monitor progress in one central location. <br /><br />
|
||||
Whether you're managing work, school, or internal personal initiatives, Leantime projects provide the structure and tools needed to turn ideas into successful outcomes.
|
||||
</p><br />
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="tertiary" onclick="leantime.helperController.closeModal()">I'll explore on my own</x-global::forms.button>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="primary" onclick="leantime.helperController.closeModal(); leantime.helperController.startProjectDashboardTour();">{{ __("buttons.start_tour") }} <i class="fa-solid fa-arrow-right"></i></x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<form hx-post="{{ BASE_URL }}/help/helperModal/dontShowAgain" hx-trigger="change" hx-swap="none">
|
||||
<label class="tw-text-sm tw-mt-sm" >
|
||||
<input type="hidden" name="modalId" value="projectDashboard" />
|
||||
<input type="checkbox" id="dontShowAgain" name="hidePermanently" style="margin-top:-2px;">
|
||||
Don't show this again
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
31
app/Domain/Help/Templates/projectDefinitionStep.blade.php
Normal file
31
app/Domain/Help/Templates/projectDefinitionStep.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<div style="max-width:900px;">
|
||||
<form class="onboardingModal" method="post" action="{{ BASE_URL }}/help/firstLogin?step={{ $nextStep }}">
|
||||
<input type="hidden" name="currentStep" value="{{ $currentStep }}" />
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>{{ __('headlines.make_it_happen') }}</h1>
|
||||
<p>{!! __('text.structured_project_thinking') !!}</p>
|
||||
<br />
|
||||
<label><strong>{{ __('label.what_are_you_trying_to_accomplish') }}</strong></label>
|
||||
<x-global::forms.textarea id="accomplish" name="accomplish" value="" placeholder="" rows="3" style="width:99%; overflow-x: hidden;"></x-global::forms.textarea>
|
||||
<br />
|
||||
<label><strong>{{ __('label.how_does_the_world_look_like') }}</strong></label>
|
||||
<x-global::forms.textarea id="wordlview" name="worldview" value="" placeholder="" rows="3" style="width:99%; overflow-x: hidden;"></x-global::forms.textarea>
|
||||
<br />
|
||||
<label><strong>{{ __('label.why_is_this_important') }}</strong></label>
|
||||
<x-global::forms.textarea id="whyImportant" name="whyImportant" value="" placeholder="" rows="3" style="width:99%; overflow-x: hidden;"></x-global::forms.textarea>
|
||||
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class='svgContainer' style="width:400px; margin-top:40px;">
|
||||
{!! file_get_contents(ROOT . "/dist/images/svg/undraw_goals_re_lu76.svg") !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-right">
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.next')" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
25
app/Domain/Help/Templates/projectIntroStep.blade.php
Normal file
25
app/Domain/Help/Templates/projectIntroStep.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<div style="max-width:700px;">
|
||||
<form class="onboardingModal" method="post" id="projectTitleOnboarding" action="{{ BASE_URL }}/help/firstLogin?step={{ $nextStep }}">
|
||||
<input type="hidden" name="currentStep" value="{{ $currentStep }}" />
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>{{ __('headlines.hi_there') }}</h1>
|
||||
<p>{!! __('text.get_organized_with_projects') !!}</p>
|
||||
<br />
|
||||
<label>{{ __('label.start_with_project_title') }}</label>
|
||||
<x-global::forms.text-input id="projectName" name="projectname" value="" placeholder="" style="width:100%;" /><br />
|
||||
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class='svgContainer' style="width:300px; margin-top:40px;">
|
||||
{!! file_get_contents(ROOT . "/dist/images/svg/undraw_game_day_ucx9.svg") !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-right">
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.next')" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
21
app/Domain/Help/Templates/projectSuccess.blade.php
Normal file
21
app/Domain/Help/Templates/projectSuccess.blade.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_events_2p66.svg') !!}
|
||||
</div>
|
||||
<h3 class="primaryColor">{!! __('headlines.congrats_on_your_project') !!}</h3><br />
|
||||
{!! __('notifications.project_created_successfully') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 align-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/retrosCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/retrosCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'retros'])
|
||||
1
app/Domain/Help/Templates/risksCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/risksCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'risks'])
|
||||
44
app/Domain/Help/Templates/roadmap.blade.php
Normal file
44
app/Domain/Help/Templates/roadmap.blade.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<x-global::undrawSvg
|
||||
image="undraw_adjustments_p22m.svg"
|
||||
maxWidth="auto"
|
||||
headlineSize="var(--font-size-xxxl)"
|
||||
maxheight="auto"
|
||||
height="250px"
|
||||
headline="{{ __('headlines.welcome_to_your_roadmap') }}"
|
||||
></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<br />
|
||||
<div id="firstLoginContent">
|
||||
<p><br />{!! __('text.milestone_helper_content') !!}
|
||||
</p><br />
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="tertiary" onclick="leantime.helperController.closeModal()">I'll explore on my own</x-global::forms.button>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" contentRole="primary" onclick="leantime.helperController.closeModal(); leantime.helperController.startMilestoneTour();">{{ __("buttons.start_tour") }} <i class="fa-solid fa-arrow-right"></i></x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12 tw-text-center">
|
||||
<form hx-post="{{ BASE_URL }}/help/helperModal/dontShowAgain" hx-trigger="change" hx-swap="none">
|
||||
<label class="tw-text-sm tw-mt-sm" >
|
||||
<input type="hidden" name="modalId" value="roadmap" />
|
||||
<input type="checkbox" id="dontShowAgain" name="hidePermanently" style="margin-top:-2px;">
|
||||
Don't show this again
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/sbCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/sbCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'sb'])
|
||||
24
app/Domain/Help/Templates/showClients.blade.php
Normal file
24
app/Domain/Help/Templates/showClients.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_complete_task_u2c3.svg') !!}
|
||||
</div><br />
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_clients_products') !!}</h3><br />
|
||||
{!! __('text.show_clients_helper_content') !!}
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
23
app/Domain/Help/Templates/showProjects.blade.php
Normal file
23
app/Domain/Help/Templates/showProjects.blade.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_Organizing_projects_0p9a.svg') !!}
|
||||
</div><br />
|
||||
<h3 class="primaryColor"></h3><br />
|
||||
{!! __('text.show_projects_helper_content') !!}
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
25
app/Domain/Help/Templates/simpleLeanCanvas.blade.php
Normal file
25
app/Domain/Help/Templates/simpleLeanCanvas.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_design_data_khdb.svg') !!}
|
||||
</div>
|
||||
|
||||
<h3 class="primaryColor">{!! __('headlines.welcome_to_simple_research_board') !!}</h3><br />
|
||||
{!! __('text.simple_lean_canvas_helper_content') !!}
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
</p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/smCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/smCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'sm'])
|
||||
1
app/Domain/Help/Templates/sqCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/sqCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'sq'])
|
||||
161
app/Domain/Help/Templates/support.blade.php
Normal file
161
app/Domain/Help/Templates/support.blade.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<div class="padding-lg" style="width:1190px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<x-global::undrawSvg image="undraw_unexpected-friends_42mc.svg" maxWidth="auto" maxheight="auto" height="250px" headline=""></x-global::undrawSvg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row onboarding">
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
|
||||
<div class="col-md-12" style="font-size:var(--font-size-l);">
|
||||
<center>
|
||||
<h1 class="fancyLink">Help us build a future where all minds thrive!</h1>
|
||||
<p>Most productivity tools assume everyone thinks the same way: linearly, with perfect focus, motivated by arbitrary deadlines. Whether you have ADHD and need dopamine hits from completed tasks, are autistic and crave consistent structure, have dyslexia and think spatially, or you're just tired of tools that don't match how your brain actually works—you've probably given up on project management entirely.</p>
|
||||
<br />
|
||||
<p>We're building Leantime for minds that work differently. That includes neurodivergent brains, but also anyone who's ever felt like existing tools fight against their natural thinking patterns.</p>
|
||||
<br /> <br />
|
||||
</center>
|
||||
|
||||
<h1 class="fancyLink">Why Leantime won't disappear</h1>
|
||||
<div class="tw-flex tw-w-full tw-justify-evenly tw-gap-5">
|
||||
<div class="tw-flex-1" style="border: 1px solid var(--main-border-color); padding:15px; border-radius:var(--box-radius);">
|
||||
<strong style="margin-bottom:5px; display:block;">6+ years</strong>
|
||||
With consistent full time development since 2021.
|
||||
</div>
|
||||
<div class="tw-flex-1" style="border: 1px solid var(--main-border-color); padding:15px; border-radius:var(--box-radius);">
|
||||
<strong style="margin-bottom:5px; display:block;">Self-funded/Community-funded</strong>
|
||||
No VC pressure to pivot or monetize aggressively
|
||||
</div>
|
||||
<div class="tw-flex-1" style="border: 1px solid var(--main-border-color); padding:15px; border-radius:var(--box-radius);">
|
||||
<strong style="margin-bottom:5px; display:block;">AGPL-3.0</strong>
|
||||
Core will always remain open source by license
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br /><br /><br />
|
||||
<h1 class="fancyLink">How can you help?</h1>
|
||||
<div class="tw-flex tw-w-full tw-justify-evenly tw-gap-5">
|
||||
<div class="tw-flex-1" style="background:var(--header-gradient); color:var(--main-titles-color); padding:15px; border-radius:var(--box-radius);">
|
||||
<strong style="margin-bottom:5px; display:block; color:var(--main-titles-color);">Direct Sponsorship through Github</strong>
|
||||
Fund open source development that benefits everyone<br /><br />
|
||||
<x-global::forms.button tag="a" link="https://github.com/sponsors/Leantime" contentRole="primary" target="_blank" style="background:var(--main-titles-color); color:var(--accent1);">Sponsor Leantime</x-global::forms.button>
|
||||
</div>
|
||||
<div class="tw-flex-1" style="background:var(--header-gradient); color:var(--main-titles-color); padding:15px; border-radius:var(--box-radius);">
|
||||
<strong style="margin-bottom:5px; display:block; color:var(--main-titles-color);">Purchase Plugins</strong>
|
||||
Get advanced features while supporting development<br /><br />
|
||||
<x-global::forms.button tag="a" link="{{ BASE_URL }}/plugins/marketplace" contentRole="primary" style="background:var(--main-titles-color); color:var(--accent1);" target="_blank">Browse Marketplace</x-global::forms.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br /><br /><br />
|
||||
<h1 class="fancyLink">How does your money help?</h1>
|
||||
<div class="tw-flex tw-w-full tw-justify-evenly tw-gap-5">
|
||||
<div class="tw-flex-1" >
|
||||
<div style="background:var(--dropdown-link-hover-bg); padding:15px; border-radius:var(--box-radius);">
|
||||
<small>Funds from</small><br /><strong style="margin-bottom:5px; display:block;">Github Sponsorships</strong>
|
||||
<ul style="margin-left:15px;">
|
||||
<li>New open source features</li>
|
||||
<li>Accessibility improvements</li>
|
||||
<li>Community-requested enhancements</li>
|
||||
<li>Translation support</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="padding:5px 10px;">
|
||||
<strong><em>Recent impact:<br/>Your sponsorships funded our new docker image improvements</em></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex-1" >
|
||||
<div style="background:var(--dropdown-link-hover-bg); padding:15px; border-radius:var(--box-radius);">
|
||||
<small>Funds from</small><br /> <strong style="margin-bottom:5px; display:block;">Plugin Sales</strong>
|
||||
<ul style="margin-left:15px;">
|
||||
<li>Bug fixes and stability</li>
|
||||
<li>Plugin development & maintenance</li>
|
||||
<li>Testing and quality assurance</li>
|
||||
<li>Documentation improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="padding:5px 10px;">
|
||||
<strong><em>Recent impact:<br/>Funded the My Work dashboard Updates</em></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex-1" >
|
||||
<div style="background:var(--dropdown-link-hover-bg); padding:15px; border-radius:var(--box-radius);">
|
||||
<small>Funds from</small><br /><strong style="margin-bottom:5px; display:block;">SaaS Revenue</strong>
|
||||
<ul style="margin-left:15px;">
|
||||
<li>Server infrastructure</li>
|
||||
<li>Website hosting</li>
|
||||
<li>Development tools</li>
|
||||
<li>Administrative costs</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="padding:5px 10px;">
|
||||
<strong><em>Recent impact:<br/>Covers our server cost to host website, cloud and marketplace</em></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br /><br /><br />
|
||||
<h1 class="fancyLink">Your impact in numbers</h1>
|
||||
<div class="tw-flex tw-w-full tw-justify-center tw-gap-4">
|
||||
<div class="tw-text-center tw-flex-1" style="background:#D6F3FF; padding:15px; border-radius:var(--box-radius); ">
|
||||
<span style="color:var(--accent1); font-weight:bold; font-size:var(--font-size-xl);">50,000+</span>
|
||||
<p>Installations you're supporting</p>
|
||||
</div>
|
||||
<div class="tw-text-center tw-flex-1" style="background:#EBF9FF; padding:15px; border-radius:var(--box-radius); ">
|
||||
<span style="color:var(--accent1); font-weight:bold; font-size:var(--font-size-xl);">200+</span>
|
||||
<p>Closed Bugs in 2024</p>
|
||||
</div>
|
||||
<div class="tw-text-center tw-flex-1" style="background:#FEEBF3; padding:15px; border-radius:var(--box-radius); ">
|
||||
<span style="color:var(--accent1); font-weight:bold; font-size:var(--font-size-xl);">40+</span>
|
||||
<p>Languages translated by the community</p>
|
||||
</div>
|
||||
<div class="tw-text-center tw-flex-1" style="background:#FBFDED; padding:15px; border-radius:var(--box-radius); ">
|
||||
<span style="color:var(--accent1); font-weight:bold; font-size:var(--font-size-xl);">100%</span>
|
||||
<p>Of sponsorship goes to development</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
|
||||
<h1 class="fancyLink">Who's behind Leantime?</h1>
|
||||
|
||||
<div class="tw-flex tw-w-full tw-justify-evenly tw-gap-5">
|
||||
<div class="tw-flex-1" style="background:var(--dropdown-link-hover-bg); padding:15px; border-radius:var(--box-radius);">
|
||||
<img src="{{ BASE_URL }}/dist/images/marcel.png" style="float:right; width:100px; border:none; box-shadow:none; margin-left:10px; margin-bottom:10px;"/>
|
||||
<p><strong style="margin-bottom:5px; display:block;">👋 I'm Marcel</strong>German immigrant, dad to an autistic daughter, and living with ADHD myself.</p>
|
||||
<br />
|
||||
<p>Traditional project management tools never clicked for me. They felt like they were built by neurotypical minds for neurotypical minds. When I was freelancing and struggling to keep client projects organized, I started building something that worked with my brain instead of against it.</p>
|
||||
|
||||
<p>What started as a personal solution became Leantime when I realized millions of others needed the same thing.</p><br />
|
||||
<a href="https://www.linkedin.com/in/marcelfolaron/" target="_blank" ><i class="fa fa-linkedin"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex-1" style="background:var(--dropdown-link-hover-bg); padding:15px; border-radius:var(--box-radius);">
|
||||
<img src="{{ BASE_URL }}/dist/images/gloria.png" style="float:right; width:100px; border:none; box-shadow:none; margin-left:10px; margin-bottom:10px;"/>
|
||||
<p><strong style="margin-bottom:5px; display:block;">👋 And I'm Gloria</strong>Former ER nurse turned product manager, first-generation Hispanic entrepreneur.</p>
|
||||
<br /><p>I spent years in high-pressure medical environments where organization literally saves lives. But when I started my own business, traditional project tools felt overwhelming and disconnected from how I actually think and work.</p>
|
||||
<p>My background in behavioral science and motivation research drives how we build features that don't just organize tasks—they help you actually want to complete them.</p><br />
|
||||
<a href="https://www.linkedin.com/in/gloriafolaron/" target="_blank" ><i class="fa fa-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br /><br />
|
||||
<div>
|
||||
<center>
|
||||
<p>We can create a world where no one has to fight their tools to do great work. Your sponsorship funds a future where software adapts to human diversity, not the other way around.</p><br /> <br />
|
||||
<h1 class="fancyLink">Ready to make a direct impact?</h1><p>Every contribution—from $1 to $100—goes directly to making Leantime better for everyone.</p>
|
||||
<br />
|
||||
<div class="tw-text-center">
|
||||
<x-global::forms.button tag="a" contentRole="primary" class="btn-lg" link="https://github.com/sponsors/Leantime" target="_blank" rel="noopener noreferrer">Start Sponsoring Today</x-global::forms.button>
|
||||
</div>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
app/Domain/Help/Templates/swotCanvas.blade.php
Normal file
1
app/Domain/Help/Templates/swotCanvas.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@include('canvas::helper', ['canvasName' => 'swot'])
|
||||
24
app/Domain/Help/Templates/wiki.blade.php
Normal file
24
app/Domain/Help/Templates/wiki.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:50%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_real_time_collaboration_c62i.svg') !!}
|
||||
</div>
|
||||
<h3 class="primaryColor">Documentation where you can find it</h3><br />
|
||||
<p>Our docs allow you to write and share documentation with your team. You can create multiple spaces to organize your documentation into teams, areas or document category.<br/>
|
||||
Create documents to share knowledge, processes and procedures. You can also create a document to share a link to a file or a folder in your cloud storage.<br/>
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
13
app/Domain/Help/register.php
Normal file
13
app/Domain/Help/register.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Leantime\Core\Events\EventDispatcher;
|
||||
|
||||
EventDispatcher::addEventListener('leantime.domain.auth.*.userSignUpSuccess', function ($params) {
|
||||
|
||||
$userId = session('userdata.id');
|
||||
$userRole = session('userdata.role');
|
||||
|
||||
$helperService = app()->make(\Leantime\Domain\Help\Services\Helper::class);
|
||||
$helperService->createDefaultProject($userId, $userRole);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user