This Command Center is designed to become the top-level view across the financial system rather than a separate dashboard.
JOM / Presence
YOUR STORY. YOUR JOURNEY.
Profiles and journeys are the public expression of the system. Manage the identity behind the work.
✦
Profile system
Creator identity, disciplines, location, links and public presence.
01
Journey library
The journeys people create, share and discover through JOM.
System / Settings
KEEP JOM CONNECTED.
Owner access and connected systems. More integrations can be added without changing the core operating layer.
Owner account
Private
New task
New opportunity
New community post
Authenticated owner account
ACTIVE';
const [tasks,opps,posts,biz]=await Promise.all([count('command_tasks','owner_id=eq.'+user.id+'&status=neq.done'),count('command_opportunities','owner_id=eq.'+user.id),count('community_posts','visibility=eq.public'),count('command_businesses','owner_id=eq.'+user.id)]);$('mTasks').textContent=tasks;$('mOpps').textContent=opps;$('mPosts').textContent=posts;$('mBusinesses').textContent=biz;
await Promise.all([loadTasks(),loadOpps(),loadCommunity(),loadActivity()]);$('focusTitle').textContent=tasks?'Move what matters.':'Build the system.';$('focusCopy').textContent=tasks?tasks+' open priorities are currently waiting for movement. Start with the highest-impact item.':'Your operating layer is live. Add the first priority, opportunity or business and JOM will start building your command view.'}
async function loadTasks(){const data=await rest('command_tasks','owner_id=eq.'+user.id+'&order=created_at.desc&limit=40');const open=data.filter(x=>x.status!=='done');$('taskCount').textContent=data.length+' records';const html=open.length?open.map(x=>'
'+esc(x.title)+'
'+esc(x.description||'No description')+(x.due_at?' · Due '+new Date(x.due_at).toLocaleDateString():'')+'
'}}
async function enter(s){session=s;localStorage.setItem(SK,JSON.stringify(s));try{const owners=await rest('command_owners','select=user_id&user_id=eq.'+encodeURIComponent(s.user.id)+'&limit=1');if(!owners.length){localStorage.removeItem(SK);session=null;$('ccApp').classList.add('hidden');$('ccAuth').classList.remove('hidden');msg('ccLoginMsg','This account is authenticated but is not authorised for the JOM Command Center.');return;}$('ccAuth').classList.add('hidden');$('ccApp').classList.remove('hidden');await load()}catch(e){localStorage.removeItem(SK);session=null;$('ccApp').classList.add('hidden');$('ccAuth').classList.remove('hidden');msg('ccLoginMsg','Command Center access could not be verified. Please try again.')}}
document.querySelectorAll('.cc-nav button').forEach(b=>b.onclick=()=>showSec(b.dataset.sec));document.querySelectorAll('[data-sec-jump]').forEach(b=>b.onclick=()=>showSec(b.dataset.secJump));$('ccRefresh').onclick=()=>load().then(()=>toast('Command Center refreshed')).catch(e=>toast(e.message));$('ccMobileMenu').onclick=()=>{const nav=document.querySelector('.cc-side');if(nav)nav.style.display=nav.style.display==='block'?'none':'block'};
$('ccShowSignup').onclick=()=>{$('ccLoginPanel').classList.add('hidden');$('ccSignupPanel').classList.remove('hidden')};$('ccShowLogin').onclick=()=>{$('ccSignupPanel').classList.add('hidden');$('ccLoginPanel').classList.remove('hidden')};
$('ccSignup').onclick=async()=>{try{const email=$('ccSignupEmail').value.trim(),password=$('ccSignupPassword').value,name=$('ccName').value.trim();if(password.length<8)throw Error('Password must be at least 8 characters.');const d=await auth('/auth/v1/登録',{email,password,data:{display_name:name},redirect_to:'https://www.journeyofmany.com/jom-owner-command-center/'});if(!d.access_token){msg('ccSignupMsg','Account created. Check your email to confirm it, then return here to sign in.');return}await enter({access_token:d.access_token,refresh_token:d.refresh_token,user:d.user})}catch(e){msg('ccSignupMsg',e.message)}};
$('ccLogin').onclick=async()=>{try{const d=await auth('/auth/v1/token?grant_type=password',{email:$('ccEmail').value.trim(),password:$('ccPassword').value});await enter({access_token:d.access_token,refresh_token:d.refresh_token,user:d.user})}catch(e){msg('ccLoginMsg',e.message)}};
$('ccSignout').onclick=()=>{localStorage.removeItem(SK);location.reload()};document.querySelectorAll('[data-close-modal]').forEach(b=>b.onclick=()=>b.closest('[id$="Modal"]').classList.add('hidden'));
document.querySelectorAll('[data-modal]').forEach(b=>b.onclick=()=>{($(b.dataset.modal==='task'?'ccTaskModal':b.dataset.modal==='opp'?'ccOppModal':'ccPostModal')).classList.remove('hidden')});
$('savePost').onclick=async()=>{const body=$('postBody').value.trim();if(!body)return toast('Write something first.');try{const r=await fetch(API+'/rest/v1/community_posts',{method:'POST',headers:hdr(session.access_token),body:JSON.stringify({author_id:user.id,body,post_type:'post',visibility:'public'})});if(!r.ok)throw Error((await r.json()).message||'Could not publish post');$('ccPostModal').classList.add('hidden');$('postBody').value='';toast('Post published');await load()}catch(e){toast(e.message)}};
document.addEventListener('click',async e=>{const b=e.target.closest('[data-like-post]');if(!b||!session)return;try{const post=b.dataset.likePost;const existing=await rest('community_likes','select=post_id&post_id=eq.'+encodeURIComponent(post)+'&user_id=eq.'+encodeURIComponent(user.id));if(existing.length){const r=await fetch(API+'/rest/v1/community_likes?post_id=eq.'+encodeURIComponent(post)+'&user_id=eq.'+encodeURIComponent(user.id),{method:'DELETE',headers:hdr(session.access_token)});if(!r.ok)throw Error('Could not remove like');b.textContent='Like'}else{const r=await fetch(API+'/rest/v1/community_likes',{method:'POST',headers:hdr(session.access_token),body:JSON.stringify({post_id:post,user_id:user.id})});if(!r.ok)throw Error((await r.json()).message||'Could not like post');b.textContent='Liked'}await loadCommunity()}catch(err){toast(err.message)}});\n$('saveTask').onclick=async()=>{const title=$('taskTitle').value.trim();if(!title)return toast('Add a task title.');try{await rest('command_tasks','',{prefer:'return=representation'});const r=await fetch(API+'/rest/v1/command_tasks',{method:'POST',headers:hdr(session.access_token),body:JSON.stringify({owner_id:user.id,title,description:$('taskDesc').value.trim(),priority:$('taskPriority').value,due_at:$('taskDue').value?new Date($('taskDue').value+'T12:00:00').toISOString():null})});if(!r.ok)throw Error((await r.json()).message||'Could not create task');$('ccTaskModal').classList.add('hidden');$('taskTitle').value='';$('taskDesc').value='';$('taskDue').value='';toast('Priority added');await load()}catch(e){toast(e.message)}};
$('saveOpp').onclick=async()=>{const name=$('oppName').value.trim();if(!name)return toast('Add an opportunity name.');try{const r=await fetch(API+'/rest/v1/command_opportunities',{method:'POST',headers:hdr(session.access_token),body:JSON.stringify({owner_id:user.id,name,contact_name:$('oppContact').value.trim(),contact_email:$('oppEmail').value.trim()||null,value:Number($('oppValue').value||0),stage:$('oppStage').value,next_action:$('oppNext').value.trim()})});if(!r.ok)throw Error((await r.json()).message||'Could not create opportunity');$('ccOppModal').classList.add('hidden');['oppName','oppContact','oppEmail','oppValue','oppNext'].forEach(id=>$(id).value='');toast('Opportunity added');await load()}catch(e){toast(e.message)}};
document.addEventListener('click',async e=>{const o=e.target.closest('[data-advance-opp]');if(o&&session){const stages=['new','contacted','proposal','won','lost'];try{const current=await rest('command_opportunities','id=eq.'+encodeURIComponent(o.dataset.advanceOpp));if(!current.length)return;const i=stages.indexOf(current[0].stage||'new');const next=stages[Math.min(i+1,stages.length-1)];if(next===current[0].stage)return toast('Opportunity is already at the final stage.');o.disabled=true;const r=await fetch(API+'/rest/v1/command_opportunities?id=eq.'+encodeURIComponent(o.dataset.advanceOpp),{method:'PATCH',headers:{...hdr(session.access_token),'Prefer':'return=minimal'},body:JSON.stringify({stage:next})});if(!r.ok)throw Error((await r.json()).message||'Could not advance opportunity');toast('Opportunity moved to '+next);await load()}catch(err){toast(err.message);o.disabled=false}return}const b=e.target.closest('[data-task-done]');if(!b||!session)return;const id=b.dataset.taskDone;b.disabled=true;try{const r=await fetch(API+'/rest/v1/command_tasks?id=eq.'+encodeURIComponent(id),{method:'PATCH',headers:{...hdr(session.access_token),'Prefer':'return=minimal'},body:JSON.stringify({status:'done',completed_at:new Date().toISOString()})});if(!r.ok)throw Error((await r.json()).message||'Could not complete task');toast('Task completed');await load()}catch(err){toast(err.message);b.disabled=false}});const saved=localStorage.getItem(SK);if(saved){try{session=JSON.parse(saved);user=session.user;$('ccAuth').classList.add('hidden');$('ccApp').classList.remove('hidden');load().catch(()=>{localStorage.removeItem(SK);location.reload()})}catch(e){localStorage.removeItem(SK)}}
async function loadProfileSubmissions(){const box=$('ccProfileSubmissions');if(!box||!session)return;box.innerHTML='