richardkeep/nova-timenow
A Laravel Nova field that shows the current time and updates live in your resources and dashboards. Configure timezone and display format to add a real-time clock wherever you need it.
Hard precondition: this package only does anything inside a Laravel Nova admin panel. No Nova, no evaluation — there is nothing to wire up.
Step 0 — confirm what the artifact actually is before you plan any work. The metadata is inconsistent: description.md describes a field that pre-fills "now" into date/time inputs, while the repo name and the "current time from different timezones" tagline describe a dashboard card. Those are different use cases (data-entry hygiene vs. at-a-glance awareness) with different effort. Spend fifteen minutes in the source, not in the README:
git clone https://github.com/richardkeep/nova-timenow /tmp/timenow
ls -R /tmp/timenow/src /tmp/timenow/resources
cat /tmp/timenow/composer.json # php/laravel/nova constraints
grep -rn "class .* extends" /tmp/timenow/src
If you find extends Laravel\Nova\Card, it's a dashboard widget. If you find extends Laravel\Nova\Fields\Field, it's a form input. Also check whether the clock is computed in PHP or in the Vue component — that changes what the thing is trustworthy for.
Step 1 — install pinned, on a branch.
composer require richardkeep/nova-timenow:^1.0 --no-interaction
php artisan nova:publish # only if the package ships assets you need
Pin the tag. With 9 stars and 0 dependents, you are the compatibility test suite; a floating constraint buys you nothing but surprise.
Step 2 — register on a scratch dashboard, not the default one. Create app/Nova/Dashboards/OpsOverview.php and add the card there so you can look at it without committing to it:
public function cards()
{
return [
(new \Richardkeep\TimeNow\TimeNow())
->withMeta(['timezones' => ['UTC', 'Europe/London', 'America/New_York']])
->width('1/3'),
];
}
Adjust the constructor/meta shape to whatever src/ actually exposes — the three common Nova card shapes are a bare new TimeNow(), a fluent ->timezones([...]), or ->withMeta(['timezones' => [...]]).
First use case worth the install: an ops/support/on-call overview dashboard where someone scheduling an escalation can read APAC/EMEA/AMER wall-clock time without a browser tab switch. Configure two or three real team timezones — not a globe of twenty. That's the whole value proposition: a repeated micro-friction removed from every scheduling decision.
Done when: the card renders at the intended width in your Nova version, in light and dark mode, every configured zone resolves to a real row, and you've verified rollback (deregister the card, composer remove). No migrations are involved, so the spike costs an hour, not a sprint.
One card per region group, sized to Nova's grid. Grouping beats a single card with nine rows — it stays scannable, and you can reorder or delete a region without editing an array:
public function cards()
{
return [
(new TimeNow())->withMeta(['timezones' => ['UTC']])->width('1/3'),
(new TimeNow())->withMeta(['timezones' => ['Europe/London', 'Europe/Berlin']])->width('1/3'),
(new TimeNow())->withMeta(['timezones' => ['America/New_York', 'America/Los_Angeles']])->width('1/3'),
];
}
Derive the zones at request time, don't hardcode them. If your Nova User carries a timezone or team attribute, build the card list per request so people see their own team's clocks:
public function cards(Request $request)
{
$zones = $request->user()->team_timezones ?: ['UTC'];
return [
(new TimeNow())->withMeta([
'timezones' => array_values(array_intersect($zones, timezone_identifiers_list())),
])->width('1/2'),
];
}
The array_intersect guard is not optional — see Gotchas.
Dashboard-scoped by default, global only by request. Put the card in a purpose-built Dashboard class's cards(). Reach for NovaServiceProvider::cards() only if it genuinely belongs on the main dashboard for everyone; an unrequested card on the default dashboard is how admin panels accrete clutter.
If the source turns out to be a field, the pattern is different and simpler:
public function fields(NovaRequest $request)
{
return [
TimeNow::make('Logged At')
->default(fn () => now($request->user()->timezone))
->sortable(),
];
}
Here the day-to-day leverage is data-entry accuracy — stamping the current moment without a click — and it's only worth it on resources where operators repeatedly do that. Verify the underlying column type (time/timestamp, not a string) and how it serializes to the model; a mistyped date field is worse than a plain DateTime::make().
Fork into nova-components for anything load-bearing. The functional surface is roughly fifty lines: a config array of IANA identifiers, a render loop, and a view. When you need real behaviour, own it:
php artisan nova:card acme/timezones
// nova-components/Timezones/src/Card.php
public function component() { return 'timezones'; }
public function timezones(array $zones): static
{
return $this->withMeta(['timezones' => $zones]);
}
<!-- nova-components/Timezones/resources/js/components/Card.vue -->
<template>
<Card class="flex flex-col justify-center px-6 py-4">
<div v-for="tz in zones" :key="tz" class="flex justify-between py-1">
<span class="text-80">{{ tz }}</span>
<span class="font-bold">{{ format(tz) }}</span>
</div>
</Card>
</template>
<script>
export default {
props: ['card'],
data: () => ({ tick: null }),
computed: {
zones() { return this.card.timezones || ['UTC'] }
},
mounted() { this.tick = setInterval(() => this.$forceUpdate(), 1000) },
beforeUnmount() { clearInterval(this.tick) },
methods: {
format(tz) {
return new Intl.DateTimeFormat(undefined, {
timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: false,
}).format(new Date())
}
}
}
</script>
Intl.DateTimeFormat with a timeZone option gives you DST correctness, viewer locale, and 24-hour formatting for free — no dependency, no per-request Carbon cost, no assets to rebuild when Nova bumps.
Verify in staging before promoting:
InvalidTimeZoneException or a silently blank row.1/3, 1/2, and full width; Nova's grid is unforgiving.text-80, text-90, etc.) your Nova major ships.The field-vs-card ambiguity is a real trap, not a documentation nit. Scoping a "one-click now" ticket against a timezone-clock card (or the reverse) burns a sprint. If README and description.md still disagree after your read, trust src/.
Invalid IANA identifiers fail quietly. EST, PST, CST are not IANA zones at all; US/Eastern is valid but deprecated. Depending on where the conversion happens you get a thrown exception or a blank row. Filter defensively:
$zones = array_values(array_filter($zones, fn ($tz) => in_array($tz, timezone_identifiers_list())));
Note timezone_identifiers_list() also returns UTC offsets like +05:00 — if you only want city zones, restrict with DateTimeZone::PER_COUNTRY or a whitelist.
Client clock vs server clock is a correctness question. If the Vue component reads the browser clock, the card shows the viewer's laptop time converted — fine for "what time is it in London," wrong the moment someone screenshots it into an incident timeline, because the viewer's system clock may be skewed. Prefer computing from a server-supplied timestamp (now()->timestamp) passed through card meta. If the package doesn't expose one, that alone justifies the fork.
Stale assets after a Nova major bump. These cards ship compiled JS built against a specific Nova/Tailwind version. After upgrading Nova, run php artisan nova:publish and rebuild the package (or your fork) — otherwise you get a blank card or [Vue warn]: Unknown custom element. Add "re-verify the timezone card" to your Nova upgrade checklist; nothing upstream will test it for you.
Config location is not obvious. Timezones may live in withMeta, in a published config/timenow.php, or be hardcoded in the Vue component. Grep before assuming:
grep -rn "timezones" vendor/richardkeep/nova-timenow --include="*.php" --include="*.vue"
12/24-hour and locale. If the package hardcodes HH:mm or h:mm A, no config will fix it. Intl.DateTimeFormat with hour12 unset follows the viewer's locale, which is usually what you want — another fork trigger if your team spans locales.
Rollback is free — use it as your evaluation budget. Deregister the card, composer remove richardkeep/nova-timenow, php artisan view:clear, php artisan nova:publish. Because removal costs nothing, trial it on a non-critical dashboard with real users for a week before deciding. The cost is an hour, not a project.
Supply-chain reality check. Nine stars, zero dependents, single maintainer, December 2024 release. Recent is not maintained. Record the maintainer in your dependency register, pin the tag, and set a reminder to re-check the repo at your next Nova upgrade. Don't let on-call procedures depend on it; if it becomes load-bearing, that's the signal to fork — not to open an issue and wait.
Extend, don't patch. For business-hours highlighting, a "next handoff" marker, or per-user zones, run php artisan nova:card and own the fifty lines. Subclassing a vendor card buys a merge conflict at the next Nova major for no benefit at this size.
Never derive business rules from the display. No SLA windows, on-call routing, or shift boundaries computed from what the card shows. It's a clock, not a time service — every consumer should read its own Carbon::now($tz) (or its own Intl conversion) from an authoritative source.
How can I help you explore Laravel packages today?