feat(ui): welcome screen for user dashboard (#7030)

It is shown when there's no activity in the feed.

This is a partial implementation of https://github.com/go-gitea/gitea/pull/32990.

Differences:
* drawer icon instead of package icon
* h2 instead of h3
* explore links include a link to organizations list
* explore links are hidden for hidden explore sections
* locales are in JSON, I think it's the time to start using it, the hint is simpler and doesn't lie about following users to get their updates in the feed, which isn't a feature yet
* hint uses general hint color instead of input placeholder color
    * the large icon still uses placeholder color, but I think it's ok

Things to improve later:
* use 24px variant of icon. This will require reworking `tools/generate-svg.js`
* the vue part wasn't ported, but it'd be also nice to have

Inspired-by: Kerwin Bryant <kerwin612@qq.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7030
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
This commit is contained in:
0ko 2025-02-23 08:41:31 +00:00
parent cddf608cb9
commit ec35eb2506
6 changed files with 52 additions and 2 deletions

View file

@ -1,4 +1,9 @@
{ {
"home.welcome.no_activity": "No activity",
"home.welcome.activity_hint": "There is nothing in your feed yet. Your actions and activity from repositories that you watch will show up here.",
"home.explore_repos": "Explore repositories",
"home.explore_users": "Explore users",
"home.explore_orgs": "Explore organizations",
"repo.pulls.merged_title_desc": { "repo.pulls.merged_title_desc": {
"one": "merged %[1]d commit from <code>%[2]s</code> into <code>%[3]s</code> %[4]s", "one": "merged %[1]d commit from <code>%[2]s</code> into <code>%[3]s</code> %[4]s",
"other": "merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s" "other": "merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s"

View file

@ -90,6 +90,8 @@ func Dashboard(ctx *context.Context) {
cnt, _ := organization.GetOrganizationCount(ctx, ctxUser) cnt, _ := organization.GetOrganizationCount(ctx, ctxUser)
ctx.Data["UserOrgsCount"] = cnt ctx.Data["UserOrgsCount"] = cnt
ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["Date"] = date ctx.Data["Date"] = date
var uid int64 var uid int64

View file

@ -5,7 +5,11 @@
<div class="flex-container-main"> <div class="flex-container-main">
{{template "base/alert" .}} {{template "base/alert" .}}
{{template "user/heatmap" .}} {{template "user/heatmap" .}}
{{template "user/dashboard/feeds" .}} {{if .Feeds}}
{{template "user/dashboard/feeds" .}}
{{else}}
{{template "user/dashboard/guide" .}}
{{end}}
</div> </div>
{{template "user/dashboard/repolist" .}} {{template "user/dashboard/repolist" .}}
</div> </div>

View file

@ -0,0 +1,16 @@
<div id="empty-feed" class="tw-text-center tw-p-8">
{{svg "octicon-inbox" 64 "tw-text-placeholder-text"}}
<h2>{{ctx.Locale.Tr "home.welcome.no_activity"}}</h2>
<p class="help">{{ctx.Locale.Tr "home.welcome.activity_hint"}}</p>
<div>
<a href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "home.explore_repos"}}</a>
{{if not .UsersPageIsDisabled}}
<span>·</span>
<a href="{{AppSubUrl}}/explore/users">{{ctx.Locale.Tr "home.explore_users"}}</a>
{{end}}
{{if not .OrganizationsPageIsDisabled}}
<span>·</span>
<a href="{{AppSubUrl}}/explore/organizations">{{ctx.Locale.Tr "home.explore_orgs"}}</a>
{{end}}
</div>
</div>

View file

@ -1,5 +1,5 @@
// Copyright 2024 The Forgejo Authors. All rights reserved. // Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: GPL-3.0-or-later
package integration package integration
@ -38,6 +38,25 @@ func TestUserDashboardActionLinks(t *testing.T) {
assert.EqualValues(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text())) assert.EqualValues(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text()))
} }
func TestUserDashboardFeedWelcome(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
// User2 has some activity in feed
session := loginUser(t, "user2")
page := NewHTMLParser(t, session.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK).Body)
testUserDashboardFeedType(t, page, false)
// User1 doesn't have any activity in feed
session = loginUser(t, "user1")
page = NewHTMLParser(t, session.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK).Body)
testUserDashboardFeedType(t, page, true)
}
func testUserDashboardFeedType(t *testing.T, page *HTMLDoc, isEmpty bool) {
page.AssertElement(t, "#activity-feed", !isEmpty)
page.AssertElement(t, "#empty-feed", isEmpty)
}
func TestDashboardTitleRendering(t *testing.T) { func TestDashboardTitleRendering(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})

View file

@ -79,3 +79,7 @@
.dashboard .secondary-nav .ui.dropdown { .dashboard .secondary-nav .ui.dropdown {
max-width: 100%; max-width: 100%;
} }
.dashboard .help {
color: var(--color-secondary-dark-8);
}