Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 11 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions src/components/copyString.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useState } from "react";
import { useUniversalCopy } from '../hooks/useCopy.js';

export default function CopyExample({text}) {
const copy = useUniversalCopy();
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
const ok = await copy(text);
setCopied(ok);

// Auto-hide after 2 seconds
setTimeout(() => setCopied(false), 2000);
};

return (
<span style={{paddingLeft:5}}>
<svg onClick={handleCopy} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-copy">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666" />
<path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" />
</svg>

{copied && (
<div style={{ marginTop: 10, color: "green" }}>
Copied to clipboard
</div>
)}
</span>
);
}
5 changes: 3 additions & 2 deletions src/features/widgets/components/profileWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FormatPhoneNumber from "../../../util/phoneNumberFormatter";
import Avatar from '../../../components/avatar';
import StatusPill from '../../../pages/customers/statusPill.jsx';
import SocialMediaLink from "../../../components/socialMediaLink";
import CopyString from '../../../components/copyString.jsx';

var GET_DATA = gql`query {
countries{
Expand Down Expand Up @@ -69,12 +70,12 @@ const ProfileWidget = ({ customer, widget }) => {
<svg xmlns="http://www.w3.org/2000/svg" className="icon me-2 text-muted" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M7.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592 -5.592a2.41 2.41 0 0 0 0 -3.408l-7.71 -7.71a2 2 0 0 0 -1.414 -.586h-5.172a3 3 0 0 0 -3 3z" /></svg>
Handle
</dd>
<dd className="col-7 text-end">{customer.webAlias ?? customer.id}</dd>
<dd className="col-7 text-end">{customer.webAlias ?? customer.id} <CopyString text={customer.webAlias ?? customer.id} /></dd>
{true && <>
<dd className="col-5">
<svg xmlns="http://www.w3.org/2000/svg" className="icon me-2 text-muted" width="24" height="24" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path><path d="M3 7l9 6l9 -6"></path></svg>
Email</dd>
<dd className="col-7 text-end">{customer.emailAddress}</dd>
<dd className="col-7 text-end">{customer.emailAddress} <CopyString text={customer.emailAddress} /></dd>


<dd className="col-5">
Expand Down
38 changes: 38 additions & 0 deletions src/hooks/useCopy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useCallback } from "react";

export function useUniversalCopy() {
const copy = useCallback(async (text) => {
const isIOS = /ipad|iphone|ipod/i.test(navigator.userAgent);

// Modern API (not reliable on iOS Safari)
if (!isIOS && navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// fallback below
}
}

// Fallback for iOS + older browsers
const textarea = document.createElement("textarea");
textarea.value = text;

// iOS requires visible-but-offscreen element
textarea.style.position = "fixed";
textarea.style.left = "-9999px";

document.body.appendChild(textarea);

textarea.focus();
textarea.select();
textarea.setSelectionRange(0, 99999); // iOS requirement

const success = document.execCommand("copy");
document.body.removeChild(textarea);

return success;
}, []);

return copy;
}
Loading