mirror of
https://github.com/alexgo-io/ballot.git
synced 2026-01-12 08:54:02 +08:00
feature: Summary Page can be created with list of published polls.
This commit is contained in:
@@ -86,6 +86,10 @@ export function DashboardNavBarComponent() {
|
||||
>
|
||||
My votes
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="/summary">
|
||||
Summary page
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
onClick={() => {
|
||||
switchAccount(window?.location?.href);
|
||||
|
||||
302
components/summary/BuilderComponent.js
Normal file
302
components/summary/BuilderComponent.js
Normal file
@@ -0,0 +1,302 @@
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { Constants } from "../../common/constants";
|
||||
import { getFileFromGaia, getMyStxAddress, getStacksAPIPrefix, getUserData, putFileToGaia, userSession } from "../../services/auth";
|
||||
import { convertToDisplayDateFormat } from "../../services/utils";
|
||||
import styles from "../../styles/Builder.module.css";
|
||||
import dashboardStyles from "../../styles/Dashboard.module.css";
|
||||
import ChoosePollsPopup from "./ChoosePollsPopup";
|
||||
|
||||
export default function SummaryBuilderComponent(props) {
|
||||
// Variables
|
||||
|
||||
// Gaia address
|
||||
const [gaiaAddress, setGaiaAddress] = useState();
|
||||
|
||||
// Summary object
|
||||
const [summaryObject, setSummaryObject] = useState();
|
||||
|
||||
// Processing flag
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
// Error message
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
// Show/Hide ChoosePolls Popup
|
||||
const [showChoosePollsPopupFlag, setShowChoosePollsPopupFlag] = useState(false);
|
||||
|
||||
const [urlSuffix, setUrlSuffix] = useState();
|
||||
|
||||
// Functions
|
||||
useEffect(() => {
|
||||
let isCancelled = false;
|
||||
|
||||
// Get Summary object
|
||||
getFileFromGaia("summary.json", { decrypt: false }).then(
|
||||
(response) => {
|
||||
if (response && !isCancelled) {
|
||||
setSummaryObject(JSON.parse(response));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
// File does not exit in gaia
|
||||
if (error && error.code == "does_not_exist" && !isCancelled) {
|
||||
// Initialize new poll
|
||||
setSummaryObject(initializeNewSummary());
|
||||
}
|
||||
});
|
||||
|
||||
// Get gaia address
|
||||
if (userSession && userSession.isUserSignedIn()) {
|
||||
setGaiaAddress(getUserData()?.gaiaHubConfig?.address);
|
||||
}
|
||||
|
||||
// Get .btc address
|
||||
getBTCDomainFromBlockchain();
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getBTCDomainFromBlockchain = async () => {
|
||||
// Get btc domain for logged in user
|
||||
const response = await fetch(
|
||||
getStacksAPIPrefix() + "/v1/addresses/stacks/" + getMyStxAddress()
|
||||
);
|
||||
const responseObject = await response.json();
|
||||
|
||||
// Testnet code
|
||||
if (Constants.STACKS_MAINNET_FLAG == false) {
|
||||
setUrlSuffix(getUserData()?.gaiaHubConfig?.address);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get btc dns
|
||||
if (responseObject?.names?.length > 0) {
|
||||
const btcDNS = responseObject.names.filter((bns) =>
|
||||
bns.endsWith(".btc")
|
||||
);
|
||||
|
||||
// Check does BTC dns is available
|
||||
if (btcDNS && btcDNS.length > 0) {
|
||||
const _dns = btcDNS[0];
|
||||
|
||||
setUrlSuffix(_dns);
|
||||
} else {
|
||||
setUrlSuffix(getUserData()?.gaiaHubConfig?.address);
|
||||
}
|
||||
} else {
|
||||
setUrlSuffix(getUserData()?.gaiaHubConfig?.address);
|
||||
}
|
||||
};
|
||||
|
||||
function initializeNewSummary() {
|
||||
return {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"polls": {
|
||||
"list": [],
|
||||
"ref": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = e => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
// If value is empty, then delete key from previous state
|
||||
if (!value && summaryObject) {
|
||||
// Delete key from JSON
|
||||
delete summaryObject[name];
|
||||
} else {
|
||||
// Update the value
|
||||
summaryObject[name] = value;
|
||||
}
|
||||
|
||||
setSummaryObject({ ...summaryObject });
|
||||
};
|
||||
|
||||
function openChoosePollsPopup() {
|
||||
setShowChoosePollsPopupFlag(true);
|
||||
}
|
||||
|
||||
function publishSummary() {
|
||||
// Start processing
|
||||
setIsProcessing(true);
|
||||
|
||||
// Clear message
|
||||
setErrorMessage("");
|
||||
|
||||
// Save to gaia
|
||||
putFileToGaia("summary.json", JSON.stringify(summaryObject), { "encrypt": false }).then(response => {
|
||||
// Saved successfully message
|
||||
setErrorMessage("Summary page is published.");
|
||||
|
||||
// Stop processing
|
||||
setIsProcessing(false);
|
||||
});
|
||||
}
|
||||
|
||||
function getEachRow(pollIndexObject) {
|
||||
const gaiaAddress = getUserData()?.gaiaHubConfig?.address;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Title */}
|
||||
<div className="d-flex align-items-center" style={{ marginBottom: "10px", columnGap: "10px", width: "100%" }}>
|
||||
<div className="text-truncate" style={{ fontSize: "18px", fontWeight: 600 }}>
|
||||
{pollIndexObject?.title ? pollIndexObject?.title : "..."}
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className={pollIndexObject?.status == "draft" ? dashboardStyles.all_polls_status_box_draft : ((pollIndexObject?.endAt && (new Date(pollIndexObject?.endAt) < new Date()))) ? dashboardStyles.all_polls_status_box_closed : dashboardStyles.all_polls_status_box_active}>
|
||||
{
|
||||
pollIndexObject?.status == "draft" ? "Draft" :
|
||||
((pollIndexObject?.endAt && (new Date(pollIndexObject?.endAt) < new Date())) ?
|
||||
"Closed" : "Active")
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{
|
||||
pollIndexObject?.description ?
|
||||
<p className={"text_truncate_2" + ' ' + dashboardStyles.all_polls_description}>
|
||||
{pollIndexObject?.description ? pollIndexObject?.description : "..."}
|
||||
</p>
|
||||
: <></>
|
||||
}
|
||||
|
||||
<div style={{ fontSize: "14px", color: "#737373" }}>
|
||||
<span>
|
||||
Last Modified : {convertToDisplayDateFormat(pollIndexObject?.updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<Button variant="danger" onClick={(event) => { event.stopPropagation(); deletePoll(pollIndexObject, setAllPolls) }}
|
||||
disabled={isDeleting}>
|
||||
Delete
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Design
|
||||
return (
|
||||
<>
|
||||
{summaryObject ?
|
||||
<>
|
||||
<div className={styles.builder_container}>
|
||||
{/* Left side */}
|
||||
<div className={styles.builder_left} style={{ marginBottom: "100px" }}>
|
||||
{/* Title */}
|
||||
<h5 style={{ fontSize: "20px", fontWeight: "600" }}>Summary</h5>
|
||||
|
||||
{/* Fields */}
|
||||
<Form style={{ margin: "20px 0 20px 0" }}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label className='ballot_labels'>Title</Form.Label>
|
||||
<Form.Control type="text" name="title" value={summaryObject.title} onChange={handleChange}
|
||||
className="ballot_input" />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label className='ballot_labels'>Description</Form.Label>
|
||||
<Form.Control as="textarea" name="description" value={summaryObject.description} rows={5} onChange={handleChange} className="ballot_input" />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<div style={{ display: "flex", marginTop: "10px" }}>
|
||||
<Button style={{ width: "100%" }} className="action_dashed_btn" onClick={() => { openChoosePollsPopup(); }}>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="12" fill="#ECEFF1" />
|
||||
<path d="M12 8V12M12 16V12M12 12H16M12 12H8" stroke="black" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
|
||||
Choose polls
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
|
||||
{/* List of polls */}
|
||||
<div className={dashboardStyles.all_polls_list_outline_box}>
|
||||
{summaryObject?.polls?.list.map(
|
||||
(pollId, i) => (
|
||||
<div key={i} className={dashboardStyles.all_polls_list_box}>
|
||||
{getEachRow(summaryObject?.polls?.ref[pollId])}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side */}
|
||||
<div className={styles.builder_right}>
|
||||
<div style={{ position: "sticky", top: "119px" }}>
|
||||
<div className={styles.builder_right_section}>
|
||||
|
||||
<a href={"/" + urlSuffix} target="_blank" style={{ textDecoration: "none" }}>
|
||||
<Button style={{ width: "100%", marginBottom: "10px" }} className="action_secondary_btn">
|
||||
Preview
|
||||
<svg style={{ marginLeft: "6px" }}
|
||||
width="8"
|
||||
height="8"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.5044 0.743397C3.5044 0.33283 3.83723 -6.71395e-08 4.2478 0L11.2566 6.60206e-07C11.6672 6.60206e-07 12 0.33283 12 0.743397L12 7.7522C12 8.16277 11.6672 8.4956 11.2566 8.4956C10.846 8.4956 10.5132 8.16277 10.5132 7.7522V2.53811L1.26906 11.7823C0.978742 12.0726 0.50805 12.0726 0.217736 11.7823C-0.0725787 11.4919 -0.0725784 11.0213 0.217736 10.7309L9.46189 1.48679L4.2478 1.48679C3.83723 1.48679 3.5044 1.15396 3.5044 0.743397Z"
|
||||
fill="initial"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<Button variant="dark" style={{ width: "100%" }}
|
||||
onClick={() => { publishSummary() }} disabled={isProcessing}>
|
||||
Publish
|
||||
</Button>
|
||||
|
||||
{/* Error Message */}
|
||||
{errorMessage &&
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<span style={{ fontSize: "14px" }}>{errorMessage}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<div style={{ width: "100%", maxWidth: "52px", height: "17px", marginBottom: "20px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
|
||||
<div style={{ width: "100px", height: "22px", marginBottom: "8px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
<div style={{ width: "100%", height: "36px", marginBottom: "16px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
|
||||
<div style={{ width: "130px", height: "22px", marginBottom: "8px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
<div style={{ width: "100%", height: "132px", marginBottom: "16px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
|
||||
<div style={{ width: "150px", height: "22px", marginBottom: "8px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
<div style={{ width: "100%", height: "36px", marginBottom: "16px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
|
||||
<div style={{ width: "170px", height: "22px", marginBottom: "8px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
<div style={{ width: "100%", height: "36px", marginBottom: "16px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
{/* Choose polls popup */}
|
||||
<ChoosePollsPopup summaryObject={summaryObject} handleSummaryChange={handleChange}
|
||||
showChoosePollsPopupFlag={showChoosePollsPopupFlag} setShowChoosePollsPopupFlag={setShowChoosePollsPopupFlag} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
210
components/summary/ChoosePollsPopup.js
Normal file
210
components/summary/ChoosePollsPopup.js
Normal file
@@ -0,0 +1,210 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Modal, Spinner, Form, Button } from "react-bootstrap";
|
||||
import { getFileFromGaia } from "../../services/auth";
|
||||
import { convertToDisplayDateFormat } from "../../services/utils";
|
||||
import styles from "../../styles/ChoosePollsPopup.module.css";
|
||||
|
||||
export default function ChoosePollsPopup(props) {
|
||||
// Parent parameters
|
||||
const { showChoosePollsPopupFlag, summaryObject, handleSummaryChange } = props;
|
||||
|
||||
// Variables
|
||||
// Handle close popup
|
||||
const handleCloseChoosePollsPopup = () => {
|
||||
props.setShowChoosePollsPopupFlag(false);
|
||||
};
|
||||
|
||||
// Summary polls
|
||||
const [summaryPolls, setSummaryPolls] = useState();
|
||||
|
||||
// All polls
|
||||
const [allPolls, setAllPolls] = useState();
|
||||
|
||||
// Loading
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Functions
|
||||
useEffect(() => {
|
||||
if (showChoosePollsPopupFlag) {
|
||||
// Take summary polls
|
||||
setSummaryPolls(JSON.parse(JSON.stringify(summaryObject?.polls)));
|
||||
|
||||
// Start loading
|
||||
setIsLoading(true);
|
||||
|
||||
getFileFromGaia("pollIndex.json", {}).then(
|
||||
(response) => {
|
||||
if (response) {
|
||||
setAllPolls(JSON.parse(response));
|
||||
}
|
||||
|
||||
// Stop loading
|
||||
setIsLoading(false);
|
||||
},
|
||||
(error) => {
|
||||
// File does not exit in gaia
|
||||
if (error && error.code == "does_not_exist") {
|
||||
setAllPolls({
|
||||
list: [],
|
||||
ref: {}
|
||||
});
|
||||
}
|
||||
|
||||
// Stop loading
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [showChoosePollsPopupFlag]);
|
||||
|
||||
function getEachRow(pollIndexObject) {
|
||||
return (
|
||||
<div>
|
||||
{/* Title */}
|
||||
<div className="d-flex align-items-center" style={{ marginBottom: "10px", columnGap: "10px", width: "100%" }}>
|
||||
<div className="text-truncate" style={{ fontSize: "18px", fontWeight: 600 }}>
|
||||
{pollIndexObject?.title ? pollIndexObject?.title : "..."}
|
||||
</div>
|
||||
{/* Status */}
|
||||
<div className={pollIndexObject?.status == "draft" ? styles.all_polls_status_box_draft : ((pollIndexObject?.endAt && (new Date(pollIndexObject?.endAt) < new Date()))) ? styles.all_polls_status_box_closed : styles.all_polls_status_box_active}>
|
||||
{
|
||||
pollIndexObject?.status == "draft" ? "Draft" :
|
||||
((pollIndexObject?.endAt && (new Date(pollIndexObject?.endAt) < new Date())) ?
|
||||
"Closed" : "Active")
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{
|
||||
pollIndexObject?.description ?
|
||||
<p className={"text_truncate_2" + ' ' + styles.all_polls_description}>
|
||||
{pollIndexObject?.description ? pollIndexObject?.description : "..."}
|
||||
</p>
|
||||
: <></>
|
||||
}
|
||||
|
||||
<div style={{ fontSize: "14px", color: "#737373" }}>
|
||||
<span>
|
||||
Last Modified : {convertToDisplayDateFormat(pollIndexObject?.updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function onClickOfPolls(pollId) {
|
||||
if (!summaryPolls?.ref[pollId]) {
|
||||
summaryPolls.list.push(pollId);
|
||||
summaryPolls.ref[pollId] = allPolls?.ref?.[pollId];
|
||||
} else {
|
||||
const summaryIndex = summaryPolls.list.findIndex(item => item == pollId);
|
||||
if (summaryIndex >= 0) {
|
||||
summaryPolls.list.splice(summaryIndex, 1);
|
||||
}
|
||||
delete summaryPolls.ref[pollId];
|
||||
}
|
||||
|
||||
setSummaryPolls({ ...summaryPolls });
|
||||
}
|
||||
|
||||
function saveSummaryPolls() {
|
||||
handleSummaryChange({
|
||||
target: {
|
||||
name: "polls",
|
||||
value: { ...summaryPolls }
|
||||
}
|
||||
});
|
||||
|
||||
handleCloseChoosePollsPopup();
|
||||
}
|
||||
|
||||
const handleChange = e => {
|
||||
const { name, value } = e.target;
|
||||
}
|
||||
|
||||
// View
|
||||
return (
|
||||
<>
|
||||
{/* QR code */}
|
||||
<Modal
|
||||
show={showChoosePollsPopupFlag}
|
||||
onHide={handleCloseChoosePollsPopup}
|
||||
keyboard={false}
|
||||
centered
|
||||
size="xl"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className={styles.summary_modal_header_box}>
|
||||
<div>Choose polls</div>
|
||||
<button
|
||||
className={styles.summary_modal_close_icon_btn_box}
|
||||
onClick={handleCloseChoosePollsPopup}
|
||||
>
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 10 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0.898377 0.898804C1.2108 0.586385 1.71733 0.586385 2.02975 0.898804L4.9996 3.86865L7.96945 0.898804C8.28186 0.586385 8.7884 0.586385 9.10082 0.898804C9.41324 1.21122 9.41324 1.71776 9.10082 2.03018L6.13097 5.00002L9.10082 7.96987C9.41324 8.28229 9.41324 8.78882 9.10082 9.10124C8.7884 9.41366 8.28186 9.41366 7.96945 9.10124L4.9996 6.13139L2.02975 9.10124C1.71733 9.41366 1.2108 9.41366 0.898377 9.10124C0.585958 8.78882 0.585958 8.28229 0.898377 7.96987L3.86823 5.00002L0.898377 2.03018C0.585958 1.71776 0.585958 1.21122 0.898377 0.898804Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className={styles.summary_modal_body_box} >
|
||||
{
|
||||
// Loading
|
||||
isLoading ? (
|
||||
<div style={{ textAlign: "center", padding: "10px" }}>
|
||||
<Spinner animation="border" variant="secondary" size="md" />
|
||||
</div>
|
||||
) : // Once data found
|
||||
(allPolls && allPolls?.list?.length > 0) ? (
|
||||
<div className={styles.all_polls_list_outline_box}>
|
||||
{allPolls?.list.map(
|
||||
(pollId, i) => (
|
||||
(allPolls?.ref?.[pollId] && allPolls?.ref?.[pollId]?.status != "draft") &&
|
||||
<div key={i} style={{ display: "flex" }} className={styles.all_polls_list_box} onClick={() => { onClickOfPolls(pollId) }}>
|
||||
<div>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Check
|
||||
inline
|
||||
type="checkbox"
|
||||
name={pollId}
|
||||
onChange={handleChange}
|
||||
checked={summaryPolls?.ref?.[pollId] ? true : false}
|
||||
/>
|
||||
</Form.Group>
|
||||
</div>
|
||||
<div >
|
||||
{getEachRow(allPolls.ref[pollId])}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: "0px 20px 10px", fontSize: "14px" }}>
|
||||
Only published polls will be listed here.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<Modal.Footer>
|
||||
<Button className="action_secondary_btn" onClick={handleCloseChoosePollsPopup}>Close</Button>
|
||||
<Button variant="dark" onClick={() => { saveSummaryPolls() }} disabled={isLoading}>Done</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
101
components/summary/SummaryComponent.js
Normal file
101
components/summary/SummaryComponent.js
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { convertToDisplayDateFormat } from "../../services/utils";
|
||||
import styles from "../../styles/Dashboard.module.css";
|
||||
|
||||
export default function SummaryComponent(props) {
|
||||
// Variables
|
||||
const {
|
||||
summaryObject,
|
||||
gaiaAddress,
|
||||
allPolls } = props;
|
||||
|
||||
// Function
|
||||
function getEachRow(pollIndexObject) {
|
||||
return (
|
||||
<Link href={pollIndexObject?.status == "draft" ? `/builder/${pollIndexObject.id}/draft` : `/${pollIndexObject.id}/${gaiaAddress}`}>
|
||||
<div>
|
||||
{/* Title */}
|
||||
<div className="d-flex align-items-center" style={{ marginBottom: "10px", columnGap: "10px", width: "100%" }}>
|
||||
<div className="text-truncate" style={{ fontSize: "18px", fontWeight: 600 }}>
|
||||
{pollIndexObject?.title ? pollIndexObject?.title : "..."}
|
||||
</div>
|
||||
{/* Status */}
|
||||
<div className={pollIndexObject?.status == "draft" ? styles.all_polls_status_box_draft : ((pollIndexObject?.endAt && (new Date(pollIndexObject?.endAt) < new Date()))) ? styles.all_polls_status_box_closed : styles.all_polls_status_box_active}>
|
||||
{
|
||||
pollIndexObject?.status == "draft" ? "Draft" :
|
||||
((pollIndexObject?.endAt && (new Date(pollIndexObject?.endAt) < new Date())) ?
|
||||
"Closed" : "Active")
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{
|
||||
pollIndexObject?.description ?
|
||||
<p className={"text_truncate_2" + ' ' + styles.all_polls_description}>
|
||||
{pollIndexObject?.description ? pollIndexObject?.description : "..."}
|
||||
</p>
|
||||
: <></>
|
||||
}
|
||||
|
||||
<div style={{ fontSize: "14px", color: "#737373" }}>
|
||||
<span>
|
||||
Last Modified : {convertToDisplayDateFormat(pollIndexObject?.updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
// Design
|
||||
return (
|
||||
<>
|
||||
{summaryObject &&
|
||||
<section>
|
||||
<div className={styles.dashboard_container}>
|
||||
<div style={{ padding: "10px 0 100px 0", maxWidth: "700px", width: "100%" }}>
|
||||
|
||||
{/* Title */}
|
||||
<h1 style={{ fontSize: "24px", fontWeight: "600" }}>{summaryObject?.title}</h1>
|
||||
|
||||
{/* Description */}
|
||||
<div style={{ marginBottom: "24px", whiteSpace: "pre-wrap" }}>
|
||||
<p style={{ lineHeight: "1.7" }}
|
||||
dangerouslySetInnerHTML={{ __html: summaryObject?.description }}>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* List of all polls */}
|
||||
{allPolls?.list && allPolls?.ref ?
|
||||
(allPolls?.list?.length > 0) &&
|
||||
<>
|
||||
{/* List of polls */}
|
||||
<div className={styles.all_polls_list_outline_box}>
|
||||
{allPolls?.list.map(
|
||||
(pollId, i) => (
|
||||
<div key={i} className={styles.all_polls_list_box}>
|
||||
{getEachRow(allPolls.ref[pollId])}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
{/* Loading */}
|
||||
<div style={{ width: "100%", maxWidth: "100px", height: "24px", marginBottom: "40px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
|
||||
<div style={{ width: "100%", height: "110px", marginBottom: "10px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
<div style={{ width: "100%", height: "110px", marginBottom: "10px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
<div style={{ width: "100%", height: "110px", backgroundColor: "#eceff1", borderRadius: "4px" }}></div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
125
pages/[param].js
Normal file
125
pages/[param].js
Normal file
@@ -0,0 +1,125 @@
|
||||
import Head from "next/head";
|
||||
import { Col, Container, Row } from "react-bootstrap";
|
||||
import { Constants } from "../common/constants";
|
||||
import SummaryComponent from "../components/summary/SummaryComponent";
|
||||
import { getStacksAPIPrefix } from "../services/auth";
|
||||
|
||||
export default function SummaryPage(props) {
|
||||
// Variables
|
||||
const { summaryObject, gaiaAddress, allPolls } = props;
|
||||
const title = `${summaryObject?.title} | Ballot`;
|
||||
const description = summaryObject?.description?.substr(0, 160);
|
||||
const metaImage = "https://ballot.gg/images/ballot-meta.png";
|
||||
const displayURL = "";
|
||||
|
||||
// Functions
|
||||
|
||||
// Design
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<meta name="robots" content="index,follow" />
|
||||
|
||||
{/* Favicon */}
|
||||
<link rel="icon" href={"/favicon.ico"} />
|
||||
|
||||
{/* Facebook Meta Tags */}
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:url" content={displayURL} />
|
||||
<meta property="og:image" content={metaImage} />
|
||||
<meta property="og:site_name" content="ballot.gg" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
|
||||
{/* Twitter Meta Tags */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:url" content={displayURL} />
|
||||
<meta name="twitter:image" content={metaImage} />
|
||||
{/* <meta name="twitter:site" content="@ballot_gg" /> */}
|
||||
</Head>
|
||||
|
||||
{/* Outer layer */}
|
||||
<Container className="ballot_container">
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
{/* Body */}
|
||||
<SummaryComponent summaryObject={summaryObject} gaiaAddress={gaiaAddress} allPolls={allPolls} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// This gets called on every request
|
||||
export async function getServerSideProps(context) {
|
||||
// Get path param
|
||||
const { params } = context;
|
||||
const { param } = params;
|
||||
let summaryObject = null;
|
||||
let gaiaAddress = null;
|
||||
let allPolls = null;
|
||||
|
||||
// If it is .btc address
|
||||
if (param?.toString().endsWith(".btc")) {
|
||||
try {
|
||||
// Get name details
|
||||
const getZoneFileUrl = `${getStacksAPIPrefix()}/v1/names/${param}/zonefile`;
|
||||
const response = await fetch(getZoneFileUrl);
|
||||
const zoneFile = await response.json();
|
||||
|
||||
// Get profile details
|
||||
if (zoneFile && zoneFile["zonefile"]) {
|
||||
const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
const profileUrl = zoneFile["zonefile"].match(urlRegex)[0];
|
||||
const response = await fetch(profileUrl);
|
||||
const profileObject = await response.json();
|
||||
|
||||
// Get gaia address
|
||||
if (profileObject[0]['decodedToken']?.['payload']?.["claim"]?.['apps']?.["https://ballot.gg"]) {
|
||||
const gaiaPrefixUrl = profileObject[0]['decodedToken']?.['payload']?.["claim"]?.['apps']?.["https://ballot.gg"];
|
||||
const splittedArray = gaiaPrefixUrl.split("/");
|
||||
|
||||
gaiaAddress = splittedArray.pop();
|
||||
while (splittedArray.length > 0 && !gaiaAddress) {
|
||||
gaiaAddress = splittedArray.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
} else if (param) {
|
||||
gaiaAddress = param;
|
||||
}
|
||||
|
||||
if (gaiaAddress) {
|
||||
// Form gaia url
|
||||
let summaryGaiaUrl = Constants.GAIA_HUB_PREFIX + gaiaAddress + "/summary.json";
|
||||
|
||||
try {
|
||||
const response = await fetch(summaryGaiaUrl);
|
||||
summaryObject = await response.json();
|
||||
allPolls = summaryObject?.polls ? summaryObject?.polls : null;
|
||||
} catch (error) {
|
||||
// Summary not found
|
||||
summaryObject = null;
|
||||
allPolls = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass data to the page via props
|
||||
return {
|
||||
props: {
|
||||
summaryObject,
|
||||
gaiaAddress,
|
||||
allPolls
|
||||
},
|
||||
};
|
||||
}
|
||||
23
pages/summary/index.js
Normal file
23
pages/summary/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Col, Container, Row } from "react-bootstrap";
|
||||
import { DashboardNavBarComponent } from "../../components/common/DashboardNavBarComponent";
|
||||
import SummaryBuilderComponent from "../../components/summary/BuilderComponent";
|
||||
|
||||
export default function SummaryBuilder() {
|
||||
// Design
|
||||
return (
|
||||
<>
|
||||
{/* Outer layer */}
|
||||
<Container className="ballot_container">
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
{/* Dashboard nav bar */}
|
||||
<DashboardNavBarComponent />
|
||||
|
||||
{/* Body */}
|
||||
<SummaryBuilderComponent />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
69
styles/ChoosePollsPopup.module.css
Normal file
69
styles/ChoosePollsPopup.module.css
Normal file
@@ -0,0 +1,69 @@
|
||||
/* Modal */
|
||||
.summary_modal_header_box {
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.summary_modal_close_icon_btn_box {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: 0.3s all ease;
|
||||
}
|
||||
.summary_modal_body_box {
|
||||
padding: 0 20px 20px 20px;
|
||||
max-height: 75vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.all_polls_list_outline_box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
.all_polls_list_box {
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
background-color: rgba(236, 239, 241, 0.3);
|
||||
width: 100%;
|
||||
transition: 0.2s ease all;
|
||||
}
|
||||
.all_polls_list_box:hover {
|
||||
background-color: rgba(236, 239, 241, 0.6);
|
||||
}
|
||||
.all_polls_status_box_active {
|
||||
padding: 1px 8px;
|
||||
background-color: #21b66f;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
border-radius: 24px;
|
||||
}
|
||||
.all_polls_status_box_draft {
|
||||
padding: 1px 8px;
|
||||
background-color: #fca43e;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
border-radius: 24px;
|
||||
}
|
||||
.all_polls_status_box_closed {
|
||||
padding: 1px 8px;
|
||||
background-color: #fa4949;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
border-radius: 24px;
|
||||
}
|
||||
.all_polls_description {
|
||||
font-size: 15px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
line-height: 1.6;
|
||||
}
|
||||
Reference in New Issue
Block a user