diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index eb8672bc42..d7329e4b2f 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -30,6 +30,7 @@
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
+ "moment": "^2.26.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "^6.0.0-alpha.5",
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx b/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx
new file mode 100644
index 0000000000..9eccf81818
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { FC } from 'react';
+import { Header, HomepageTimer, Page, pageTheme } from '@backstage/core';
+import { getTimeBasedGreeting } from './utils/timeUtil';
+
+const CatalogLayout: FC<{}> = props => {
+ const { children } = props;
+ // const profile = useProfile();
+ const profile = { givenName: 'friend' };
+ const greeting = getTimeBasedGreeting();
+
+ return (
+
+
+ {children}
+
+ );
+};
+
+export default CatalogLayout;
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
index 7601031699..2e1989323c 100644
--- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
+++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
@@ -64,7 +64,7 @@ describe('CatalogPage', () => {
),
);
expect(
- await rendered.findByText('Keep track of your software'),
+ await rendered.findByText('Backstage Service Catalog'),
).toBeInTheDocument();
});
});
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
index c8074b1cc9..3ba63188c1 100644
--- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
+++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
@@ -19,14 +19,11 @@ import {
Content,
ContentHeader,
DismissableBanner,
- Header,
HeaderTabs,
- HomepageTimer,
- Page,
- pageTheme,
SupportButton,
useApi,
} from '@backstage/core';
+import CatalogLayout from './CatalogLayout';
import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder';
import { Button, Link, makeStyles, Typography } from '@material-ui/core';
import Edit from '@material-ui/icons/Edit';
@@ -155,10 +152,7 @@ export const CatalogPage: FC<{}> = () => {
];
return (
-
-
+
= () => {
}
id="catalog_page_welcome_banner"
/>
-
-
+
);
};
diff --git a/plugins/catalog/src/components/CatalogPage/utils/locales/goodAfternoon.locales.json b/plugins/catalog/src/components/CatalogPage/utils/locales/goodAfternoon.locales.json
new file mode 100644
index 0000000000..4d48666666
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogPage/utils/locales/goodAfternoon.locales.json
@@ -0,0 +1,134 @@
+{
+ "English": "Good afternoon",
+ "Abkhaz": "Уа, мшы бзиа",
+ "Adyghe": "Уимафэ шІу",
+ "Afrikaans": "Goeie middag",
+ "Albanian": "Mirëdita",
+ "Aleut": "Angaliichxizax̂",
+ "Aklan": "Mayad nga hapon",
+ "Alsatian": "Güete Nàchmittag",
+ "Arabic": "مساء الخير",
+ "Armenian": "Բարի կէսօր:",
+ "Assamese": "শুভ আবেলি",
+ "Asturian": "Bones tardes",
+ "Azerbaijani": "Günortanız xeyir",
+ "Bashkir": "хәйерле көн",
+ "Basque": "Arratsalde on",
+ "Bavarian": "Guadn åmd",
+ "Bengali": "শুভ বিকাল",
+ "Bhojpuri": "राम राम",
+ "Bosnian": "Dobar dan",
+ "Breton": "Demat",
+ "Bulgarian": "Добър ден",
+ "Cebuano": "Maayong Hapon",
+ "Chamorro": "Buenas tåtdes",
+ "Chinese": "午安",
+ "Cornish": "Dohajydh da",
+ "Corsican": "Bonghjornu",
+ "Croatian": "Dobar dan",
+ "Cuyonon": "Mayad nga apon",
+ "Czech": "Dobré odpoledne",
+ "Danish": "God eftermiddag",
+ "Dutch": "Goedemiddag",
+ "Estonian": "Tere päevast",
+ "Ewe": "Ŋdɔ",
+ "Faroese": "Góðan dagin",
+ "Fijian": "Bula",
+ "Finnish": "Hyvää päivää",
+ "Flemish": "Ghoeie middagh",
+ "French": "Bon après-midi",
+ "Frisian (North)": "Moin",
+ "Frisian (West)": "Goeie middei",
+ "Friulian": "Bundì",
+ "Galician": "Boa tarde",
+ "Georgian": "დილა მშვიდობისა",
+ "German": "Guten Tag",
+ "Greek": "Καλό απόγευμα",
+ "Greenlandic": "Inuugujaq",
+ "Haitian Creole": "Bon apre-midi",
+ "Hausa": "Barka da yamma",
+ "Hawaiian": "Aloha ʻauinalā",
+ "Hebrew": "אחר צהריים טובים",
+ "Hindi": "नमस्कार",
+ "Icelandic": "Góðan dag",
+ "Iloko": "Naimbag a malem",
+ "Indonesian": "Selamat sore",
+ "Iñupiaq": "Unnusatkun",
+ "Gaelic": "Tráthnóna maith duit",
+ "Italian": "Buongiorno",
+ "Japanese": "こんにちは",
+ "Javanese": "Sugeng siang",
+ "Jèrriais": "Bouônjour",
+ "Kannada": "ಶುಭ ಮಧ್ಯಾಹ್ನ",
+ "Kazakh": "Kayırlı kun",
+ "Khmer": "ទិវាសួស្ដី",
+ "Korean": "안녕하세요",
+ "Kurdish": "Rojbash",
+ "Kyrgyz": "Кутмандуу күнүңүз менен",
+ "Latvian": "Labdien",
+ "Limburgish": "Gojemiddig",
+ "Lithuanian": "Laba diena",
+ "Lozi": "Ki musihali",
+ "Lule Sámi": "Buorre biejvve",
+ "Luxembourgish": "Gudde Mëtteg",
+ "Macedonian": "Добар ден",
+ "Malagasy": "Manao ahoana e",
+ "Maltese": "Wara nofsinhar it-tajjeb",
+ "Mam": "Qa’lte",
+ "Manx": "Fastyr mie",
+ "Māori": "Kia ora",
+ "Middle Egyptian Hieroglyphs": "𓄤 𓅓𓈙𓂋𓅱𓇰",
+ "Nepali": "नमस्कार",
+ "Newari": "भिं न्हि",
+ "Northern Sámi": "Buorre beaivvi",
+ "Northern Sotho": "Dumêlang",
+ "Norwegian": "God ettermiddag",
+ "Occitan": "Bon vèspre",
+ "Odia": "ସୁଭ ଖରା ବେଳ",
+ "Papiamento": "Bon tardi",
+ "Pashto": "ورځ مو په خير",
+ "Polish": "Dzień dobry",
+ "Portuguese": "Boa tarde",
+ "Romanian": "Bună ziua",
+ "Russian": "Добрый день",
+ "Sakha": "Үтүө күнүнэн",
+ "Sardinian": "Bona sera",
+ "Scottish Gaelic": "Feasgar math",
+ "Scots": "Guid efternuin",
+ "Serbian": "Добар дан",
+ "Shona": "Masikati",
+ "Sicilian": "Bon jornu",
+ "Sinhala": "සුභ දවාලක්",
+ "Slovenian": "Dober dan",
+ "Somali": "Galab wanaagsan",
+ "Southern Sámi": "Buerie biejjie",
+ "Sumerian": "𒄭𒌓𒁀",
+ "Swahili": "Habari ya mchana",
+ "Swedish": "God eftermiddag",
+ "Swiss German": "Gueten Abig",
+ "Tagalog": "Magandang hapon po",
+ "Tamil": "மதிய வணக்கம்",
+ "Tatar": "Xäyerle kön",
+ "Telugu": "శుభ మద్యాహ్నం",
+ "Tetum": "Botarde",
+ "Thai": "สวัสดีครับ",
+ "Tibetan": "ཉིན་གུང་བདེ་ལེགས།",
+ "Tok Pisin": "Apinum",
+ "Tongan": "Mālō tau ma‘u e ho‘ata ni",
+ "Tsonga": "Indzengha",
+ "Tswana": "Thupama e e monate",
+ "Turkish": "Tünaydın",
+ "Ukrainian": "Доброго дня",
+ "Urdu": "روز بخير",
+ "Uzbek": "Xayrli kun",
+ "Venetian": "Bondì",
+ "Vietnamese": "Xin chào",
+ "Võro": "Hüvvä lõunaaigo",
+ "Welsh": "P'nawn da",
+ "Xhosa": "Uben' emva kwemini entle",
+ "Yapese": "Fal'e misii",
+ "Yiddish": "א גוט אָוונט",
+ "Yucatec Maya": "Ma'lob chi'inil K'iin",
+ "Zazaki": "Perocê şıma xeyr bo",
+ "Zulu": "Sawubona"
+}
diff --git a/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json b/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json
new file mode 100644
index 0000000000..a70aa122b6
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json
@@ -0,0 +1,100 @@
+{
+ "Afrikaans": "Goeienaand",
+ "Aklanon": "Mayad nga gabi-i",
+ "Albanian": "Mirëmbrëma",
+ "Anglo-Saxon": "God æfen",
+ "Arabic": "Masaa al-khair",
+ "Aragonese": "Buena nuei",
+ "Armenian": "Pari yerego",
+ "Aromunian": "Bunã seara",
+ "Asante": "Maadwó",
+ "Asturian": "Bona nuechi",
+ "Ateso": "Akwar najokan",
+ "Aymara": "Winas tartis",
+ "Azerbaijani": "Axşamınız xeyir",
+ "Basque": "Arratsalde on",
+ "Belorussian": "Dobry viechar",
+ "Bemba": "Cungulopo mukwai",
+ "Bengali": "Shuvo shandhya",
+ "Bilen": "Aja kunduKw’ma?",
+ "Breton": "Noz vat",
+ "Bulgarian": "Dobar vecher",
+ "Cantonese": "Maan ngon",
+ "Cassubian": "Dobri wieczór",
+ "Catalan": "Bona tarda",
+ "Chamorro": "Pupuengin maolek",
+ "Chechen": "Sürea dika yoila",
+ "Comoran": "Bariza massihou",
+ "Cornish": "Gorthugher da",
+ "Croatian": "Dobra večer",
+ "Czech": "Dobrý večer",
+ "Danish": "God aften",
+ "Dutch": "Goedenavond",
+ "Edo": "Ób’ótà",
+ "English": "Good evening",
+ "Esperanto": "Bonan vesperon",
+ "Estonian": "Tere õhtust",
+ "Faroese": "Gott kvøld",
+ "Farsi": "Shab beh'khayr",
+ "Fijian": "Ni sa bogi",
+ "Finnish": "Hyvää iltaa",
+ "French": "Bonsoir",
+ "Galician": "Boas noites",
+ "Georgian": "Saghamo mshvidobisa",
+ "German": "Guten Abend",
+ "Greek": "Kalinishta",
+ "Guarani": "Mba’éichapa ndepyhare",
+ "Hebrew": "Erev tov",
+ "Hungarian": "Jó estét",
+ "Icelandic": "Gott kvöld",
+ "Indonesian": "Selamat malam",
+ "Irish Gaelic": "Tráthnóna",
+ "Italian": "Buona sera",
+ "Japanese": "こんばんは",
+ "Japanese (Romanji)": "Konban wa",
+ "Kazakh": "Kayırlı keş",
+ "Kurdish": "Evarbash",
+ "Latvian": "Labvakar",
+ "Lithuanian": "Labas vakaras",
+ "Luganda": "Osiibye otya nno",
+ "Luo": "Oimore",
+ "Luxembourgish": "Gudden Owend",
+ "Macedonian": "Dobra vecher",
+ "Malay": "Selamat malam",
+ "Maltese": "Il-lejla t-tajba",
+ "Mandarin": "Wan shang hao",
+ "Maori": "Kia orana ‘i teia a’ia’i",
+ "Marshallese": "Yokwe in jota",
+ "Mawe": "Awãe aiko",
+ "Middle Egyptian Hieroglyphs": "𓄤 𓅱𓃉𓇰",
+ "Mongolian": "Odoin mend",
+ "Norwegian [Bokmaal]": "God kveld",
+ "Odia": "ସୁଭସନ୍ଧ୍ୟା",
+ "Polish": "Dobry wieczór",
+ "Portuguese": "Boa noite",
+ "Quiché": "Xe q’ij",
+ "Romani [Sinte]": "Lashi rachi",
+ "Romanian": "Bunã seara",
+ "Russian": "Dobry vyecher",
+ "Scottish Gaelic": "Feasgar mhath",
+ "Scots": "Guid eenin",
+ "Sesotho": "Fonane",
+ "Serbian": "Добро вече",
+ "Slovak": "Dober vecher",
+ "Slovenian": "Dober vecher",
+ "Somali": "Habeen wanaagsan",
+ "Spanish": "Buenas tardes",
+ "Sumerian": "𒄭𒈪",
+ "Swedish": "God afton",
+ "Tagalog": "Magandang gabi",
+ "Tatar": "Xäyerle kiç",
+ "Telugu" : "శుభ సాయంత్రం",
+ "Thai": "Sawat-dii torn khum",
+ "Turkish": "İyi akşamlar",
+ "Ukrainian": "Dobry vechir",
+ "Uzbek": "Xayrli kech",
+ "Welsh": "Noswaith dda",
+ "Yiddish": "Ah gutn ovnt",
+ "Chinese": "晚安",
+ "Valyrian": "sȳz bantis"
+}
diff --git a/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json b/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json
new file mode 100644
index 0000000000..a67c292d83
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json
@@ -0,0 +1,64 @@
+{
+ "English": "Good morning",
+ "Afrikaans": "Goeiemôre, Môre",
+ "Albanian": "Mirëmëngjes",
+ "Arabic": "صباح الخير",
+ "Armenian": "Paree looys",
+ "Azerbaijani": "Sabahınız xeyir",
+ "Bengali": "shuprobhat",
+ "Bulgarian": "Dobro utro",
+ "Catalan": "Bon dia",
+ "Chinese": "早安",
+ "Croatian": "Dobro jutro",
+ "Czech": "Dobré ráno",
+ "Danish": "God morgen",
+ "Dutch": "Goedemorgen",
+ "Esperanto": "Bonan matenon",
+ "Estonian": "Tere hommikust",
+ "Finnish": "Hyvää huomenta",
+ "French": "Bonjour",
+ "German": "Guten Morgen",
+ "Greek": "Kaliméra",
+ "Kalaallisut": "Iterluarit",
+ "Hawaiian": "Aloha kakahiaka",
+ "Hebrew": "boker tov",
+ "Hindi": "Namaste",
+ "Hungarian": "Jó reggelt",
+ "Indonesian": "Selamat pagi",
+ "Irish": "Dia dhuit",
+ "Italian": "Buongiorno",
+ "Japanese": "お早う",
+ "Korean": "안녕하세요",
+ "Kazakh": "Kayırlı tan",
+ "Kurdish": "Beyanî baş",
+ "Lithuanian": "Labas rytas",
+ "Macedonian": "Добро утро",
+ "Malay": "Selamat pagi",
+ "Maltese": "Bonġu",
+ "Middle Egyptian Hieroglyphs": "𓄤 𓃀 𓂓 𓇳𓐅",
+ "Nepali": "subha prabhat",
+ "Norwegian": "God morgen",
+ "Odia": "ସୁପ୍ରଭାତ",
+ "Polish": "Dzień dobry",
+ "Portuguese": "Bom dia",
+ "Romanian": "Bună dimineaţa",
+ "Russian": "Доброе утро",
+ "Serbian": "Добро јутро",
+ "Spanish": "Buenos días",
+ "Sumerian": "𒄭𒌓",
+ "Swahili": "Habari ya asubuhi",
+ "Swedish": "God morgon",
+ "Tagalog": "Magandang umaga po",
+ "Taiwanese": "爻早",
+ "Tatar": "Xäyerle irtä",
+ "Telugu": "శుభోదయం",
+ "Thai": "สวัสดีครับ",
+ "Turkish": "Günaydın",
+ "Ukrainian": "Доброго ранку",
+ "Urdu": "صبح بخير",
+ "Uzbek": "Xayrli tong",
+ "Vietnamese": "Chào buổi sáng",
+ "Welsh": "Bore da",
+ "Latvian": "Labrīt",
+ "Valyrian": "Sȳz ñāqes"
+}
diff --git a/plugins/catalog/src/components/CatalogPage/utils/timeUtil.js b/plugins/catalog/src/components/CatalogPage/utils/timeUtil.js
new file mode 100644
index 0000000000..327f4177a8
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogPage/utils/timeUtil.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import moment from 'moment';
+import goodMorning from './locales/goodMorning.locales.json';
+import goodAfternoon from './locales/goodAfternoon.locales.json';
+import goodEvening from './locales/goodEvening.locales.json';
+
+/**
+ * Validates that a date is a valid ISO string.
+ *
+ * @param date A date string
+ * @returns {bool} Whether the date is valid or not.
+ */
+export function isValidDate(date) {
+ return moment(date).isValid();
+}
+
+/**
+ * Validates that a date is a valid ISO string and a specific format.
+ *
+ * @param date A date string
+ * @param format A format string or an array of format strings to validate against.
+ * @returns {bool} Whether the date is valid or not according to the format.
+ */
+export function isValidDateAndFormat(date, format) {
+ return moment(date, format, true).isValid();
+}
+
+export function relativeTime(timestamp) {
+ return moment(timestamp).fromNow();
+}
+
+// Select a large random integer at startup, to prevent the greetings to change every time the user
+// navigates.
+const greetingRandomSeed = Math.floor(Math.random() * 1000000);
+
+export function getTimeBasedGreeting() {
+ const random = array => array[greetingRandomSeed % array.length];
+
+ const currentHour = new Date(Date.now()).getHours();
+ if (currentHour >= 23) {
+ return {
+ language: 'Seriously',
+ greeting: 'Get some rest',
+ };
+ }
+ const timeOfDay = hour => {
+ if (hour < 12) return goodMorning;
+ if (hour < 17) return goodAfternoon;
+ return goodEvening;
+ };
+ const greetings = timeOfDay(currentHour);
+ const greetingsKey = random(Object.keys(greetings));
+ return {
+ language: greetingsKey,
+ greeting: greetings[greetingsKey],
+ };
+}
diff --git a/plugins/catalog/src/components/CatalogPage/utils/timeUtil.test.js b/plugins/catalog/src/components/CatalogPage/utils/timeUtil.test.js
new file mode 100644
index 0000000000..d18b4a5b20
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogPage/utils/timeUtil.test.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { isValidDateAndFormat, getTimeBasedGreeting } from './timeUtil';
+
+it('validates time format', () => {
+ const valid = isValidDateAndFormat(
+ '1970-01-01T00:00:00',
+ 'YYYY-MM-DD[T]HH:mm:ss',
+ );
+ const invalid = isValidDateAndFormat(
+ '1970/01/01T00:00:00',
+ 'YYYY-MM-DD[T]HH:mm:ss',
+ );
+ expect(valid).toBe(true);
+ expect(invalid).toBe(false);
+});
+
+it('has greeting and language', () => {
+ const greeting = getTimeBasedGreeting();
+ expect(greeting).toHaveProperty('greeting');
+ expect(greeting).toHaveProperty('language');
+});
+
+it('greets late at night', () => {
+ jest
+ .spyOn(global.Date, 'now')
+ .mockImplementationOnce(() => new Date('1970-01-01T23:00:00').valueOf());
+ const greeting = getTimeBasedGreeting();
+ expect(greeting.greeting).toBe('Get some rest');
+});
diff --git a/yarn.lock b/yarn.lock
index 2afeb2eb37..1d946711ae 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13137,7 +13137,7 @@ moment@2.24.0:
resolved "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
-moment@^2.25.3:
+moment@^2.25.3, moment@^2.26.0:
version "2.26.0"
resolved "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==