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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/getting-started/choose-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ choose the needed community or group.
the right context.
Community Dashboard needs a selected community.
Group Dashboard needs both a selected community and a selected group.
On phones, the mobile layout hides the dashboard links; choose **Desktop version** from the same
menu to make them reappear.

## Fixed Role Model

Expand Down
7 changes: 7 additions & 0 deletions docs/support/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Public pages are usable on mobile, so you can discover groups and events from yo
user and group Check-In sections are also mobile-friendly. Other dashboard sections are currently
desktop-only.

If you need one of those sections from a phone or tablet, open the user menu at the top right and
choose **Desktop version**. The site reloads with the full desktop layout, so everything is
available, although it is not optimized for small screens. The choice applies to the whole site
and is remembered in that browser until you pick **Mobile version** from the same menu. It only
changes the layout, never your permissions, and it requires JavaScript and cookies. Because the
switch reloads the page, save any unfinished work first.

## Where Do I Submit a Speaker Proposal?

Proposal creation and submission happen in two different places. First create your proposals in
Expand Down
5 changes: 5 additions & 0 deletions ocg-server/static/css/styles.src.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
@source "static/js";
@source "templates";

/* Active while the desktop version mode is on (set by common/base.html). */
@custom-variant viewport-desktop (&:where(html[data-viewport-mode="desktop"] *));

@theme {
--font-inter: Inter, sans-serif;
--font-sans:
Expand Down Expand Up @@ -671,6 +674,7 @@ input:placeholder-shown {
.icon-copy { --icon-url: url('/static/images/icons/copy.svg'); }
.icon-csv { --icon-url: url('/static/images/icons/csv.svg'); }
.icon-date { --icon-url: url('/static/images/icons/date.svg'); }
.icon-desktop { --icon-url: url('/static/images/icons/desktop.svg'); }
.icon-docs { --icon-url: url('/static/images/icons/docs.svg'); }
.icon-download { --icon-url: url('/static/images/icons/download.svg'); }
.icon-drag { --icon-url: url('/static/images/icons/drag.svg'); }
Expand Down Expand Up @@ -711,6 +715,7 @@ input:placeholder-shown {
.icon-megaphone { --icon-url: url('/static/images/icons/megaphone.svg'); }
.icon-members { --icon-url: url('/static/images/icons/members.svg'); }
.icon-meeting { --icon-url: url('/static/images/icons/meeting.svg'); }
.icon-mobile { --icon-url: url('/static/images/icons/mobile.svg'); }
.icon-network { --icon-url: url('/static/images/icons/network.svg'); }
.icon-next { --icon-url: url('/static/images/icons/next.svg'); }
.icon-pencil { --icon-url: url('/static/images/icons/pencil.svg'); }
Expand Down
1 change: 1 addition & 0 deletions ocg-server/static/images/icons/desktop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ocg-server/static/images/icons/mobile.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
91 changes: 91 additions & 0 deletions ocg-server/static/js/common/viewport-mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Switches between the responsive layout and the forced desktop version mode.
*
* The preference lives in a JavaScript-readable cookie that the inline
* initializer in templates/common/base.html applies before first paint; the
* server never reads it. Selecting a mode writes or clears the cookie and
* reloads the page so the initializer runs again for the current URL.
*/
import { closestElement } from "/static/js/common/dom.js";

// Scoped to buttons: the same attribute marks the active mode on `<html>`.
const ACTION_SELECTOR = "button[data-viewport-mode]";
const COOKIE_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
export const DESKTOP_MODE = "desktop";
export const VIEWPORT_MODE_ATTRIBUTE = "data-viewport-mode";
export const VIEWPORT_MODE_COOKIE = "ocg_viewport_mode";

let clickHandlerBound = false;
let reloadHandler = () => window.location.reload();

/**
* Binds the delegated click handler for `button[data-viewport-mode]` controls.
*
* Delegated on the document so the inline menu, the lazily loaded user menu
* partial and HTMX body swaps all share one listener.
* @returns {void}
*/
export const initViewportModeToggle = () => {
if (clickHandlerBound) {
return;
}

document.addEventListener("click", handleClick);
clickHandlerBound = true;
};

/**
* Builds the `document.cookie` assignment string for a viewport mode.
* @param {string} mode - `desktop` writes the long-lived cookie; any other value expires it.
* @param {{ secure?: boolean }} [options] - Adds the `Secure` attribute when the page is served over HTTPS.
* @returns {string} Cookie string ready to assign to `document.cookie`.
*/
export const buildViewportModeCookie = (mode, { secure = false } = {}) => {
const isDesktop = mode === DESKTOP_MODE;
const parts = [
`${VIEWPORT_MODE_COOKIE}=${isDesktop ? DESKTOP_MODE : ""}`,
"Path=/",
`Max-Age=${isDesktop ? COOKIE_MAX_AGE_SECONDS : 0}`,
"SameSite=Lax",
];
if (secure) {
parts.push("Secure");
}

return parts.join("; ");
};

/**
* Persists the requested viewport mode and reloads the page to apply it.
* @param {string} mode - `desktop` enables the desktop version; any other value restores the default.
* @returns {void}
*/
export const setViewportMode = (mode) => {
document.cookie = buildViewportModeCookie(mode, { secure: window.location.protocol === "https:" });
reloadHandler();
};

/**
* Overrides the page reload used after the cookie changes.
* @param {() => void} handler - Replacement reload callback (used by tests).
* @returns {void}
*/
export const setViewportModeReloadHandler = (handler) => {
reloadHandler = handler;
};

/**
* Applies the mode of the clicked `button[data-viewport-mode]` control.
* @param {MouseEvent} event - Document click event.
* @returns {void}
*/
const handleClick = (event) => {
const control = closestElement(event.target, ACTION_SELECTOR);
if (!control) {
return;
}

setViewportMode(control.dataset.viewportMode);
};

initViewportModeToggle();
15 changes: 13 additions & 2 deletions ocg-server/templates/common/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
<!DOCTYPE html>
<html lang="en" class="h-full min-h-screen">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{# Desktop version mode: apply the cookie before first paint. Inline and synchronous on
purpose; a module or deferred script would run after layout. Cookie and attribute names
mirror static/js/common/viewport-mode.js. -#}
<script>
if (document.cookie.split(";").some((c) => c.trim() === "ocg_viewport_mode=desktop")) {
document.documentElement.setAttribute("data-viewport-mode", "desktop");
document.querySelector('meta[name="viewport"]').setAttribute("content", "width=1280");
}
</script>

{# Page title -#}
{% block head_title -%}
<title>Open Community Groups</title>
Expand Down Expand Up @@ -49,8 +61,6 @@
sizes="any">

{% endif -%}
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

{# Styles -#}
<link rel="stylesheet"
Expand Down Expand Up @@ -89,6 +99,7 @@
<script type="module" src="/static/js/common/modals/modal-toggle-bindings.js"></script>
<script type="module" src="/static/js/common/form-validation.js"></script>
<script type="module" src="/static/js/common/ad-banner.js"></script>
<script type="module" src="/static/js/common/viewport-mode.js"></script>
{# End scripts -#}
</head>
{# djlint:off #}
Expand Down
20 changes: 12 additions & 8 deletions ocg-server/templates/common/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
aria-hidden="true"></li>

{# Mobile Check-In links -#}
<li class="lg:hidden" role="none">
<li class="md:hidden" role="none">
<a href="/dashboard/user?tab=check-in"
hx-boost="true"
hx-target="body"
Expand All @@ -171,7 +171,7 @@
</li>

{% if user.belongs_to_any_group_team.unwrap_or(false) -%}
<li class="lg:hidden" role="none">
<li class="md:hidden" role="none">
<a href="/dashboard/group?tab=check-in"
hx-boost="true"
hx-target="body"
Expand All @@ -187,7 +187,7 @@
{% endif -%}
{# End mobile Check-In links -#}

<li class="hidden lg:block" role="none">
<li class="hidden md:block" role="none">
<a href="/dashboard/user?tab=groups"
hx-boost="true"
hx-target="body"
Expand All @@ -201,7 +201,7 @@
</a>
</li>

<li class="hidden lg:block" role="none">
<li class="hidden md:block" role="none">
<a href="/dashboard/user?tab=events"
hx-boost="true"
hx-target="body"
Expand All @@ -215,13 +215,13 @@
</a>
</li>

<li class="hidden lg:block border-t border-stone-200 mt-2 pt-2"
<li class="hidden md:block border-t border-stone-200 mt-2 pt-2"
role="separator"
aria-hidden="true"></li>

{# Dashboard links -#}
{% if user.belongs_to_community_team.unwrap_or(false) -%}
<li class="hidden lg:block" role="none">
<li class="hidden md:block" role="none">
<a href="/dashboard/community"
hx-boost="true"
hx-target="body"
Expand All @@ -237,7 +237,7 @@
{% endif -%}

{% if user.belongs_to_any_group_team.unwrap_or(false) -%}
<li class="hidden lg:block" role="none">
<li class="hidden md:block" role="none">
<a href="/dashboard/group"
hx-boost="true"
hx-target="body"
Expand All @@ -252,7 +252,7 @@
</li>
{% endif -%}

<li class="hidden lg:block" role="none">
<li class="hidden md:block" role="none">
<a href="/dashboard/user"
hx-boost="true"
hx-target="body"
Expand All @@ -267,6 +267,8 @@
</li>
{# End dashboard links -#}

{{ header::viewport_mode_items() -}}

{# Logout -#}
<li class="border-t border-stone-200 mt-2 pt-2" role="none">
<form action="/log-out" method="post" hx-boost="false" target="_self">
Expand Down Expand Up @@ -381,6 +383,8 @@
</a>
</li>
{# End log in -#}

{{ header::viewport_mode_items() -}}
</ul>
</div>
{# End user dropdown menu for non-logged users -#}
Expand Down
7 changes: 2 additions & 5 deletions ocg-server/templates/dashboard/dashboard_base.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% extends "common/base.html" -%}
{% import "macros/dashboard.html" as dashboard -%}

{% block skip_link -%}
{# Skip navigation link -#}
Expand Down Expand Up @@ -30,11 +31,7 @@

{% block main -%}
{% block mobile_dashboard_view -%}
<div class="fixed inset-0 z-10 flex items-center justify-center bg-stone-100 px-6 pt-16 md:hidden">
<div class="max-w-md rounded-lg border-4 border-dashed border-primary-600 bg-stone-100 p-8 text-center text-stone-800">
<p class="text-xl font-medium">This dashboard is not optimized yet for mobile devices</p>
</div>
</div>
{{ dashboard::mobile_notice() -}}
{% endblock mobile_dashboard_view -%}

{# Main Content -#}
Expand Down
6 changes: 1 addition & 5 deletions ocg-server/templates/dashboard/group/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,7 @@
{% block mobile_dashboard_view -%}
<div id="mobile-dashboard-view" class="contents">
{% if !content.is_check_in() && !is_check_in_fallback -%}
<div class="fixed inset-0 z-10 flex items-center justify-center bg-stone-100 px-6 pt-16 md:hidden">
<div class="max-w-md rounded-lg border-4 border-dashed border-primary-600 bg-stone-100 p-8 text-center text-stone-800">
<p class="text-xl font-medium">This dashboard is not optimized yet for mobile devices</p>
</div>
</div>
{{ dashboard::mobile_notice() -}}
{% endif -%}
</div>
{% endblock mobile_dashboard_view -%}
Expand Down
6 changes: 1 addition & 5 deletions ocg-server/templates/dashboard/user/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@

{% block mobile_dashboard_view -%}
{% if !content.is_check_in() -%}
<div class="fixed inset-0 z-10 flex items-center justify-center bg-stone-100 px-6 pt-16 md:hidden">
<div class="max-w-md rounded-lg border-4 border-dashed border-primary-600 bg-stone-100 p-8 text-center text-stone-800">
<p class="text-xl font-medium">This dashboard is not optimized yet for mobile devices</p>
</div>
</div>
{{ dashboard::mobile_notice() -}}
{% endif -%}
{% endblock mobile_dashboard_view -%}

Expand Down
17 changes: 17 additions & 0 deletions ocg-server/templates/macros/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,23 @@
{% endmacro menu_title -%}
{# End menu title -#}

{# Mobile notice -#}
{# Used by dashboards to cover unsupported content on small screens and point to the desktop
version option in the user menu. -#}
{% macro mobile_notice() -%}
<div class="fixed inset-0 z-10 flex items-center justify-center bg-stone-100 px-6 pt-16 md:hidden">
<div class="max-w-md rounded-lg border-4 border-dashed border-primary-600 bg-stone-100 p-8 text-center text-stone-800">
<p class="text-xl font-medium">This dashboard is not optimized yet for mobile devices</p>
<p class="hidden pointer-coarse:block text-sm text-stone-600 mt-4">
If you prefer, you can load the desktop version from the user menu at the top right. It is
not optimized for small screens, but everything is available, and you can switch back from
the same menu.
</p>
</div>
</div>
{% endmacro mobile_notice -%}
{# End mobile notice -#}

{# Modal close button #}
{# Used by dashboard modal headers as the shared close control. #}
{% macro modal_close_button(id = "", attrs = "") -%}
Expand Down
33 changes: 33 additions & 0 deletions ocg-server/templates/macros/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,36 @@
border-primary-500{%- else -%}border-transparent hover:border-primary-300{%- endif -%} {{ extra_styles_ }}">{{ content }}</a>
{% endmacro link -%}
{# End link -#}

{# Viewport mode items -#}
{# Used by the header user menu to switch between the responsive layout and the desktop version.
Visibility is CSS-only: "Desktop version" shows below xl on coarse-pointer devices while the
default mode is active; "Mobile version" shows whenever the desktop mode attribute is set. -#}
{% macro viewport_mode_items() -%}
<li class="hidden max-xl:pointer-coarse:not-viewport-desktop:block border-t border-stone-200 mt-2 pt-2"
role="none">
<button type="button"
data-viewport-mode="desktop"
class="block w-full text-start px-4 py-2 hover:bg-stone-100"
role="menuitem">
<span class="flex items-center">
<span class="svg-icon size-4 icon-desktop bg-stone-600"></span>
<span class="ms-2 text-xs/6">Desktop version</span>
</span>
</button>
</li>

<li class="hidden viewport-desktop:block border-t border-stone-200 mt-2 pt-2"
role="none">
<button type="button"
data-viewport-mode="default"
class="block w-full text-start px-4 py-2 hover:bg-stone-100"
role="menuitem">
<span class="flex items-center">
<span class="svg-icon size-4 icon-mobile bg-stone-600"></span>
<span class="ms-2 text-xs/6">Mobile version</span>
</span>
</button>
</li>
{% endmacro viewport_mode_items -%}
{# End viewport mode items -#}
Loading
Loading