diff --git a/web/.gitignore b/web/.gitignore index aea676d7e..5d4a959bc 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -2,4 +2,5 @@ node_modules dist coverage .env -yarn-error* \ No newline at end of file +yarn-error* +.DS_Store \ No newline at end of file diff --git a/web/config/moduleFederation.config.js b/web/config/moduleFederation.config.js index d9f0664b6..fc66a7cea 100644 --- a/web/config/moduleFederation.config.js +++ b/web/config/moduleFederation.config.js @@ -35,7 +35,6 @@ module.exports = { './App': './src/App.tsx', './Welcome': './src/views/Welcome/Welcome.tsx', './Repos': './src/views/Repos/Repos.tsx', - './NewRepo': './src/views/NewRepo/NewRepo.tsx', './RepoFiles': './src/views/RepoFiles/RepoFiles.tsx', './RepoFileDetails': './src/views/RepoFileDetails/RepoFileDetails.tsx', './RepoCommits': './src/views/RepoCommits/RepoCommits.tsx', diff --git a/web/config/webpack.dev.js b/web/config/webpack.dev.js index 7c4e9e5a0..61cda5be7 100644 --- a/web/config/webpack.dev.js +++ b/web/config/webpack.dev.js @@ -34,10 +34,10 @@ const devConfig = { hot: true, host: 'localhost', historyApiFallback: true, - port: 3000, + port: 3001, proxy: { '/api': { - target: targetLocalHost ? 'http://localhost:3001' : baseUrl, + target: targetLocalHost ? 'http://localhost:3000' : baseUrl, logLevel: 'debug', secure: false, changeOrigin: true diff --git a/web/package.json b/web/package.json index 6b872ee8d..25fa7a9c0 100644 --- a/web/package.json +++ b/web/package.json @@ -88,7 +88,6 @@ "react-popper": "^2.2.3", "react-qr-code": "^1.1.1", "react-router-dom": "^5.2.0", - "react-shadow": "^19.0.3", "react-split-pane": "^0.1.92", "react-table": "^7.1.0", "react-table-sticky": "^1.1.3", diff --git a/web/src/App.tsx b/web/src/App.tsx index 7fe7fc7e9..a78b6d3a1 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -29,9 +29,10 @@ const App: React.FC = React.memo(function App({ }: AppProps) { const [strings, setStrings] = useState() const [token, setToken] = useAPIToken(apiToken) - const getRequestOptions = useCallback((): Partial => { - return buildResfulReactRequestOptions(hooks.useGetToken?.() || apiToken || 'default') - }, []) // eslint-disable-line react-hooks/exhaustive-deps + const getRequestOptions = useCallback( + (): Partial => buildResfulReactRequestOptions(hooks.useGetToken?.() || apiToken || token), + [apiToken, token, hooks] + ) // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { languageLoader(lang).then(setStrings) diff --git a/web/src/RouteDefinitions.ts b/web/src/RouteDefinitions.ts index 4fb74eefd..1805de80a 100644 --- a/web/src/RouteDefinitions.ts +++ b/web/src/RouteDefinitions.ts @@ -9,6 +9,8 @@ interface PathProps { commitId?: string } +interface QueryProps {} + export const pathProps: Readonly> = { accountId: ':accountId', orgIdentifier: ':orgIdentifier', @@ -37,10 +39,6 @@ export default { ({ orgIdentifier, projectIdentifier }: PathProps) => `/scm/orgs/${orgIdentifier}/projects/${projectIdentifier}/repos` ), - toSCMNewRepo: withAccountId( - ({ orgIdentifier, projectIdentifier }: PathProps) => - `/scm/orgs/${orgIdentifier}/projects/${projectIdentifier}/repos/new` - ), toSCMRepoSettings: withAccountId( ({ orgIdentifier, projectIdentifier, repoName }: RequireField) => `/scm/orgs/${orgIdentifier}/projects/${projectIdentifier}/repos/${repoName}/settings` diff --git a/web/src/RouteDestinations.tsx b/web/src/RouteDestinations.tsx index e9ec34695..4c94587b6 100644 --- a/web/src/RouteDestinations.tsx +++ b/web/src/RouteDestinations.tsx @@ -4,7 +4,6 @@ import { SignIn } from 'pages/SignIn/SignIn' import { SignUp } from 'pages/SignUp/SignUp' import routes, { pathProps } from 'RouteDefinitions' import Welcome from 'views/Welcome/Welcome' -import NewRepo from 'views/NewRepo/NewRepo' import Repos from 'views/Repos/Repos' import RepoSettings from 'views/RepoSettings/RepoSettings' import RepoFiles from 'views/RepoFiles/RepoFiles' @@ -27,9 +26,6 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations - - - diff --git a/web/src/bootstrap.tsx b/web/src/bootstrap.tsx index 20d476700..29a6a6125 100644 --- a/web/src/bootstrap.tsx +++ b/web/src/bootstrap.tsx @@ -9,6 +9,6 @@ import './bootstrap.scss' window.STRIP_SCM_PREFIX = true ReactDOM.render( - , + , document.getElementById('react-root') ) diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 74f50adf6..54c47865d 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -3,17 +3,29 @@ * Use the command `yarn strings` to regenerate this file. */ export interface StringsMap { + addGitIgnore: string + addLicense: string + addReadMe: string cancel: string commits: string content: string + create: string + createRepo: string + description: string email: string + enterDescription: string + enterRepoName: string existingAccount: string + failedToCreateRepo: string files: string history: string name: string newFile: string newFolder: string + newRepo: string noAccount: string + none: string + ok: string pageNotFound: string password: string private: string @@ -21,11 +33,17 @@ export interface StringsMap { pullRequests: string 'repos.activities': string 'repos.data': string + 'repos.enterBranchName': string 'repos.lastChange': string 'repos.name': string + 'repos.noDataMessage': string + 'repos.noDataTitle': string 'repos.updated': string repositories: string + search: string settings: string signIn: string signUp: string + 'validation.gitBranchNameInvalid': string + 'validation.namePatternIsNotValid': string } diff --git a/web/src/hooks/useAPIToken.ts b/web/src/hooks/useAPIToken.ts index 3f19bb2d1..8af9e20f5 100644 --- a/web/src/hooks/useAPIToken.ts +++ b/web/src/hooks/useAPIToken.ts @@ -1,6 +1,6 @@ import { useLocalStorage } from 'hooks/useLocalStorage' -const API_TOKEN_KEY = 'HARNESS_STANDALONE_APP_API_TOKEN' +const API_TOKEN_KEY = 'HARNESS_SCM_STANDALONE_APP_API_TOKEN' /** * Get and Set API token to use in Restful React calls. diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index f25ffcad5..08c3f53cc 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -9,6 +9,8 @@ public: Public private: Private cancel: Cancel name: Name +search: Search +description: Description repositories: Repositories files: Files commits: Commits @@ -18,9 +20,26 @@ newFile: New File newFolder: New Folder content: Content history: History +newRepo: New Repository +createRepo: Create Repository +enterRepoName: Enter Repository Name +enterDescription: Enter a description (optional) +addLicense: Add License +none: None +create: Create +ok: OK +addGitIgnore: Add a .gitignore +addReadMe: Add a README file +failedToCreateRepo: Failed to create Repository. Please try again. repos: name: Repo Name data: Repo Data activities: Monthly Activities updated: Updated Date lastChange: Last Change + noDataTitle: No Repository Found + noDataMessage: Create a new repository by clicking the button below. + enterBranchName: Enter a branch name +validation: + namePatternIsNotValid: 'Name can only contain alphanumerics, . _ and -' + gitBranchNameInvalid: Branch name is invalid. diff --git a/web/src/types/Repository.ts b/web/src/types/Repository.ts new file mode 100644 index 000000000..08c4c0cc3 --- /dev/null +++ b/web/src/types/Repository.ts @@ -0,0 +1,17 @@ +export interface Repository { + id: number + spaceId: number + pathName: string + path: string + name: string + description?: string + isPublic: boolean + createdBy: number + created: number + updated: number + forkId: number + numForks: number + numPulls: number + numClosedPulls: number + numOpenPulls: number +} diff --git a/web/src/utils/GitUtils.ts b/web/src/utils/GitUtils.ts new file mode 100644 index 000000000..79fc2a7b7 --- /dev/null +++ b/web/src/utils/GitUtils.ts @@ -0,0 +1,25 @@ +// Code copied from https://github.com/vweevers/is-git-ref-name-valid and +// https://github.com/vweevers/is-git-branch-name-valid (MIT, © Vincent Weevers) +// Last updated for git 2.29.0. + +import type { IconName } from '@harness/icons' + +// eslint-disable-next-line no-control-regex +const badGitRefRegrex = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/ +const badGitBranchRegrex = /^(-|HEAD$)/ + +function isGitRefValid(name: string, onelevel: boolean): boolean { + return !badGitRefRegrex.test(name) && (!!onelevel || name.includes('/')) +} + +export function isGitBranchNameValid(name: string): boolean { + return isGitRefValid(name, true) && !badGitBranchRegrex.test(name) +} + +export const GitIcon: Readonly> = { + FILE: 'file', + REPOSITORY: 'git-repo', + COMMIT: 'git-branch-existing', + PULL_REQUEST: 'git-pull', + SETTINGS: 'cog' +} diff --git a/web/src/utils/Utils.ts b/web/src/utils/Utils.ts index 15358ee94..9f127cf63 100644 --- a/web/src/utils/Utils.ts +++ b/web/src/utils/Utils.ts @@ -5,7 +5,15 @@ import moment from 'moment' import { useEffect } from 'react' import { useAppContext } from 'AppContext' +export const LIST_FETCHING_PAGE_SIZE = 20 +export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a' +export const X_TOTAL = 'x-total' +export const X_TOTAL_PAGES = 'x-total-pages' +export const X_PER_PAGE = 'x-per-page' export type Unknown = any // eslint-disable-line @typescript-eslint/no-explicit-any +export const DEFAULT_BRANCH_NAME = 'main' +export const REGEX_VALID_REPO_NAME = /^[A-Za-z0-9_.-][A-Za-z0-9 _.-]*$/ +export const SUGGESTED_BRANCH_NAMES = ['main', 'master'] /** This utility shows a toaster without being bound to any component. * It's useful to show cross-page/component messages */ @@ -25,7 +33,7 @@ export const MonacoEditorOptions = { codeLens: false, scrollBeyondLastLine: false, smartSelect: false, - tabSize: 4, + tabSize: 2, insertSpaces: true, overviewRulerBorder: false } @@ -45,13 +53,38 @@ export const deselectAllMonacoEditor = (editor?: EDITOR.IStandaloneCodeEditor): }, 0) } -export const LIST_FETCHING_PAGE_SIZE = 20 -export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a' - export const displayDateTime = (value: number): string | null => { return value ? moment.unix(value / 1000).format(DEFAULT_DATE_FORMAT) : null } +const LOCALE = Intl.NumberFormat().resolvedOptions?.().locale || 'en-US' + +/** + * Format a timestamp to short format time (i.e: 7:41 AM) + * @param timestamp Timestamp + * @param timeStyle Optional DateTimeFormat's `timeStyle` option. + */ +export function formatTime(timestamp: number, timeStyle = 'short'): string { + return new Intl.DateTimeFormat(LOCALE, { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: TS built-in type for DateTimeFormat is not correct + timeStyle + }).format(new Date(timestamp)) +} + +/** + * Format a timestamp to medium format date (i.e: Jan 1, 2021) + * @param timestamp Timestamp + * @param dateStyle Optional DateTimeFormat's `dateStyle` option. + */ +export function formatDate(timestamp: number, dateStyle = 'medium'): string { + return new Intl.DateTimeFormat(LOCALE, { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: TS built-in type for DateTimeFormat is not correct + dateStyle + }).format(new Date(timestamp)) +} + export enum Editions { ENTERPRISE = 'ENTERPRISE', TEAM = 'TEAM', diff --git a/web/src/views/NewRepo/NewRepo.module.scss b/web/src/views/NewRepo/NewRepo.module.scss deleted file mode 100644 index 3373a8539..000000000 --- a/web/src/views/NewRepo/NewRepo.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.main { - padding: var(--spacing-xlarge); -} diff --git a/web/src/views/NewRepo/NewRepo.tsx b/web/src/views/NewRepo/NewRepo.tsx deleted file mode 100644 index 1eae832d5..000000000 --- a/web/src/views/NewRepo/NewRepo.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' -import { Container } from '@harness/uicore' -import css from './NewRepo.module.scss' - -export default function NewRepo(): JSX.Element { - return NewRepo! -} diff --git a/web/src/views/RepoFiles/RepoFiles.tsx b/web/src/views/RepoFiles/RepoFiles.tsx index 7103d568b..b97972450 100644 --- a/web/src/views/RepoFiles/RepoFiles.tsx +++ b/web/src/views/RepoFiles/RepoFiles.tsx @@ -5,6 +5,7 @@ import SplitPane from 'react-split-pane' import { PopoverInteractionKind } from '@blueprintjs/core' import { useStrings } from 'framework/strings' import { ButtonRoleProps } from 'utils/Utils' +import { GitIcon } from 'utils/GitUtils' import { ResourceTree } from './ResourceTree/ResourceTree' import { ResourceContent } from './ResourceContent/ResourceContent' import css from './RepoFiles.module.scss' @@ -27,7 +28,7 @@ export default function RepoFiles(): JSX.Element { , - iconProps: { name: 'file' }, + iconProps: { name: GitIcon.FILE }, disabled: false }, { id: 'commits', title: getString('commits'), panel: , - iconProps: { name: 'git-branch-existing' }, + iconProps: { name: GitIcon.COMMIT }, disabled: true }, { id: 'pull-requests', title: getString('pullRequests'), panel: , - iconProps: { name: 'git-pull' }, + iconProps: { name: GitIcon.PULL_REQUEST }, disabled: true }, { id: 'settings', title: getString('settings'), panel: , - iconProps: { name: 'cog' }, + iconProps: { name: GitIcon.SETTINGS }, disabled: true } ]}> diff --git a/web/src/views/RepoFiles/ResourceContent/ResourceContent.tsx b/web/src/views/RepoFiles/ResourceContent/ResourceContent.tsx index e656810f2..d8cd0db5f 100644 --- a/web/src/views/RepoFiles/ResourceContent/ResourceContent.tsx +++ b/web/src/views/RepoFiles/ResourceContent/ResourceContent.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useRef, useState } from 'react' import cx from 'classnames' // import root from 'react-shadow' -// import MonacoEditor from 'react-monaco-editor' +import MonacoEditor from 'react-monaco-editor' import type { editor as EDITOR } from 'monaco-editor/esm/vs/editor/editor.api' import { Container, @@ -22,8 +22,9 @@ import { useStrings } from 'framework/strings' // import markdownCSS from '!raw-loader!@uiw/react-markdown-preview/dist/markdown.css' import markdown from './sampleREADME.md' import css from './ResourceContent.module.scss' +import sampleCSs from '!raw-loader!./ResourceContent.module.scss' -import('!raw-loader!./sampleREADME.md').then(foo => console.log('dynamic data:', foo.default)) +// import('!raw-loader!./sampleREADME.md').then(foo => console.log('dynamic data:', foo.default)) // TODO: USE FROM SERVICE (DOES NOT EXIST YET) interface Folder { @@ -169,9 +170,11 @@ export function FolderListing(): JSX.Element { const inputContainerRef = useRef(null) const [inputEditor, setInputEditor] = useState() + console.log({ sampleCSs }) + return ( - + {/* className={css.table} columns={columns} data={repodata || []} @@ -179,7 +182,7 @@ export function FolderListing(): JSX.Element { // onPolicyClicked(data) }} getRowClassName={() => css.row} - /> + /> */} @@ -187,27 +190,20 @@ export function FolderListing(): JSX.Element {