diff --git a/src/components/menu/account-view/saves-section.tsx b/src/components/menu/account-view/saves-section.tsx index 31f79bf..d8b2afb 100644 --- a/src/components/menu/account-view/saves-section.tsx +++ b/src/components/menu/account-view/saves-section.tsx @@ -3,7 +3,7 @@ import { RMSection, RMSectionBody, RMSectionHeader } from '@railmapgen/mantine-c import { logger } from '@railmapgen/rmg-runtime'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { MdDeleteOutline, MdOutlineSync, MdOutlineSyncAlt } from 'react-icons/md'; +import { MdDeleteOutline, MdOutlineShare, MdOutlineSync, MdOutlineSyncAlt } from 'react-icons/md'; import { useRootDispatch, useRootSelector } from '../../../redux'; import { fetchSaveList, logout, syncAfterLogin } from '../../../redux/account/account-slice'; import { addNotification } from '../../../redux/notification/notification-slice'; @@ -11,7 +11,9 @@ import { setLastChangedAtTimeStamp } from '../../../redux/rmp-save/rmp-save-slic import { apiFetch } from '../../../util/api'; import { API_ENDPOINT, APISaveList, SAVE_KEY } from '../../../util/constants'; import { getRMPSave, notifyRMPSaveChange, setRMPSave } from '../../../util/local-storage-save'; +import { getRandomCityName } from '../../../util/random-save-name'; import InlineEdit from '../../common/inline-edit'; +import ShareModal from './share-modal'; const MAXIMUM_FREE_SAVE = 1; const MAXIMUM_SAVE = 10; @@ -38,6 +40,8 @@ const SavesSection = () => { const [syncButtonIsLoading, setSyncButtonIsLoading] = React.useState(undefined as number | undefined); const [deleteButtonIsLoading, setDeleteButtonIsLoading] = React.useState(undefined as number | undefined); + const [shareModalOpened, setShareModalOpened] = React.useState(false); + const [shareSaveID, setShareSaveID] = React.useState(undefined); const showErrorToast = (msg: string) => dispatch( @@ -56,7 +60,7 @@ const SavesSection = () => { return; } const { data, hash } = save; - const index = getRandomCity(); + const index = getRandomCityName(); try { const rep = await apiFetch( API_ENDPOINT.SAVES, @@ -206,6 +210,11 @@ const SavesSection = () => { dispatch(fetchSaveList()); }; + const handleOpenShareModal = (saveID: number) => { + setShareSaveID(saveID); + setShareModalOpened(true); + }; + return ( @@ -224,7 +233,7 @@ const SavesSection = () => { handleEditSaveName(_.id, val)} - textInputWidth="207px" + textInputWidth="177px" /> { > {_.id === currentSaveId ? : } + handleOpenShareModal(_.id)} title={t('Share')}> + + { ))} + setShareModalOpened(false)} + shareSaveInfo={saveList?.find(s => s.id === shareSaveID)} + /> ); }; export default SavesSection; - -const CITIES_LIST = [ - 'Tokyo', - 'Paris', - 'London', - 'New York', - 'Shanghai', - 'Dubai', - 'Sydney', - 'Rio de Janeiro', - 'Cairo', - 'Moscow', - 'Rome', - 'Berlin', - 'Madrid', - 'Seoul', - 'Singapore', - 'Hong Kong', - 'Beijing', - 'Mexico City', - 'Buenos Aires', - 'Istanbul', - 'Lagos', - 'Jakarta', - 'Osaka', - 'Manila', - 'Chongqing', - 'Karachi', - 'Dhaka', - 'Lima', - 'Bangkok', - 'Lahore', - 'Ho Chi Minh City', - 'Shenzhen', - 'Tianjin', - 'Taipei', - 'Bogota', - 'Baghdad', - 'Riyadh', - 'Santiago', - 'Guangzhou', - 'Yangon', - 'Johannesburg', - 'Toronto', - 'Montreal', - 'Vancouver', - 'Sao Paulo', - 'Kuala Lumpur', - 'Athens', - 'Lisbon', - 'Dublin', - 'Amsterdam', - 'Vienna', - 'Prague', - 'Budapest', - 'Warsaw', - 'Stockholm', - 'Oslo', - 'Helsinki', - 'Copenhagen', - 'Brussels', - 'Geneva', - 'Zurich', - 'Barcelona', - 'Milan', - 'Florence', - 'Venice', - 'Munich', - 'Hamburg', - 'Frankfurt', - 'Kyoto', - 'Nairobi', - 'Cape Town', - 'Casablanca', - 'Marrakesh', - 'Accra', - 'Addis Ababa', - 'Alexandria', - 'Ankara', - 'Jerusalem', - 'Tel Aviv', - 'Beirut', - 'Abu Dhabi', - 'Doha', - 'Kuwait City', - 'Muscat', - 'Amman', - 'Baku', - 'Tbilisi', - 'Yerevan', - 'Almaty', - 'Tashkent', - 'Kabul', - 'Tehran', - 'Damascus', - "Sana'a", - 'Aden', - 'Manama', - 'Jeddah', - 'Mecca', - 'Medina', - 'Salalah', - 'Nicosia', - 'Thessaloniki', - 'Sofia', - 'Bucharest', - 'Belgrade', - 'Zagreb', - 'Sarajevo', - 'Skopje', - 'Tirana', - 'Pristina', - 'Podgorica', - 'Ljubljana', - 'Bratislava', - 'Krakow', - 'Gdansk', - 'Wroclaw', - 'Poznan', - 'Vilnius', - 'Riga', - 'Tallinn', - 'Reykjavik', - 'Manchester', - 'Birmingham', - 'Glasgow', - 'Edinburgh', - 'Cardiff', - 'Belfast', - 'Marseille', - 'Lyon', - 'Toulouse', - 'Nice', - 'Nantes', - 'Strasbourg', - 'Bordeaux', - 'Lille', - 'Cologne', - 'Stuttgart', - 'Dusseldorf', - 'Dortmund', - 'Essen', - 'Leipzig', - 'Dresden', - 'Hanover', - 'Nuremberg', - 'Bremen', - 'Rotterdam', - 'The Hague', - 'Utrecht', - 'Eindhoven', - 'Porto', - 'Valencia', - 'Seville', - 'Zaragoza', - 'Malaga', - 'Murcia', - 'Palma de Mallorca', - 'Las Palmas de Gran Canaria', - 'Antwerp', - 'Ghent', - 'Charleroi', - 'Liège', - 'Bern', - 'Basel', - 'Lausanne', - 'Graz', - 'Linz', - 'Salzburg', - 'Innsbruck', - 'Naples', - 'Turin', - 'Palermo', - 'Genoa', - 'Bologna', - 'Bari', - 'Catania', - 'Verona', - 'Melbourne', - 'Brisbane', - 'Perth', - 'Adelaide', - 'Gold Coast', - 'Canberra', - 'Auckland', - 'Wellington', - 'Christchurch', - 'Dunedin', - 'Suva', - 'Port Moresby', - 'Noumea', - 'Papeete', - 'Honolulu', - 'Quezon City', - 'Davao City', - 'Cebu City', - 'Chiang Mai', - 'Phuket', - 'George Town', - 'Ipoh', - 'Johor Bahru', - 'Surabaya', - 'Bandung', - 'Medan', - 'Semarang', - 'Santiago', - 'Lima', - 'Bogota', - 'Caracas', - 'Quito', - 'La Paz', - 'Sucre', - 'Buenos Aires', - 'Cordoba', - 'Rosario', - 'Mendoza', - 'Valparaiso', - 'Concepcion', - 'Arequipa', - 'Trujillo', - 'Chiclayo', - 'Piura', - 'Medellin', - 'Cali', - 'Barranquilla', - 'Cartagena', - 'Maracaibo', - 'Valencia', - 'Barquisimeto', - 'Ciudad Guayana', - 'Guayaquil', - 'Cuenca', - 'Santo Domingo', - 'Santa Cruz de la Sierra', - 'Cochabamba', - 'Guadalajara', - 'Monterrey', - 'Puebla', - 'Tijuana', - 'Leon', - 'Juarez', - 'Ecatepec', - 'Zapopan', - 'Nezahualcoyotl', - 'Chihuahua', - 'Naucalpan', - 'Merida', - 'Aguascalientes', - 'San Luis Potosi', - 'Hermosillo', - 'Saltillo', - 'Mexicali', - 'Culiacan', - 'Acapulco', - 'Guadalupe', - 'Cancun', - 'Torreon', - 'El Paso', - 'Tucson', - 'Phoenix', - 'Los Angeles', - 'San Diego', - 'San Jose', - 'San Francisco', - 'Seattle', - 'Portland', - 'Las Vegas', - 'Denver', - 'Chicago', - 'Houston', - 'Dallas', - 'San Antonio', - 'Austin', - 'Philadelphia', - 'Washington D.C.', - 'Boston', - 'Miami', - 'Atlanta', - 'Orlando', - 'Tampa', - 'Detroit', - 'Cleveland', - 'Minneapolis', - 'St. Louis', - 'Kansas City', - 'New Orleans', - 'Charlotte', - 'Nashville', - 'Indianapolis', - 'Columbus', - 'Cincinnati', - 'Pittsburgh', - 'Milwaukee', - 'Calgary', - 'Edmonton', - 'Winnipeg', - 'Ottawa', - 'Quebec City', - 'Halifax', - 'Chengdu', - 'Wuhan', - 'Hangzhou', - 'Suzhou', - 'Xian', - 'Nanjing', - 'Zhengzhou', - 'Jinan', - 'Qingdao', - 'Hefei', - 'Fuzhou', - 'Xiamen', - 'Ningbo', - 'Wuxi', - 'Kunming', - 'Changsha', - 'Nanchang', - 'Shijiazhuang', - 'Harbin', - 'Changchun', - 'Taiyuan', - 'Urumqi', - 'Lanzhou', - 'Yinchuan', - 'Xining', - 'Guiyang', - 'Lhasa', - 'Hohhot', - 'Nanning', - 'Haikou', - 'Sanya', - 'Zhuhai', - 'Foshan', - 'Dongguan', - 'Zhongshan', - 'Shantou', - 'Wenzhou', - 'Jiaxing', - 'Shaoxing', - 'Taizhou', - 'Jinhua', - 'Quanzhou', - 'Putian', - 'Zhangzhou', - 'Longyan', - 'Sanming', - 'Nanping', - 'Yingtan', - 'Shangrao', - 'Jiujiang', - 'Jingdezhen', - 'Pingxiang', - 'Xinyu', - 'Yichun', - 'Ganzhou', - 'Qingyuan', - 'Shaoguan', - 'Heyuan', - 'Meizhou', - 'Shanwei', - 'Yangjiang', - 'Zhanjiang', - 'Maoming', - 'Zhaoqing', - 'Yunfu', - 'Laibin', - 'Chongzuo', - 'Hechi', - 'Baise', - 'Wuzhou', - 'Fangchenggang', - 'Qinzhou', - 'Beihai', - 'Guigang', - 'Yulin', - 'Cenxi', - 'Dongxing', - 'Anshan', - 'Fushun', - 'Jilin City', - 'Qiqihar', - 'Daqing', - 'Mudanjiang', - 'Jixi', -]; - -const getRandomCity = () => { - const randomIndex = Math.floor(Math.random() * CITIES_LIST.length); - return CITIES_LIST[randomIndex]; -}; diff --git a/src/components/menu/account-view/share-modal.tsx b/src/components/menu/account-view/share-modal.tsx new file mode 100644 index 0000000..01e67c0 --- /dev/null +++ b/src/components/menu/account-view/share-modal.tsx @@ -0,0 +1,222 @@ +import { + ActionIcon, + Alert, + Button, + CopyButton, + Group, + Modal, + Space, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { MdCheck, MdContentCopy, MdWarning } from 'react-icons/md'; +import { useRootDispatch, useRootSelector } from '../../../redux'; +import { fetchSaveList, logout } from '../../../redux/account/account-slice'; +import { addNotification } from '../../../redux/notification/notification-slice'; +import { apiFetch } from '../../../util/api'; +import { API_ENDPOINT, APISaveInfo } from '../../../util/constants'; + +const defaultSaveInfo: APISaveInfo = { + index: '0', + id: 0, + hash: '', + lastUpdateAt: '', +}; + +interface ShareModalProps { + opened: boolean; + onClose: () => void; + shareSaveInfo?: APISaveInfo; +} + +const ShareModal: React.FC = ({ opened, onClose, shareSaveInfo }) => { + const { t } = useTranslation(); + const [copied, setCopied] = useState(false); + const dispatch = useRootDispatch(); + const { id: saveID, index, share } = shareSaveInfo ?? defaultSaveInfo; + + const { token, activeSubscriptions } = useRootSelector(state => state.account); + + const handleCopy = () => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); // Reset copied status after 2 seconds + }; + + const handleCreateShare = async () => { + try { + const rep = await apiFetch(API_ENDPOINT.SHARE + `/${saveID}`, { method: 'POST' }, token); + if (rep.status === 401) { + dispatch( + addNotification({ + type: 'error', + title: t('Session expired.'), + message: t('Please log in again.'), + duration: 5000, + }) + ); + dispatch(logout()); + return; + } + if (rep.status !== 201) { + dispatch( + addNotification({ + type: 'error', + title: t('Error'), + message: await rep.text(), + duration: 5000, + }) + ); + return; + } + dispatch(fetchSaveList()); + dispatch( + addNotification({ + type: 'success', + title: t('Success'), + message: t('Share link created successfully.'), + duration: 3000, + }) + ); + } catch (e) { + dispatch( + addNotification({ + type: 'error', + title: t('Error'), + message: (e as Error).message, + duration: 5000, + }) + ); + } + }; + + const handleDeleteShare = async () => { + try { + const rep = await apiFetch(API_ENDPOINT.SHARE + `/${saveID}`, { method: 'DELETE' }, token); + if (rep.status === 401) { + dispatch( + addNotification({ + type: 'error', + title: t('Session expired.'), + message: t('Please log in again.'), + duration: 5000, + }) + ); + dispatch(logout()); + return; + } + if (rep.status !== 204) { + dispatch( + addNotification({ + type: 'error', + title: t('Error'), + message: await rep.text(), + duration: 5000, + }) + ); + return; + } + dispatch(fetchSaveList()); + dispatch( + addNotification({ + type: 'success', + title: t('Success'), + message: t('Share link deleted successfully.'), + duration: 3000, + }) + ); + } catch (e) { + dispatch( + addNotification({ + type: 'error', + title: t('Error'), + message: (e as Error).message, + duration: 5000, + }) + ); + } + }; + + return ( + {`${t('Share work {{index}}', { index })}`}} + size="lg" + > + {t('Share this link with others to allow them to view this work.')} + {share ? ( + + + {({ copied: btnCopied, copy }) => ( + + { + copy(); + handleCopy(); + }} + > + {btnCopied || copied ? : } + + + )} + + } + /> + + + {t('This link is valid until:')}{' '} + {new Date( + share.validUntil ?? activeSubscriptions.RMP_EXPORT ?? Date.now().toLocaleString() + ).toLocaleString()} + + + + + ) : ( + + + + )} + + + } + title={t( + 'Please be aware that sharing this link does not create a separate copy of your work. Instead, everyone who opens the link will always see the most up-to-date version. So, if you make changes after sharing the link, those changes will be visible to everyone who has the link.' + )} + /> + + } + title={t( + 'Link will be valid as long as you have an active subscription. Otherwise, the link will be invalid after 1 day.' + )} + /> + + + + + + ); +}; + +export default ShareModal; diff --git a/src/i18n/translations/ja.json b/src/i18n/translations/ja.json index 7a24f61..90f2dd3 100644 --- a/src/i18n/translations/ja.json +++ b/src/i18n/translations/ja.json @@ -23,6 +23,12 @@ "text4": "ご不明な場合、または当社の開発者の指示なしに操作している場合は、このダイアログを閉じてください。" }, + "Session expired.": "セッションの有効期限が切れました。", + "Please log in again.": "再度ログインしてください。", + "Error": "失敗", + "Success": "成功", + "Close": "閉じる", + "About": "概要", "Allow cookies to help improve our website": "Cookie を許可して網頁の改善にご協力ください", "Appearance": "外観", @@ -164,5 +170,17 @@ "Replace cloud with local": "雲を本地で置き換える", "Download Local save": "本地保存をダウンロード", "Replace local with cloud": "本地を雲で置き換える", - "Download Cloud save": "雲保存をダウンロード" + "Download Cloud save": "雲保存をダウンロード", + + "Share work {{index}}": "作品 {{index}} を共有", + "Share this link with others to allow them to view this work.": "このリンクを他の方と共有し、作品の閲覧を許可してください。", + "Copied": "コピーしました", + "Copy": "コピー", + "Share link created successfully.": "共有リンクが正常に作成されました。", + "Share link deleted successfully.": "共有リンクが正常に削除されました。", + "This link is valid until:": "このリンクの有効期限:", + "Delete Share": "共有を削除", + "Create Share Link": "共有リンクを作成", + "Please be aware that sharing this link does not create a separate copy of your work. Instead, everyone who opens the link will always see the most up-to-date version. So, if you make changes after sharing the link, those changes will be visible to everyone who has the link.": "ご注意:このリンクを共有しても、作品の個別のコピーは作成されません。代わりに、リンクを開いた人は常に最新バージョンを閲覧することになります。そのため、リンク共有後に変更を加えた場合、その変更はリンクをお持ちのすべての方に表示されます。", + "Link will be valid as long as you have an active subscription. Otherwise, the link will be invalid after 1 day.": "有効なご購読がある限り、リンクは有効です。それ以外の場合、リンクは 1 日後に無効になります。" } diff --git a/src/i18n/translations/ko.json b/src/i18n/translations/ko.json index 18e903d..e3a5c7c 100644 --- a/src/i18n/translations/ko.json +++ b/src/i18n/translations/ko.json @@ -23,6 +23,12 @@ "text4": "확실하지 않거나 당사 개발자의 안내 없이 작업하는 경우 이 대화 상자를 닫으십시오." }, + "Session expired.": "세션이 만료되었습니다.", + "Please log in again.": "다시 로그인해주세요.", + "Error": "오류", + "Success": "성공", + "Close": "닫기", + "About": "정보", "Allow cookies to help improve our website": "쿠키를 허용하여 웹사이트 개선에 도움을 주세요", "Appearance": "모양", @@ -158,11 +164,23 @@ "CDKey could be purchased in the following sites:": "CD키는 다음 사이트에서 구매할 수 있습니다:", "Enter your CDKey here": "여기에 CD키를 입력하세요", - "Oops! It seems there's a conflict": "おっと!競合が発生したようです (おっと!きょうごうが はっせいした ようです)", + "Oops! It seems there's a conflict": "이런! 충돌이 발생한 것 같습니다.", "The local save is newer than the cloud one. Which one would you like to keep?": "로컬 저장 파일이 클라우드 저장 파일보다 최신입니다. 어떤 버전을 유지하시겠습니까?", "Local save": "로컬 저장 파일", "Replace cloud with local": "클라우드를 로컬 저장 파일로 교체", "Download Local save": "로컬 저장 파일 다운로드", "Replace local with cloud": "로컬을 클라우드 저장 파일로 교체", - "Download Cloud save": "클라우드 저장 파일 다운로드" + "Download Cloud save": "클라우드 저장 파일 다운로드", + + "Share work {{index}}": "작업 공유 {{index}}", + "Share this link with others to allow them to view this work.": "이 링크를 다른 사람과 공유하여 작업을 볼 수 있도록 하세요.", + "Copied": "복사됨", + "Copy": "복사", + "Share link created successfully.": "공유 링크가 성공적으로 만들어졌습니다.", + "Share link deleted successfully.": "공유 링크가 성공적으로 삭제되었습니다.", + "This link is valid until:": "이 링크는 다음 날짜까지 유효합니다:", + "Delete Share": "공유 삭제", + "Create Share Link": "공유 링크 만들기", + "Please be aware that sharing this link does not create a separate copy of your work. Instead, everyone who opens the link will always see the most up-to-date version. So, if you make changes after sharing the link, those changes will be visible to everyone who has the link.": "이 링크를 공유해도 작업의 별도 사본이 만들어지지 않습니다. 대신 링크를 여는 모든 사람은 항상 최신 버전을 보게 됩니다. 따라서 링크를 공유한 후 변경하면 해당 변경 사항이 링크를 가진 모든 사람에게 표시됩니다.", + "Link will be valid as long as you have an active subscription. Otherwise, the link will be invalid after 1 day.": "활성 구독이 있는 한 링크는 유효합니다. 그렇지 않으면 링크는 1일 후에 만료됩니다." } diff --git a/src/i18n/translations/zh-Hans.json b/src/i18n/translations/zh-Hans.json index a468bea..e4a49b2 100644 --- a/src/i18n/translations/zh-Hans.json +++ b/src/i18n/translations/zh-Hans.json @@ -23,6 +23,12 @@ "text4": "如果您不确定,或者不是在我们的开发者的指引下操作,请关闭这个对话框。" }, + "Session expired.": "会话已过期。", + "Please log in again.": "请重新登录。", + "Error": "错误", + "Success": "成功", + "Close": "关闭", + "About": "关于", "Allow cookies to help improve our website": "允许用 Cookies 帮助改进本网站", "Appearance": "外观", @@ -164,5 +170,17 @@ "Replace cloud with local": "用本地存档替换云端", "Download Local save": "下载本地存档", "Replace local with cloud": "用云端存档替换本地", - "Download Cloud save": "下载云端存档" + "Download Cloud save": "下载云端存档", + + "Share work {{index}}": "分享作品 {{index}}", + "Share this link with others to allow them to view this work.": "分享此链接给他人以查看此作品。", + "Copied": "已复制", + "Copy": "复制", + "Share link created successfully.": "分享链接创建成功。", + "Share link deleted successfully.": "分享链接删除成功。", + "This link is valid until:": "此链接有效期至:", + "Delete Share": "删除分享", + "Create Share Link": "创建分享链接", + "Please be aware that sharing this link does not create a separate copy of your work. Instead, everyone who opens the link will always see the most up-to-date version. So, if you make changes after sharing the link, those changes will be visible to everyone who has the link.": "请注意,分享此链接不会创建您作品的副本。相反,每个打开链接的人都将始终看到最新版本。因此,如果您在分享链接后进行更改,这些更改将对拥有该链接的每个人可见。", + "Link will be valid as long as you have an active subscription. Otherwise, the link will be invalid after 1 day.": "只要您拥有有效订阅,链接便会一直有效。否则,链接将在创建后 1 天内失效。" } diff --git a/src/i18n/translations/zh-Hant.json b/src/i18n/translations/zh-Hant.json index ca5df50..fd2cd34 100644 --- a/src/i18n/translations/zh-Hant.json +++ b/src/i18n/translations/zh-Hant.json @@ -23,6 +23,12 @@ "text4": "若你未能確定,或不是在我們的開發人員的指引下操作,請關閉這個視窗。" }, + "Session expired.": "工作階段已過期。", + "Please log in again.": "請重新登入。", + "Error": "錯誤", + "Success": "成功", + "Close": "關閉", + "About": "關於", "Allow cookies to help improve our website": "容許以 Cookies 幫助改進本網站", "Appearance": "外觀", @@ -164,5 +170,17 @@ "Replace cloud with local": "用本地存檔替換雲端", "Download Local save": "下載本地存檔", "Replace local with cloud": "用雲端存檔替換本地", - "Download Cloud save": "下載雲端存檔" + "Download Cloud save": "下載雲端存檔", + + "Share work {{index}}": "分享作品 {{index}}", + "Share this link with others to allow them to view this work.": "分享此連結給他人以檢視此作品。", + "Copied": "已複製", + "Copy": "複製", + "Share link created successfully.": "分享連結建立成功。", + "Share link deleted successfully.": "分享連結刪除成功。", + "This link is valid until:": "此連結有效期至:", + "Delete Share": "刪除分享", + "Create Share Link": "建立分享連結", + "Please be aware that sharing this link does not create a separate copy of your work. Instead, everyone who opens the link will always see the most up-to-date version. So, if you make changes after sharing the link, those changes will be visible to everyone who has the link.": "請注意,分享此連結不會建立您作品的副本。相反,每個開啟連結的人都將始終看到最新版本。因此,如果您在分享連結後進行變更,這些變更將對擁有該連結的每個人可見。", + "Link will be valid as long as you have an active subscription. Otherwise, the link will be invalid after 1 day.": "只要您擁有有效訂閱,連結便會一直有效。否則,連結將在 1 天後失效。" } diff --git a/src/util/constants.ts b/src/util/constants.ts index b85533f..66271cb 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -51,21 +51,28 @@ export enum API_ENDPOINT { SUBSCRIPTION = '/subscription', SUBSCRIPTION_REDEEM = '/subscription/redeem', SAVES = '/saves', + SHARE = '/share', } export const API_URL = 'https://railmapgen.org/v1'; // export const API_URL = 'http://localhost:3000/v1'; +type Timestamp = `${number}-${number}-${number}T${number}:${number}:${number}Z`; + export interface APILoginResponse { user: { id: number; name: string }; - tokens: { access: { token: string; expires: string }; refresh: { token: string; expires: string } }; + tokens: { access: { token: string; expires: Timestamp }; refresh: { token: string; expires: Timestamp } }; } export interface APISaveInfo { index: string; id: number; hash: string; - lastUpdateAt: string; + lastUpdateAt: Timestamp; + share?: { + s: string; + validUntil: Timestamp | null; + }; } export interface APISaveList { diff --git a/src/util/random-save-name.ts b/src/util/random-save-name.ts new file mode 100644 index 0000000..1ce8828 --- /dev/null +++ b/src/util/random-save-name.ts @@ -0,0 +1,387 @@ +const CITIES_LIST = [ + 'Tokyo', + 'Paris', + 'London', + 'New York', + 'Shanghai', + 'Dubai', + 'Sydney', + 'Rio de Janeiro', + 'Cairo', + 'Moscow', + 'Rome', + 'Berlin', + 'Madrid', + 'Seoul', + 'Singapore', + 'Hong Kong', + 'Beijing', + 'Mexico City', + 'Buenos Aires', + 'Istanbul', + 'Lagos', + 'Jakarta', + 'Osaka', + 'Manila', + 'Chongqing', + 'Karachi', + 'Dhaka', + 'Lima', + 'Bangkok', + 'Lahore', + 'Ho Chi Minh City', + 'Shenzhen', + 'Tianjin', + 'Taipei', + 'Bogota', + 'Baghdad', + 'Riyadh', + 'Santiago', + 'Guangzhou', + 'Yangon', + 'Johannesburg', + 'Toronto', + 'Montreal', + 'Vancouver', + 'Sao Paulo', + 'Kuala Lumpur', + 'Athens', + 'Lisbon', + 'Dublin', + 'Amsterdam', + 'Vienna', + 'Prague', + 'Budapest', + 'Warsaw', + 'Stockholm', + 'Oslo', + 'Helsinki', + 'Copenhagen', + 'Brussels', + 'Geneva', + 'Zurich', + 'Barcelona', + 'Milan', + 'Florence', + 'Venice', + 'Munich', + 'Hamburg', + 'Frankfurt', + 'Kyoto', + 'Nairobi', + 'Cape Town', + 'Casablanca', + 'Marrakesh', + 'Accra', + 'Addis Ababa', + 'Alexandria', + 'Ankara', + 'Jerusalem', + 'Tel Aviv', + 'Beirut', + 'Abu Dhabi', + 'Doha', + 'Kuwait City', + 'Muscat', + 'Amman', + 'Baku', + 'Tbilisi', + 'Yerevan', + 'Almaty', + 'Tashkent', + 'Kabul', + 'Tehran', + 'Damascus', + "Sana'a", + 'Aden', + 'Manama', + 'Jeddah', + 'Mecca', + 'Medina', + 'Salalah', + 'Nicosia', + 'Thessaloniki', + 'Sofia', + 'Bucharest', + 'Belgrade', + 'Zagreb', + 'Sarajevo', + 'Skopje', + 'Tirana', + 'Pristina', + 'Podgorica', + 'Ljubljana', + 'Bratislava', + 'Krakow', + 'Gdansk', + 'Wroclaw', + 'Poznan', + 'Vilnius', + 'Riga', + 'Tallinn', + 'Reykjavik', + 'Manchester', + 'Birmingham', + 'Glasgow', + 'Edinburgh', + 'Cardiff', + 'Belfast', + 'Marseille', + 'Lyon', + 'Toulouse', + 'Nice', + 'Nantes', + 'Strasbourg', + 'Bordeaux', + 'Lille', + 'Cologne', + 'Stuttgart', + 'Dusseldorf', + 'Dortmund', + 'Essen', + 'Leipzig', + 'Dresden', + 'Hanover', + 'Nuremberg', + 'Bremen', + 'Rotterdam', + 'The Hague', + 'Utrecht', + 'Eindhoven', + 'Porto', + 'Valencia', + 'Seville', + 'Zaragoza', + 'Malaga', + 'Murcia', + 'Palma de Mallorca', + 'Las Palmas de Gran Canaria', + 'Antwerp', + 'Ghent', + 'Charleroi', + 'Liège', + 'Bern', + 'Basel', + 'Lausanne', + 'Graz', + 'Linz', + 'Salzburg', + 'Innsbruck', + 'Naples', + 'Turin', + 'Palermo', + 'Genoa', + 'Bologna', + 'Bari', + 'Catania', + 'Verona', + 'Melbourne', + 'Brisbane', + 'Perth', + 'Adelaide', + 'Gold Coast', + 'Canberra', + 'Auckland', + 'Wellington', + 'Christchurch', + 'Dunedin', + 'Suva', + 'Port Moresby', + 'Noumea', + 'Papeete', + 'Honolulu', + 'Quezon City', + 'Davao City', + 'Cebu City', + 'Chiang Mai', + 'Phuket', + 'George Town', + 'Ipoh', + 'Johor Bahru', + 'Surabaya', + 'Bandung', + 'Medan', + 'Semarang', + 'Lima', + 'Bogota', + 'Caracas', + 'Quito', + 'La Paz', + 'Sucre', + 'Buenos Aires', + 'Cordoba', + 'Rosario', + 'Mendoza', + 'Valparaiso', + 'Concepcion', + 'Arequipa', + 'Trujillo', + 'Chiclayo', + 'Piura', + 'Medellin', + 'Cali', + 'Barranquilla', + 'Cartagena', + 'Maracaibo', + 'Valencia', + 'Barquisimeto', + 'Ciudad Guayana', + 'Guayaquil', + 'Cuenca', + 'Santo Domingo', + 'Santa Cruz de la Sierra', + 'Cochabamba', + 'Guadalajara', + 'Monterrey', + 'Puebla', + 'Tijuana', + 'Leon', + 'Juarez', + 'Ecatepec', + 'Zapopan', + 'Nezahualcoyotl', + 'Chihuahua', + 'Naucalpan', + 'Merida', + 'Aguascalientes', + 'San Luis Potosi', + 'Hermosillo', + 'Saltillo', + 'Mexicali', + 'Culiacan', + 'Acapulco', + 'Guadalupe', + 'Cancun', + 'Torreon', + 'El Paso', + 'Tucson', + 'Phoenix', + 'Los Angeles', + 'San Diego', + 'San Jose', + 'San Francisco', + 'Seattle', + 'Portland', + 'Las Vegas', + 'Denver', + 'Chicago', + 'Houston', + 'Dallas', + 'San Antonio', + 'Austin', + 'Philadelphia', + 'Washington D.C.', + 'Boston', + 'Miami', + 'Atlanta', + 'Orlando', + 'Tampa', + 'Detroit', + 'Cleveland', + 'Minneapolis', + 'St. Louis', + 'Kansas City', + 'New Orleans', + 'Charlotte', + 'Nashville', + 'Indianapolis', + 'Columbus', + 'Cincinnati', + 'Pittsburgh', + 'Milwaukee', + 'Calgary', + 'Edmonton', + 'Winnipeg', + 'Ottawa', + 'Quebec City', + 'Halifax', + 'Chengdu', + 'Wuhan', + 'Hangzhou', + 'Suzhou', + 'Xian', + 'Nanjing', + 'Zhengzhou', + 'Jinan', + 'Qingdao', + 'Hefei', + 'Fuzhou', + 'Xiamen', + 'Ningbo', + 'Wuxi', + 'Kunming', + 'Changsha', + 'Nanchang', + 'Shijiazhuang', + 'Harbin', + 'Changchun', + 'Taiyuan', + 'Urumqi', + 'Lanzhou', + 'Yinchuan', + 'Xining', + 'Guiyang', + 'Lhasa', + 'Hohhot', + 'Nanning', + 'Haikou', + 'Sanya', + 'Zhuhai', + 'Foshan', + 'Dongguan', + 'Zhongshan', + 'Shantou', + 'Wenzhou', + 'Jiaxing', + 'Shaoxing', + 'Taizhou', + 'Jinhua', + 'Quanzhou', + 'Putian', + 'Zhangzhou', + 'Longyan', + 'Sanming', + 'Nanping', + 'Yingtan', + 'Shangrao', + 'Jiujiang', + 'Jingdezhen', + 'Pingxiang', + 'Xinyu', + 'Yichun', + 'Ganzhou', + 'Qingyuan', + 'Shaoguan', + 'Heyuan', + 'Meizhou', + 'Shanwei', + 'Yangjiang', + 'Zhanjiang', + 'Maoming', + 'Zhaoqing', + 'Yunfu', + 'Laibin', + 'Chongzuo', + 'Hechi', + 'Baise', + 'Wuzhou', + 'Fangchenggang', + 'Qinzhou', + 'Beihai', + 'Guigang', + 'Yulin', + 'Cenxi', + 'Dongxing', + 'Anshan', + 'Fushun', + 'Jilin City', + 'Qiqihar', + 'Daqing', + 'Mudanjiang', + 'Jixi', +]; + +export const getRandomCityName = () => { + const randomIndex = Math.floor(Math.random() * CITIES_LIST.length); + return CITIES_LIST[randomIndex]; +};