fingers crossed this helps a bit

This commit is contained in:
Mahad Kalam 2026-02-10 00:00:37 +00:00
parent 26f3d4e814
commit 4c64cffb1e
9 changed files with 149 additions and 12 deletions

1
.gitignore vendored
View file

@ -49,3 +49,4 @@ public/vite-dev
public/vite-ssr
.vite
public/vite

View file

@ -1,3 +1,4 @@
web: bin/rails server -b 0.0.0.0
css: bin/rails tailwindcss:watch
vite: bin/vite dev
ssr: bin/vite ssr

View file

@ -31,7 +31,7 @@ class InertiaController < ApplicationController
admin_links: inertia_admin_links,
viewer_links: inertia_viewer_links,
superadmin_links: inertia_superadmin_links,
activities_html: inertia_activities_html
activities: inertia_activities_props
}
end
@ -120,9 +120,22 @@ class InertiaController < ApplicationController
{ label: label, href: href, active: active, badge: badge }
end
def inertia_activities_html
def inertia_activities_props
return nil unless defined?(@activities) && @activities.present?
helpers.render_activities(@activities)
@activities.filter_map do |activity|
owner = activity.owner
next unless owner
message = inertia_activity_message(activity)
next if message.blank?
{
id: activity.id,
owner: inertia_user_mention_props(owner),
message: message
}
end
end
def inertia_footer_props
@ -164,7 +177,8 @@ class InertiaController < ApplicationController
avatar_url: user.avatar_url,
title: title,
country_code: user.country_code,
country_name: country_name
country_name: country_name,
impersonate_path: impersonate_path_for(user)
}
end
@ -182,6 +196,49 @@ class InertiaController < ApplicationController
}
end
def inertia_activity_message(activity)
params = activity.parameters&.with_indifferent_access || {}
project = params[:project].presence
case activity.key
when "user.first_signup"
"just signed in for the first time"
when "user.first_heartbeat"
base = "just started tracking their coding time"
project ? "#{base} on #{project}!" : "#{base}!"
when "user.started_working"
base = "just started working"
project ? "#{base} on #{project}" : base
when "user.coding_session"
base = "just finished coding"
duration = helpers.short_time_simple(params[:duration_seconds].to_i)
if project
"#{base} on #{project} for #{duration}"
else
"#{base} for #{duration}"
end
when "physical_mail.mail_sent"
mission_type = params[:humanized_mission_type].presence || "a mission"
"was just sent a letter for '#{mission_type}'"
when "physical_mail.first_streak_achieved"
"just hit their first 7 day coding streak!"
else
nil
end
end
def impersonate_path_for(user)
return nil unless current_user
return nil unless current_user.admin_level.in?(%w[admin superadmin])
return nil if user == current_user
return nil if user.admin_level == "superadmin"
return impersonate_user_path(user) if user.admin_level != "admin"
return impersonate_user_path(user) if current_user.admin_level == "superadmin"
nil
end
def currently_hacking_props
data = Cache::CurrentlyHackingJob.perform_now
users = (data[:users] || []).map do |u|

View file

@ -5,6 +5,7 @@
title?: string | null;
country_code?: string | null;
country_name?: string | null;
impersonate_path?: string | null;
};
let { user }: { user: NavUserMention } = $props();
@ -35,14 +36,27 @@
<span class="inline-flex items-center gap-1">
{user.display_name}
</span>
{#if user.country_code}
{@const flagUrl = countryFlagUrl(user.country_code)}
{#if flagUrl}
<span title={user.country_name || undefined} class="flex items-center">
<img
src={countryFlagUrl(user.country_code)}
src={flagUrl}
alt={`${user.country_code} flag`}
class="inline-block w-5 h-5 align-middle"
loading="lazy"
/>
</span>
{/if}
{#if user.impersonate_path}
<span class="admin-tool">
<a
href={user.impersonate_path}
class="text-primary font-bold hover:text-red-300 transition-colors duration-200"
data-turbo-frame="_top"
data-turbo-prefetch="false"
>
&#129400;
</a>
</span>
{/if}
</div>

View file

@ -1,5 +1,5 @@
import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte'
import { mount } from 'svelte'
import { hydrate, mount } from 'svelte'
import AppLayout from '../layouts/AppLayout.svelte'
createInertiaApp({
@ -22,7 +22,9 @@ createInertiaApp({
setup({ el, App, props }) {
if (el) {
mount(App, { target: el, props })
const hasServerMarkup = el.hasChildNodes()
const render = hasServerMarkup ? hydrate : mount
render(App, { target: el, props })
} else {
console.error(
'Missing root element.\n\n' +

View file

@ -21,6 +21,7 @@
title?: string | null;
country_code?: string | null;
country_name?: string | null;
impersonate_path?: string | null;
};
type NavStreak = {
@ -32,6 +33,12 @@
show_super_class?: boolean;
};
type NavActivity = {
id: number;
owner?: NavUserMentionType | null;
message: string;
};
type LayoutNav = {
flash: { message: string; class_name: string }[];
user_present: boolean;
@ -44,7 +51,7 @@
admin_links: NavLink[];
viewer_links: NavLink[];
superadmin_links: NavLink[];
activities_html?: string | null;
activities?: NavActivity[] | null;
};
type Footer = {
@ -340,8 +347,17 @@
</div>
{/if}
{#if layout.nav.activities_html}
<div class="pt-2">{@html layout.nav.activities_html}</div>
{#if layout.nav.activities && layout.nav.activities.length > 0}
<div class="pt-2 space-y-2">
{#each layout.nav.activities as activity (activity.id)}
<div class="flex flex-wrap items-center gap-1">
{#if activity.owner}
<NavUserMention user={activity.owner} />
{/if}
<span>{activity.message}</span>
</div>
{/each}
</div>
{/if}
</nav>
</div>

41
app/javascript/ssr/ssr.ts Normal file
View file

@ -0,0 +1,41 @@
import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte'
import createServer from '@inertiajs/svelte/server'
import { render } from 'svelte/server'
import AppLayout from '../layouts/AppLayout.svelte'
createServer((page) =>
createInertiaApp({
page,
resolve: (name) => {
const pages = import.meta.glob<ResolvedComponent>('../pages/**/*.svelte', {
eager: true,
})
const pageComponent = pages[`../pages/${name}.svelte`]
if (!pageComponent) {
console.error(`Missing Inertia page component: '${name}.svelte'`)
}
return {
default: pageComponent.default,
layout: pageComponent.layout || AppLayout,
} as ResolvedComponent
},
setup({ App, props }) {
return render(App, { props })
},
defaults: {
form: {
forceIndicesArrayFormatInFormData: false,
},
future: {
useScriptElementForInitialPage: true,
useDataInertiaHeadAttribute: true,
useDialogForErrorModal: true,
preserveEqualProps: true,
},
},
}),
)

View file

@ -6,4 +6,6 @@ InertiaRails.configure do |config|
config.always_include_errors_hash = true
config.use_script_element_for_initial_page = true
config.use_data_inertia_head_attribute = true
config.ssr_enabled = ViteRuby.config.ssr_build_enabled
config.ssr_url = ENV.fetch("INERTIA_SSR_URL", "http://127.0.0.1:13714")
end

View file

@ -12,5 +12,8 @@
"autoBuild": true,
"publicOutputDir": "vite-test",
"port": 3037
},
"production": {
"ssrBuildEnabled": true
}
}
}