diff --git a/.env.development b/.env.development index 8cc885f3..9265a58a 100644 --- a/.env.development +++ b/.env.development @@ -9,7 +9,6 @@ NEXT_PUBLIC_FAUCET_URL=http://$BASE_ADDRESS:4500 NEXT_PUBLIC_IMAGES_URL=http://$BASE_ADDRESS:3000 NEXT_PUBLIC_CURRENCY_TOKEN=lore NEXT_PUBLIC_ADVANCE_CURRENCY_TOKEN=ulore -NEXT_PUBLIC_OBJECTS_URL=http://$BASE_ADDRESS:5001 NEXT_PUBLIC_EXPLORER_URL=http://explorer.$BASE_ADDRESS NEXT_PUBLIC_DOCS_URL=http://docs.$BASE_ADDRESS NEXT_PUBLIC_FORUM_URL=http://forum.$BASE_ADDRESS @@ -17,7 +16,6 @@ NEXT_PUBLIC_HEADER_MESSAGE= NEXT_PUBLIC_GITOPIA_ADDRESS= NEXT_PUBLIC_NETWORK_RELEASE_NOTES= NEXT_PUBLIC_GAS_PRICE=0.001ulore -NEXT_PUBLIC_GIT_SERVER_WALLET_ADDRESS=gitopia1jnq4pk0ene8xne4a43p2a2xpdhf3jqgsgu04n9 NEXT_PUBLIC_PLAUSIBLE_DOMAIN=$BASE_ADDRESS NEXT_PUBLIC_GRAPHQL_HTTP_URL=http://localhost:8000/subgraphs/name/gitopia/feed-alpha NEXT_PUBLIC_SERVER_URL=http://$BASE_ADDRESS:3000 @@ -29,4 +27,5 @@ NEXT_PUBLIC_REWARD_SERVICE_URL=http://$BASE_ADDRESS:10500 NEXT_PUBLIC_REWARD_START=1694802600 NEXT_PUBLIC_REWARD_DEADLINE=1700073000 NEXT_PUBLIC_FEE_GRANTER=gitopia12sjhqc3rqgvu3zpg8ekmwl005rp4ys58ekqg89 -NEXT_PUBLIC_NETWORK_TYPE=testnet \ No newline at end of file +NEXT_PUBLIC_NETWORK_TYPE=testnet +NEXT_PUBLIC_FALLBACK_STORAGE_PROVIDER=gitopia15nv5vf6fmww8cxr6emrzxjvj36x5n8xvsxsqpw \ No newline at end of file diff --git a/.env.production b/.env.production index e224f0b6..926fcbee 100644 --- a/.env.production +++ b/.env.production @@ -9,7 +9,6 @@ NEXT_PUBLIC_FAUCET_URL=https://faucet.$BASE_ADDRESS NEXT_PUBLIC_IMAGES_URL=https://$BASE_ADDRESS NEXT_PUBLIC_CURRENCY_TOKEN=lore NEXT_PUBLIC_ADVANCE_CURRENCY_TOKEN=ulore -NEXT_PUBLIC_OBJECTS_URL=https://server.$BASE_ADDRESS NEXT_PUBLIC_EXPLORER_URL=https://explorer.$BASE_ADDRESS NEXT_PUBLIC_DOCS_URL=https://docs.$BASE_ADDRESS NEXT_PUBLIC_FORUM_URL=https://forum.$BASE_ADDRESS @@ -19,7 +18,6 @@ NEXT_PUBLIC_GITOPIA_ADDRESS= NEXT_PUBLIC_NETWORK_RELEASE_NOTES=https://forum.$BASE_ADDRESS NEXT_PUBLIC_GAS_PRICE=0.001ulore NEXT_PUBLIC_PLAUSIBLE_DOMAIN=$BASE_ADDRESS -NEXT_PUBLIC_GIT_SERVER_WALLET_ADDRESS= NEXT_PUBLIC_GRAPHQL_HTTP_URL= NEXT_PUBLIC_SERVER_URL=https://$BASE_ADDRESS NEXT_PUBLIC_STORAGE_BRIDGE_WALLET_ADDRESS= @@ -30,4 +28,5 @@ NEXT_PUBLIC_REWARD_SERVICE_URL= NEXT_PUBLIC_REWARD_START=1694802600 NEXT_PUBLIC_REWARD_DEADLINE=1700073000 NEXT_PUBLIC_FEE_GRANTER= -NEXT_PUBLIC_NETWORK_TYPE=mainnet \ No newline at end of file +NEXT_PUBLIC_NETWORK_TYPE=mainnet +NEXT_PUBLIC_FALLBACK_STORAGE_PROVIDER=gitopia15nv5vf6fmww8cxr6emrzxjvj36x5n8xvsxsqpw \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 25258db5..f0d89113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ ## Changelog +### v4.1.0 - 16th Sep 2025 + +- Implement fallback storage provider + +### v4.0.0 - 8th Sep 2025 + +- Upgrade to gitopia-js v3.0.0 +- Implement new storage provider architecture with automatic provider selection based on latency +- Add storage information display in repository clone info with dedicated "Storage Info" tab +- Introduce storage provider dropdown with separate tabs for API and storage providers +- Complete rewrite of create and update release workflows with authenticated file upload +- Implement new fork workflow with updated fork messages +- Update pull request merge flow with new merge steps for both user and DAO repositories +- Handle DAO create release proposals in group proposal execution +- Update merge proposals for DAO repositories +- Add storage update proposal handling for release asset management +- Remove deprecated fork merge access dialogs and authorization flows +- Remove `authorizeGitServer` function and related authorization components +- Remove deprecated `AccountGrants` component and related authorization functionalities +- Fix redirect handling on repository deletion +- Implement storage queries across the application +- Replace hardcoded storage URLs with dynamic provider selection +- Don't show proposals tab in user profile +- Show treasury balance limited to two decimal places +- Prevent header re-rendering when switching tabs +- Display link to parent repository in forked repository views +- Add ChainRoot link in the explorers dropdown + +### v3.1.0 - 2nd Dec 2024 + +- Add button for Member Dashboard in public view (if member) and public view in Member dashboard +- Improve the dao dashboard and some other minor improvements +- Fix overflow of voting chart in dao creation step +- Sanitize dao names before posting the transaction + +### v3.0.0 - 2nd Dec 2024 + +- Implement the new dao creation and proposal workflows + ### v2.2.2 - 3rd Sep 2024 - Fix missing apiClient in getUser and getRepository methods diff --git a/components/account/DaoTreasuryStats.js b/components/account/DaoTreasuryStats.js index 8cd942bc..3ece2022 100644 --- a/components/account/DaoTreasuryStats.js +++ b/components/account/DaoTreasuryStats.js @@ -14,8 +14,12 @@ function DaoTreasuryStats({ dao, className = "", getBalance, advanceUser }) { const balance = await getBalance(cosmosBankApiClient, dao.address); setTreasuryBalance( advanceUser === true - ? balance + " " + process.env.NEXT_PUBLIC_ADVANCE_CURRENCY_TOKEN - : balance / 1000000 + " " + process.env.NEXT_PUBLIC_CURRENCY_TOKEN + ? balance.toFixed(2) + + " " + + process.env.NEXT_PUBLIC_ADVANCE_CURRENCY_TOKEN + : (balance / 1000000).toFixed(2) + + " " + + process.env.NEXT_PUBLIC_CURRENCY_TOKEN ); } initBalance(); diff --git a/components/account/daoHeader.js b/components/account/daoHeader.js index 7db24aae..b611f882 100644 --- a/components/account/daoHeader.js +++ b/components/account/daoHeader.js @@ -11,6 +11,8 @@ import AccountAvatar from "../account/avatar"; import DaoWebsite from "../dao/website"; import { useApiClient } from "../../context/ApiClientContext"; import DaoTreasuryStats from "./DaoTreasuryStats"; +import Link from "next/link"; +import { Users2 } from "lucide-react"; function AccountDaoHeader(props) { const [isEditable, setIsEditable] = useState(false); @@ -49,7 +51,17 @@ function AccountDaoHeader(props) {
-
About
+
+
About
+ {props.isMember && ( + + + + )} +
- {/* Add the treasury stats component */} ); diff --git a/components/account/grants.js b/components/account/grants.js deleted file mode 100644 index 62fdfd73..00000000 --- a/components/account/grants.js +++ /dev/null @@ -1,17 +0,0 @@ -import ToggleStorageBridgeAuthorization from "../repository/toggleStorageBridgeAuthorization"; -import ToggleGitServerAuthorization from "../repository/toggleGitServerAuthorization"; - -function AccountGrants({ address, ...props }) { - return ( -
-
- -
-
- -
-
- ); -} - -export default AccountGrants; diff --git a/components/dashboard/ProposalDetailsModal.js b/components/dashboard/ProposalDetailsModal.js index 7eb9e210..0d60d930 100644 --- a/components/dashboard/ProposalDetailsModal.js +++ b/components/dashboard/ProposalDetailsModal.js @@ -76,6 +76,7 @@ const ProposalDetailsModal = ({ policyInfo, selectedAddress, cosmosGroupApiClient, + storageApiClient, }) => { const [tally, setTally] = useState(null); const [loading, setLoading] = useState(true); @@ -142,7 +143,7 @@ const ProposalDetailsModal = ({ veto: (vetoVotes / totalWeight) * 100, quorum: policyInfo?.info.decision_policy["@type"] === - "/cosmos.group.v1.PercentageDecisionPolicy" + "/cosmos.group.v1.PercentageDecisionPolicy" ? parseFloat(policyInfo.info.decision_policy.percentage) * 100 : 0, }; @@ -205,9 +206,8 @@ const ProposalDetailsModal = ({ {/* Execution Status */} {proposal.status === "PROPOSAL_STATUS_ACCEPTED" && (
@@ -216,15 +216,15 @@ const ProposalDetailsModal = ({ {isTextProposal ? "Text Proposal Passed" : canExecute - ? "Proposal Execution Required" - : "Proposal Executed"} + ? "Proposal Execution Required" + : "Proposal Executed"}

{isTextProposal ? "This is a text-only proposal that required no execution." : canExecute - ? "This proposal has passed but hasn't been executed yet. Execute it to apply the changes." - : "This proposal has been executed successfully."} + ? "This proposal has passed but hasn't been executed yet. Execute it to apply the changes." + : "This proposal has been executed successfully."}

diff --git a/components/dashboard/ProposalsSection.js b/components/dashboard/ProposalsSection.js index 7a31e0ec..64ccbe14 100644 --- a/components/dashboard/ProposalsSection.js +++ b/components/dashboard/ProposalsSection.js @@ -199,10 +199,10 @@ const ProposalCard = ({ vote.option === "VOTE_OPTION_YES" ? "text-success" : vote.option === "VOTE_OPTION_NO" - ? "text-error" - : vote.option === "VOTE_OPTION_NO_WITH_VETO" - ? "text-warning" - : "text-muted" + ? "text-error" + : vote.option === "VOTE_OPTION_NO_WITH_VETO" + ? "text-warning" + : "text-muted" } > {vote.option.replace("VOTE_OPTION_", "")} @@ -266,9 +266,8 @@ const ProposalFilters = ({ onFilter, activeFilter }) => ( @@ -347,6 +346,7 @@ export default function ProposalsSection({ cosmosBankApiClient, cosmosFeegrantApiClient, cosmosGroupApiClient, + storageApiClient, } = useApiClient(); const [isExecuting, setIsExecuting] = useState(false); const dispatch = useDispatch(); @@ -405,7 +405,7 @@ export default function ProposalsSection({ } }, [proposals, cosmosGroupApiClient]); - const handleExecuteProposal = async (proposalId) => { + const handleExecuteProposal = async (proposal) => { setIsExecuting(true); try { const result = await dispatch( @@ -413,13 +413,24 @@ export default function ProposalsSection({ apiClient, cosmosBankApiClient, cosmosFeegrantApiClient, - proposalId + storageApiClient, + proposal ) ); if (result && result.code === 0) { // Refresh proposals list after successful execution await onRefreshProposals(); + + // Fetch updated proposal data to update the modal UI + try { + const updatedProposalRes = await cosmosGroupApiClient.queryProposal(proposal.id); + if (updatedProposalRes && updatedProposalRes.data && updatedProposalRes.data.proposal) { + setSelectedProposal(updatedProposalRes.data.proposal); + } + } catch (error) { + console.error("Error fetching updated proposal:", error); + } } } catch (error) { console.error("Error executing proposal:", error); @@ -519,13 +530,14 @@ export default function ProposalsSection({ setSelectedProposal(null); }} onVote={onVote} - onExecute={() => handleExecuteProposal(selectedProposal.id)} + onExecute={() => handleExecuteProposal(selectedProposal)} isExecuting={isExecuting} groupInfo={groupInfo} policyInfo={policyInfo} selectedAddress={selectedAddress} votes={proposalVotes[selectedProposal.id]} cosmosGroupApiClient={cosmosGroupApiClient} + storageApiClient={storageApiClient} /> )}
diff --git a/components/dashboard/dao.js b/components/dashboard/dao.js index 9997351d..26ad2783 100644 --- a/components/dashboard/dao.js +++ b/components/dashboard/dao.js @@ -3,7 +3,6 @@ import Link from "next/link"; import { connect, useDispatch } from "react-redux"; import DAOMembersList from "./DAOMembersList"; import { createGroupProposal } from "../../store/actions/dao"; -import AccountGrants from "../account/grants"; import GreetDao from "../greetDao"; import { useApiClient } from "../../context/ApiClientContext"; import getGroupInfo from "../../helpers/getGroupInfo"; @@ -242,41 +241,49 @@ function DaoDashboard({ dao = {}, advanceUser, ...props }) { }; const renderHeader = () => ( -
-
-
- {dao?.avatarUrl ? ( - {dao?.name} - ) : ( -
- {dao?.name?.[0]} -
- )} +
+
+
+
+ {dao?.avatarUrl ? ( + {dao?.name} + ) : ( +
+ {dao?.name?.[0]} +
+ )} +
-
-
- - {dao?.name} - -
- - {dao?.address?.slice(0, 8)}...{dao?.address?.slice(-8)} - - + {dao?.name} + +
+ + {dao?.address?.slice(0, 8)}...{dao?.address?.slice(-8)} + + +
+ + +
); @@ -365,13 +372,6 @@ function DaoDashboard({ dao = {}, advanceUser, ...props }) { onRefreshProposals={fetchProposals} /> ); - case "authorizations": - return ( -
-

Authorizations

- -
- ); default: return null; } diff --git a/components/dashboard/publicTabs.js b/components/dashboard/publicTabs.js index f1d0b205..2d373357 100644 --- a/components/dashboard/publicTabs.js +++ b/components/dashboard/publicTabs.js @@ -60,32 +60,34 @@ export default function PublicTabs({ ) : null} {showPeople ? ( - - - - - People - - ) : null} + <> + + + + + People + - - - - - Proposals - + + + + + Proposals + + + ) : null} {showProposal ? ( +
  • + + ChainRoot + +
  • {props.activeWallet.isLedger || - props.activeWallet.isKeplr ? ( + props.activeWallet.isKeplr ? ( { - const { updateApiClient } = useApiClient(); + const { updateApiClient, allStorageProviders, setActiveStorageProvider, storageProviderName } = useApiClient(); + const [activeTab, setActiveTab] = useState("api"); const [customProvider, setCustomProvider] = useState({ name: "Custom", apiEndpoint: "", @@ -86,66 +88,100 @@ const Providers = ({ selectedProvider, setSelectedProvider, notify }) => { }; return ( -
    -
    API Provider
    - {providersWithCustom.map((provider, i) => ( - - ))} - - {showCustomProviderInputs && ( -
    -
    Custom Provider
    - - handleCustomProviderChange("apiEndpoint", e.target.value) - } - className="input input-bordered w-full mb-2" - /> - - handleCustomProviderChange("rpcEndpoint", e.target.value) - } - className="input input-bordered w-full mb-2" - /> - {validationError && ( -
    {validationError}
    - )} +
    + + + {activeTab === 'api' && ( +
    + {providersWithCustom.map((provider, i) => ( + + ))} + {showCustomProviderInputs && ( +
    +
    Custom Provider
    + + handleCustomProviderChange("apiEndpoint", e.target.value) + } + className="input input-bordered w-full mb-2" + /> + + handleCustomProviderChange("rpcEndpoint", e.target.value) + } + className="input input-bordered w-full mb-2" + /> + {validationError && ( +
    {validationError}
    + )} + +
    + )} +
    + )} + + {activeTab === 'storage' && ( +
    + {allStorageProviders.map((provider, i) => ( + + ))}
    )}
    diff --git a/components/repository/cloneRepoInfo.js b/components/repository/cloneRepoInfo.js index 1664a469..9431ff55 100644 --- a/components/repository/cloneRepoInfo.js +++ b/components/repository/cloneRepoInfo.js @@ -1,40 +1,51 @@ import { useEffect, useState } from "react"; import { connect } from "react-redux"; import { notify } from "reapop"; +import { useApiClient } from "../../context/ApiClientContext"; -function CloneRepoInfo({ remoteUrl, backups, ...props }) { +function CloneRepoInfo({ remoteUrl, repositoryId, ...props }) { const [tab, setTab] = useState("gitopia"); const [cloneCmd, setCloneCmd] = useState("git clone " + remoteUrl); - const [isIpfsEnabled, setIsIpfsEnabled] = useState(false); - const [ipfsLatestCid, setIpfsLatestCid] = useState(""); - const [isArweaveEnabled, setIsArweaveEnabled] = useState(false); - const [arweaveLatestCid, setArweaveLatestCid] = useState(""); + const [storageInfo, setStorageInfo] = useState(null); + const { storageApiClient } = useApiClient(); useEffect(() => { if (tab === "gitopia") { setCloneCmd("git clone " + remoteUrl); - } else if (tab === "ipfs") { - setCloneCmd("ipfs_clone " + remoteUrl); - } else if (tab === "arweave") { - setCloneCmd("arweave_clone " + remoteUrl); } }, [tab, remoteUrl]); useEffect(() => { - setIsIpfsEnabled(false); - setIsArweaveEnabled(false); - if (backups) { - backups.map((b) => { - if (b.store === "IPFS" && b.refs?.length) { - setIsIpfsEnabled(true); - setIpfsLatestCid(b.refs[b.refs.length - 1]); - } else if (b.store === "ARWEAVE" && b.refs?.length) { - setIsArweaveEnabled(true); - setArweaveLatestCid(b.refs[b.refs.length - 1]); + const fetchStorageInfo = async () => { + if (repositoryId && storageApiClient) { + try { + const response = await storageApiClient.queryRepositoryPackfile(repositoryId); + // log response + console.log(response); + setStorageInfo(response.data.packfile); + } catch (error) { + console.error("Error fetching storage info:", error); } - }); + } + }; + + if (tab === "storage") { + fetchStorageInfo(); + } + }, [tab, repositoryId, storageApiClient]); + + const formatSize = (bytes) => { + const units = ['B', 'KB', 'MB', 'GB']; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; } - }, [backups]); + + return `${size.toFixed(2)} ${units[unitIndex]}`; + }; return (
    @@ -66,36 +77,17 @@ function CloneRepoInfo({ remoteUrl, backups, ...props }) { > Gitopia Server - {isIpfsEnabled ? ( - - ) : ( - "" - )} - {isArweaveEnabled ? ( - - ) : ( - "" - )} +
    {tab === "gitopia" ? ( <> @@ -130,95 +122,80 @@ function CloneRepoInfo({ remoteUrl, backups, ...props }) { Learn more
    - - ) : ( - "" - )} -
    - {tab === "ipfs" || tab === "arweave" ? ( -
    -
    - {tab === "ipfs" ? ( - - ) : ( - - )} -
    - - -
    - ) : ( - "" - )} - {tab === "gitopia" ? ( -
    - - + + + + +
    - ) : ( - "" - )} -
    + + ) : tab === "storage" ? ( +
    + {storageInfo ? ( +
    +
    + IPFS + Storage Information +
    +
    +
    + Provider: + + {storageInfo.creator} + +
    +
    + Size: + {formatSize(parseInt(storageInfo.size))} +
    +
    + IPFS CID: + + {storageInfo.cid} + +
    +
    + Root Hash: + + {storageInfo.root_hash} + +
    +
    +
    + ) : ( +
    Loading storage information...
    + )} +
    + ) : null}
  • ); diff --git a/components/repository/deleteRepository.js b/components/repository/deleteRepository.js index e80811b6..61f0356d 100644 --- a/components/repository/deleteRepository.js +++ b/components/repository/deleteRepository.js @@ -4,6 +4,7 @@ import { deleteRepository } from "../../store/actions/repository"; import { useApiClient } from "../../context/ApiClientContext"; function DeleteRepository({ + repositoryId = null, repoName = "", currentOwnerId = "", onSuccess, @@ -12,8 +13,13 @@ function DeleteRepository({ const [isDeleting, setIsDeleting] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const [typedData, setTypedData] = useState(""); - const { apiClient, cosmosBankApiClient, cosmosFeegrantApiClient } = - useApiClient(); + const { + apiClient, + cosmosBankApiClient, + cosmosFeegrantApiClient, + storageApiClient, + storageProviderAddress, + } = useApiClient(); return (
    @@ -27,7 +33,7 @@ function DeleteRepository({ @@ -87,14 +93,17 @@ function DeleteRepository({ apiClient, cosmosBankApiClient, cosmosFeegrantApiClient, + storageApiClient, + storageProviderAddress, { + repositoryId, ownerId: currentOwnerId, name: repoName, } ) .then(async (res) => { - if (res.code == 0) { - if (onSuccess) await onSuccess; + if (res && res.code == 0) { + if (onSuccess) await onSuccess(); setConfirmDelete(false); setIsDeleting(false); setTypedData(""); diff --git a/components/repository/diffView.js b/components/repository/diffView.js index 7c9fa5d5..87c5c252 100644 --- a/components/repository/diffView.js +++ b/components/repository/diffView.js @@ -23,9 +23,9 @@ function DiffView({ commentsAllowed = true, comments = [], refreshComments, - onViewTypeChange = () => {}, + onViewTypeChange = () => { }, showFile = null, - getCommentView = () => {}, + getCommentView = () => { }, isPullDiff = false, ...props }) { @@ -38,7 +38,7 @@ function DiffView({ const [loading, setLoading] = useState(false); const [scrollingToFile, setScrollingToFile] = useState(false); const paginationLimit = 10; - const { apiClient, cosmosBankApiClient, cosmosFeegrantApiClient } = + const { apiClient, cosmosBankApiClient, cosmosFeegrantApiClient, storageApiUrl } = useApiClient(); const renderGutter = ({ @@ -329,6 +329,7 @@ function DiffView({ let data; if (isPullDiff) { data = await getPullDiff( + storageApiUrl, baseRepoId, repoId, previousSha, @@ -337,7 +338,7 @@ function DiffView({ limit ); } else { - data = await getDiff(repoId, currentSha, previousSha, offset, limit); + data = await getDiff(storageApiUrl, repoId, currentSha, previousSha, offset, limit); } let newFiles = [...oldFiles]; diff --git a/components/repository/header.js b/components/repository/header.js index 3c3d4469..08368a05 100644 --- a/components/repository/header.js +++ b/components/repository/header.js @@ -7,11 +7,12 @@ import { Trash2, User, Users, + GitBranch, } from "lucide-react"; import { useRouter } from "next/router"; import shrinkAddress from "../../helpers/shrinkAddress"; -import getDao from "../../helpers/getDao"; import { useApiClient } from "../../context/ApiClientContext"; +import getRepositoryById from "../../helpers/getRepositoryById"; import DAOProtectionBadge from "./DaoProtectionBadge"; const OwnershipBadge = ({ type }) => { @@ -32,6 +33,43 @@ const OwnershipBadge = ({ type }) => { ); }; +const ForkParentInfo = ({ parentId }) => { + const [parentRepo, setParentRepo] = useState(null); + const { apiClient } = useApiClient(); + + useEffect(() => { + async function fetchParentRepo() { + try { + const parentRepo = await getRepositoryById(apiClient, parentId); + setParentRepo(parentRepo); + } catch (error) { + console.error("Error fetching parent repository:", error); + } + } + + if (parentId) { + fetchParentRepo(); + } + }, [parentId, apiClient]); + + if (!parentRepo) return null; + + return ( +
    + + Forked from + + {shrinkAddress(parentRepo.owner.id)} + / + {parentRepo.name} + +
    + ); +}; + const RepositoryHeader = ({ repository, daoData, selectedAddress }) => { const [forkTargetShown, setForkTargetShown] = useState(false); const [branchCount, setBranchCount] = useState(0); @@ -86,45 +124,51 @@ const RepositoryHeader = ({ repository, daoData, selectedAddress }) => {
    -
    -

    -
    - {isDAORepository ? ( - - ) : ( - - )} +
    +
    +

    +
    + {isDAORepository ? ( + + ) : ( + + )} + + {shrinkAddress(repository.owner.id)} + +
    + / - {shrinkAddress(repository.owner.id)} + {repository.name} -

    - / - - {repository.name} - -

    -
    + +
    -
    - - {isDAORepository && daoData && ( - - )} - {repository.fork && ( -
    - -
    + {repository.fork && repository.parent && ( + )} + +
    + + {isDAORepository && daoData && ( + + )} + {repository.fork && ( +
    + +
    + )} +
    diff --git a/components/repository/mergePullRequestView.js b/components/repository/mergePullRequestView.js index 9a90bf5f..367580c1 100644 --- a/components/repository/mergePullRequestView.js +++ b/components/repository/mergePullRequestView.js @@ -3,17 +3,16 @@ import { useEffect, useState } from "react"; import { notify } from "reapop"; import { updatePullRequestState, - authorizeGitServer, mergePullRequest, mergePullRequestForDao, } from "../../store/actions/repository"; import pullRequestStateClass from "../../helpers/pullRequestStateClass"; import mergePullRequestCheck from "../../helpers/mergePullRequestCheck"; import getPullRequestMergePermission from "../../helpers/getPullRequestMergePermission"; -import getGitServerAuthorization from "../../helpers/getGitServerAuthStatus"; import getDao from "../../helpers/getDao"; import { useApiClient } from "../../context/ApiClientContext"; import { useRouter } from "next/router"; +import getBranchSha from "../../helpers/getBranchSha"; function MergePullRequestView({ repository, @@ -25,10 +24,6 @@ function MergePullRequestView({ const [stateClass, setStateClass] = useState(""); const [iconType, setIconType] = useState("check"); const [message, setMessage] = useState(""); - const [pullMergeAccess, setPullMergeAccess] = useState(false); - const [pullMergeAccessDialogShown, setPullMergeAccessDialogShown] = - useState(false); - const [isGrantingAccess, setIsGrantingAccess] = useState(false); const [requiresProposal, setRequiresProposal] = useState(false); const [isCreatingProposal, setIsCreatingProposal] = useState(false); const { @@ -36,6 +31,9 @@ function MergePullRequestView({ cosmosBankApiClient, cosmosFeegrantApiClient, cosmosGroupApiClient, + storageApiClient, + storageProviderAddress, + storageApiUrl, } = useApiClient(); const [daoInfo, setDaoInfo] = useState(null); const router = useRouter(); @@ -63,6 +61,7 @@ function MergePullRequestView({ } setIsMerging(true); const res = await mergePullRequestCheck( + storageApiUrl, pullRequest.iid, pullRequest.base.repositoryId, pullRequest.head.repositoryId, @@ -95,15 +94,21 @@ function MergePullRequestView({ const createMergeProposal = async () => { setIsCreatingProposal(true); try { + const baseCommitSha = getBranchSha( + pullRequest.base.branch, + repository.branches, + ); const result = await props.mergePullRequestForDao( apiClient, cosmosBankApiClient, cosmosFeegrantApiClient, cosmosGroupApiClient, + storageProviderAddress, { repositoryId: repository.id, iid: pullRequest.iid, groupId: daoInfo.group_id, + baseCommitSha, } ); @@ -135,29 +140,25 @@ function MergePullRequestView({ ); if (user && user.havePermission) { - let access = await getGitServerAuthorization( - apiClient, - props.selectedAddress + const baseCommitSha = getBranchSha( + pullRequest.base.branch, + repository.branches, ); - if (!access) { - setPullMergeAccessDialogShown(true); - setIsMerging(false); - return; - } - const res = await props.mergePullRequest( apiClient, cosmosBankApiClient, cosmosFeegrantApiClient, + storageApiClient, + storageProviderAddress, { repositoryId: repository.id, iid: pullRequest.iid, - branchName: pullRequest.head.branch, + baseCommitSha, } ); if (res) { - if (res.state === "TASK_STATE_SUCCESS") refreshPullRequest(); + if (res.code === 0) refreshPullRequest(); } else { props.notify("Unknown error", "error"); } @@ -183,17 +184,6 @@ function MergePullRequestView({ } }, [pullRequest, props.selectedAddress, requiresProposal]); - const refreshPullMergeAccess = async (mergeAfter = false) => { - setPullMergeAccess( - await getGitServerAuthorization(apiClient, props.selectedAddress) - ); - if (mergeAfter) setTimeout(mergePull, 0); - }; - - useEffect(() => { - refreshPullMergeAccess(); - }, [props.selectedAddress]); - const getMergeButtonText = () => { if (isCreatingProposal) return "Creating Proposal..."; if (isMerging) return "Merging..."; @@ -270,9 +260,8 @@ function MergePullRequestView({ {pullRequest.state === "OPEN" && (
    - - -
    -
    -

    - Gitopia data server does not have repository merge access on behalf - of your account. -

    -

    Server Address:

    -

    - {process.env.NEXT_PUBLIC_GIT_SERVER_WALLET_ADDRESS} -

    -
    - - -
    -
    -
    ); } @@ -347,7 +286,6 @@ const mapStateToProps = (state) => { export default connect(mapStateToProps, { notify, updatePullRequestState, - authorizeGitServer, mergePullRequest, mergePullRequestForDao, })(MergePullRequestView); diff --git a/components/repository/releaseView.js b/components/repository/releaseView.js index cf0fa33d..c3f6dcac 100644 --- a/components/repository/releaseView.js +++ b/components/repository/releaseView.js @@ -5,18 +5,20 @@ import shrinkSha from "../../helpers/shrinkSha"; import { useState } from "react"; import MarkdownWrapper from "../markdownWrapper"; import AccountCard from "../account/card"; +import { useApiClient } from "../../context/ApiClientContext"; export default function ReleaseView({ release, repository, latest = false, showEditControls = false, - onDelete = () => {}, + onDelete = () => { }, noLink = false, ...props }) { const [isDeleting, setIsDeleting] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); + const { storageApiUrl } = useApiClient(); return (
    @@ -140,7 +142,7 @@ export default function ReleaseView({ target="_blank" rel="noreferrer" href={ - process.env.NEXT_PUBLIC_OBJECTS_URL + + storageApiUrl + "/releases/" + repository.owner.id + "/" + diff --git a/components/repository/supportOwner.js b/components/repository/supportOwner.js index e6f600a5..3aa70c68 100644 --- a/components/repository/supportOwner.js +++ b/components/repository/supportOwner.js @@ -53,10 +53,15 @@ function SupportOwner({ repository, ownerAddress, isMobile, ...props }) { useEffect(() => { async function initBalance() { const balance = await props.getBalance(cosmosBankApiClient, ownerAddress); + // In the initBalance function inside useEffect setOwnerBalance( props.advanceUser === true - ? balance + " " + process.env.NEXT_PUBLIC_ADVANCE_CURRENCY_TOKEN - : balance / 1000000 + " " + process.env.NEXT_PUBLIC_CURRENCY_TOKEN + ? balance.toFixed(2) + + " " + + process.env.NEXT_PUBLIC_ADVANCE_CURRENCY_TOKEN + : (balance / 1000000).toFixed(2) + + " " + + process.env.NEXT_PUBLIC_CURRENCY_TOKEN ); } initBalance(); diff --git a/components/repository/toggleGitServerAuthorization.js b/components/repository/toggleGitServerAuthorization.js deleted file mode 100644 index 3bda903a..00000000 --- a/components/repository/toggleGitServerAuthorization.js +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect, useState } from "react"; -import { connect } from "react-redux"; -import { updateAddressGrant } from "../../store/actions/user"; -import getGitServerAuthStatus from "../../helpers/getGitServerAuthStatus"; -import { useApiClient } from "../../context/ApiClientContext"; - -function ToggleGitServerAuthorization({ address, onSuccess, ...props }) { - const [currentState, setCurrentState] = useState(false); - const [isToggling, setIsToggling] = useState(true); - const { apiClient, cosmosBankApiClient, cosmosFeeegrantApiClient } = - useApiClient(); - - const toggleGrant = async () => { - setIsToggling(true); - const res = await props.updateAddressGrant( - apiClient, - cosmosBankApiClient, - cosmosFeeegrantApiClient, - address, - 0, - !currentState - ); - if (res && res.code === 0) { - if (onSuccess) await onSuccess(!currentState); - setCurrentState(!currentState); - } - setIsToggling(false); - }; - - useEffect(() => { - async function initAddress() { - setIsToggling(true); - setCurrentState(await getGitServerAuthStatus(apiClient, address)); - setIsToggling(false); - } - initAddress(); - }, [address]); - - return ( - - ); -} - -const mapStateToProps = (state) => { - return { - selectedAddress: state.wallet.selectedAddress, - }; -}; - -export default connect(mapStateToProps, { updateAddressGrant })( - ToggleGitServerAuthorization -); diff --git a/components/repository/toggleStorageBridgeAuthorization.js b/components/repository/toggleStorageBridgeAuthorization.js deleted file mode 100644 index d87f1102..00000000 --- a/components/repository/toggleStorageBridgeAuthorization.js +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect, useState } from "react"; -import { connect } from "react-redux"; -import { updateAddressGrant } from "../../store/actions/user"; -import getStorageBridgeAuthStatus from "../../helpers/getStorageBridgeAuthStatus"; -import { useApiClient } from "../../context/ApiClientContext"; - -function ToggleStorageBridgeAuthorization({ address, onSuccess, ...props }) { - const [currentState, setCurrentState] = useState(false); - const [isToggling, setIsToggling] = useState(true); - const { apiClient, cosmosBankApiClient, cosmosFeegrantApiClient } = - useApiClient(); - - const toggleGrant = async () => { - setIsToggling(true); - const res = await props.updateAddressGrant( - apiClient, - cosmosBankApiClient, - cosmosFeegrantApiClient, - address, - 1, - !currentState - ); - if (res && res.code === 0) { - if (onSuccess) await onSuccess(!currentState); - setCurrentState(!currentState); - } - setIsToggling(false); - }; - - useEffect(() => { - async function initAddress() { - setIsToggling(true); - setCurrentState(await getStorageBridgeAuthStatus(apiClient, address)); - setIsToggling(false); - } - initAddress(); - }, [address]); - - return ( - - ); -} - -const mapStateToProps = (state) => { - return { - selectedAddress: state.wallet.selectedAddress, - }; -}; - -export default connect(mapStateToProps, { updateAddressGrant })( - ToggleStorageBridgeAuthorization -); diff --git a/context/ApiClientContext.js b/context/ApiClientContext.js index 46b899c8..d7258578 100644 --- a/context/ApiClientContext.js +++ b/context/ApiClientContext.js @@ -6,7 +6,11 @@ import { Api as CosmosFeegrantApi } from "../store/cosmos.feegrant.v1beta1/rest" import { Api as CosmosGovApi } from "../store/cosmos.gov.v1beta1/module/rest"; import { Api as IbcAppTransferApi } from "../store/ibc.applications.transfer.v1/module/rest"; import { Api as CosmosGroupApi } from "../store/cosmos.group.v1/rest"; +import { Api as StorageApi } from "../store/gitopia.gitopia.storage/rest"; import selectProvider from "../helpers/providerSelector"; +import selectStorageProvider, { + getSavedStorageProvider, +} from "../helpers/storageProviderSelector"; import { setConfig } from "../store/actions/env"; const ApiClientContext = createContext(); @@ -22,9 +26,14 @@ export const ApiClientProvider = ({ children }) => { const [cosmosGovApiClient, setCosmosGovApiClient] = useState(null); const [ibcAppTransferApiClient, setIbcAppTransferApiClient] = useState(null); const [cosmosGroupApiClient, setCosmosGroupApiClient] = useState(null); + const [storageApiClient, setStorageApiClient] = useState(null); const [providerName, setProviderName] = useState(null); const [apiUrl, setApiUrl] = useState(null); const [rpcUrl, setRpcUrl] = useState(null); + const [storageApiUrl, setStorageApiUrl] = useState(null); + const [storageProviderAddress, setStorageProviderAddress] = useState(null); + const [storageProviderName, setStorageProviderName] = useState(null); + const [allStorageProviders, setAllStorageProviders] = useState([]); const [loading, setLoading] = useState(true); const dispatch = useDispatch(); @@ -53,6 +62,13 @@ export const ApiClientProvider = ({ children }) => { }); setCosmosGroupApiClient(newCosmosGroupApiClient); + const newStorageApiClient = new StorageApi({ + baseURL: apiNode, + }); + setStorageApiClient(newStorageApiClient); + + updateStorageProvider(newStorageApiClient); + setProviderName(name); setApiUrl(apiNode); setRpcUrl(rpcNode); @@ -67,6 +83,55 @@ export const ApiClientProvider = ({ children }) => { dispatch(setConfig({ config: { apiNode, rpcNode } })); }; + const updateStorageProvider = async (client) => { + const res = await client.queryActiveProviders(); + const providers = res.data.providers ?? []; + setAllStorageProviders(providers); + + if (providers.length > 0) { + const savedProvider = getSavedStorageProvider(); + if ( + savedProvider && + providers.some((p) => p.creator === savedProvider.creator) + ) { + setActiveStorageProvider(savedProvider); + return savedProvider; + } + + const provider = await selectStorageProvider(providers); + if (provider) { + setActiveStorageProvider(provider); + return provider; + } + } + + // Fallback to specific storage provider when no active provider is found + try { + const fallbackAddress = process.env.NEXT_PUBLIC_FALLBACK_STORAGE_PROVIDER; + if (fallbackAddress) { + const fallbackRes = await client.queryProvider(fallbackAddress); + if (fallbackRes.data.provider) { + const fallbackProvider = fallbackRes.data.provider; + setActiveStorageProvider(fallbackProvider); + return fallbackProvider; + } + } + } catch (error) { + console.warn("Failed to query fallback storage provider:", error); + } + + return null; + }; + + const setActiveStorageProvider = (provider) => { + if (provider) { + setStorageApiUrl(provider.api_url.replace(/\/$/, "")); // trim trailing slash + setStorageProviderAddress(provider.creator); + setStorageProviderName(provider.moniker); + localStorage.setItem("storageProviderInfo", JSON.stringify(provider)); + } + }; + useEffect(() => { const cachedProviderInfo = localStorage.getItem("providerInfo"); @@ -107,7 +172,14 @@ export const ApiClientProvider = ({ children }) => { cosmosGovApiClient, ibcAppTransferApiClient, cosmosGroupApiClient, + storageApiClient, + storageApiUrl, + storageProviderAddress, + storageProviderName, + allStorageProviders, updateApiClient, + updateStorageProvider, + setActiveStorageProvider, }} > {!loading && children} diff --git a/helpers/checkLatency.js b/helpers/checkLatency.js index 0519db7e..34dfddcc 100644 --- a/helpers/checkLatency.js +++ b/helpers/checkLatency.js @@ -1,13 +1,13 @@ import axios from "axios"; -const checkLatency = async (provider) => { +const checkLatency = async (url) => { const startTime = Date.now(); try { - await axios.get(provider.rpcEndpoint); + await axios.get(url); const endTime = Date.now(); return endTime - startTime; } catch (error) { - console.error(`Error checking latency for ${provider.rpcEndpoint}:`, error); + console.error(`Error checking latency for ${url}:`, error); return Infinity; } }; diff --git a/helpers/forkRepositoryFiles.js b/helpers/forkRepositoryFiles.js deleted file mode 100644 index 97672be3..00000000 --- a/helpers/forkRepositoryFiles.js +++ /dev/null @@ -1,24 +0,0 @@ -export default async function forkRepositoryFiles(sourceRepoId, targetRepoId) { - if (!sourceRepoId || !targetRepoId) return; - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/fork" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/fork"; - let obj = {}; - let params = { - source_repository_id: Number(sourceRepoId), - target_repository_id: Number(targetRepoId), - }; - await fetch(baseUrl, { - method: "POST", // *GET, POST, PUT, DELETE, etc. - mode: "cors", // no-cors, *cors, same-origin - cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached - body: JSON.stringify(params), - }) - .then((response) => { - obj = response.json(); - }) - .catch((err) => console.error(err)); - - return obj; -} diff --git a/helpers/getCommit.js b/helpers/getCommit.js index e8ce531a..d59d8a6f 100644 --- a/helpers/getCommit.js +++ b/helpers/getCommit.js @@ -1,7 +1,7 @@ const validSha = new RegExp(/^[a-f0-9]{40}$/); import axios from "../helpers/axiosFetch"; -export default async function getCommit(repoId = null, commitSha = null) { +export default async function getCommit(storageApiUrl, repoId = null, commitSha = null) { let obj = {}; if (repoId === null) { return obj; @@ -9,10 +9,7 @@ export default async function getCommit(repoId = null, commitSha = null) { if (!validSha.test(commitSha)) { return obj; } - let baseUrl = - process.env.NODE_ENV === "development" - ? "/api/commits" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/commits"; + let baseUrl = storageApiUrl + "/commits"; let params = { repository_id: Number(repoId), }; diff --git a/helpers/getCommitHistory.js b/helpers/getCommitHistory.js index 0803ecb3..25e55034 100644 --- a/helpers/getCommitHistory.js +++ b/helpers/getCommitHistory.js @@ -3,6 +3,7 @@ import axios from "../helpers/axiosFetch"; const validSha = new RegExp(/^[a-f0-9]{40}$/); export default async function getCommitHistory( + storageApiUrl, repoId = null, initCommitSha = null, path = null, @@ -14,7 +15,7 @@ export default async function getCommitHistory( if (!validSha.test(initCommitSha)) { return obj; } - let baseUrl = process.env.NEXT_PUBLIC_OBJECTS_URL + "/commits"; + let baseUrl = storageApiUrl + "/commits"; let params = { repository_id: Number(repoId), init_commit_id: initCommitSha, diff --git a/helpers/getContent.js b/helpers/getContent.js index c017825d..7f1c6603 100644 --- a/helpers/getContent.js +++ b/helpers/getContent.js @@ -2,6 +2,7 @@ const validSha = new RegExp(/^[a-f0-9]{40}$/); import axios from "../helpers/axiosFetch"; export default async function getContent( + storageApiUrl, repoId = null, commitSha = null, path = null, @@ -13,10 +14,7 @@ export default async function getContent( if (!validSha.test(commitSha)) { return obj; } - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/content" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/content"; + const baseUrl = storageApiUrl + "/content"; let params = { repository_id: Number(repoId), ref_id: commitSha, diff --git a/helpers/getDiff.js b/helpers/getDiff.js index 91347868..515822fa 100644 --- a/helpers/getDiff.js +++ b/helpers/getDiff.js @@ -2,6 +2,7 @@ import isNumber from "lodash/isNumber"; const validSha = new RegExp(/^[a-f0-9]{40}$/); export default async function getDiff( + storageApiUrl, repoId = null, commitSha = null, prevCommitSha = null, @@ -16,10 +17,7 @@ export default async function getDiff( if (!isNumber(numRepoId)) { return obj; } - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/diff" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/diff"; + const baseUrl = storageApiUrl + "/diff"; let params = { repository_id: numRepoId, commit_sha: commitSha, diff --git a/helpers/getDiffStats.js b/helpers/getDiffStats.js index 2fe901a6..f425bf28 100644 --- a/helpers/getDiffStats.js +++ b/helpers/getDiffStats.js @@ -2,6 +2,7 @@ import isNumber from "lodash/isNumber"; const validSha = new RegExp(/^[a-f0-9]{40}$/); export default async function getDiffStats( + storageApiUrl, repoId = null, commitSha = null, prevCommitSha, @@ -14,10 +15,7 @@ export default async function getDiffStats( if (!isNumber(numRepoId)) { return obj; } - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/diff" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/diff"; + const baseUrl = storageApiUrl + "/diff"; let params = { repository_id: numRepoId, commit_sha: commitSha, diff --git a/helpers/getGitServerAuthStatus.js b/helpers/getGitServerAuthStatus.js deleted file mode 100644 index a01edb57..00000000 --- a/helpers/getGitServerAuthStatus.js +++ /dev/null @@ -1,17 +0,0 @@ -export default async function getGitServerAuthStatus(apiClient, userAddress) { - if (!userAddress) return null; - try { - const res = await apiClient.queryCheckGitServerAuthorization( - userAddress, - process.env.NEXT_PUBLIC_GIT_SERVER_WALLET_ADDRESS - ); - if (res.status === 200 && res.data.haveAuthorization) { - return true; - } else { - return false; - } - } catch (e) { - console.error(e); - return null; - } -} diff --git a/helpers/getPullDiff.js b/helpers/getPullDiff.js index 4d05f45e..06881d70 100644 --- a/helpers/getPullDiff.js +++ b/helpers/getPullDiff.js @@ -2,6 +2,7 @@ import isNumber from "lodash/isNumber"; const validSha = new RegExp(/^[a-f0-9]{40}$/); export default async function getPullDiff( + storageApiUrl, baseRepoId = null, headRepoId = null, baseCommitSha = null, @@ -18,10 +19,7 @@ export default async function getPullDiff( if (!isNumber(baseRepoIdNum) || !isNumber(headRepoIdNum)) { return obj; } - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/pull/diff" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/diff"; + const baseUrl = storageApiUrl + "/pull/diff"; let params = { base_repository_id: baseRepoIdNum, head_repository_id: headRepoIdNum, diff --git a/helpers/getPullDiffStats.js b/helpers/getPullDiffStats.js index 33caa3af..ce55bcf2 100644 --- a/helpers/getPullDiffStats.js +++ b/helpers/getPullDiffStats.js @@ -2,6 +2,7 @@ import isNumber from "lodash/isNumber"; const validSha = new RegExp(/^[a-f0-9]{40}$/); export default async function getPullDiffStats( + storageApiUrl, baseRepoId = null, headRepoId = null, baseCommitSha = null, @@ -16,10 +17,7 @@ export default async function getPullDiffStats( if (!isNumber(baseRepoIdNum) || !isNumber(headRepoIdNum)) { return obj; } - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/pull/diff" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/diff"; + const baseUrl = storageApiUrl + "/pull/diff"; let params = { base_repository_id: baseRepoIdNum, head_repository_id: headRepoIdNum, diff --git a/helpers/getPullRequestCommits.js b/helpers/getPullRequestCommits.js index cf7d02dd..d5c0b5f7 100644 --- a/helpers/getPullRequestCommits.js +++ b/helpers/getPullRequestCommits.js @@ -1,6 +1,7 @@ const validSha = new RegExp(/^[a-f0-9]{40}$/); export default async function getPullRequestCommits( + storageApiUrl, baseRepoId = null, headRepoId = null, baseBranch = null, @@ -18,10 +19,7 @@ export default async function getPullRequestCommits( return obj; } - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/pull/commits" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/commits"; + const baseUrl = storageApiUrl + "/pull/commits"; let params = { base_repository_id: Number(baseRepoId), head_repository_id: Number(headRepoId), diff --git a/helpers/getStorageBridgeAuthStatus.js b/helpers/getStorageBridgeAuthStatus.js deleted file mode 100644 index 9dfecd77..00000000 --- a/helpers/getStorageBridgeAuthStatus.js +++ /dev/null @@ -1,22 +0,0 @@ -import { useApiClient } from "../context/ApiClientContext"; - -export default async function getStorageBridgeAuthStatus( - apiClient, - userAddress -) { - if (!userAddress) return null; - try { - const res = await apiClient.queryCheckStorageProviderAuthorization( - userAddress, - process.env.NEXT_PUBLIC_STORAGE_BRIDGE_WALLET_ADDRESS - ); - if (res.status === 200 && res.data.haveAuthorization) { - return true; - } else { - return false; - } - } catch (e) { - console.error(e); - return null; - } -} diff --git a/helpers/gitopiaLive.js b/helpers/gitopiaLive.js index 6308392d..a1dbcd06 100644 --- a/helpers/gitopiaLive.js +++ b/helpers/gitopiaLive.js @@ -289,6 +289,7 @@ function GitopiaLive(props) { "/" + props.repository.name } + repositoryId={props.repository.id} />
    ) : ( diff --git a/helpers/mergePullRequest.js b/helpers/mergePullRequest.js deleted file mode 100644 index 835c651f..00000000 --- a/helpers/mergePullRequest.js +++ /dev/null @@ -1,40 +0,0 @@ -export default async function mergePullRequest( - pullReqIid, - baseRepoId, - headRepoId, - baseBranch, - headBranch, - mergeStyle, - userName, - userEmail, - sender -) { - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/pull/merge" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/merge"; - let obj = {}; - let params = { - base_repository_id: Number(baseRepoId), - head_repository_id: Number(headRepoId), - base_branch: baseBranch, - head_branch: headBranch, - merge_style: mergeStyle, - user_name: userName, - user_email: userEmail, - sender: sender, - pull_request_iid: Number(pullReqIid), - }; - await fetch(baseUrl, { - method: "POST", // *GET, POST, PUT, DELETE, etc. - mode: "cors", // no-cors, *cors, same-origin - cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached - body: JSON.stringify(params), - }) - .then((response) => { - obj = response.json(); - }) - .catch((err) => console.error(err)); - - return obj; -} diff --git a/helpers/mergePullRequestCheck.js b/helpers/mergePullRequestCheck.js index e74388ac..eedf82b6 100644 --- a/helpers/mergePullRequestCheck.js +++ b/helpers/mergePullRequestCheck.js @@ -1,4 +1,5 @@ export default async function mergePullRequestCheck( + storageApiUrl, pullReqIid, baseRepoId, headRepoId, @@ -9,10 +10,7 @@ export default async function mergePullRequestCheck( userEmail, sender ) { - const baseUrl = - process.env.NODE_ENV === "development" - ? "/api/pull/check" - : process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/check"; + const baseUrl = storageApiUrl + "/pull/check"; let obj = {}; let params = { base_repository_id: Number(baseRepoId), diff --git a/helpers/providerSelector.js b/helpers/providerSelector.js index b7edd13d..0bd8ac66 100644 --- a/helpers/providerSelector.js +++ b/helpers/providerSelector.js @@ -3,7 +3,9 @@ import providers from "../providers.json"; const selectProvider = async () => { if (process.env.NODE_ENV === "production") { - const latencies = await Promise.all(providers.map(checkLatency)); + const latencies = await Promise.all( + providers.map((p) => checkLatency(p.rpcEndpoint)) + ); const lowestLatencyIndex = latencies.indexOf(Math.min(...latencies)); return providers[lowestLatencyIndex]; } diff --git a/helpers/storageProviderSelector.js b/helpers/storageProviderSelector.js new file mode 100644 index 00000000..976e6226 --- /dev/null +++ b/helpers/storageProviderSelector.js @@ -0,0 +1,28 @@ +import checkLatency from "./checkLatency"; + +const selectStorageProvider = async (providers) => { + try { + if (providers.length === 0) { + return null; + } + + const latencies = await Promise.all( + providers.map((p) => checkLatency(p.apiUrl)) + ); + const lowestLatencyIndex = latencies.indexOf(Math.min(...latencies)); + return providers[lowestLatencyIndex]; + } catch (error) { + console.error("Failed to select storage provider:", error); + return null; + } +} + +export default selectStorageProvider; + +export const getSavedStorageProvider = () => { + const providerInfo = localStorage.getItem("storageProviderInfo"); + if (providerInfo) { + return JSON.parse(providerInfo); + } + return null; +}; diff --git a/next.config.js b/next.config.js index 8f589f4c..df8ccae6 100644 --- a/next.config.js +++ b/next.config.js @@ -63,55 +63,19 @@ module.exports = withBundleAnalyzer({ }, ...(process.env.NODE_ENV === "production" ? { - compiler: { - removeConsole: { - exclude: ["error"], - }, - reactRemoveProperties: { properties: ["^data-test$"] }, + compiler: { + removeConsole: { + exclude: ["error"], }, - } + reactRemoveProperties: { properties: ["^data-test$"] }, + }, + } : {}), async rewrites() { return [ { source: "/api/faucet", destination: process.env.NEXT_PUBLIC_FAUCET_URL, - }, - { - source: "/api/objects/:path*", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/objects/:path*", - }, - { - source: "/api/diff", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/diff", - }, - { - source: "/api/pull/diff", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/diff", - }, - { - source: "/api/fork", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/fork", - }, - { - source: "/api/pull/merge", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/merge", - }, - { - source: "/api/pull/commits", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/commits", - }, - { - source: "/api/pull/check", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/pull/check", - }, - { - source: "/api/content", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/content", - }, - { - source: "/api/commits/:path*", - destination: process.env.NEXT_PUBLIC_OBJECTS_URL + "/commits/:path*", } ]; }, diff --git a/package.json b/package.json index 2452c4a6..75ddf8fd 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@cosmjs/ledger-amino": "^0.32.4", "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", - "@gitopia/gitopia-js": "2.1.0", + "@gitopia/gitopia-js": "3.0.0", "@heroicons/react": "^2.1.5", "@ledgerhq/hw-transport-webhid": "^6.27.13", "@ledgerhq/hw-transport-webusb": "^6.27.13", diff --git a/pages/[userId]/[repositoryId]/commit/[commitId].js b/pages/[userId]/[repositoryId]/commit/[commitId].js index 1048d6f5..14992d10 100644 --- a/pages/[userId]/[repositoryId]/commit/[commitId].js +++ b/pages/[userId]/[repositoryId]/commit/[commitId].js @@ -14,6 +14,7 @@ import CommitDetailRow from "../../../../components/repository/commitDetailRow"; import DiffView from "../../../../components/repository/diffView"; import getCommit from "../../../../helpers/getCommit"; import { useErrorStatus } from "../../../../hooks/errorHandler"; +import { useApiClient } from "../../../../context/ApiClientContext"; export async function getStaticProps() { return { props: {} }; @@ -30,6 +31,7 @@ function RepositoryCommitDiffView(props) { const router = useRouter(); const { repository } = useRepository(); const { setErrorStatusCode } = useErrorStatus(); + const { storageApiUrl } = useApiClient(); const [viewType, setViewType] = useState("unified"); @@ -44,9 +46,10 @@ function RepositoryCommitDiffView(props) { useEffect(() => { async function initDiff() { if (repository.id) { - const c = await getCommit(repository.id, router.query.commitId); + const c = await getCommit(storageApiUrl, repository.id, router.query.commitId); if (c && c.id) { const data = await getDiffStats( + storageApiUrl, Number(repository.id), router.query.commitId ); diff --git a/pages/[userId]/[repositoryId]/commits/[[...branch]].js b/pages/[userId]/[repositoryId]/commits/[[...branch]].js index 0b7321b7..08b713c0 100644 --- a/pages/[userId]/[repositoryId]/commits/[[...branch]].js +++ b/pages/[userId]/[repositoryId]/commits/[[...branch]].js @@ -16,6 +16,7 @@ import CommitDetailRow from "../../../../components/repository/commitDetailRow"; import getCommitHistory from "../../../../helpers/getCommitHistory"; import { useErrorStatus } from "../../../../hooks/errorHandler"; import pluralize from "../../../../helpers/pluralize"; +import { useApiClient } from "../../../../context/ApiClientContext"; export async function getStaticProps() { return { props: {} }; @@ -38,6 +39,7 @@ function RepositoryCommitTreeView(props) { const [loadingMore, setLoadingMore] = useState(false); const [branchName, setBranchName] = useState(""); const [commitsLength, setCommitsLength] = useState(0); + const { storageApiUrl } = useApiClient(); useEffect(() => { async function initBranch() { @@ -73,6 +75,7 @@ function RepositoryCommitTreeView(props) { if (branchName === "") return; setLoadingMore(true); const res = await getCommitHistory( + storageApiUrl, repository.id, getBranchSha(branchName, repository.branches, repository.tags), null, diff --git a/pages/[userId]/[repositoryId]/compare/[[...branchTuple]].js b/pages/[userId]/[repositoryId]/compare/[[...branchTuple]].js index 9d458aa5..5d93b989 100644 --- a/pages/[userId]/[repositoryId]/compare/[[...branchTuple]].js +++ b/pages/[userId]/[repositoryId]/compare/[[...branchTuple]].js @@ -70,7 +70,7 @@ function RepositoryCompareView(props) { const [textEntered, setEnteredText] = useState(""); const [issueList, setIssueList] = useState([]); const [issueArray, setIssueArray] = useState([]); - const { apiClient, cosmosBankApiClient, cosmosFeegrantApiClient } = + const { apiClient, cosmosBankApiClient, cosmosFeegrantApiClient, storageApiUrl } = useApiClient(); const setDefaultBranches = (r) => { @@ -290,12 +290,14 @@ function RepositoryCompareView(props) { async function initStats() { const [diff, commits] = await Promise.all([ getPullDiffStats( + storageApiUrl, compare.target.repository.id, compare.source.repository.id, compare.target.sha, compare.source.sha ), await getPullRequestCommits( + storageApiUrl, compare.target.repository.id, compare.source.repository.id, compare.target.name, @@ -558,7 +560,7 @@ function RepositoryCompareView(props) { > {issue.title.split(" ").length > 4 ? issue.title.split(" ").splice(0, 4).join(" ") + - "..." + "..." : issue.title}
    repository.owner.type === "USER" ? [ - { - id: repository.owner.address, - permission: "CREATOR", - }, - ] + { + id: repository.owner.address, + permission: "CREATOR", + }, + ] : [])(), ...repository.collaborators, ]} @@ -694,14 +696,14 @@ function RepositoryCompareView(props) {
    {reviewers.length ? reviewers.map((a, i) => ( -
    - -
    - )) +
    + +
    + )) : "No one"}
    @@ -712,11 +714,11 @@ function RepositoryCompareView(props) { ...(() => repository.owner.type === "USER" ? [ - { - id: repository.owner.address, - permission: "CREATOR", - }, - ] + { + id: repository.owner.address, + permission: "CREATOR", + }, + ] : [])(), ...repository.collaborators, ]} @@ -727,14 +729,14 @@ function RepositoryCompareView(props) {
    {assignees.length ? assignees.map((a, i) => ( -
    - -
    - )) +
    + +
    + )) : "No one"}
    @@ -751,24 +753,24 @@ function RepositoryCompareView(props) {
    {labels.length ? labels.map((l, i) => { - let label = find(repository.labels, { - id: l, - }) || { - name: "", - color: "", - }; - return ( - - - ); - }) + let label = find(repository.labels, { + id: l, + }) || { + name: "", + color: "", + }; + return ( + + + ); + }) : "None yet"}
    @@ -819,17 +821,17 @@ function RepositoryCompareView(props) { {compare.source.repository.id === - compare.target.repository.id + compare.target.repository.id ? compare.target.name + - " is ahead of " + - compare.source.name + " is ahead of " + + compare.source.name : shrinkAddress(compare.target.repository.owner.id) + - "/" + - compare.target.name + - " is ahead of " + - shrinkAddress(compare.source.repository.owner.id) + - "/" + - compare.source.name} + "/" + + compare.target.name + + " is ahead of " + + shrinkAddress(compare.source.repository.owner.id) + + "/" + + compare.source.name}
    @@ -853,21 +855,21 @@ function RepositoryCompareView(props) { {compare.source.repository.id === - compare.target.repository.id + compare.target.repository.id ? "There isn't anything to compare. " + - compare.source.name + - " and " + - compare.target.name + - " are the same." + compare.source.name + + " and " + + compare.target.name + + " are the same." : "There isn't anything to compare. " + - shrinkAddress(compare.source.repository.owner.id) + - "/" + - compare.source.name + - " and " + - shrinkAddress(compare.target.repository.owner.id) + - "/" + - compare.target.name + - " are the same."} + shrinkAddress(compare.source.repository.owner.id) + + "/" + + compare.source.name + + " and " + + shrinkAddress(compare.target.repository.owner.id) + + "/" + + compare.target.name + + " are the same."} diff --git a/pages/[userId]/[repositoryId]/fork.js b/pages/[userId]/[repositoryId]/fork.js index ad212e2d..fcf6b03c 100644 --- a/pages/[userId]/[repositoryId]/fork.js +++ b/pages/[userId]/[repositoryId]/fork.js @@ -12,10 +12,8 @@ import useRepository from "../../../hooks/useRepository"; import TextInput from "../../../components/textInput"; import { forkRepository, - authorizeGitServer, } from "../../../store/actions/repository"; import { useRouter } from "next/router"; -import getGitServerAuthorization from "../../../helpers/getGitServerAuthStatus"; import { useApiClient } from "../../../context/ApiClientContext"; export async function getStaticProps() { @@ -63,9 +61,6 @@ function RepositoryInvokeForkView(props) { ); const [isForking, setIsForking] = useState(false); const [forkingSuccess, setForkingSuccess] = useState(false); - const [forkingAccess, setForkingAccess] = useState(false); - const [grantAccessDialogShown, setGrantAccessDialogShown] = useState(false); - const [isGrantingAccess, setIsGrantingAccess] = useState(false); const sanitizedNameTest = new RegExp(/[^\w.-]/g); const router = useRouter(); @@ -125,14 +120,6 @@ function RepositoryInvokeForkView(props) { }; const invokeForkRepository = async () => { - let forkingAccess = await getGitServerAuthorization( - apiClient, - props.selectedAddress - ); - if (!forkingAccess) { - setGrantAccessDialogShown(true); - return; - } setIsForking(true); let validate = await validateRepository(); console.log(validate); @@ -163,11 +150,7 @@ function RepositoryInvokeForkView(props) { useEffect(() => { setForkRepositoryName(repository?.name || ""); setForkRepositoryDescription(repository?.description || ""); - if (repository?.owner?.address === props.currentDashboard) { - setOwnerId(""); - } else { - setOwnerId(props.currentDashboard); - } + setOwnerId(props.currentDashboard); setForkOnlyOneBranchName(repository?.defaultBranch); }, [repository, props.currentDashboard]); @@ -358,68 +341,6 @@ function RepositoryInvokeForkView(props) { - -
    -
    -

    - Gitopia data server does not have repository forking access on - behalf of your account. -

    -

    Server Address:

    -

    - {process.env.NEXT_PUBLIC_GIT_SERVER_WALLET_ADDRESS} -

    -
    - - -
    -
    -