diff --git a/app/actions/tenant.go b/app/actions/tenant.go
index 9ecb47c03..c702f1a72 100644
--- a/app/actions/tenant.go
+++ b/app/actions/tenant.go
@@ -194,6 +194,7 @@ type UpdateTenantSettings struct {
Title string `json:"title"`
Invitation string `json:"invitation"`
WelcomeMessage string `json:"welcomeMessage"`
+ WelcomeHeader string `json:"welcomeHeader"`
Locale string `json:"locale"`
CNAME string `json:"cname" format:"lower"`
}
@@ -242,6 +243,10 @@ func (action *UpdateTenantSettings) Validate(ctx context.Context, user *entity.U
result.AddFieldFailure("invitation", "Invitation must have less than 60 characters.")
}
+ if len(action.WelcomeHeader) > 100 {
+ result.AddFieldFailure("welcomeHeader", "Welcome Header must have less than 100 characters.")
+ }
+
if !i18n.IsValidLocale(action.Locale) {
result.AddFieldFailure("locale", "Locale is invalid.")
}
diff --git a/app/handlers/admin.go b/app/handlers/admin.go
index 17a4561b7..ec48fe800 100644
--- a/app/handlers/admin.go
+++ b/app/handlers/admin.go
@@ -56,6 +56,7 @@ func UpdateSettings() web.HandlerFunc {
Title: action.Title,
Invitation: action.Invitation,
WelcomeMessage: action.WelcomeMessage,
+ WelcomeHeader: action.WelcomeHeader,
CNAME: action.CNAME,
Locale: action.Locale,
},
diff --git a/app/handlers/post.go b/app/handlers/post.go
index ced787e70..0cb663431 100644
--- a/app/handlers/post.go
+++ b/app/handlers/post.go
@@ -61,7 +61,8 @@ func Index() web.HandlerFunc {
return c.Page(http.StatusOK, web.Props{
Page: "Home/Home.page",
Description: description,
- Data: data,
+ // Header: c.Tenant().WelcomeHeader,
+ Data: data,
})
}
}
diff --git a/app/models/cmd/tenant.go b/app/models/cmd/tenant.go
index 62b0006fc..a4dcbc9a9 100644
--- a/app/models/cmd/tenant.go
+++ b/app/models/cmd/tenant.go
@@ -30,6 +30,7 @@ type UpdateTenantSettings struct {
Title string
Invitation string
WelcomeMessage string
+ WelcomeHeader string
CNAME string
Locale string
}
diff --git a/app/models/entity/tenant.go b/app/models/entity/tenant.go
index 98c92e2a6..69165efc3 100644
--- a/app/models/entity/tenant.go
+++ b/app/models/entity/tenant.go
@@ -9,6 +9,7 @@ type Tenant struct {
Subdomain string `json:"subdomain"`
Invitation string `json:"invitation"`
WelcomeMessage string `json:"welcomeMessage"`
+ WelcomeHeader string `json:"welcomeHeader"`
CNAME string `json:"cname"`
Status enum.TenantStatus `json:"status"`
Locale string `json:"locale"`
diff --git a/app/pkg/web/react_test.go b/app/pkg/web/react_test.go
index b6921cba8..4d60c7204 100644
--- a/app/pkg/web/react_test.go
+++ b/app/pkg/web/react_test.go
@@ -52,7 +52,8 @@ func TestReactRenderer_RenderEmptyHomeHTML(t *testing.T) {
},
})
Expect(html).ContainsSubstring(`
DEV
`)
- Expect(html).ContainsSubstring(``)
+ Expect(html).ContainsSubstring(`
)}
-
- Sign in
-
+
+
+
+
+ Sign in
+
+
+
)}
diff --git a/public/components/Reactions.tsx b/public/components/Reactions.tsx
index 68b8c26ad..c50a285f1 100644
--- a/public/components/Reactions.tsx
+++ b/public/components/Reactions.tsx
@@ -75,7 +75,7 @@ export const Reactions: React.FC = ({ emojiSelectorRef, toggleRe
"clickable hover:bg-gray-200": fider.session.isAuthenticated && !reaction.includesMe,
})}
>
- {reaction.emoji} {reaction.count}
+ {reaction.emoji} {reaction.count}
))}
>
diff --git a/public/components/ShowPostResponse.tsx b/public/components/ShowPostResponse.tsx
index 2a77afeac..feafdd1bf 100644
--- a/public/components/ShowPostResponse.tsx
+++ b/public/components/ShowPostResponse.tsx
@@ -1,17 +1,17 @@
import React from "react"
import { PostResponse, PostStatus } from "@fider/models"
-import { Icon, Markdown } from "@fider/components"
+import { Icon, Markdown, UserName, Moment } from "@fider/components"
import HeroIconDuplicate from "@fider/assets/images/heroicons-duplicate.svg"
import HeroIconCheck from "@fider/assets/images/heroicons-check-circle.svg"
import HeroIconSparkles from "@fider/assets/images/heroicons-sparkles-outline.svg"
import HeroIconLightBulb from "@fider/assets/images/heroicons-lightbulb.svg"
import HeroIconThumbsUp from "@fider/assets/images/heroicons-thumbsup.svg"
import HeroIconThumbsDown from "@fider/assets/images/heroicons-thumbsdown.svg"
-import { HStack, VStack } from "./layout"
-import { timeSince } from "@fider/services"
+import { HStack } from "./layout"
import { Trans } from "@lingui/react/macro"
+import { useFider } from "@fider/hooks"
-type Size = "micro" | "small" | "normal"
+type Size = "micro" | "small" | "xsmall" | "normal"
interface PostResponseProps {
status: string
@@ -20,30 +20,41 @@ interface PostResponseProps {
}
export const ResponseDetails = (props: PostResponseProps): JSX.Element | null => {
+ const fider = useFider()
const status = PostStatus.Get(props.status)
if (!props.response) {
return null
}
+ const { bg } = getLozengeProps(status)
+
return (
-
-
- {timeSince("en", new Date(), props.response.respondedAt, "date")}
- {props.response?.text && status !== PostStatus.Duplicate && (
-
-
+
+
+
+
+
+ •
+
+
- )}
- {status === PostStatus.Duplicate && props.response.original && (
-
- )}
-
+ {props.response?.text && status !== PostStatus.Duplicate && (
+
+
+
+ )}
+
+ {status === PostStatus.Duplicate && props.response.original && (
+
+ )}
+
+
)
}
@@ -94,6 +105,17 @@ export const ResponseLozenge = (props: PostResponseProps): JSX.Element | null =>
return
{translatedStatus}
}
+ if (props.size === "xsmall") {
+ return (
+
+
+
+ {translatedStatus}
+
+
+ )
+ }
+
return (
diff --git a/public/components/VoteCounter.scss b/public/components/VoteCounter.scss
index 40d7926a7..b0f977b77 100644
--- a/public/components/VoteCounter.scss
+++ b/public/components/VoteCounter.scss
@@ -2,58 +2,129 @@
.c-vote-counter {
&__button {
- font-size: get("font.size.lg");
- width: sizing(12);
- height: sizing(12);
+ width: sizing(14);
+ height: sizing(14);
cursor: pointer;
text-align: center;
margin: 0 auto;
- padding: 3px 0 8px 0;
- color: var(--colors-gray-600);
+ padding: spacing(3) 0;
+ color: var(--colors-primary-base);
display: flex;
flex-direction: column;
align-items: center;
- border-radius: 8px;
- transition: all 0.15s ease;
- background-color: transparent;
- border: 1px solid transparent;
-
- svg {
- color: var(--colors-gray-600);
- margin-bottom: -2px;
- transition: all 0.15s ease;
+ justify-content: center;
+ gap: spacing(2);
+ border-radius: 16px;
+ transition: all 0.2s ease;
+ background-color: var(--colors-blue-50);
+ border: 2px solid var(--colors-blue-100);
+
+ @include dark-mode {
+ background-color: var(--colors-blue-900);
+ border-color: var(--colors-blue-800);
+ color: var(--colors-blue-300);
}
&:hover {
- background-color: var(--colors-gray-50);
+ background-color: var(--colors-blue-100);
+ border-color: var(--colors-primary-base);
color: var(--colors-primary-base);
- svg {
+
+ @include dark-mode {
+ background-color: var(--colors-blue-800);
+ border-color: var(--colors-blue-500);
+ color: var(--colors-blue-200);
+ }
+
+ .c-vote-counter__icon {
color: var(--colors-primary-base);
- transform: scale(1.1);
+ transform: translateY(-1px);
+
+ @include dark-mode {
+ color: var(--colors-blue-200);
+ }
}
}
&--voted {
- justify-content: center;
- padding: 0;
+ padding: spacing(3) 0;
+ gap: spacing(2);
font-weight: get("font.weight.bold");
- background-color: var(--colors-blue-50);
+ background-color: var(--colors-blue-100);
color: var(--colors-primary-base);
- border-color: var(--colors-primary-light);
+ border-color: var(--colors-primary-base);
+
+ @include dark-mode {
+ background-color: var(--colors-blue-800);
+ color: var(--colors-blue-200);
+ border-color: var(--colors-blue-500);
+ }
- svg {
+ .c-vote-counter__icon {
color: var(--colors-primary-base);
+
+ @include dark-mode {
+ color: var(--colors-blue-200);
+ }
+ }
+
+ .c-vote-counter__count {
+ font-weight: get("font.weight.bold");
}
&:hover {
- background-color: var(--colors-blue-100);
+ background-color: var(--colors-blue-200);
+ border-color: var(--colors-primary-base);
+
+ @include dark-mode {
+ background-color: var(--colors-blue-700);
+ border-color: var(--colors-blue-400);
+ }
}
}
&--disabled {
- justify-content: center;
- padding: 0;
+ padding: spacing(3) 0;
+ gap: spacing(2);
@include disabled();
}
+
+ &--large {
+ width: sizing(18);
+ height: sizing(20);
+ padding: spacing(5) 0;
+ gap: spacing(2);
+ border-radius: 20px;
+
+ .c-vote-counter__icon {
+ width: 28px;
+ height: 28px;
+ stroke-width: 2.5;
+ }
+
+ .c-vote-counter__count {
+ font-size: 24px;
+ font-weight: get("font.weight.bold");
+ line-height: 1;
+ }
+ }
+ }
+
+ &__icon {
+ width: 18px;
+ height: 18px;
+ color: var(--colors-primary-base);
+ transition: all 0.2s ease;
+ flex-shrink: 0;
+
+ @include dark-mode {
+ color: var(--colors-blue-300);
+ }
+ }
+
+ &__count {
+ font-size: get("font.size.sm");
+ font-weight: get("font.weight.semibold");
+ line-height: 1;
}
}
diff --git a/public/components/VoteCounter.tsx b/public/components/VoteCounter.tsx
index 5604e153b..7f346675f 100644
--- a/public/components/VoteCounter.tsx
+++ b/public/components/VoteCounter.tsx
@@ -5,14 +5,16 @@ import { Post, PostStatus } from "@fider/models"
import { actions, classSet } from "@fider/services"
import { Icon, SignInModal } from "@fider/components"
import { useFider } from "@fider/hooks"
-import FaCaretUp from "@fider/assets/images/fa-caretup.svg"
+import ChevronUp from "@fider/assets/images/chevron-up.svg"
export interface VoteCounterProps {
post: Post
+ size?: "default" | "large"
}
export const VoteCounter = (props: VoteCounterProps) => {
const fider = useFider()
+ const { size = "default" } = props
const [hasVoted, setHasVoted] = useState(props.post.hasVoted)
const [votesCount, setVotesCount] = useState(props.post.votesCount)
const [isSignInModalOpen, setIsSignInModalOpen] = useState(false)
@@ -38,23 +40,23 @@ export const VoteCounter = (props: VoteCounterProps) => {
const isDisabled = status.closed || fider.isReadOnly
const className = classSet({
- "border-gray-200 border rounded-md bg-gray-100": true,
"c-vote-counter__button": true,
"c-vote-counter__button--voted": !status.closed && hasVoted,
"c-vote-counter__button--disabled": isDisabled,
+ "c-vote-counter__button--large": size === "large",
})
const vote = (
- {!hasVoted && }
- {votesCount}
+ {!hasVoted && }
+ {votesCount}
)
const disabled = (
- {!hasVoted && }
- {votesCount}
+ {!hasVoted && }
+ {votesCount}
)
diff --git a/public/components/common/Avatar.scss b/public/components/common/Avatar.scss
index a725df538..c00ef9682 100644
--- a/public/components/common/Avatar.scss
+++ b/public/components/common/Avatar.scss
@@ -5,5 +5,8 @@
vertical-align: middle;
display: inline-block;
border: 2px solid var(--colors-white);
+ @include dark-mode {
+ border: 2px solid var(--colors-gray-200);
+ }
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
diff --git a/public/components/common/Avatar.tsx b/public/components/common/Avatar.tsx
index 7475b579c..bc98d93bd 100644
--- a/public/components/common/Avatar.tsx
+++ b/public/components/common/Avatar.tsx
@@ -9,10 +9,10 @@ interface AvatarProps {
avatarURL: string
name: string
}
- size?: "small" | "normal"
+ size?: "small" | "normal" | "large"
}
export const Avatar = (props: AvatarProps) => {
- const size = props.size === "small" ? "h-6 w-6" : "h-8 w-8"
+ const size = props.size === "small" ? "h-6 w-6" : props.size === "large" ? "h-11 w-11" : "h-8 w-8"
return
}
diff --git a/public/components/common/UserName.scss b/public/components/common/UserName.scss
index c76381f9f..bd37021b5 100644
--- a/public/components/common/UserName.scss
+++ b/public/components/common/UserName.scss
@@ -20,7 +20,7 @@
div {
svg {
height: 14px;
- vertical-align: text-bottom;
+ vertical-align: middle;
margin-inline-start: 2px;
align-items: flex-end;
}
diff --git a/public/components/common/form/CommentEditor.scss b/public/components/common/form/CommentEditor.scss
index ae33d3b62..3cb547d71 100644
--- a/public/components/common/form/CommentEditor.scss
+++ b/public/components/common/form/CommentEditor.scss
@@ -75,6 +75,10 @@
overflow: hidden;
background-color: var(--colors-white);
+ @include dark-mode {
+ background-color: var(--colors-gray-100);
+ }
+
&.m-error {
border-color: var(--colors-red-600);
}
diff --git a/public/models/identity.ts b/public/models/identity.ts
index 4e724f9e2..33728d220 100644
--- a/public/models/identity.ts
+++ b/public/models/identity.ts
@@ -6,6 +6,7 @@ export interface Tenant {
locale: string
invitation: string
welcomeMessage: string
+ welcomeHeader: string
status: TenantStatus
isPrivate: boolean
logoBlobKey: string
diff --git a/public/pages/Administration/pages/GeneralSettings.page.tsx b/public/pages/Administration/pages/GeneralSettings.page.tsx
index a08a2ed8c..1a44518fa 100644
--- a/public/pages/Administration/pages/GeneralSettings.page.tsx
+++ b/public/pages/Administration/pages/GeneralSettings.page.tsx
@@ -11,6 +11,7 @@ const GeneralSettingsPage = () => {
const fider = useFider()
const [title, setTitle] = useState(fider.session.tenant.name)
const [welcomeMessage, setWelcomeMessage] = useState(fider.session.tenant.welcomeMessage)
+ const [welcomeHeader, setWelcomeHeader] = useState(fider.session.tenant.welcomeHeader)
const [invitation, setInvitation] = useState(fider.session.tenant.invitation)
const [logo, setLogo] = useState(undefined)
const [cname, setCNAME] = useState(fider.session.tenant.cname)
@@ -18,7 +19,7 @@ const GeneralSettingsPage = () => {
const [error, setError] = useState(undefined)
const handleSave = async (e: ButtonClickEvent) => {
- const result = await actions.updateTenantSettings({ title, cname, welcomeMessage, invitation, logo, locale })
+ const result = await actions.updateTenantSettings({ title, cname, welcomeMessage, welcomeHeader, invitation, logo, locale })
if (result.ok) {
e.preventEnable()
location.href = `/`
@@ -48,6 +49,20 @@ const GeneralSettingsPage = () => {
Keep it short and snappy. Your product / service name is usually best.
+
+
+ Large header text shown on the home page. Leave empty to hide. Wrap text with underscores (e.g., _highlighted_) to show it in blue.
+
+
+