diff --git a/lang/default.json b/lang/default.json index 6c91282314..5fe4dcda75 100644 --- a/lang/default.json +++ b/lang/default.json @@ -3,6 +3,9 @@ "defaultMessage": "Please verify email first", "description": "src/views/Me/Settings/Settings/Password/index.tsx" }, + "+56XIp": { + "defaultMessage": "LV1 Moonlight Dream" + }, "+63O1f": { "defaultMessage": "Incorrect email or password", "description": "USER_PASSWORD_INVALID" @@ -53,6 +56,9 @@ "defaultMessage": "Unknown error. Please try again later.", "description": "UNKNOWN_ERROR" }, + "/faseS": { + "defaultMessage": "Share link copied" + }, "/jJLYy": { "defaultMessage": "Transactions" }, @@ -323,6 +329,9 @@ "defaultMessage": "Are you sure you want to delete draft?", "description": "src/components/DraftDigest/Feed/DeleteButton.tsx" }, + "7ZXP9S": { + "defaultMessage": "LV2 Meteor Canoe" + }, "7cwoRo": { "defaultMessage": "The author has disabled comments for this article" }, @@ -541,6 +550,10 @@ "defaultMessage": "Badges", "description": "src/components/UserProfile/index.tsx" }, + "DdVBFV": { + "defaultMessage": "Wings have sprouted on the badge, and the halo of dreams is beginning to spin. You have gathered 10 companions on the Nomad's path. Invite another 10 fellow travelers to move towards the final destination together, where the highest level of honor awaits you.", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "Dq29Hb": { "defaultMessage": "You have not connected your email yet. For security, email is required for top-up.", "description": "src/components/Dialogs/BindEmailHintDialog/index.tsx" @@ -609,6 +622,9 @@ "defaultMessage": "Collection is deleted", "description": "src/components/CollectionDigest/DropdownActions/DeleteCollection/Dialog.tsx" }, + "FuU2MU": { + "defaultMessage": "LV4 Firebolt" + }, "FxrSCh": { "defaultMessage": "This ID cannot be modified. Are you sure you want to use {id} as your Matters ID?", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" @@ -1215,6 +1231,10 @@ "defaultMessage": "Start writing", "description": "src/components/Buttons/StartWriting/index.tsx" }, + "Z31Fz+": { + "defaultMessage": "Under the moonlight, your dreams are about to come true. The Moonlight Dream badge glorifies your initial participation in the Nomad Matters. To reach the next level, invite 5 friends to join the journey with you. Click here in the last paragraph to find out how to invite new companions", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "ZAs170": { "defaultMessage": "Profile", "description": "src/views/Circle/Settings/index.tsx" @@ -1310,6 +1330,10 @@ "defaultMessage": "Featured", "description": "src/components/Comment/PinnedLabel/index.tsx" }, + "cQ+Lyq": { + "defaultMessage": "Nomad Matters Badge", + "description": "src/views/User/UserProfile/Badges/index.tsx" + }, "cQYXjl": { "defaultMessage": "Currency", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" @@ -1460,6 +1484,10 @@ "defaultMessage": "Number of readers", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "iIa+u8": { + "defaultMessage": "Badge Name", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "icdrwy": { "defaultMessage": "Followed You", "description": "src/components/Buttons/FollowUser/FollowState.tsx" @@ -1660,6 +1688,10 @@ "defaultMessage": "Following", "description": "src/components/UserProfile/index.tsx" }, + "p0oPHP": { + "defaultMessage": "Breaking through history! The torch you've ignited will be the most dazzling support on the Nomad's journey. Congratulations on reaching the final destination of the Nomad Matters with 20 friends and earning the highest-level Firebolt badge.", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "p5qZnJ": { "defaultMessage": "liked", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" @@ -1705,6 +1737,10 @@ "defaultMessage": "Archived", "description": "src/views/Me/Works/WorksTabs/index.tsx" }, + "qTjjF0": { + "defaultMessage": "The dazzling light of a meteor shower is there to illuminate the night sky. Congratulations on having invited 5 companions to participate in Nomad Matters. Invite 5 more to level up, the more the merrier!", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "qVhxp5": { "defaultMessage": "Enter", "description": "src/components/Buttons/UniversalAuth/index.tsx" @@ -1771,6 +1807,9 @@ "defaultMessage": "This post may include sensitive content and has been marked by the {actor} as restricted content. Are you sure you want to expand the full text?", "description": "src/views/ArticleDetail/Wall/Sensitive/index.tsx" }, + "sLiIAz": { + "defaultMessage": "LV3 Nimbus Ferry" + }, "sPgUkN": { "defaultMessage": "This Month", "description": "src/views/Circle/Analytics/IncomeAnalytics/index.tsx" diff --git a/lang/en.json b/lang/en.json index b353fbc186..fddadebab2 100644 --- a/lang/en.json +++ b/lang/en.json @@ -3,6 +3,9 @@ "defaultMessage": "Please verify email first", "description": "src/views/Me/Settings/Settings/Password/index.tsx" }, + "+56XIp": { + "defaultMessage": "LV1 Moonlight Dream" + }, "+63O1f": { "defaultMessage": "Incorrect email or password", "description": "USER_PASSWORD_INVALID" @@ -53,6 +56,9 @@ "defaultMessage": "Unknown error. Please try again later.", "description": "UNKNOWN_ERROR" }, + "/faseS": { + "defaultMessage": "Share link copied" + }, "/jJLYy": { "defaultMessage": "Transactions" }, @@ -323,6 +329,9 @@ "defaultMessage": "Are you sure you want to delete draft?", "description": "src/components/DraftDigest/Feed/DeleteButton.tsx" }, + "7ZXP9S": { + "defaultMessage": "LV2 Meteor Canoe" + }, "7cwoRo": { "defaultMessage": "The author has disabled comments for this article" }, @@ -541,6 +550,10 @@ "defaultMessage": "Badges", "description": "src/components/UserProfile/index.tsx" }, + "DdVBFV": { + "defaultMessage": "Wings have sprouted on the badge, and the halo of dreams is beginning to spin. You have gathered 10 companions on the Nomad's path. Invite another 10 fellow travelers to move towards the final destination together, where the highest level of honor awaits you.", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "Dq29Hb": { "defaultMessage": "You have not connected your email yet. For security, email is required for top-up.", "description": "src/components/Dialogs/BindEmailHintDialog/index.tsx" @@ -609,6 +622,9 @@ "defaultMessage": "Collection is deleted", "description": "src/components/CollectionDigest/DropdownActions/DeleteCollection/Dialog.tsx" }, + "FuU2MU": { + "defaultMessage": "LV4 Firebolt" + }, "FxrSCh": { "defaultMessage": "This ID cannot be modified. Are you sure you want to use {id} as your Matters ID?", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" @@ -1215,6 +1231,10 @@ "defaultMessage": "Start writing", "description": "src/components/Buttons/StartWriting/index.tsx" }, + "Z31Fz+": { + "defaultMessage": "Under the moonlight, your dreams are about to come true. The Moonlight Dream badge glorifies your initial participation in the Nomad Matters. To reach the next level, invite 5 friends to join the journey with you. Click here in the last paragraph to find out how to invite new companions", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "ZAs170": { "defaultMessage": "Profile", "description": "src/views/Circle/Settings/index.tsx" @@ -1310,6 +1330,10 @@ "defaultMessage": "Featured", "description": "src/components/Comment/PinnedLabel/index.tsx" }, + "cQ+Lyq": { + "defaultMessage": "Nomad Matters Badge", + "description": "src/views/User/UserProfile/Badges/index.tsx" + }, "cQYXjl": { "defaultMessage": "Currency", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" @@ -1460,6 +1484,10 @@ "defaultMessage": "Number of readers", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "iIa+u8": { + "defaultMessage": "Badge Name", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "icdrwy": { "defaultMessage": "Followed You", "description": "src/components/Buttons/FollowUser/FollowState.tsx" @@ -1660,6 +1688,10 @@ "defaultMessage": "Following", "description": "src/components/UserProfile/index.tsx" }, + "p0oPHP": { + "defaultMessage": "Breaking through history! The torch you've ignited will be the most dazzling support on the Nomad's journey. Congratulations on reaching the final destination of the Nomad Matters with 20 friends and earning the highest-level Firebolt badge.", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "p5qZnJ": { "defaultMessage": "liked", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" @@ -1705,6 +1737,10 @@ "defaultMessage": "Archived", "description": "src/views/Me/Works/WorksTabs/index.tsx" }, + "qTjjF0": { + "defaultMessage": "The dazzling light of a meteor shower is there to illuminate the night sky. Congratulations on having invited 5 companions to participate in Nomad Matters. Invite 5 more to level up, the more the merrier!", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "qVhxp5": { "defaultMessage": "Enter", "description": "src/components/Buttons/UniversalAuth/index.tsx" @@ -1771,6 +1807,9 @@ "defaultMessage": "This post may include sensitive content and has been marked by the {actor} as restricted content. Are you sure you want to expand the full text?", "description": "src/views/ArticleDetail/Wall/Sensitive/index.tsx" }, + "sLiIAz": { + "defaultMessage": "LV3 Nimbus Ferry" + }, "sPgUkN": { "defaultMessage": "This Month", "description": "src/views/Circle/Analytics/IncomeAnalytics/index.tsx" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 7cd22a78c0..f23cadfac8 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -3,6 +3,9 @@ "defaultMessage": "请先验证邮箱", "description": "src/views/Me/Settings/Settings/Password/index.tsx" }, + "+56XIp": { + "defaultMessage": "LV1 月之梦" + }, "+63O1f": { "defaultMessage": "邮箱或密码错误", "description": "USER_PASSWORD_INVALID" @@ -53,6 +56,9 @@ "defaultMessage": "不知道哪里出错了,过几分钟看看", "description": "UNKNOWN_ERROR" }, + "/faseS": { + "defaultMessage": "已复制分享连结" + }, "/jJLYy": { "defaultMessage": "交易记录" }, @@ -323,6 +329,9 @@ "defaultMessage": "确认要删除草稿吗?", "description": "src/components/DraftDigest/Feed/DeleteButton.tsx" }, + "7ZXP9S": { + "defaultMessage": "LV2 流星号" + }, "7cwoRo": { "defaultMessage": "因为作者设置,你无法参与讨论。" }, @@ -541,6 +550,10 @@ "defaultMessage": "徽章", "description": "src/components/UserProfile/index.tsx" }, + "DdVBFV": { + "defaultMessage": "徽章长出了羽翼,梦想的光轮正开始转动,你已在游牧者之路聚集了 10 位伙伴。再邀请 10 位同行者,一起迈向终点站,最高等级的荣誉正在等着你。", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "Dq29Hb": { "defaultMessage": "你尚未绑定邮箱。基于资金安全考虑,储值需绑定邮箱。", "description": "src/components/Dialogs/BindEmailHintDialog/index.tsx" @@ -609,6 +622,9 @@ "defaultMessage": "选集已刪除", "description": "src/components/CollectionDigest/DropdownActions/DeleteCollection/Dialog.tsx" }, + "FuU2MU": { + "defaultMessage": "LV4 火闪电" + }, "FxrSCh": { "defaultMessage": "ID 设置后无法修改,确认使用 {id} 作为 Matters ID 吗?", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" @@ -1215,6 +1231,10 @@ "defaultMessage": "开始创作", "description": "src/components/Buttons/StartWriting/index.tsx" }, + "Z31Fz+": { + "defaultMessage": "月色之下梦想即将实现,月之梦徽章代表你参与了游牧者计画(参赛或支持了优质计画)。接下来邀请 5 位伙伴一同踏上旅程,将获得更高等级的徽章。 如何邀请伙伴?点此看最后一段", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "ZAs170": { "defaultMessage": "基本资料", "description": "src/views/Circle/Settings/index.tsx" @@ -1310,6 +1330,10 @@ "defaultMessage": "作者精选", "description": "src/components/Comment/PinnedLabel/index.tsx" }, + "cQ+Lyq": { + "defaultMessage": "游牧者计画纪念徽章", + "description": "src/views/User/UserProfile/Badges/index.tsx" + }, "cQYXjl": { "defaultMessage": "汇率币别", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" @@ -1460,6 +1484,10 @@ "defaultMessage": "读者数量", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "iIa+u8": { + "defaultMessage": "徽章名称", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "icdrwy": { "defaultMessage": "追踪了你", "description": "src/components/Buttons/FollowUser/FollowState.tsx" @@ -1660,6 +1688,10 @@ "defaultMessage": "追踪中", "description": "src/components/UserProfile/index.tsx" }, + "p0oPHP": { + "defaultMessage": "突破历史!你点燃的火炬将是游牧者路途上最耀眼的支持。恭喜你与 20 位好友一同抵达游牧者计画的终点,并获得最高等级火闪电徽章。", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "p5qZnJ": { "defaultMessage": "赞赏了", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" @@ -1705,6 +1737,10 @@ "defaultMessage": "已归档", "description": "src/views/Me/Works/WorksTabs/index.tsx" }, + "qTjjF0": { + "defaultMessage": "流星雨的绚烂光芒足以点亮夜空,你已经邀请了 5 位伙伴关注游牧者计画,再邀请 5 位就能继续升级!", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "qVhxp5": { "defaultMessage": "进入", "description": "src/components/Buttons/UniversalAuth/index.tsx" @@ -1771,6 +1807,9 @@ "defaultMessage": "此文已被{actor}标示为限制级内容,是否确定展开全文?", "description": "src/views/ArticleDetail/Wall/Sensitive/index.tsx" }, + "sLiIAz": { + "defaultMessage": "LV3 光轮号" + }, "sPgUkN": { "defaultMessage": "本月营收", "description": "src/views/Circle/Analytics/IncomeAnalytics/index.tsx" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index c3fb379920..28b7e0467c 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -3,6 +3,9 @@ "defaultMessage": "請先驗證郵箱", "description": "src/views/Me/Settings/Settings/Password/index.tsx" }, + "+56XIp": { + "defaultMessage": "LV1 月之夢" + }, "+63O1f": { "defaultMessage": "郵件地址或密碼錯誤", "description": "USER_PASSWORD_INVALID" @@ -53,6 +56,9 @@ "defaultMessage": "不知道哪裏出錯了,過幾分鐘看看", "description": "UNKNOWN_ERROR" }, + "/faseS": { + "defaultMessage": "已複製分享連結" + }, "/jJLYy": { "defaultMessage": "交易記錄" }, @@ -323,6 +329,9 @@ "defaultMessage": "確認要刪除草稿嗎?", "description": "src/components/DraftDigest/Feed/DeleteButton.tsx" }, + "7ZXP9S": { + "defaultMessage": "LV2 流星號" + }, "7cwoRo": { "defaultMessage": "因爲作者設置,你無法參與討論。" }, @@ -541,6 +550,10 @@ "defaultMessage": "徽章", "description": "src/components/UserProfile/index.tsx" }, + "DdVBFV": { + "defaultMessage": "徽章長出了羽翼,夢想的光輪正開始轉動,你已在遊牧者之路聚集了 10 位夥伴。再邀請 10 位同行者,一起邁向終點站,最高等級的榮譽正在等著你。", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "Dq29Hb": { "defaultMessage": "你尚未綁定電子郵件。 基於資金安全考慮,儲值需綁定郵箱。", "description": "src/components/Dialogs/BindEmailHintDialog/index.tsx" @@ -609,6 +622,9 @@ "defaultMessage": "選集已刪除", "description": "src/components/CollectionDigest/DropdownActions/DeleteCollection/Dialog.tsx" }, + "FuU2MU": { + "defaultMessage": "LV4 火閃電" + }, "FxrSCh": { "defaultMessage": "ID 設置後無法修改,確認使用 {id} 作為 Matters ID 嗎?", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" @@ -1215,6 +1231,10 @@ "defaultMessage": "開始創作", "description": "src/components/Buttons/StartWriting/index.tsx" }, + "Z31Fz+": { + "defaultMessage": "月色之下夢想即將實現,月之夢徽章代表你參與了遊牧者計畫(參賽或支持了優質計畫)。接下來邀請 5 位夥伴一同踏上旅程,將獲得更高等級的徽章。如何邀請夥伴?點此看最後一段", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "ZAs170": { "defaultMessage": "基本資料", "description": "src/views/Circle/Settings/index.tsx" @@ -1310,6 +1330,10 @@ "defaultMessage": "作者精選", "description": "src/components/Comment/PinnedLabel/index.tsx" }, + "cQ+Lyq": { + "defaultMessage": "遊牧者計畫紀念徽章", + "description": "src/views/User/UserProfile/Badges/index.tsx" + }, "cQYXjl": { "defaultMessage": "匯率幣別", "description": "src/views/Me/Settings/Misc/Currency/index.tsx" @@ -1460,6 +1484,10 @@ "defaultMessage": "讀者數量", "description": "src/components/ArticleDigest/Published/FooterActions/index.tsx" }, + "iIa+u8": { + "defaultMessage": "徽章名稱", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "icdrwy": { "defaultMessage": "追蹤了你", "description": "src/components/Buttons/FollowUser/FollowState.tsx" @@ -1660,6 +1688,10 @@ "defaultMessage": "追蹤中", "description": "src/components/UserProfile/index.tsx" }, + "p0oPHP": { + "defaultMessage": "突破歷史!你點燃的火炬將是遊牧者路途上最耀眼的支持。恭喜你與 20 位好友一同抵達遊牧者計畫的終點,並獲得最高等級火閃電徽章。", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "p5qZnJ": { "defaultMessage": "讚賞了", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" @@ -1705,6 +1737,10 @@ "defaultMessage": "已封存", "description": "src/views/Me/Works/WorksTabs/index.tsx" }, + "qTjjF0": { + "defaultMessage": "流星雨的絢爛光芒足以點亮夜空,你已經邀請了 5 位夥伴關注遊牧者計畫,再邀請 5 位就能繼續升級!", + "description": "src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + }, "qVhxp5": { "defaultMessage": "進入", "description": "src/components/Buttons/UniversalAuth/index.tsx" @@ -1771,6 +1807,9 @@ "defaultMessage": "此文已被{actor}標示為限制級內容,是否確定展開全文?", "description": "src/views/ArticleDetail/Wall/Sensitive/index.tsx" }, + "sLiIAz": { + "defaultMessage": "LV3 光輪號" + }, "sPgUkN": { "defaultMessage": "本月營收", "description": "src/views/Circle/Analytics/IncomeAnalytics/index.tsx" diff --git a/package.json b/package.json index fe1a01b5ca..d958dd451c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "prepare": "husky install", - "vercel-build": "set -xe; npm run gen:type && if [[ \"$NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF\" =~ release/* ]] ; then cp -va .env.prod .env.local ; echo 'NEXT_PUBLIC_SITE_DOMAIN=web-next.matters.town' | tee -a .env.local; else cp -va .env.dev .env.local ; echo 'NEXT_PUBLIC_SITE_DOMAIN=web-dev.matters.town' | tee -a .env.local ; fi && echo 'NEXT_PUBLIC_NEXT_ASSET_DOMAIN=' | tee -a .env.local && npm run build", + "vercel-build": "set -xe; npm run gen:type && if [[ \"$NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF\" =~ release/* ]] ; then cp -va .env.prod .env.local ; echo 'NEXT_PUBLIC_SITE_DOMAIN=web-next.matters.town' | tee -a .env.local; elif [[ \"$NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF\" =~ feat/nomad-badges ]] ; then cp -va .env.dev .env.local ; echo 'NEXT_PUBLIC_SITE_DOMAIN=nomadbadge-dev.matters.town' | tee -a .env.local ; else cp -va .env.dev .env.local ; echo 'NEXT_PUBLIC_SITE_DOMAIN=web-dev.matters.town' | tee -a .env.local ; fi && echo 'NEXT_PUBLIC_NEXT_ASSET_DOMAIN=' | tee -a .env.local && npm run build", "i18n:extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --id-interpolation-pattern '[sha512:contenthash:base64:6]' --out-file lang/default.json", "i18n:generate": "node bin/i18nGenerate.js", "i18n:compile": "formatjs compile-folder --ast lang compiled-lang", diff --git a/public/static/icons/48px/badge-architect.svg b/public/static/icons/48px/badge-architect.svg new file mode 100644 index 0000000000..2734fe3319 --- /dev/null +++ b/public/static/icons/48px/badge-architect.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/icons/48px/badge-civic-liker.svg b/public/static/icons/48px/badge-civic-liker.svg new file mode 100644 index 0000000000..7d5d996e2d --- /dev/null +++ b/public/static/icons/48px/badge-civic-liker.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/icons/48px/badge-golden-motor.svg b/public/static/icons/48px/badge-golden-motor.svg new file mode 100644 index 0000000000..e3448b2f0b --- /dev/null +++ b/public/static/icons/48px/badge-golden-motor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/icons/48px/badge-nomad1-moon.svg b/public/static/icons/48px/badge-nomad1-moon.svg new file mode 100644 index 0000000000..ff44360a02 --- /dev/null +++ b/public/static/icons/48px/badge-nomad1-moon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/static/icons/48px/badge-nomad2-star.svg b/public/static/icons/48px/badge-nomad2-star.svg new file mode 100644 index 0000000000..86b9054245 --- /dev/null +++ b/public/static/icons/48px/badge-nomad2-star.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/static/icons/48px/badge-nomad3-light.svg b/public/static/icons/48px/badge-nomad3-light.svg new file mode 100644 index 0000000000..f9caf3d736 --- /dev/null +++ b/public/static/icons/48px/badge-nomad3-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/static/icons/48px/badge-nomad4-fire.svg b/public/static/icons/48px/badge-nomad4-fire.svg new file mode 100644 index 0000000000..5bf9793740 --- /dev/null +++ b/public/static/icons/48px/badge-nomad4-fire.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/static/icons/48px/badge-seed.svg b/public/static/icons/48px/badge-seed.svg new file mode 100644 index 0000000000..a29b14671e --- /dev/null +++ b/public/static/icons/48px/badge-seed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/icons/48px/badge-traveloggers.svg b/public/static/icons/48px/badge-traveloggers.svg new file mode 100644 index 0000000000..c2241fd98e --- /dev/null +++ b/public/static/icons/48px/badge-traveloggers.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/static/images/badge-nomad1-background.svg b/public/static/images/badge-nomad1-background.svg new file mode 100644 index 0000000000..d71463d256 --- /dev/null +++ b/public/static/images/badge-nomad1-background.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/badge-nomad2-background.svg b/public/static/images/badge-nomad2-background.svg new file mode 100644 index 0000000000..cc525b9103 --- /dev/null +++ b/public/static/images/badge-nomad2-background.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/badge-nomad3-background.svg b/public/static/images/badge-nomad3-background.svg new file mode 100644 index 0000000000..651a0cc23a --- /dev/null +++ b/public/static/images/badge-nomad3-background.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/badge-nomad4-background.svg b/public/static/images/badge-nomad4-background.svg new file mode 100644 index 0000000000..b12ad78e3c --- /dev/null +++ b/public/static/images/badge-nomad4-background.svg @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/enums/events.ts b/src/common/enums/events.ts index 06f8237214..2e5331ce2f 100644 --- a/src/common/enums/events.ts +++ b/src/common/enums/events.ts @@ -20,6 +20,7 @@ export const OPEN_UNIVERSAL_AUTH_DIALOG = 'openUniversalAuthDialog' export const CLOSE_ACTIVE_DIALOG = 'closeActiveDialog' export const OPEN_SUBSCRIBE_CIRCLE_DIALOG = 'openSubscribeCircleDialog' export const OPEN_SET_USER_NAME_DIALOG = 'openSetUserNameDialog' +export const OPEN_SHOW_NOMAD_BADGE_DIALOG = 'openShowNomadBadgeDialog' // Toast export const TOAST_SEND_EMAIL_VERIFICATION = 'toastSendEmailVerification' diff --git a/src/components/Dialog/Header/index.tsx b/src/components/Dialog/Header/index.tsx index fa20d948de..ca732462f6 100644 --- a/src/components/Dialog/Header/index.tsx +++ b/src/components/Dialog/Header/index.tsx @@ -1,4 +1,5 @@ import { VisuallyHidden } from '@reach/visually-hidden' +import classNames from 'classnames' import { FormattedMessage } from 'react-intl' import { TextId } from '~/common/enums' @@ -9,6 +10,7 @@ import styles from './styles.module.css' export interface HeaderProps { title: TextId | React.ReactNode + titleLeft?: boolean hasSmUpTitle?: boolean leftBtn?: React.ReactNode @@ -18,14 +20,24 @@ export interface HeaderProps { closeDialog?: () => any } -const Title = ({ title }: Pick) => ( -

+const Title = ({ + title, + titleLeft, +}: Pick) => ( +

{typeof title === 'string' ? : title}

) const Header: React.FC = ({ title, + titleLeft, hasSmUpTitle = true, leftBtn, rightBtn, @@ -40,7 +52,7 @@ const Header: React.FC = ({ <>
- + <Title title={title} titleLeft={titleLeft} /> {leftBtn && <section className={styles.left}>{leftBtn}</section>} {!leftBtn && closeDialog && ( <section className={styles.left}> diff --git a/src/components/Dialog/Header/styles.module.css b/src/components/Dialog/Header/styles.module.css index 0b9efe6d1a..2c8f554ccc 100644 --- a/src/components/Dialog/Header/styles.module.css +++ b/src/components/Dialog/Header/styles.module.css @@ -8,14 +8,17 @@ border-radius: var(--spacing-x-tight) var(--spacing-x-tight) 0 0; & .title { + font-size: var(--font-size-lg); + font-weight: var(--font-weight-medium); + line-height: 1.75rem; + } + + & .titleCenter { @mixin flex-center-all; position: absolute; inset: 0; padding: var(--spacing-x-loose) 0 var(--spacing-x-tight); - font-size: var(--font-size-lg); - font-weight: var(--font-weight-medium); - line-height: 1.75rem; } & .left, diff --git a/src/components/TranslationsProvider/index.tsx b/src/components/TranslationsProvider/index.tsx index ea13fbd8d8..2c0b34569d 100644 --- a/src/components/TranslationsProvider/index.tsx +++ b/src/components/TranslationsProvider/index.tsx @@ -49,6 +49,10 @@ export const TranslationsProvider = ({ <IntlProvider locale={toLocale(ssrLang)} messages={ssrLang != lang ? messages : ssrMessages} + defaultRichTextElements={{ + b: (chunks) => <b>{chunks}</b>, + br: () => <br />, + }} > {children} </IntlProvider> diff --git a/src/views/User/UserProfile/AsideUserProfile/index.tsx b/src/views/User/UserProfile/AsideUserProfile/index.tsx index a8eaaee8a8..8dafd8980c 100644 --- a/src/views/User/UserProfile/AsideUserProfile/index.tsx +++ b/src/views/User/UserProfile/AsideUserProfile/index.tsx @@ -2,7 +2,7 @@ import dynamic from 'next/dynamic' import { useContext, useEffect } from 'react' import { FormattedMessage } from 'react-intl' -import { TEST_ID } from '~/common/enums' +import { OPEN_SHOW_NOMAD_BADGE_DIALOG, TEST_ID } from '~/common/enums' import { numAbbr, toPath } from '~/common/utils' import { Avatar, @@ -18,10 +18,13 @@ import { } from '~/components' import { UserProfileUserPublicQuery } from '~/gql/graphql' +// import { BadgeNomadDialog } from '../BadgeNomadDialog' +import { BadgeNomadLabel } from '../BadgeNomadLabel' import { ArchitectBadge, CivicLikerBadge, GoldenMotorBadge, + // NomadBadge, SeedBadge, TraveloggersBadge, } from '../Badges' @@ -38,11 +41,12 @@ const DynamicWalletLabel = dynamic(() => import('../WalletLabel'), { }) export const AsideUserProfile = () => { - const { isInPath, getQuery } = useRoute() + const { isInPath, getQuery, router } = useRoute() const viewer = useContext(ViewerContext) // public user data const userName = getQuery('name') + const showBadges = 'showBadges' in router.query const isInUserPage = isInPath('USER_ARTICLES') || isInPath('USER_COLLECTIONS') const isMe = !userName || viewer.userName === userName @@ -50,6 +54,11 @@ export const AsideUserProfile = () => { page: 'userProfile', userName, }) + const shareLink = + typeof window !== 'undefined' + ? `${window.location.origin}${userProfilePath.href}?showBadges` + : '' + const { data, loading, client } = usePublicQuery<UserProfileUserPublicQuery>( USER_PROFILE_PUBLIC, { @@ -71,6 +80,12 @@ export const AsideUserProfile = () => { }) }, [user?.id, viewer.id]) + useEffect(() => { + if (showBadges) { + window.dispatchEvent(new CustomEvent(OPEN_SHOW_NOMAD_BADGE_DIALOG)) + } + }, [showBadges]) + /** * Render */ @@ -88,6 +103,13 @@ export const AsideUserProfile = () => { const hasArchitectBadge = badges.some((b) => b.type === 'architect') const hasGoldenMotorBadge = badges.some((b) => b.type === 'golden_motor') const hasTraveloggersBadge = !!user.info.cryptoWallet?.hasNFTs + const nomadBadgeType = badges.filter((b) => + ['nomad1', 'nomad2', 'nomad3', 'nomad4'].includes(b.type) + ) // nomad1 nomad2 nomad3 nomad4 + const hasNomadBadge = nomadBadgeType?.length >= 1 + const nomadBadgeLevel = ( + hasNomadBadge ? Number.parseInt(nomadBadgeType[0].type.charAt(5)) : 1 + ) as 1 | 2 | 3 | 4 const userState = user.status?.state as string const isCivicLiker = user.liker.civicLiker @@ -210,13 +232,22 @@ export const AsideUserProfile = () => { </span> </section> - {(hasTraveloggersBadge || + {(hasNomadBadge || + hasTraveloggersBadge || hasSeedBadge || hasGoldenMotorBadge || hasArchitectBadge || isCivicLiker || user?.info.ethAddress) && ( <section className={styles.badges}> + {hasNomadBadge && ( + <BadgeNomadLabel + hasTooltip + nomadBadgeLevel={nomadBadgeLevel} + totalReferredCount={user.status?.totalReferredCount || 0} + shareLink={shareLink} + /> + )} {hasTraveloggersBadge && <TraveloggersBadge hasTooltip />} {hasSeedBadge && <SeedBadge hasTooltip />} {hasGoldenMotorBadge && <GoldenMotorBadge hasTooltip />} diff --git a/src/views/User/UserProfile/BadgeNomadLabel/index.tsx b/src/views/User/UserProfile/BadgeNomadLabel/index.tsx new file mode 100644 index 0000000000..e24bddc1b4 --- /dev/null +++ b/src/views/User/UserProfile/BadgeNomadLabel/index.tsx @@ -0,0 +1,259 @@ +import classNames from 'classnames' +// import Link from 'next/link' +// import { useEffect } from 'react' +import { FormattedMessage } from 'react-intl' + +import { ReactComponent as Nomad1Background } from '@/public/static/images/badge-nomad1-background.svg' +import { ReactComponent as Nomad2Background } from '@/public/static/images/badge-nomad2-background.svg' +import { ReactComponent as Nomad3Background } from '@/public/static/images/badge-nomad3-background.svg' +import { ReactComponent as Nomad4Background } from '@/public/static/images/badge-nomad4-background.svg' +import { OPEN_SHOW_NOMAD_BADGE_DIALOG } from '~/common/enums' +import { + Button, + CopyToClipboard, + Dialog, + // IconWallet20, + IconArrowLeft16, + // TextIcon, + Tooltip, + useDialogSwitch, + useEventListener, +} from '~/components' +import { UserStatus } from '~/gql/graphql' + +import { NomadBadge } from '../Badges' +import styles from './styles.module.css' + +type BadgeNomadLabelProps = { + hasTooltip?: boolean + nomadBadgeLevel: 1 | 2 | 3 | 4 + totalReferredCount?: UserStatus['totalReferredCount'] + shareLink?: string + // defaultShowDialog: boolean +} + +type BadgeNomadDialogProps = BadgeNomadLabelProps & { + // content?: React.ReactNode + children: ({ openDialog }: { openDialog: () => void }) => React.ReactNode + + isNested?: boolean // if nested by another dialog +} + +export const BadgeNomadDialog: React.FC<BadgeNomadDialogProps> = ({ + // content, + children, + nomadBadgeLevel, + totalReferredCount, + shareLink, + isNested, +}) => { + const { show, openDialog, closeDialog } = useDialogSwitch(false) // the initial value to open not working + + useEventListener(OPEN_SHOW_NOMAD_BADGE_DIALOG, openDialog) + + return ( + <> + {children({ openDialog })} + + <Dialog isOpen={show} onDismiss={closeDialog}> + {isNested && ( + <Dialog.Header + title={<span />} + leftBtn={ + <Button onClick={closeDialog}> + <IconArrowLeft16 size="mdS" color="greyDarker" /> + </Button> + } + /> + )} + + <Dialog.Content fixedHeight> + <section + className={classNames({ + [styles.dialogContent]: true, + [styles.noHeader]: !isNested, + })} + > + {nomadBadgeLevel === 4 ? ( + <Nomad4Background /> + ) : nomadBadgeLevel === 3 ? ( + <Nomad3Background /> + ) : nomadBadgeLevel === 2 ? ( + <Nomad2Background /> + ) : ( + <Nomad1Background /> + )} + <section className={styles.info}> + <h3 className={styles.title}> + {nomadBadgeLevel === 4 ? ( + <FormattedMessage defaultMessage="LV4 Firebolt" id="FuU2MU" /> + ) : nomadBadgeLevel === 3 ? ( + <FormattedMessage + defaultMessage="LV3 Nimbus Ferry" + id="sLiIAz" + /> + ) : nomadBadgeLevel === 2 ? ( + <FormattedMessage + defaultMessage="LV2 Meteor Canoe" + id="7ZXP9S" + /> + ) : ( + <FormattedMessage + defaultMessage="LV1 Moonlight Dream" + id="+56XIp" + /> + )} + </h3> + <p className={styles.description}> + {nomadBadgeLevel === 4 ? ( + <FormattedMessage + defaultMessage="Breaking through history! The torch you've ignited will be the most dazzling support on the Nomad's journey. Congratulations on reaching the final destination of the Nomad Matters with 20 friends and earning the highest-level Firebolt badge." + id="p0oPHP" + values={{ + br: <br />, + // b: (chunks) => <b>{chunks}</b>, + }} + description="src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + /> + ) : nomadBadgeLevel === 3 ? ( + <FormattedMessage + defaultMessage="Wings have sprouted on the badge, and the halo of dreams is beginning to spin. You have gathered 10 companions on the Nomad's path. Invite another 10 fellow travelers to move towards the final destination together, where the highest level of honor awaits you." + id="DdVBFV" + values={{ + br: <br />, + // b: (chunks) => <b>{chunks}</b>, + }} + description="src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + /> + ) : nomadBadgeLevel === 2 ? ( + <FormattedMessage + defaultMessage="The dazzling light of a meteor shower is there to illuminate the night sky. Congratulations on having invited 5 companions to participate in Nomad Matters. Invite 5 more to level up, the more the merrier!" + id="qTjjF0" + values={{ + br: <br />, + // b: (chunks) => <b>{chunks}</b>, + }} + description="src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + /> + ) : ( + <FormattedMessage + defaultMessage="Under the moonlight, your dreams are about to come true. The Moonlight Dream badge glorifies your initial participation in the Nomad Matters. To reach the next level, invite 5 friends to join the journey with you. <a>Click here in the last paragraph to find out how to invite new companions</a>" + id="Z31Fz+" + values={{ + br: <br />, + a: (chunks) => ( + <a href={'/@hi176/464035-nomad-matters'}>{chunks}</a> + ), + }} + description="src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + /> + )} + </p> + </section> + </section> + </Dialog.Content> + + <Dialog.Footer + // stickBottom + btns={ + <> + {!isNested && ( + <Dialog.RoundedButton + text={<FormattedMessage defaultMessage="Close" id="rbrahO" />} + color="greyDarker" + onClick={closeDialog} + /> + )} + <CopyToClipboard + text={shareLink!} + successMessage={ + <FormattedMessage + defaultMessage="Share link copied" + id="/faseS" + /> + } + > + <Dialog.RoundedButton + text={<FormattedMessage defaultMessage="Share" id="OKhRC6" />} + color={isNested ? 'greyDarker' : 'green'} + /> + </CopyToClipboard> + </> + } + smUpBtns={ + <> + <Dialog.TextButton + text={ + isNested ? ( + <FormattedMessage defaultMessage="Back" id="cyR7Kh" /> + ) : ( + <FormattedMessage defaultMessage="Close" id="rbrahO" /> + ) + } + color="greyDarker" + onClick={closeDialog} + /> + <CopyToClipboard + text={shareLink!} + successMessage={ + <FormattedMessage + defaultMessage="Share link copied" + id="/faseS" + /> + } + > + <Dialog.TextButton + text={<FormattedMessage defaultMessage="Share" id="OKhRC6" />} + color="green" + /> + </CopyToClipboard> + </> + } + /> + </Dialog> + </> + ) +} + +export const BadgeNomadLabel: React.FC<BadgeNomadLabelProps> = ({ + hasTooltip, + nomadBadgeLevel, + totalReferredCount, + shareLink, + // defaultShowDialog, +}) => ( + <BadgeNomadDialog + nomadBadgeLevel={nomadBadgeLevel} + totalReferredCount={totalReferredCount} + shareLink={shareLink} + > + {({ openDialog }) => { + const Content = ( + <span className={styles.badge} onClick={openDialog}> + <NomadBadge level={nomadBadgeLevel!} /> + </span> + ) + + return ( + <> + {hasTooltip && ( + <Tooltip + content={ + <FormattedMessage + defaultMessage="Badge Name" + id="iIa+u8" + description="src/views/User/UserProfile/BadgeNomadLabel/index.tsx" + /> + } + placement="top" + > + {Content} + </Tooltip> + )} + + {!hasTooltip && Content} + </> + ) + }} + </BadgeNomadDialog> +) diff --git a/src/views/User/UserProfile/BadgeNomadLabel/styles.module.css b/src/views/User/UserProfile/BadgeNomadLabel/styles.module.css new file mode 100644 index 0000000000..bb419d7203 --- /dev/null +++ b/src/views/User/UserProfile/BadgeNomadLabel/styles.module.css @@ -0,0 +1,67 @@ +.badge { + @mixin transition; + + font-size: 0; + color: var(--color-grey-darker); + cursor: pointer; + transition-property: color; + + &:hover, + &:focus { + color: var(--color-matters-green); + } +} + +.helpIcon { + font-size: 0; +} + +.dialogContent { + @mixin flex-center-all; + + flex-direction: column; + font-size: var(--font-size-sm); + color: var(--color-grey-darker); + + &.noHeader { + padding-top: 2rem; + padding-bottom: 3rem; + } + + & svg { + height: 215px; + } + + & .info { + @mixin flex-center-all; + + flex-direction: column; + text-align: center; + + & .title { + padding-top: 1rem; + padding-bottom: 1rem; + font-size: var(--font-size-lg); + font-weight: var(--font-weight-medium); + line-height: 1.875rem; + color: var(--color-black); + } + + & .description { + line-height: 1.375rem; + color: var(--color-grey-darker); + + & b { + color: var(--color-black); + } + + & a { + text-decoration: underline; + } + + & a:hover { + color: var(--color-green); + } + } + } +} diff --git a/src/views/User/UserProfile/Badges/index.tsx b/src/views/User/UserProfile/Badges/index.tsx index 4f28fceb61..bd5a7d3163 100644 --- a/src/views/User/UserProfile/Badges/index.tsx +++ b/src/views/User/UserProfile/Badges/index.tsx @@ -1,10 +1,19 @@ -import { ReactComponent as IconArchitectBadge } from '@/public/static/icons/16px/badge-architect.svg' -import { ReactComponent as IconGoldenMotorBadge } from '@/public/static/icons/16px/badge-golden-motor.svg' -import { ReactComponent as IconSeedBadge } from '@/public/static/icons/16px/badge-seed.svg' -import { ReactComponent as IconTraveloggersBadge } from '@/public/static/icons/16px/traveloggers.svg' -import { ReactComponent as IconCivicLikerBadge } from '@/public/static/icons/20px/badge-civic-liker.svg' -import { Tooltip, Translate, withIcon } from '~/components' +import classNames from 'classnames' +import { FormattedMessage } from 'react-intl' +import { ReactComponent as IconArchitectBadge } from '@/public/static/icons/48px/badge-architect.svg' +import { ReactComponent as IconCivicLikerBadge } from '@/public/static/icons/48px/badge-civic-liker.svg' +import { ReactComponent as IconGoldenMotorBadge } from '@/public/static/icons/48px/badge-golden-motor.svg' +import { ReactComponent as IconNomad1Badge } from '@/public/static/icons/48px/badge-nomad1-moon.svg' +import { ReactComponent as IconNomad2Badge } from '@/public/static/icons/48px/badge-nomad2-star.svg' +import { ReactComponent as IconNomad3Badge } from '@/public/static/icons/48px/badge-nomad3-light.svg' +import { ReactComponent as IconNomad4Badge } from '@/public/static/icons/48px/badge-nomad4-fire.svg' +import { ReactComponent as IconSeedBadge } from '@/public/static/icons/48px/badge-seed.svg' +import { ReactComponent as IconTraveloggersBadge } from '@/public/static/icons/48px/badge-traveloggers.svg' +import { IconArrowRight16, Tooltip, Translate, withIcon } from '~/components' +import { UserStatus } from '~/gql/graphql' + +import { BadgeNomadDialog } from '../BadgeNomadLabel' import styles from './styles.module.css' type badgePros = { @@ -19,7 +28,7 @@ export const SeedBadge = ({ isInDialog, hasTooltip }: badgePros) => { if (isInDialog) { return ( <section className={styles.item}> - <>{withIcon(IconSeedBadge)({ size: 'md' })}</> + <>{withIcon(IconSeedBadge)({ size: 'xl' })}</> <section>{copy}</section> </section> ) @@ -53,7 +62,7 @@ export const GoldenMotorBadge = ({ isInDialog, hasTooltip }: badgePros) => { if (isInDialog) { return ( <section className={styles.item}> - <>{withIcon(IconGoldenMotorBadge)({ size: 'md' })}</> + <>{withIcon(IconGoldenMotorBadge)({ size: 'xl' })}</> <section>{copy}</section> </section> ) @@ -87,7 +96,7 @@ export const ArchitectBadge = ({ isInDialog, hasTooltip }: badgePros) => { if (isInDialog) { return ( <section className={styles.item}> - <>{withIcon(IconArchitectBadge)({ size: 'md' })}</> + <>{withIcon(IconArchitectBadge)({ size: 'xl' })}</> <section>{copy}</section> </section> ) @@ -117,7 +126,7 @@ export const CivicLikerBadge = ({ isInDialog, hasTooltip }: badgePros) => { if (isInDialog) { return ( <section className={styles.item}> - <>{withIcon(IconCivicLikerBadge)({ size: 'md' })}</> + <>{withIcon(IconCivicLikerBadge)({ size: 'xl' })}</> <section>{copy}</section> </section> ) @@ -151,7 +160,7 @@ export const TraveloggersBadge = ({ isInDialog, hasTooltip }: badgePros) => { if (isInDialog) { return ( <section className={styles.item}> - <>{withIcon(IconTraveloggersBadge)({ size: 'md' })}</> + <>{withIcon(IconTraveloggersBadge)({ size: 'xl' })}</> <section>{copy}</section> </section> ) @@ -172,3 +181,154 @@ export const TraveloggersBadge = ({ isInDialog, hasTooltip }: badgePros) => { </span> ) } + +export const NomadBadge = ({ + isInDialog, + hasTooltip, + level, + totalReferredCount, + shareLink, +}: badgePros & { + level: 1 | 2 | 3 | 4 + totalReferredCount?: UserStatus['totalReferredCount'] + shareLink?: string +}) => { + const copy = ( + <Translate zh_hant="數字游民" zh_hans="数字游民" en="Nomad Matters" /> + ) + + let withIconComp = withIcon(IconNomad1Badge) + switch (level) { + case 2: + withIconComp = withIcon(IconNomad2Badge) + break + case 3: + withIconComp = withIcon(IconNomad3Badge) + break + case 4: + withIconComp = withIcon(IconNomad4Badge) + break + } + + if (isInDialog) { + return ( + <BadgeNomadDialog + isNested + nomadBadgeLevel={level} + totalReferredCount={totalReferredCount} + shareLink={shareLink} + > + {({ openDialog }) => ( + <section + className={classNames([styles.item, styles.itemNomad])} + onClick={openDialog} + > + <>{withIconComp({ size: 'xl' })}</> + <section className={styles.info}> + <section> + {level === 4 ? ( + <FormattedMessage defaultMessage="LV4 Firebolt" id="FuU2MU" /> + ) : level === 3 ? ( + <FormattedMessage + defaultMessage="LV3 Nimbus Ferry" + id="sLiIAz" + /> + ) : level === 2 ? ( + <FormattedMessage + defaultMessage="LV2 Meteor Canoe" + id="7ZXP9S" + /> + ) : ( + <FormattedMessage + defaultMessage="LV1 Moonlight Dream" + id="+56XIp" + /> + )} + </section> + <section className={styles.subtitle}> + <FormattedMessage + defaultMessage="Nomad Matters Badge" + id="cQ+Lyq" + description="src/views/User/UserProfile/Badges/index.tsx" + /> + </section> + </section> + <IconArrowRight16 size="md" /> + </section> + )} + </BadgeNomadDialog> + ) + } + if (hasTooltip) { + return ( + <Tooltip content={copy} placement="top"> + <span className={styles.badge}>{withIconComp({ size: 'mdS' })}</span> + </Tooltip> + ) + } + + return <span className={styles.badge}>{withIconComp({ size: 'mdS' })}</span> +} + +export interface BadgesOptions { + hasNomadBadge?: boolean + nomadBadgeLevel?: 1 | 2 | 3 | 4 + totalReferredCount?: UserStatus['totalReferredCount'] + hasTraveloggersBadge?: boolean + hasSeedBadge?: boolean + hasGoldenMotorBadge?: boolean + hasArchitectBadge?: boolean + isCivicLiker?: boolean +} + +export const Badges = ({ + isInDialog, + hasNomadBadge, + nomadBadgeLevel, + totalReferredCount, + hasTraveloggersBadge, + hasSeedBadge, + hasGoldenMotorBadge, + hasArchitectBadge, + isCivicLiker, + shareLink, +}: BadgesOptions & { + isInDialog?: boolean + shareLink?: string +}) => + isInDialog ? ( + <span className={styles.badgesInDialog}> + {hasNomadBadge && ( + <section className={styles.badgesGroup}> + <NomadBadge + isInDialog + level={nomadBadgeLevel!} + totalReferredCount={totalReferredCount} + shareLink={shareLink} + /> + </section> + )} + {(hasTraveloggersBadge || + hasSeedBadge || + hasGoldenMotorBadge || + hasArchitectBadge || + isCivicLiker) && ( + <section className={styles.badgesGroup}> + {hasTraveloggersBadge && <TraveloggersBadge isInDialog />} + {hasSeedBadge && <SeedBadge isInDialog />} + {hasGoldenMotorBadge && <GoldenMotorBadge isInDialog />} + {hasArchitectBadge && <ArchitectBadge isInDialog />} + {isCivicLiker && <CivicLikerBadge isInDialog />} + </section> + )} + </span> + ) : ( + <span className={styles.badgesInPage}> + {hasNomadBadge && <NomadBadge level={nomadBadgeLevel!} />} + {hasTraveloggersBadge && <TraveloggersBadge />} + {hasSeedBadge && <SeedBadge />} + {hasGoldenMotorBadge && <GoldenMotorBadge />} + {hasArchitectBadge && <ArchitectBadge />} + {isCivicLiker && <CivicLikerBadge />} + </span> + ) diff --git a/src/views/User/UserProfile/Badges/styles.module.css b/src/views/User/UserProfile/Badges/styles.module.css index c5a37ba016..3f16da9e66 100644 --- a/src/views/User/UserProfile/Badges/styles.module.css +++ b/src/views/User/UserProfile/Badges/styles.module.css @@ -3,14 +3,52 @@ } .item { - @mixin flex-center-all; + @mixin flex-center-start; - flex-direction: column; - font-size: var(--font-size-sm); - line-height: 1.375rem; - color: var(--color-grey-dark); + min-height: 3rem; + font-size: var(--font-size-md); + font-weight: var(--font-weight-normal); + line-height: 1.5rem; + color: var(--color-black); & > * + * { - margin-top: var(--spacing-tight); + margin-left: var(--spacing-base-loose); } + + &.itemNomad { + @mixin flex-center-start; + + cursor: pointer; + + & .info { + flex-grow: 1; + + & .subtitle { + line-height: 1.375rem; + color: var(--color-grey-darker); + } + } + } +} + +.badgesInDialog { + display: flex; + flex-direction: column; + gap: var(--spacing-loose); + + & .badgesGroup { + display: flex; + flex-direction: column; + gap: var(--spacing-base-loose); + align-items: stretch; + padding: var(--spacing-base) var(--spacing-base-loose); + border: 1px solid var(--color-grey-light); + border-radius: var(--spacing-base); + } +} + +.badgesInPage { + display: flex; + align-items: center; + cursor: pointer; } diff --git a/src/views/User/UserProfile/BadgesDialog/index.tsx b/src/views/User/UserProfile/BadgesDialog/index.tsx index 0ce3a531da..be4ca8af3a 100644 --- a/src/views/User/UserProfile/BadgesDialog/index.tsx +++ b/src/views/User/UserProfile/BadgesDialog/index.tsx @@ -1,14 +1,28 @@ import { FormattedMessage } from 'react-intl' -import { Dialog, useDialogSwitch } from '~/components' +import { OPEN_SHOW_NOMAD_BADGE_DIALOG } from '~/common/enums' +import { + Button, + Dialog, + IconClose20, + useDialogSwitch, + useEventListener, +} from '~/components' -interface BadgesDialogProps { +import { BadgesOptions } from '../Badges' + +interface BadgesDialogProps extends BadgesOptions { content: React.ReactNode children: ({ openDialog }: { openDialog: () => void }) => React.ReactNode + defaultShow: boolean } -const BaseBadgesDialog = ({ content, children }: BadgesDialogProps) => { - const { show, openDialog, closeDialog } = useDialogSwitch(true) +export const BaseBadgesDialog = ({ + content, + children, + defaultShow, +}: BadgesDialogProps) => { + const { show, openDialog, closeDialog } = useDialogSwitch(defaultShow) return ( <> @@ -23,16 +37,40 @@ const BaseBadgesDialog = ({ content, children }: BadgesDialogProps) => { description="src/components/UserProfile/index.tsx" /> } + titleLeft + rightBtn={ + <Button onClick={closeDialog}> + <IconClose20 size="mdS" color="greyDarker" />{' '} + </Button> + } /> - <Dialog.Content>{content}</Dialog.Content> + <Dialog.Content fixedHeight>{content}</Dialog.Content> + + <Dialog.Footer + smUpBtns={ + <Dialog.TextButton + text={<FormattedMessage defaultMessage="Close" id="rbrahO" />} + color="greyDarker" + onClick={closeDialog} + /> + } + /> </Dialog> </> ) } -export const BadgesDialog = (props: BadgesDialogProps) => ( - <Dialog.Lazy mounted={<BaseBadgesDialog {...props} />}> - {({ openDialog }) => <>{props.children({ openDialog })}</>} - </Dialog.Lazy> -) +export const BadgesDialog = (props: BadgesDialogProps) => { + const Children = ({ openDialog }: { openDialog: () => void }) => { + // useEventListener(OPEN_SET_USER_NAME_DIALOG, openDialog) + useEventListener(OPEN_SHOW_NOMAD_BADGE_DIALOG, openDialog) + return <>{props?.children({ openDialog })}</> + } + + return ( + <Dialog.Lazy mounted={<BaseBadgesDialog {...props} />}> + {({ openDialog }) => <Children openDialog={openDialog} />} + </Dialog.Lazy> + ) +} diff --git a/src/views/User/UserProfile/gql.ts b/src/views/User/UserProfile/gql.ts index 31912a5b73..d1ea233968 100644 --- a/src/views/User/UserProfile/gql.ts +++ b/src/views/User/UserProfile/gql.ts @@ -39,6 +39,7 @@ const fragments = { } status { state + totalReferredCount } ownCircles { ...DigestRichCirclePublic diff --git a/src/views/User/UserProfile/index.tsx b/src/views/User/UserProfile/index.tsx index 90191efcee..c2ef23c495 100644 --- a/src/views/User/UserProfile/index.tsx +++ b/src/views/User/UserProfile/index.tsx @@ -3,8 +3,8 @@ import dynamic from 'next/dynamic' import { useContext, useEffect } from 'react' import { FormattedMessage } from 'react-intl' -import { TEST_ID } from '~/common/enums' -import { numAbbr } from '~/common/utils' +import { OPEN_SHOW_NOMAD_BADGE_DIALOG, TEST_ID } from '~/common/enums' +import { numAbbr, toPath } from '~/common/utils' import { Avatar, Button, @@ -23,13 +23,7 @@ import { import { UserProfileUserPublicQuery } from '~/gql/graphql' import UserTabs from '../UserTabs' -import { - ArchitectBadge, - CivicLikerBadge, - GoldenMotorBadge, - SeedBadge, - TraveloggersBadge, -} from './Badges' +import { Badges } from './Badges' import { BadgesDialog } from './BadgesDialog' import CircleWidget from './CircleWidget' import DropdownActions from './DropdownActions' @@ -45,11 +39,16 @@ const DynamicWalletLabel = dynamic(() => import('./WalletLabel'), { }) export const UserProfile = () => { - const { getQuery } = useRoute() + const { + getQuery, // replaceQuery, + router, + } = useRoute() const viewer = useContext(ViewerContext) // public user data const userName = getQuery('name') + // const searchParams = useSearchParams() // next v13; call searchParams.has('...') + const showBadges = 'showBadges' in router.query const isMe = !userName || viewer.userName === userName const { data, loading, client } = usePublicQuery<UserProfileUserPublicQuery>( USER_PROFILE_PUBLIC, @@ -72,6 +71,12 @@ export const UserProfile = () => { }) }, [user?.id, viewer.id]) + useEffect(() => { + if (showBadges) { + window.dispatchEvent(new CustomEvent(OPEN_SHOW_NOMAD_BADGE_DIALOG)) + } + }, [showBadges]) + /** * Render */ @@ -88,27 +93,33 @@ export const UserProfile = () => { return <Throw404 /> } + const userProfilePath = toPath({ + page: 'userProfile', + userName, + }) + const shareLink = + typeof window !== 'undefined' + ? `${window.location.origin}${userProfilePath.href}?showBadges` + : '' + const badges = user.info.badges || [] const circles = user.ownCircles || [] const hasSeedBadge = badges.some((b) => b.type === 'seed') const hasArchitectBadge = badges.some((b) => b.type === 'architect') const hasGoldenMotorBadge = badges.some((b) => b.type === 'golden_motor') const hasTraveloggersBadge = !!user.info.cryptoWallet?.hasNFTs + const nomadBadgeType = badges.filter((b) => + ['nomad1', 'nomad2', 'nomad3', 'nomad4'].includes(b.type) + ) // nomad1 nomad2 nomad3 nomad4 + const hasNomadBadge = nomadBadgeType?.length >= 1 + const nomadBadgeLevel = ( + hasNomadBadge ? Number.parseInt(nomadBadgeType[0].type.charAt(5)) : 1 + ) as 1 | 2 | 3 | 4 const profileCover = user.info.profileCover const userState = user.status?.state as string const isCivicLiker = user.liker.civicLiker - const Badges = ({ isInDialog }: { isInDialog?: boolean }) => ( - <span className={isInDialog ? styles.badgesInDialog : styles.badgesInPage}> - {hasTraveloggersBadge && <TraveloggersBadge isInDialog={isInDialog} />} - {hasSeedBadge && <SeedBadge isInDialog={isInDialog} />} - {hasGoldenMotorBadge && <GoldenMotorBadge isInDialog={isInDialog} />} - {hasArchitectBadge && <ArchitectBadge isInDialog={isInDialog} />} - {isCivicLiker && <CivicLikerBadge isInDialog={isInDialog} />} - </span> - ) - /** * Inactive User */ @@ -195,11 +206,35 @@ export const UserProfile = () => { > {user.displayName} </h1> - <BadgesDialog content={<Badges isInDialog />}> + <BadgesDialog + defaultShow={showBadges} + content={ + <Badges + isInDialog + hasNomadBadge={hasNomadBadge} + nomadBadgeLevel={nomadBadgeLevel} + totalReferredCount={user.status?.totalReferredCount || 0} + hasTraveloggersBadge={hasTraveloggersBadge} + hasSeedBadge={hasSeedBadge} + hasGoldenMotorBadge={hasGoldenMotorBadge} + hasArchitectBadge={hasArchitectBadge} + isCivicLiker={isCivicLiker} + shareLink={shareLink} + /> + } + > {({ openDialog }) => { return ( <span className={styles.badges} onClick={openDialog}> - <Badges /> + <Badges + hasNomadBadge={hasNomadBadge} + nomadBadgeLevel={nomadBadgeLevel} + hasTraveloggersBadge={hasTraveloggersBadge} + hasSeedBadge={hasSeedBadge} + hasGoldenMotorBadge={hasGoldenMotorBadge} + hasArchitectBadge={hasArchitectBadge} + isCivicLiker={isCivicLiker} + /> </span> ) }} diff --git a/src/views/User/UserProfile/styles.module.css b/src/views/User/UserProfile/styles.module.css index 6660b0b56f..25f8d796a1 100644 --- a/src/views/User/UserProfile/styles.module.css +++ b/src/views/User/UserProfile/styles.module.css @@ -99,18 +99,6 @@ } } -.badgesInDialog { - display: grid; - grid-template-columns: 50% 50%; - grid-row-gap: var(--spacing-loose); - margin-bottom: var(--spacing-loose); -} - -.badgesInPage { - display: flex; - align-items: center; -} - .follow { padding: 0 var(--spacing-base); margin-top: var(--spacing-base);