This commit is contained in:
Joey Kimsey
2024-12-21 16:26:05 -05:00
parent 0c2fa757dd
commit f8e75e847d
59 changed files with 861 additions and 387 deletions

View File

@ -71,6 +71,7 @@ class Bookmarks extends Controller {
}
public function index() {
self::$title = 'Manage Bookmarks - {SITENAME}';
if ( Input::exists('submit') ) {
$prefs = new Preferences;
$user = new User;
@ -116,6 +117,7 @@ class Bookmarks extends Controller {
}
public function unsorted() {
self::$title = 'Unsorted Bookmarks - {SITENAME}';
$bookmarks = self::$bookmarks->noFolder();
Views::view( 'bookmarks.bookmarks.unsorted', $bookmarks );
}
@ -147,6 +149,7 @@ class Bookmarks extends Controller {
}
public function createBookmark( $id = null ) {
self::$title = 'Add Bookmark - {SITENAME}';
$folderID = Input::get('folder_id') ? Input::get('folder_id') : $id;
$folderID = Input::post('folder_id') ? Input::post('folder_id') : $id;
$this->setFolderSelect( $folderID );
@ -182,6 +185,7 @@ class Bookmarks extends Controller {
}
public function editBookmark( $id = null ) {
self::$title = 'Edit Bookmark - {SITENAME}';
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : '';
$bookmark = self::$bookmarks->findById( $id );
@ -263,6 +267,7 @@ class Bookmarks extends Controller {
}
public function createFolder( $id = 0 ) {
self::$title = 'Create Folder - {SITENAME}';
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : $id;
$folders = self::$folders->simpleByUser();
if ( ! empty( $folders ) ) {
@ -287,6 +292,7 @@ class Bookmarks extends Controller {
}
public function editFolder( $id = null ) {
self::$title = 'Edit Folder - {SITENAME}';
$folder = self::$folders->findById( $id );
if ( $folder == false ) {
@ -345,6 +351,11 @@ class Bookmarks extends Controller {
* Dashboards
*/
public function addDash() {
self::$title = 'Add Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to add dashboards.' );
return $this->index();
}
$folders = self::$folders->byUser() ?? [];
if ( !empty( $folders ) ) {
@ -387,6 +398,11 @@ class Bookmarks extends Controller {
}
public function editDash( $id = null ) {
self::$title = 'Edit Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to edit dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findById( $id );
if ( $dash == false ) {
@ -449,6 +465,10 @@ class Bookmarks extends Controller {
}
public function deleteDash( $id = null ) {
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to delete dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findById( $id );
if ( $dash == false ) {
Issues::add( 'error', 'Unknown Dashboard' );
@ -468,6 +488,11 @@ class Bookmarks extends Controller {
}
public function dashboard( $uuid = null ) {
self::$title = 'Bookmark Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to view dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findByUuid( $uuid );
if ( $dash == false ) {
return $this->dashboards();
@ -476,6 +501,32 @@ class Bookmarks extends Controller {
Issues::add( 'error', 'You do not have permission to view this dash.' );
return $this->dashboards();
}
if ( Input::exists( 'submit' ) ) {
if ( Forms::check( 'updateDashboard' ) ) {
$filters = '';
$folders = '';
if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) {
$filters = implode( ',', Input::post('link_filter') );
}
if ( ! empty( Input::post('link_order') ) ) {
$folders = Input::post('link_order');
}
$result = self::$dashboards->updateDash( $dash->ID, $filters, $folders );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] );
} else {
Issues::add( 'success', 'Your dashboard has been saved.' );
}
} else {
Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] );
}
unset( $_POST );
}
$dash = self::$dashboards->findByUuid( $uuid );
$foldersArray = [];
if ( ! empty( $dash->link_order ) ) {
@ -504,12 +555,19 @@ class Bookmarks extends Controller {
if ( ! empty( $dash->saved_prefs ) ) {
$this->setDashToggles( explode( ',', $dash->saved_prefs ) );
} else {
$this->setDashToggles( [] );
}
return Views::view( 'bookmarks.dashboards.view', $dash );
}
public function dashboards() {
self::$title = 'Bookmark Dashboards - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to manage dashboards.' );
return $this->index();
}
$dashboards = self::$dashboards->byUser();
return Views::view( 'bookmarks.dashboards.list', $dashboards );
}
@ -517,7 +575,7 @@ class Bookmarks extends Controller {
/**
* Functionality
*/
public function publish( $id = null ) {
public function publish( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
@ -627,6 +685,7 @@ class Bookmarks extends Controller {
}
public function import() {
self::$title = 'Bookmark Import - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to import bookmarks.' );
return $this->index();
@ -641,50 +700,44 @@ class Bookmarks extends Controller {
return Views::view( 'bookmarks.import' );
}
if (isset($_FILES['bookmark_file'])) {
if ( isset( $_FILES['bookmark_file'] ) ) {
$file = $_FILES['bookmark_file'];
// Check file size
if ($file['size'] > 1024 * 1024) { // 1024 KB = 1 MB
die('The file is too large. Maximum size is 1 MB.');
Issues::add( 'error', 'There is a 1 meg limit on bookmark imports at this time.' );
return Views::view( 'bookmarks.import' );
}
// Check file extension
$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($fileExtension !== 'html') {
die('Invalid file type. Only .html files are allowed.');
Issues::add( 'error', 'Invalid file type. Only .html files are allowed.' );
return Views::view( 'bookmarks.import' );
}
// Proceed with file processing
$fileContent = file_get_contents($file['tmp_name']);
} else {
die('No file was uploaded.');
Issues::add( 'error', 'No Import detected' );
return Views::view( 'bookmarks.import' );
}
$out = $this->parseBookmarks($fileContent);
$date = 'today';
$date = date('F j, Y');
$description = 'Imported on ' . $date . ' from file: ' . $file['name'];
$importFolder = self::$folders->create( 'New Import', 0, $description );
// $importFolder = 1;
// echo 'make import folder: ' . PHP_EOL;
foreach ($out as $folder => $bookmarks) {
// echo 'make folder: ' . $folder . PHP_EOL;
$currentFolder = self::$folders->create( $folder, $importFolder, $description );
foreach ($bookmarks as $index => $bookmark) {
// echo 'make folder: ' . $bookmark['url'] . PHP_EOL;
self::$bookmarks->create( $bookmark['name'], $bookmark['url'], $currentFolder);
}
}
Session::flash( 'success', 'Your Bookmark has been created.' );
Redirect::to( 'bookmarks/bookmarks/'. $importFolder );
// echo '</pre>';
// exit;
// dv ( $out );
}
public function export() {
self::$title = 'Bookmark Export - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to export bookmarks.' );
return $this->index();
@ -720,7 +773,11 @@ class Bookmarks extends Controller {
Session::flash( 'error', 'Folder not found.' );
return Redirect::to( 'bookmarks/index' );
}
$links = self::$bookmarks->byFolder( $folder->ID );
$hiddenExcluded = ! ( Input::post('hiddenIncluded') ?? false );
$archivedExcluded = ! ( Input::post('archivedIncluded') ?? false );
$links = self::$bookmarks->byFolder( $folder->ID, $hiddenExcluded, $archivedExcluded );
$htmlDoc .= $this->exportFolder( $folder->title, $folder->createdAt, $folder->createdAt, $links );
}
$htmlDoc .= '</DL><p>' . PHP_EOL;
@ -774,6 +831,10 @@ class Bookmarks extends Controller {
return Views::view( 'bookmarks.share', $panelArray );
}
/**
* Private
*/
private function exportFolder( $title, $editedAt, $createdAt, $links ) {
$htmlDoc = '<DT><H3 ADD_DATE="'.$createdAt.'" LAST_MODIFIED="'.$editedAt.'">'.$title.'</H3>' . PHP_EOL;
$htmlDoc .= '<DL><p>' . PHP_EOL;

View File

@ -29,6 +29,7 @@ class BookmarksForms extends Forms {
self::addHandler( 'exportBookmarks', __CLASS__, 'exportBookmarks' );
self::addHandler( 'createDashboard', __CLASS__, 'createDashboard' );
self::addHandler( 'editDashboard', __CLASS__, 'editDashboard' );
self::addHandler( 'updateDashboard', __CLASS__, 'updateDashboard' );
}
public static function createBookmark() {
@ -190,6 +191,17 @@ class BookmarksForms extends Forms {
return true;
}
public static function updateDashboard() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( !self::token() ) {
Check::addUserError( 'There was an issue with your request.' );
return false;
}
return true;
}
public static function editDashboard() {
if ( ! Input::exists( 'submit' ) ) {
return false;

View File

@ -1,30 +1,9 @@
let masonryInstance;
document.addEventListener('DOMContentLoaded', () => {
// Handle all bootstrap popover inits
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
const masonryContainer = document.querySelector('[data-masonry]');
if ( ! masonryContainer ) {
return;
}
const masonryInstance = new Masonry(masonryContainer, {
columnHeight: '.accordion',
percentPosition: true
});
const updateMasonryLayout = () => masonryInstance.layout();
masonryContainer.addEventListener('hidden.bs.collapse', updateMasonryLayout);
masonryContainer.addEventListener('shown.bs.collapse', updateMasonryLayout);
const observer = new MutationObserver(() => {
updateMasonryLayout();
});
// Observe all cards for changes in the DOM
document.querySelectorAll('.card').forEach((card) => {
observer.observe(card, { childList: true, subtree: true });
});
// Initialize all toggle functions
toggleVisibility('editModeSwitch', 'edit-mode');
toggleVisibility('showArchivedSwitch', 'link-archived');
@ -39,6 +18,119 @@ document.addEventListener('DOMContentLoaded', () => {
toggleVisibility('dashShowHiddenSwitch', 'link-hidden');
toggleVisibility('dashAddButtonSwitch', 'btn-addlink');
toggleVisibility('dashShareButtonSwitch', 'btn-share');
// Retrieve the list of collapsed folderCard IDs from local storage
const collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedFolders' )) || [];
// Collapse the elements stored in local storage when the page loads
collapsedFolders.forEach((folderId) => {
const collapseElement = document.querySelector(`#Collapse${folderId}`);
if (collapseElement) {
collapseElement.classList.remove('show');
}
});
// Add event listeners to track expand and collapse actions
document.querySelectorAll('.accordion-item').forEach((item) => {
const folderCardId = item.closest('.bookmark-card')?.id?.replace('folderCard', '');
if ( ! folderCardId ) return;
const collapseElement = item.querySelector('.collapse');
// Listen for collapse and expand events
collapseElement.addEventListener('hidden.bs.collapse', () => {
// Add the folderCard ID to local storage when collapsed
if (!collapsedFolders.includes(folderCardId)) {
collapsedFolders.push(folderCardId);
localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders));
}
});
collapseElement.addEventListener('shown.bs.collapse', () => {
// Remove the folderCard ID from local storage when expanded
const index = collapsedFolders.indexOf(folderCardId);
if (index > -1) {
collapsedFolders.splice(index, 1);
localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders));
}
});
});
const masonryContainer = document.getElementById("bookmarkSort");
if ( ! masonryContainer ) {
return;
}
if ( localStorage.getItem('manageFolderOrder') ) {
loadDashLinkOrder();
}
masonryInstance = new Masonry(masonryContainer, {
columnHeight: '.accordion',
itemSelector: ".bookmark-card",
percentPosition: false
});
const test = () => masonryInstance.layout();
masonryContainer.addEventListener('hidden.bs.collapse', test );
masonryContainer.addEventListener('shown.bs.collapse', test );
// Select all folder-raise and folder-lower buttons
const raiseButtons = document.querySelectorAll(".folder-raise");
const lowerButtons = document.querySelectorAll(".folder-lower");
// Function to move the folderCard up
function moveUp(event) {
const folderCard = event.target.closest(".bookmark-card");
const bookmarkSort = document.getElementById("bookmarkSort");
const orderId = document.getElementById("dashLinkOrder");
if (folderCard) {
const previousSibling = folderCard.previousElementSibling;
if (previousSibling) {
// Remove and reinsert the element before the previous sibling
bookmarkSort.removeChild(folderCard);
bookmarkSort.insertBefore(folderCard, previousSibling);
masonryInstance.reloadItems();
masonryInstance.layout();
updateDashLinkOrder();
}
}
}
// Function to move the folderCard down
function moveDown(event) {
const folderCard = event.target.closest(".bookmark-card");
const bookmarkSort = document.getElementById("bookmarkSort");
if (folderCard) {
const nextSibling = folderCard.nextElementSibling;
if (nextSibling) {
// Remove and reinsert the element after the next sibling
bookmarkSort.removeChild(folderCard);
bookmarkSort.insertBefore(folderCard, nextSibling.nextSibling);
masonryInstance.reloadItems();
masonryInstance.layout();
updateDashLinkOrder();
}
}
}
// Add event listeners to the raise buttons
raiseButtons.forEach(button => {
button.addEventListener("click", moveUp);
});
// Add event listeners to the lower buttons
lowerButtons.forEach(button => {
button.addEventListener("click", moveDown);
});
// after hiding/showing elements
masonryInstance.layout();
});
// Function to handle showing or hiding elements based on the checkbox state
@ -52,143 +144,72 @@ function toggleVisibility(switchId, className) {
// Listen for changes to the checkbox
switchElement.addEventListener('change', () => {
if (switchElement.checked) {
elementsToToggle.forEach(element => {
element.style.display = ''; // Show the element (default display)
});
} else {
elementsToToggle.forEach(element => {
element.style.display = 'none'; // Hide the element
});
}
if (switchElement.checked) {
elementsToToggle.forEach(element => {
element.style.display = '';
});
} else {
elementsToToggle.forEach(element => {
element.style.display = 'none';
});
}
if (typeof masonryInstance !== 'undefined') {
masonryInstance.reloadItems();
masonryInstance.layout()
return;
}
});
// Trigger the toggle initially to set the correct visibility on page load
switchElement.dispatchEvent(new Event('change'));
}
document.addEventListener('DOMContentLoaded', function () {
const bookmarkSort = document.getElementById('bookmarkSort');
if ( ! bookmarkSort ) {
return;
function updateDashLinkOrder() {
const bookmarkCards = document.querySelectorAll("#bookmarkSort .bookmark-card");
const ids = Array.from( bookmarkCards ).map( card => {
const match = card.id.match(/folderCard(\d+)/);
return match ? match[1] : null;
}).filter( id => id !== null );
const dashLinkOrder = document.getElementById("dashLinkOrder");
if (dashLinkOrder) {
dashLinkOrder.value = ids.join(",");
} else {
localStorage.setItem('manageFolderOrder', ids.join(","));
}
let draggingElement = null;
let placeholder = null;
let initialX = 0;
let initialY = 0;
let offsetX = 0;
let offsetY = 0;
// Function to handle the drag start
const handleDragStart = (e, element) => {
draggingElement = element;
}
// Create a placeholder to maintain layout
placeholder = document.createElement('div');
placeholder.style.height = `${draggingElement.offsetHeight}px`;
placeholder.style.marginBottom = getComputedStyle(draggingElement).marginBottom;
placeholder.classList.add('placeholder');
draggingElement.parentNode.insertBefore(placeholder, draggingElement);
const rect = element.getBoundingClientRect();
initialX = e.clientX;
initialY = e.clientY;
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
function loadDashLinkOrder() {
const storedOrder = localStorage.getItem("manageFolderOrder"); // Get the saved order
const bookmarkSort = document.getElementById("bookmarkSort");
// Prevent the element from moving in the normal flow
element.classList.add('dragging');
element.style.position = 'absolute';
draggingElement.style.width = `${rect.width}px`; // Preserve width
draggingElement.style.height = `${rect.height}px`; // Preserve height
element.style.zIndex = 1000; // Bring the element on top of others
element.style.left = `${rect.left}px`;
element.style.top = `${rect.top}px`;
e.preventDefault();
};
if (!storedOrder || !bookmarkSort) return; // Exit if no saved order or no container
// Function to handle dragging movement
const handleDragMove = (e) => {
if (draggingElement) {
const dx = e.clientX - initialX + offsetX;
const dy = e.clientY - initialY + offsetY;
draggingElement.style.left = `${dx}px`;
draggingElement.style.top = `${dy}px`;
}
};
const orderArray = storedOrder.split(","); // Convert the saved order into an array
const bookmarkCards = Array.from(document.querySelectorAll("#bookmarkSort .bookmark-card"));
// Function to handle the drag end
const handleDragEnd = () => {
if (draggingElement) {
const rect = draggingElement.getBoundingClientRect();
// Reset the position styles of the dragged element
draggingElement.style.position = '';
draggingElement.style.zIndex = ''; // Reset z-index
draggingElement.style.left = '';
draggingElement.style.top = '';
draggingElement.classList.remove('dragging');
// Re-insert the element back into the DOM properly
const closestElement = getClosestElement(rect.left, rect.top);
console.error(closestElement.id);
if (closestElement) {
bookmarkSort.insertBefore(draggingElement, closestElement);
console.log( 'insertBefore' );
} else {
bookmarkSort.appendChild(draggingElement);
console.log( 'append' );
}
// Reorder the elements after the drag ends
reorderElements();
draggingElement = null;
}
};
// Function to reorder the elements inside the container
const reorderElements = () => {
const elements = Array.from(bookmarkSort.children);
const sortedIds = elements.map(element => element.id);
console.log('New order:', sortedIds);
};
// Function to handle the drop event
const handleDrop = (e) => {
e.preventDefault();
};
// Helper function to find the closest element based on mouse position
const getClosestElement = (x, y) => {
const elements = Array.from(bookmarkSort.children).filter(
(el) => el !== placeholder && el !== draggingElement
);
let closest = null;
let closestDistance = Number.POSITIVE_INFINITY;
elements.forEach((child) => {
const rect = child.getBoundingClientRect();
const distance = Math.abs(rect.top - y);
if (distance < closestDistance) {
closestDistance = distance;
closest = child;
}
});
return closest;
};
// Attach event listeners to all .card-header elements for dragging
Array.from(document.querySelectorAll('.mover-arrow')).forEach(cardHeader => {
cardHeader.addEventListener('mousedown', (e) => handleDragStart(e, cardHeader.closest('.bookmark-card')));
// Create a map for quick lookup of cards by their ID
const cardMap = new Map();
bookmarkCards.forEach(card => {
const match = card.id.match(/folderCard(\d+)/);
if (match) cardMap.set(match[1], card);
});
// Handle the dragging process
document.addEventListener('mousemove', handleDragMove);
document.addEventListener('mouseup', handleDragEnd);
bookmarkSort.addEventListener('dragover', handleDrop); // Listen for the drop event to update the order
});
// Reorder elements based on the saved order
const orderedElements = [];
orderArray.forEach(id => {
if (cardMap.has(id)) {
orderedElements.push(cardMap.get(id)); // Add elements in the specified order
cardMap.delete(id); // Remove from the map once processed
}
});
// Add any remaining (unspecified) elements to the end of the list
const remainingElements = Array.from(cardMap.values());
orderedElements.push(...remainingElements);
// Clear the container and append the elements in the new order
bookmarkSort.innerHTML = ""; // Remove all children
orderedElements.forEach(element => bookmarkSort.appendChild(element)); // Append in order
}

View File

@ -41,7 +41,7 @@ class BookmarkDashboards extends DatabaseModel {
public function create( $title, $saved_prefs, $link_order, $description = '' ) {
if ( ! Check::dataTitle( $title ) ) {
Debug::info( 'Views: illegal title.' );
Debug::info( 'Dash: illegal title.' );
return false;
}
$fields = [
@ -54,8 +54,8 @@ class BookmarkDashboards extends DatabaseModel {
'createdAt' => time(),
];
if ( ! self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'viewCreate' );
Debug::error( "Views: not created " . var_export($fields,true) );
new CustomException( 'dashCreate' );
Debug::error( "Dash: not created " . var_export($fields,true) );
return false;
}
return self::$db->lastId();
@ -63,11 +63,11 @@ class BookmarkDashboards extends DatabaseModel {
public function update( $id, $title, $saved_prefs, $link_order, $description = '' ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Views: illegal ID.' );
Debug::info( 'Dash: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'Views: illegal title.' );
Debug::info( 'Dash: illegal title.' );
return false;
}
$fields = [
@ -77,8 +77,29 @@ class BookmarkDashboards extends DatabaseModel {
'link_order' => $link_order,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'viewUpdate' );
Debug::error( "Views: $id not updated" );
new CustomException( 'dashUpdate' );
Debug::error( "Dash: $id not updated" );
return false;
}
return true;
}
public function updateDash( $id, $saved_prefs = '', $link_order = '' ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Dash: illegal ID.' );
return false;
}
$fields = [];
$fields['saved_prefs'] = $saved_prefs;
if ( ! empty( $link_order ) ) {
$fields['link_order'] = $link_order;
}
if ( empty( $fields ) ) {
return true;
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'dashUpdate' );
Debug::error( "Dash: $id not updated" );
return false;
}
return true;

View File

@ -146,16 +146,17 @@ class Bookmarks extends DatabaseModel {
return $this->filter( $bookmarks->results() );
}
public function byFolder( $id, $limit = null ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND'];
$whereClause = array_merge( $whereClause, [ 'folderID', '=', $id ] );
if ( empty( $limit ) ) {
$bookmarks = self::$db->get( $this->tableName, $whereClause );
} else {
$bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
public function byFolder( $id, $hiddenExcluded = false, $archivedExcluded = false ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID ];
$whereClause = array_merge( $whereClause, [ 'AND', 'folderID', '=', $id ] );
if ( ! empty( $hiddenExcluded ) ) {
$whereClause = array_merge( $whereClause, [ 'AND', 'hiddenAt', 'is', 'NULL'] );
}
if ( !$bookmarks->count() ) {
if ( ! empty( $archivedExcluded ) ) {
$whereClause = array_merge( $whereClause, [ 'AND', 'archivedAt', 'is', 'NULL'] );
}
$bookmarks = self::$db->get( $this->tableName, $whereClause );
if ( ! $bookmarks->count() ) {
Debug::info( 'No Bookmarks found.' );
return false;
}
@ -343,17 +344,18 @@ class Bookmarks extends DatabaseModel {
$instance = $data;
$end = true;
}
$base_url = $this->getBaseUrl( $instance->url );
if ( empty( $instance->icon ) ) {
$instance->iconHtml = '<i class="fa fa-fw fa-link"></i>';
} else {
if (strpos($instance->icon, 'http') !== false) {
$instance->iconHtml = '<img src="' . $instance->icon .'">';
} else {
$instance->iconHtml = '<img src="' . $base_url . ltrim( $instance->icon, '/' ) .'">';
$instance->iconHtml = '<i class="fa fa-fw fa-link"></i>';
if ( ! empty( $instance->icon ) ) {
if ( strpos($instance->icon, 'http') !== false) {
$instance->iconHtml = '<img src="' . $instance->icon .'">';
} else {
if ( ! empty( $instance->url ) ) {
$base_url = $this->getBaseUrl( $instance->url );
$instance->iconHtml = '<img src="' . $base_url . ltrim( $instance->icon, '/' ) .'">';
}
}
}
}
if ( $instance->privacy == 'private' ) {
$instance->privacyBadge = '<span class="mx-2 translate-center badge bg-success rounded-pill">Private</span>';
@ -439,7 +441,7 @@ class Bookmarks extends DatabaseModel {
return false;
}
$fields = [
'hiddenAt' => 0,
'hiddenAt' => NULL,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
@ -503,7 +505,7 @@ class Bookmarks extends DatabaseModel {
return false;
}
$fields = [
'archivedAt' => 0,
'archivedAt' => NULL,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
@ -779,7 +781,7 @@ class Bookmarks extends DatabaseModel {
}
}
function isValidImageUrl($url) {
public function isValidImageUrl($url) {
$headers = @get_headers($url);
if ($headers && strpos($headers[0], '200') !== false) {

View File

@ -62,37 +62,37 @@ class Bookmarks extends Plugin {
'default' => 'false',
],
'showArchivedSwitch' => [
'pretty' => 'Bookmarks default setting for showing archived bookmarks',
'pretty' => 'Show archived bookmarks by default',
'type' => 'checkbox',
'default' => 'false',
],
'showHiddenSwitch' => [
'pretty' => 'Bookmarks default setting for showing hidden bookmarks',
'pretty' => 'Show hidden bookmarks by default',
'type' => 'checkbox',
'default' => 'false',
],
'archiveButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the archive buttons',
'pretty' => 'Show the archive buttons by default',
'type' => 'checkbox',
'default' => 'false',
],
'visibilityButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the visibility buttons',
'pretty' => 'Show the visibility buttons by default',
'type' => 'checkbox',
'default' => 'false',
],
'privacyButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the privacy buttons',
'pretty' => 'Show the privacy buttons by default',
'type' => 'checkbox',
'default' => 'true',
],
'shareButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the share buttons',
'pretty' => 'Show the share buttons by default',
'type' => 'checkbox',
'default' => 'true',
],
'addButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the add buttons',
'pretty' => 'Show the add buttons by default',
'type' => 'checkbox',
'default' => 'true',
],

View File

@ -48,7 +48,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create</button>
</div>
</fieldset>
</form>

View File

@ -48,7 +48,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Save</button>
</div>
</fieldset>
</form>

View File

@ -19,7 +19,7 @@
<td style="text-align: center;">
{privacy}
</td>
<td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-outline-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-outline-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
@ -34,5 +34,5 @@
</tbody>
</table>
<div class="text-center">
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-md btn-primary">Create</a>
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-md atb-green-bg">Create</a>
</div>

View File

@ -23,7 +23,7 @@
<td style="text-align: center;">
{privacy}
</td>
<td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-outline-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-outline-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
@ -38,7 +38,7 @@
</tbody>
</table>
<div class="text-center">
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-lg btn-primary atb-green-bg">Add</a>
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-lg atb-green-bg">Add</a>
</div>
</div>
</div>

View File

@ -3,9 +3,12 @@
<div class="card m-3 accordion">
<div class="accordion-item">
<div class="card-header accordion-header text-center bg-{color} context-main">
<span class="h4 float-left mover-arrow"><i class="fa-solid fa-arrows-up-down-left-right"></i></span>
<span class="float-start">
<button class="btn btn-sm pe-3 folder-raise context-main" type="button"><i class="fa-solid fa-arrow-up"></i></button>
<button class="btn btn-sm ps-3 folder-lower context-main" type="button"><i class="fa-solid fa-arrow-down"></i></button>
</span>
<span class="h4 text-center mx-5" data-bs-target="#Collapse{ID}" data-bs-toggle="collapse" aria-expanded="true" aria-controls="Collapse{ID}">{title}</span>
<a class="btn btn-sm btn-primary btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm atb-green-bg btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
</div>
@ -35,8 +38,8 @@
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success btn-addlink"><i class="fa fa-fw fa-plus"></i></a></td>
<span class="ms-auto">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-info-circle"></i></a></td>
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-warning edit-mode"><i class="fa fa-fw fa-pencil"></i></a></td>
<a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-danger edit-mode"><i class="fa fa-fw fa-trash"></i></a></td>
</span>

View File

@ -1,14 +1,14 @@
{LOOP}
<li class="list-group-item context-main-bg bg-{color} {hidden_class} {archived_class} mb-1">
<a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a>
<a href="{url}"> {title}</a>
<a href="{url}" class="text-decoration-none atb-green"> {title}</a>
<span class="float-end">
<span class="btn-hideshow">{hideBtn}</span>
<span class="btn-archive">{archiveBtn}</span>
<span class="btn-publish">{publish}</span>
<a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-warning edit-mode"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-danger edit-mode"><i class="fa fa-fw fa-trash"></i></a>
<a class="btn btn-sm btn-primary btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm atb-green-bg btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -3,7 +3,7 @@
<a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a>
<a href="{url}"> {title}</a>
<span class="float-end">
<a class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm atb-green-bg" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -9,7 +9,7 @@
<span class="text-center px-5">{privacyBadge}</span>
<span class="h4 text-center px-5">{title}</span>
</span>
<a class="btn btn-sm btn-primary btn-rounded btn-share atb-green-bg" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm btn-rounded btn-share atb-green-bg" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
</div>
@ -39,8 +39,8 @@
</div>
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<span class="ms-auto">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
</span>
</div>
</div>

View File

@ -4,7 +4,7 @@
<a href="{url}"> {title}</a>{privacyBadge}
<span class="float-end">
{publish}
<a class="btn btn-sm btn-primary btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm atb-green-bg btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -3,7 +3,7 @@
<a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a>
<a href="{url}"> {title}</a>
<span class="float-end">
<a class="btn btn-sm btn-primary btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm atb-green-bg btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -35,7 +35,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create</button>
</div>
</fieldset>
</form>

View File

@ -24,6 +24,4 @@
<label class="form-check-label" for="addButtonSwitch">Add Button</label>
</div>
</div>
</div>
</div>

View File

@ -35,7 +35,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Save</button>
</div>
</fieldset>
</form>

View File

@ -3,9 +3,12 @@
<div class="card m-3 accordion">
<div class="accordion-item">
<div class="card-header accordion-header text-center bg-{color} context-main">
<span class="h4 float-left mover-arrow"><i class="fa-solid fa-arrows-up-down-left-right"></i></span>
<span class="float-start">
<button class="btn btn-sm pe-3 folder-raise atb-green" type="button"><i class="fa-solid fa-arrow-up"></i></button>
<button class="btn btn-sm ps-3 folder-lower atb-green" type="button"><i class="fa-solid fa-arrow-down"></i></button>
</span>
<span class="h4 text-center mx-5" data-bs-target="#Collapse{ID}" data-bs-toggle="collapse" aria-expanded="true" aria-controls="Collapse{ID}">{title}</span>
<a class="btn btn-sm btn-primary btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<a class="btn btn-sm atb-green-bg btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i>
</a>
</div>
@ -35,8 +38,8 @@
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success btn-addlink"><i class="fa fa-fw fa-plus"></i></a></td>
<span class="ms-auto">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-info-circle"></i></a></td>
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
</span>
</div>
</div>

View File

@ -6,7 +6,7 @@
<p>From here you can easily create, update, or remove any bookmark dashboards you have created.</p>
<p>Dashboards are a feature that allow you to build customized bookmark pages that you can easily save and open la</p>
</div>
<div class="row g-3 text-center p-2" data-masonry='{ "percentPosition": false }' id="bookmarkSort">
<div class="row g-3 text-center p-2">
<table class="table context-main">
<thead>
<tr>
@ -22,7 +22,7 @@
<tr>
<td class="">{title}</td>
<td>{description}</td>
<td><a href="{ROOT_URL}bookmarks/dashboard/{uuid}" class="btn btn-sm btn-primary atb-green-bg"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/dashboard/{uuid}" class="btn btn-sm atb-green-bg"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editDash/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteDash/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
@ -37,7 +37,7 @@
</tbody>
</table>
<div class="">
<a href="{ROOT_URL}bookmarks/addDash" class="btn btn-lg btn-primary atb-green-bg">Create</a>
<a href="{ROOT_URL}bookmarks/addDash" class="btn btn-lg atb-green-bg">Create</a>
</div>
</div>
</div>

View File

@ -4,7 +4,9 @@
<hr>
<form method="post">
<fieldset>
<div class="row g-3" data-masonry='{ "percentPosition": false }' id="bookmarkSort">
<input type="hidden" name="token" value="{TOKEN}">
<input type="hidden" name="link_order" value="{link_order}" id="dashLinkOrder">
<div class="row g-3" id="bookmarkSort">
{folderPanels}
</div>
<hr>
@ -15,7 +17,7 @@
</div>
<div class="col-md-4">
<div class="h-100 d-flex align-items-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg mx-5">Save</button>
<button type="submit" name="submit" value="submit" class="btn btn-lg mx-5 atb-green-bg">Save</button>
</div>
</div>
</div>

View File

@ -2,9 +2,9 @@
<div class="offset-2 col-8 p-3 context-main-bg">
<legend class="text-center">Bookmark Export</legend>
<hr>
<h3 class="text-center text-muted">Select which folders to include in the export</h3>
<h3 class="text-center">Select which folders to include in the export</h3>
<form action="" method="post">
<div class="row g-3 col-4 offset-4" data-masonry='{ "percentPosition": false }' id="bookmarkSort">
<div class="row g-3 col-4 offset-4">
<table class="table context-main">
<thead>
<tr>
@ -39,9 +39,28 @@
</tbody>
</table>
</div>
<div class="mb-3 row">
<label for="title" class="col-lg-3 col-form-label text-end align-items-center">Bookmark Filters:</label>
<div class="col-lg-6">
<div class="p-2">
<!-- Status Filters -->
<div class="d-flex align-items-center border p-2 rounded mb-3 context-main-bg">
<div class="me-3 fw-bold">Status:</div>
<div class="form-check form-switch me-3">
<input class="form-check-input" type="checkbox" value="true" id="archivedIncluded" name="archivedIncluded">
<label class="form-check-label" for="archivedIncluded">Include Archived</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" value="true" id="hiddenIncluded" name="hiddenIncluded">
<label class="form-check-label" for="hiddenIncluded">Include Hidden</label>
</div>
</div>
</div>
</div>
</div>
<div class="text-center">
<p>Literally every browser is chromium based now, so they all have a standard import/export.</p>
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg atb-green-bg">Create Export</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create Export</button>
</div>
</form>
</div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action -->
<div class="text-center mt-5">
<a href="/brave" class="btn btn-primary atb-green-bg btn-lg">
<a href="/brave" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Brave
</a>
</div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action -->
<div class="text-center mt-5">
<a href="/chrome" class="btn btn-primary atb-green-bg btn-lg">
<a href="/chrome" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Chrome
</a>
</div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action -->
<div class="text-center mt-5">
<a href="#" class="btn btn-primary atb-green-bg btn-lg">
<a href="#" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Edge
</a>
</div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action -->
<div class="text-center mt-5">
<a href="/firefox" class="btn btn-primary atb-green-bg btn-lg">
<a href="/firefox" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Firefox
</a>
</div>

View File

@ -14,7 +14,7 @@
<i class="fab fa-chrome fa-3x text-primary mb-3"></i>
<h5 class="card-title">Chrome Extension</h5>
<p class="card-text">Enhance your Chrome browser with advanced features and effortless organization.</p>
<a href="/extensions/chrome" class="btn btn-primary atb-green-bg btn-sm">Learn More</a>
<a href="/extensions/chrome" class="btn atb-green-bg btn-sm">Learn More</a>
</div>
</div>
</div>
@ -26,7 +26,7 @@
<i class="fab fa-firefox fa-3x text-warning mb-3"></i>
<h5 class="card-title">Firefox Extension</h5>
<p class="card-text">Seamlessly integrate with Firefox for a smoother browsing experience.</p>
<a href="/extensions/firefox" class="btn btn-primary atb-green-bg btn-sm">Learn More</a>
<a href="/extensions/firefox" class="btn atb-green-bg btn-sm">Learn More</a>
</div>
</div>
</div>
@ -38,7 +38,7 @@
<i class="fab fa-opera fa-3x text-danger mb-3"></i>
<h5 class="card-title">Opera Extension</h5>
<p class="card-text">Boost your Opera browser with intuitive features and tools.</p>
<a href="/extensions/opera" class="btn btn-primary atb-green-bg btn-sm">Learn More</a>
<a href="/extensions/opera" class="btn atb-green-bg btn-sm">Learn More</a>
</div>
</div>
</div>
@ -50,7 +50,7 @@
<i class="fab fa-brave fa-3x text-orange mb-3"></i>
<h5 class="card-title">Brave Extension</h5>
<p class="card-text">Enjoy secure and private browsing with Brave, enhanced by our extension.</p>
<a href="/extensions/brave" class="btn btn-primary atb-green-bg btn-sm">Learn More</a>
<a href="/extensions/brave" class="btn atb-green-bg btn-sm">Learn More</a>
</div>
</div>
</div>
@ -62,7 +62,7 @@
<i class="fab fa-edge fa-3x text-info mb-3"></i>
<h5 class="card-title">Edge Extension</h5>
<p class="card-text">Maximize productivity on Microsoft Edge with our extension.</p>
<a href="/extensions/edge" class="btn btn-primary atb-green-bg btn-sm">Learn More</a>
<a href="/extensions/edge" class="btn atb-green-bg btn-sm">Learn More</a>
</div>
</div>
</div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action -->
<div class="text-center mt-5">
<a href="#" class="btn btn-primary atb-green-bg btn-lg">
<a href="#" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Opera
</a>
</div>

View File

@ -40,7 +40,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create</button>
</div>
</fieldset>
</form>

View File

@ -38,7 +38,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button>
<button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Save</button>
</div>
</fieldset>
</form>

View File

@ -14,7 +14,7 @@
<td>{prettyPrivacy}</td>
<td>{description}</td>
<td>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline mx-1"><i class="fa fa-fw fa-info-circle"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline mx-1"><i class="fa fa-fw fa-info-circle"></i></a>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-outline-warning mx-1"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-outline-danger mx-1"><i class="fa fa-fw fa-trash"></i></a>
</td>
@ -30,5 +30,5 @@
</tbody>
</table>
<div class="text-center">
<a href="{ROOT_URL}bookmarks/createFolder" class="btn btn-md btn-primary">Create</a>
<a href="{ROOT_URL}bookmarks/createFolder" class="btn btn-md atb-green-bg">Create</a>
</div>

View File

@ -45,7 +45,7 @@
<!-- Admin Controls -->
<div class="card-footer text-center">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-primary atb-green-bg btn-sm me-2" data-bs-toggle="tooltip" title="Broadcast Message">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn atb-green-bg btn-sm me-2" data-bs-toggle="tooltip" title="Broadcast Message">
<i class="fa fa-list"></i> Bookmarks
</a>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-warning btn-sm me-2" data-bs-toggle="tooltip" title="Edit User">

View File

@ -8,7 +8,7 @@
<input type="file" name="bookmark_file" id="bookmark_file" accept=".html">
</div>
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block atb-green-bg">Import</button>
<button type="submit" name="submit" value="submit" class="btn btn-lg center-block atb-green-bg">Import</button>
</div>
</form>
</div>

View File

@ -58,7 +58,7 @@
</fieldset>
</div>
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<button type="submit" name="submit" value="submit" class="btn btn-md btn-outline-primary my-2">Save as Default</button>
<button type="submit" name="submit" value="submit" class="btn btn-md atb-green-outline my-2">Save as Default</button>
</div>
</form>
</div>

View File

@ -19,6 +19,8 @@ use TheTempusProject\Classes\AdminController;
use TheTempusProject\Plugins\Members as MemberModel;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Hermes\Functions\Redirect;
class Members extends AdminController {
public function __construct() {
@ -28,13 +30,46 @@ class Members extends AdminController {
}
public function index( $data = null ) {
self::$title = 'Admin - Membership Scripts';
Views::view( 'members.admin.scripts' );
}
public function orphans( $data = null, $id = null ) {
self::$title = 'Admin - Orphaned PRoducts';
if ( $data = 'abandon' && ! empty( $id ) ) {
MemberModel::orphanAbandon( $id );
Redirect::to('admin/members/orphans');
}
$orphans = MemberModel::findOrphans();
Views::view( 'members.admin.orphans', $orphans );
}
public function webhooks( $data = null, $id = null ) {
self::$title = 'Admin - Membership Webhooks';
if ( $data = 'delete' && ! empty( $id ) ) {
MemberModel::webhookRemove( $id );
Redirect::to('admin/members/webhooks');
}
$data = [];
$webhooks = MemberModel::webhookList();
foreach ($webhooks->data as $key => $webhook) {
$hook = new \stdClass;
$hook->id = $webhook->id;
$hook->enabled_events = implode( ', <br>', $webhook->enabled_events );
$hook->status = $webhook->status;
$hook->url = $webhook->url;
$data[] = $hook;
}
Components::set( 'urltouse', Routes::getAddress() );
if ( !Input::exists( 'submit' ) ) {
return Views::view( 'members.admin.webhooks' );
return Views::view( 'members.admin.webhooks', $data );
}
MemberModel::webhookSetup();
Issues::add( 'success', 'Webhooks Generated' );
Issues::add( 'error', 'Now, LEAVE!' );
Redirect::to('admin/members/webhooks');
}
}

View File

@ -70,7 +70,7 @@ class Member extends Controller {
}
try {
$session = self::$stripe->billingPortal->sessions->create([
'customer' => $customer,
'customer' => $customer->stripe_customer,
'return_url' => Routes::getAddress() . 'member/manage',
]);
} catch (\Stripe\Exception\InvalidRequestException $e) {
@ -174,7 +174,7 @@ class Member extends Controller {
Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' );
return Redirect::home();
}
Views::view( 'members.landing1', $product );
Views::view( 'members.landing', $product );
}
public function checkout( $plan = 'monthly' ) {
@ -187,6 +187,19 @@ class Member extends Controller {
Issues::add( 'error', 'no customer' );
return $this->index();
}
$plan = strtolower( $plan );
if ( ! in_array( $plan, ['monthly','yearly','upgrade'] ) ) {
Session::flash( 'error', 'Unknown plan' );
return Redirect::to( 'home/index' );
}
if ( $plan === 'upgrade' ) {
$plan = 'yearly';
$successUrl = Routes::getAddress() . 'member/payment/upgrade?session_id={CHECKOUT_SESSION_ID}';
} else {
$successUrl = Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}';
}
$stripePrice = $this->findPrice( $plan );
$session = self::$stripe->checkout->sessions->create([
@ -197,7 +210,7 @@ class Member extends Controller {
'quantity' => 1,
]],
'mode' => 'subscription',
'success_url' => Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}',
'success_url' => $successUrl,
'cancel_url' => Routes::getAddress() . 'member/payment/cancel',
]);
header('Location: ' . $session->url);
@ -206,7 +219,7 @@ class Member extends Controller {
public function payment( $type = '' ) {
$type = strtolower( $type );
if ( ! in_array( $type, ['cancel','complete'] ) ) {
if ( ! in_array( $type, ['cancel','complete','upgrade'] ) ) {
Session::flash( 'error', 'Unknown Payment' );
return Redirect::to( 'home/index' );
}
@ -216,6 +229,11 @@ class Member extends Controller {
return Views::view( 'members.paymentcanceled' );
}
if ( $type == 'upgrade' ) {
self::$title = 'Better Members Area';
return Views::view( 'members.upgradeCompleted' );
}
self::$title = '(almost) Members Area';
Views::view( 'members.paymentcomplete' );
}

View File

@ -27,8 +27,8 @@ class MembershipProducts extends DatabaseModel {
public $databaseMatrix = [
[ 'name', 'varchar', '128' ],
[ 'description', 'text', '' ],
[ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922
[ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922
[ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4,399.22 === 439922
[ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4,399.22 === 439922
[ 'stripe_product', 'varchar', '64' ], // not-required to create - generated by stripe after
[ 'stripe_price_monthly', 'varchar', '64' ], // not-required to create - generated by stripe after
[ 'stripe_price_yearly', 'varchar', '64' ], // not-required to create - generated by stripe after
@ -166,7 +166,7 @@ class MembershipProducts extends DatabaseModel {
public function updateStripeYearlyPrice( $product, $yearly_price ) {
$stripe->prices->update(
$yearly->id,
$yearly_price,
['lookup_key' => ""]
);
return $this->createStripeYearlyPrice( $product, $yearly_price );
@ -174,7 +174,7 @@ class MembershipProducts extends DatabaseModel {
public function updateStripeMonthlyPrice( $product, $monthly_price ) {
$stripe->prices->update(
$monthly->id,
$monthly_price,
['lookup_key' => ""]
);
return $this->createStripeMonthlyPrice( $product, $monthly_price );
@ -208,7 +208,7 @@ class MembershipProducts extends DatabaseModel {
if ( ! $data->count() ) {
return false;
}
return $data->first();
return $this->filter( $data->first() );
}
public function mainProduct() {

View File

@ -19,6 +19,7 @@ use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Models\Memberships;
use TheTempusProject\Models\MembershipProducts as Products;
use TheTempusProject\Canary\Bin\Canary as Debug;
class Members extends Plugin {
public static $stripe;
@ -46,17 +47,17 @@ class Members extends Plugin {
'text' => '<i class="fa fa-fw fa-arrow-down"></i> Memberships',
'url' => [
[
'text' => '<i class="fa fa-fw fa-database"></i> Products',
'text' => '<i class="fa fa-fw fa-barcode"></i> Products',
'url' => '{ROOT_URL}admin/products',
],
[
'text' => '<i class="fa fa-fw fa-database"></i> Subscriptions',
'text' => '<i class="fa fa-fw fa-bag-shopping"></i> Subscriptions',
'url' => '{ROOT_URL}admin/records',
],
// [
// 'text' => '<i class="fa fa-fw fa-database"></i> Invoices',
// 'url' => '{ROOT_URL}admin/invoices',
// ],
[
'text' => '<i class="fa fa-fw fa-code"></i> Scripts',
'url' => '{ROOT_URL}admin/members',
],
],
],
];
@ -116,41 +117,52 @@ class Members extends Plugin {
];
public function __construct( $load = false ) {
if ( ! self::$loaded ) {
App::$userCPlinks[] = (object) self::$userLinks;
self::$loaded = true;
if ( ! self::$loaded && $load ) {
if ( App::$isLoggedIn ) {
App::$isMember = $this->groupHasMemberAccess( App::$activeGroup );
if ( empty( App::$isMember ) ) {
App::$isMember = $this->userHasActiveMembership( App::$activeUser->ID );
}
}
$this->filters[] = [
'name' => 'member',
'find' => '#{MEMBER}(.*?){/MEMBER}#is',
'replace' => ( App::$isMember ? '$1' : '' ),
'enabled' => true,
];
$this->filters[] = [
'name' => 'nonmember',
'find' => '#{NONMEMBER}(.*?){/NONMEMBER}#is',
'replace' => ( App::$isLoggedIn && App::$isMember == false ? '$1' : '' ),
'enabled' => true,
];
$this->filters[] = [
'name' => 'upgrade',
'find' => '#{UPGRADE}(.*?){/UPGRADE}#is',
'replace' => ( App::$isLoggedIn && ( App::$isMember === 'monthly' ) ? '$1' : '' ),
'enabled' => true,
];
}
if ( App::$isLoggedIn ) {
App::$isMember = $this->groupHasMemberAccess( App::$activeGroup );
if ( empty( App::$isMember ) ) {
App::$isMember = $this->userHasActiveMembership( App::$activeUser->ID );
parent::__construct( $load );
if ( $this->checkEnabled() && App::$isLoggedIn ) {
if ( ! self::$loaded && $load ) {
App::$userCPlinks[] = (object) self::$userLinks;
App::$topNavRightDropdown .= '<li><a href="{ROOT_URL}member/manage" class="dropdown-item"><i class="fa fa-fw fa-credit-card"></i> Subscriptions</a></li>';
}
}
$this->filters[] = [
'name' => 'member',
'find' => '#{MEMBER}(.*?){/MEMBER}#is',
'replace' => ( App::$isMember ? '$1' : '' ),
'enabled' => true,
];
$this->filters[] = [
'name' => 'nonmember',
'find' => '#{NONMEMBER}(.*?){/NONMEMBER}#is',
'replace' => ( App::$isLoggedIn && App::$isMember == false ? '$1' : '' ),
'enabled' => true,
];
$this->filters[] = [
'name' => 'upgrade',
'find' => '#{UPGRADE}(.*?){/UPGRADE}#is',
'replace' => ( App::$isLoggedIn && ( App::$isMember === 'monthly' ) ? '$1' : '' ),
'enabled' => true,
];
if ( ! self::$loaded && $load ) {
self::$loaded = true;
}
$api_key = Config::getValue( 'memberships/stripeSecret' );
if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) {
self::$stripe = false;
} else {
self::$stripe = new StripeClient( $api_key );
}
parent::__construct( $load );
}
public function groupHasMemberAccess( $activeGroup ) {
@ -169,6 +181,10 @@ class Members extends Plugin {
$products = new Products;
$product = $products->findByPriceID( $membership->subscription_price_id );
if ( empty( $product ) ) {
Debug::error('Active membership on non-existent product');
return false;
}
if ( $product->stripe_price_monthly == $membership->subscription_price_id ) {
return 'monthly';
}
@ -177,7 +193,6 @@ class Members extends Plugin {
public static function webhookSetup() {
$root = Routes::getAddress();
// $root = "https://stripe.joeykimsey.com/";
$response = self::$stripe->webhookEndpoints->create([
'enabled_events' => self::$webhookEvents,
'url' => $root . 'api/stripe/webhook',
@ -185,13 +200,66 @@ class Members extends Plugin {
return $response;
}
// public static function webhookSetup() {
// $root = Routes::getAddress();
// $root = "https://stripe.joeykimsey.com/";
// $response = self::$stripe->webhookEndpoints->create([
// 'enabled_events' => self::$webhookEvents,
// 'url' => $root . 'api/stripe/webhook',
// ]);
// return $response;
// }
public static function webhookList() {
$response = self::$stripe->webhookEndpoints->all(['limit' => 25]);
return $response;
}
public static function webhookRemove( $id ) {
$response = self::$stripe->webhookEndpoints->delete( $id, []);
return $response;
}
public static function orphanAbandon( $id ) {
$response = self::$stripe->prices->update(
$id,
['lookup_key' => ""]
);
return $response;
}
public static function findOrphans() {
$orphans = [];
// any prices with keys not represented in our local db will show as an orphan
$result = self::$stripe->prices->search([
'query' => 'lookup_key:"membership-monthly"',
]);
$products = new Products;
if ( ! empty( $result->data ) ) {
$product = $products->findByPriceID( $result->data[0]->id );
if ( empty( $product ) ) {
$data = $result->data[0];
$child = new \stdClass;
$child->price_id = $data->id;
$child->product = $data->product;
$child->lookup_key = $data->lookup_key;
$child->amount = $data->unit_amount;
$orphans[] = $child;
}
}
$result = self::$stripe->prices->search([
'query' => 'lookup_key:"membership-yearly"',
]);
if ( ! empty( $result->data ) ) {
$product = $products->findByPriceID( $result->data[0]->id );
if ( empty( $product ) ) {
$data = $result->data[0];
$child = new \stdClass;
$child->price_id = $data->id;
$child->product = $data->product;
$child->lookup_key = $data->lookup_key;
$child->amount = $data->unit_amount;
$orphans[] = $child;
}
}
return $orphans;
}
}

View File

@ -0,0 +1,35 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Stripe Webhook Generation</legend>
<hr>
{ADMIN_BREADCRUMBS}
<p>Orphans are stripe prices that have unique lookup_keys used by the membership system, but have no products currently saved.</p>
<table class="table table-striped">
<thead>
<tr>
<th style="width: 10%">price id</th>
<th style="width: 10%">amount</th>
<th style="width: 50%">lookup key</th>
<th style="width: 10%">url</th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{price_id}</td>
<td>{amount}</td>
<td>{lookup_key}</td>
<td><a href="{ROOT_URL}admin/members/orphans/adopt/{price_id}" class="btn btn-sm btn-success"><i class="fa fa-fw fa-download"></i></a></td>
<td><a href="{ROOT_URL}admin/members/orphans/abandon/{price_id}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td class="text-center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
</div>

View File

@ -45,7 +45,7 @@
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block">Send</button>
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block">Save</button>
</div>
</form>
</div>

View File

@ -0,0 +1,13 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Membership Scripts</legend>
<hr>
{ADMIN_BREADCRUMBS}
<ul class="list-unstyled">
<li>
<a href="{ROOT_URL}admin/members/webhooks">Webhook Generation</a>
</li>
<li>
<a href="{ROOT_URL}admin/members/orphans">Orphan Products</a>
</li>
</ul>
</div>

View File

@ -1,10 +1,43 @@
<legend>WARNING: Regenerating existing webhooks makes joey sad, don't do iit!</legend>
<form action="" method="post" class="form-horizontal">
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
<div class="context-main-bg context-main p-3">
<legend class="text-center">Stripe Webhook Generation</legend>
<hr>
{ADMIN_BREADCRUMBS}
<table class="table table-striped">
<thead>
<tr>
<th style="width: 10%">id</th>
<th style="width: 10%">status</th>
<th style="width: 50%">enabled_events</th>
<th style="width: 10%">url</th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{id}</td>
<td>{status}</td>
<td>{enabled_events}</td>
<td>{url}</td>
<td><a href="{ROOT_URL}admin/members/webhooks/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td class="text-center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<div class="text-center">
<h5>WARNING: Regenerating existing webhooks makes joey sad, don't do iit!</h5>
<p>The new webhooks will be generated using the base url: <strong>{urltouse}</strong></p>
<form action="" method="post" class="form-horizontal">
<div class="form-group">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-danger">re-generate</button>
</div>
</form>
</div>
</form>
</div>

View File

@ -1,21 +1,20 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 text-center">
<h2>Are You Sure You Want to Cancel?</h2>
<div class="container py-4 context-main-bg my-4">
<h2 class="text-center">Are You Sure You Want to Cancel?</h2>
<hr>
<div class="text-center">
<p class="lead">
Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits.
Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits.
</p>
<p>
Consider staying to continue enjoying the full experience of our service.
Consider staying to continue enjoying the full experience of our service.
</p>
<div class="row">
<div class="col-md-6">
<a href="{ROOT_URL}member/cancelconfirm/{cancelid}" class="btn btn-lg btn-danger btn-block">Cancel Subscription</a>
</div>
<div class="col-md-6">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block">Go Back</a>
</div>
<div class="col-md-3 offset-3">
<a href="{ROOT_URL}member/cancelconfirm/{cancelid}" class="btn btn-lg btn-outline-danger btn-block">Cancel Subscription</a>
</div>
<div class="col-md-3">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block atb-green-bg">Go Back</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,3 @@
<!-- Compare plans -->
<div class="table-responsive pricing-container container pb-4" id="compare">
<h1 class="display-6 text-center my-4">Compare plans</h1>
@ -31,7 +30,7 @@
<td><i class="fa fa-fw fa-check atb-green"></i></td>
</tr>
<tr>
<th scope="row" class="text-start">Share bookmarks abd folders</th>
<th scope="row" class="text-start">Share bookmarks and folders</th>
<td><i class="fa fa-fw fa-check"></i></td>
<td><i class="fa fa-fw fa-check atb-green"></i></td>
<td><i class="fa fa-fw fa-check atb-green"></i></td>
@ -88,9 +87,6 @@
<li>Access from any device</li>
<li>Share access with anyone</li>
</ul>
<a href="/register" class="mt-auto w-100 btn btn-lg atb-green-outline">
Sign-Up for Free
</a>
</div>
</div>
</div>
@ -100,7 +96,7 @@
<h4 class="my-0 fw-normal">Monthly</h4>
</div>
<div class="card-body d-flex flex-column">
<h1 class="card-title pricing-card-title">&#36;4.99<small class="text-muted fw-light">/month</small></h1>
<h1 class="card-title pricing-card-title">{prettyPriceMonthly}<small class="text-muted fw-light">/month</small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>Import/Export Features</li>
<li>Integration with TempusTools App (WIP)</li>
@ -108,7 +104,7 @@
<li>Direct control of Feature Development</li>
<li>Early Access to new features</li>
</ul>
<a href="/member/signup/monthly" class="mt-auto w-100 btn btn-lg atb-green-bg">
<a href="/member/checkout/monthly" class="mt-auto w-100 btn btn-lg atb-green-bg">
Get started
</a>
</div>
@ -120,11 +116,11 @@
<h4 class="my-0 fw-normal">Yearly</h4>
</div>
<div class="card-body d-flex flex-column">
<h1 class="card-title pricing-card-title">&#36;19.99<small class="text-muted fw-light">/year</small></h1>
<h1 class="card-title pricing-card-title">{prettyPriceYearly}<small class="text-muted fw-light">/year</small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>Its cheaper if you like the product</li>
</ul>
<a href="/member/signup/yearly" class="mt-auto w-100 btn btn-lg atb-green-bg">
<a href="/member/checkout/yearly" class="mt-auto w-100 btn btn-lg atb-green-bg">
Get started
</a>
</div>

View File

@ -1,7 +1,8 @@
<div class="container py-4">
<div class="container py-4 context-main-bg my-4">
<h3 class="mb-4 text-center">Manage Memberships</h3>
<hr>
<div class="row justify-content-center">
<div class="col-md-8">
<legend>Memberships</legend>
<table class="table text-center context-main">
<thead>
<tr>
@ -23,13 +24,13 @@
<td>{DTC=date}{current_period_start}{/DTC}</td>
<td>{DTC=date}{current_period_end}{/DTC}</td>
<td>
<a href="{ROOT_URL}member/pause/{ID}" class="btn btn-sm btn-primary">
<a href="{ROOT_URL}member/pause/{ID}" class="btn btn-sm btn-outline-warning">
<i class="fa fa-fw fa-pause"></i>
</a>
</td>
<td>
<a href="{ROOT_URL}member/cancel/{ID}" class="btn btn-sm btn-danger">
<i class="fa fa-fw fa-trash"></i>
<a href="{ROOT_URL}member/cancel/{ID}" class="btn btn-sm btn-outline-danger">
<i class="fa fa-fw fa-ban"></i>
</a>
</td>
</tr>
@ -43,7 +44,7 @@
{/ALT}
</tbody>
</table>
<a href="{ROOT_URL}member/managepayment" class="btn btn-sm btn-primary">
<a href="{ROOT_URL}member/managepayment" class="btn btn-sm btn-primary atb-green-bg">
Manage Payment Method
</a>
</div>

View File

@ -11,7 +11,8 @@
Right now, this entire system was built and managed by myself. I have used my own version of this for years, but translating it to a publicly available product is not a 1-to-1 job. There may be bugs or issues encountered while you use the product. I can't guarantee a fix for every need in every case immediately, but I do actively keep track of bugs and work hard to ensure everyone has a great experience using the app.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
{loggedin}<a href="/bugreport" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Report a Bug</a>{/loggedin}
<a href="/member/manage" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-bg">Manage Membership</a>
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div>

View File

@ -1,21 +1,20 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 text-center">
<h2>Are You Sure You Want to Pause?</h2>
<div class="container py-4 context-main-bg my-4">
<h2 class="text-center">Are You Sure You Want to Pause?</h2>
<hr>
<div class="text-center">
<p class="lead">
Pausing your subscription means you'll miss out on exclusive features, updates, and benefits.
Pausing your subscription means you'll miss out on exclusive features, updates, and benefits.
</p>
<p>
Consider staying to continue enjoying the full experience of our service.
Consider staying to continue enjoying the full experience of our service.
</p>
<div class="row">
<div class="col-md-6">
<a href="{ROOT_URL}member/pauseconfirm/{pauseid}" class="btn btn-lg btn-danger btn-block">Pause Subscription</a>
</div>
<div class="col-md-6">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block">Go Back</a>
</div>
<div class="col-md-3 offset-3">
<a href="{ROOT_URL}member/pauseconfirm/{pauseid}" class="btn btn-lg btn-outline-danger btn-block">Pause Subscription</a>
</div>
<div class="col-md-3">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block atb-green-bg">Go Back</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,14 @@
<div class="jumbotron text-center">
<h1>Take your time, its not a sprint, its a marathon.</h1>
<p>Its ok, no-one wants to checkout too fast, its embarrassing.</p>
<div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<h2 class="text-center atb-green mb-4">Almost there!</h2>
<p class="lead">
Take your time, its not a sprint, its a marathon.
Nno-one wants to checkout too fast, its embarrassing.
</p>
<p class="text-muted">
If you think this is a tool that you could really use and can't afford it, use the contact form below and tell me why you need it.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div>

View File

@ -1,5 +1,14 @@
<div class="jumbotron text-center">
<h1>Thanks for joining!</h1>
<p>Its people like you who keep the pixels on around here.
Its people like me who tell you life is unfair and it could take up to an hour for your membership to activate.</p>
<div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<h2 class="text-center atb-green mb-4">Thanks for joining!</h2>
<p class="lead">
Its people like you who keep the pixels on around here.
Its people like me who tell people like you that unfortunately, it could take up to an hour for your membership to activate.
</p>
<p class="text-muted">
With that said, its usually instant, and if its taking too long, just reach out to us via the contact form.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 text-center">
<div class="row">
<h2 class="text-primary mb-4">Upgrade to a Yearly Plan</h2>
<h2 class="mb-4 atb-green">Upgrade to a Yearly Plan</h2>
<p class="lead">
Save more and enjoy uninterrupted access to all features with our yearly plan!
</p>
@ -10,12 +10,12 @@
</p>
<div class="row">
<div class="col-md-6">
<a href="/member/checkout/yearly" class="btn btn-lg btn-success btn-block">
<a href="/member/checkout/upgrade" class="btn btn-lg btn-block atb-green-bg">
Upgrade to Yearly
</a>
</div>
<div class="col-md-6">
<a href="/member/upgrade" class="btn btn-lg btn-primary btn-block">
<a href="/member" class="btn btn-lg btn-block atb-green-outline">
Stay on Monthly
</a>
</div>

View File

@ -0,0 +1,13 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<h2 class="text-center atb-green mb-4">Thanks for the vote of confidence!</h2>
<p class="lead">
While you were more profitable as a monthly user, I do appreciate the vote of confidence it takes to commit to 4x the cost at once.
</p>
<p class="text-muted">
There really is no more benefit than you saving money, so enjoy the features you have already come to know and enjoy.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div>