Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fd0c437
Add static WhatsApp account linking UI to Connected Accounts section
hafiz-ahtasham-ali Feb 20, 2026
a0572d4
Load WhatsApp session data and store session ID in Clerk user metadata
hafiz-ahtasham-ali Feb 20, 2026
3c413da
Change WhatsApp widget default message from "help" to "START"
hafiz-ahtasham-ali Feb 20, 2026
009c4af
fix: add null safety to dashboard session data to prevent client-side…
hafiz-ahtasham-ali Feb 21, 2026
bbbc697
remove github and reorder auth in signup/in pages
jmoraispk Feb 21, 2026
dc53a60
put free early access button to sign-up page
jmoraispk Feb 21, 2026
2f5d136
make loglife plugin
jmoraispk Feb 21, 2026
13ce90f
add sessions plug-in instructions
jmoraispk Feb 21, 2026
3cebe1e
update docs with full setup guide and fix plugin config
jmoraispk Feb 22, 2026
b2d7afd
add self-hosting documentation and update index layout
jmoraispk Feb 22, 2026
a70321e
Add OpenClaw tricks documentation
jmoraispk Feb 22, 2026
235ffbb
fine tune user flow to dashboard
jmoraispk Feb 22, 2026
dd96314
improve pricing page
jmoraispk Feb 22, 2026
e3a370f
small fix for the animation counters
jmoraispk Feb 22, 2026
861a980
change counter to h/m only
jmoraispk Feb 22, 2026
cda3cd0
adjust timing of hosted animation
jmoraispk Feb 22, 2026
0933a0b
make flow sequential
jmoraispk Feb 22, 2026
780812a
add mintlify rules
jmoraispk Feb 22, 2026
a933857
fix sidebar order
jmoraispk Feb 22, 2026
cd46b64
add documentation section to mintlify
jmoraispk Feb 22, 2026
ae8030d
Update deployment workflow and documentation for LogLife plugin
jmoraispk Feb 22, 2026
bf476d7
add plugin unit tests
jmoraispk Feb 22, 2026
8966839
add development and security docs
jmoraispk Feb 22, 2026
c034cc6
add test for api/verification and welcome message
jmoraispk Feb 22, 2026
fe0ae5d
improve dashboard ux - enable ENTER to send msg
jmoraispk Feb 22, 2026
871f5e6
update development documentation to include requirement for two phone…
jmoraispk Feb 22, 2026
164aa47
refactor sidebar links to direct to home page instead of conditional …
jmoraispk Feb 22, 2026
45dc572
WA number verification working
jmoraispk Feb 22, 2026
ec0410d
Enhance documentation and add automated setup script for LogLife plugin
jmoraispk Feb 22, 2026
f38cb34
update docs with gateway token regen
jmoraispk Feb 22, 2026
fa873c7
Add TypeScript type declarations for openclaw/plugin-sdk
jmoraispk Feb 22, 2026
a63030c
Refactor WhatsApp disconnect logic in AccountPage
jmoraispk Feb 22, 2026
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
253 changes: 220 additions & 33 deletions website/app/account/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ export default function AccountPage() {
const [confirmPassword, setConfirmPassword] = useState("");
const [passwordLoading, setPasswordLoading] = useState(false);
const [passwordMessage, setPasswordMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
const [showWhatsAppModal, setShowWhatsAppModal] = useState(false);
const [whatsAppSessionId, setWhatsAppSessionId] = useState("");
const [whatsAppLinking, setWhatsAppLinking] = useState(false);
const [whatsAppMessage, setWhatsAppMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);

const storedSessionId = (user?.unsafeMetadata as Record<string, string> | undefined)?.whatsappSessionId || "";
const whatsAppConnected = !!storedSessionId;

React.useEffect(() => {
if (user) {
Expand Down Expand Up @@ -118,6 +125,47 @@ export default function AccountPage() {
}
};

const handleWhatsAppLink = async (e: React.FormEvent) => {
e.preventDefault();
const sid = whatsAppSessionId.trim();
if (!sid) return;

const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(sid)) {
setWhatsAppMessage({ type: "error", text: "Invalid session ID format. Expected a UUID like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" });
return;
}

setWhatsAppLinking(true);
setWhatsAppMessage(null);

try {
await user!.update({
unsafeMetadata: { ...user!.unsafeMetadata, whatsappSessionId: sid },
});
setWhatsAppMessage({ type: "success", text: "WhatsApp session linked successfully!" });
setTimeout(() => {
setShowWhatsAppModal(false);
setWhatsAppSessionId("");
setWhatsAppMessage(null);
}, 1500);
} catch {
setWhatsAppMessage({ type: "error", text: "Failed to save session ID. Please try again." });
} finally {
setWhatsAppLinking(false);
}
};

const handleWhatsAppDisconnect = async () => {
try {
await user!.update({
unsafeMetadata: { ...user!.unsafeMetadata, whatsappSessionId: undefined },
});
} catch {
// silently fail
}
};
Comment on lines 123 to 130
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting whatsappSessionId to undefined doesn't remove the key from unsafeMetadata, it sets it to undefined. To actually remove it, you should omit the key entirely or set it to null, and provide user feedback.

const handleWhatsAppDisconnect = async () => {
  try {
    const { whatsappSessionId, ...rest } = user!.unsafeMetadata as Record<string, unknown>;
    await user!.update({ unsafeMetadata: rest });
    setWhatsAppMessage({ type: "success", text: "WhatsApp disconnected successfully!" });
  } catch {
    setWhatsAppMessage({ type: "error", text: "Failed to disconnect. Please try again." });
  }
};

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptileai are you there?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm here! How can I help you with this code?


const primaryEmail = user.emailAddresses.find(
(email) => email.id === user.primaryEmailAddressId
);
Expand Down Expand Up @@ -311,41 +359,74 @@ export default function AccountPage() {
<div className="px-6 py-4 border-b border-slate-800/50">
<h2 className="text-sm font-medium text-white">Connected Accounts</h2>
</div>
<div className="p-6">
{user.externalAccounts.length > 0 ? (
<div className="space-y-3">
{user.externalAccounts.map((account) => (
<div
key={account.id}
className="flex items-center justify-between px-3 py-2.5 rounded-lg bg-slate-950/30 border border-slate-800/30"
>
<div className="flex items-center gap-3">
{account.provider === "google" && (
<svg className="w-4 h-4" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
)}
{account.provider === "github" && (
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24">
<path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
</svg>
)}
<div>
<span className="text-sm text-slate-300 capitalize">{account.provider}</span>
<span className="text-xs text-slate-500 ml-2">{account.emailAddress}</span>
</div>
</div>
<span className="px-2 py-0.5 rounded text-xs font-medium bg-slate-800/50 text-slate-400">
Connected
</span>
<div className="p-6 space-y-3">
{user.externalAccounts.map((account) => (
<div
key={account.id}
className="flex items-center justify-between px-3 py-2.5 rounded-lg bg-slate-950/30 border border-slate-800/30"
>
<div className="flex items-center gap-3">
{account.provider === "google" && (
<svg className="w-4 h-4" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
)}
{account.provider === "github" && (
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24">
<path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
</svg>
)}
<div>
<span className="text-sm text-slate-300 capitalize">{account.provider}</span>
<span className="text-xs text-slate-500 ml-2">{account.emailAddress}</span>
</div>
))}
</div>
<span className="px-2 py-0.5 rounded text-xs font-medium bg-slate-800/50 text-slate-400">
Connected
</span>
</div>
))}

{/* WhatsApp Connection */}
<div className="flex items-center justify-between px-3 py-2.5 rounded-lg bg-slate-950/30 border border-slate-800/30">
<div className="flex items-center gap-3">
<svg className="w-4 h-4 text-[#25D366]" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
</svg>
<div>
<span className="text-sm text-slate-300">WhatsApp</span>
{whatsAppConnected && (
<span className="text-xs text-slate-500 ml-2 font-mono">{storedSessionId.slice(0, 8)}...</span>
)}
</div>
</div>
) : (
<p className="text-sm text-slate-500">No connected accounts</p>
{whatsAppConnected ? (
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 rounded text-xs font-medium bg-green-500/10 text-green-400">
Connected
</span>
<button
onClick={handleWhatsAppDisconnect}
className="text-xs font-medium text-slate-500 hover:text-red-400 transition-colors cursor-pointer"
>
Disconnect
</button>
</div>
) : (
<button
onClick={() => setShowWhatsAppModal(true)}
className="px-3 py-1 rounded-lg text-xs font-medium text-emerald-400 border border-emerald-500/30 hover:bg-emerald-500/10 transition-all cursor-pointer"
>
Connect
</button>
)}
</div>

{user.externalAccounts.length === 0 && !whatsAppConnected && (
<p className="text-sm text-slate-500 pt-1">No connected accounts yet</p>
)}
</div>
</div>
Expand Down Expand Up @@ -482,6 +563,112 @@ export default function AccountPage() {
</div>
)}

{/* WhatsApp Link Modal */}
{showWhatsAppModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={() => {
setShowWhatsAppModal(false);
setWhatsAppSessionId("");
setWhatsAppMessage(null);
}}
/>
<div className="relative bg-slate-900 border border-slate-800 rounded-xl p-6 w-full max-w-md shadow-2xl">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-full bg-[#25D366]/10 flex items-center justify-center flex-shrink-0">
<svg className="w-5 h-5 text-[#25D366]" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
</svg>
</div>
<div>
<h3 className="text-lg font-semibold text-white">Connect WhatsApp</h3>
<p className="text-xs text-slate-500">Link your WhatsApp session to Loglife</p>
</div>
</div>

{/* How it works steps */}
<div className="mb-5 rounded-lg bg-slate-950/50 border border-slate-800/30 p-4">
<p className="text-xs font-medium text-slate-400 mb-3">How it works</p>
<div className="space-y-2.5">
<div className="flex items-start gap-2.5">
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-emerald-500/10 text-emerald-400 text-xs font-medium flex items-center justify-center mt-0.5">1</span>
<p className="text-xs text-slate-400">Start a conversation with the Loglife WhatsApp bot</p>
</div>
<div className="flex items-start gap-2.5">
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-emerald-500/10 text-emerald-400 text-xs font-medium flex items-center justify-center mt-0.5">2</span>
<p className="text-xs text-slate-400">Get your session ID from the bot or your admin</p>
</div>
<div className="flex items-start gap-2.5">
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-emerald-500/10 text-emerald-400 text-xs font-medium flex items-center justify-center mt-0.5">3</span>
<p className="text-xs text-slate-400">Paste the session ID below to link your account</p>
</div>
</div>
</div>

{whatsAppMessage && (
<div
className={`mb-4 px-3 py-2 rounded-lg text-xs ${
whatsAppMessage.type === "success"
? "bg-green-500/10 text-green-400"
: "bg-red-500/10 text-red-400"
}`}
>
{whatsAppMessage.text}
</div>
)}

<form onSubmit={handleWhatsAppLink}>
<div className="mb-4">
<label className="block text-xs font-medium text-slate-400 mb-1.5">
Session ID
</label>
<input
type="text"
value={whatsAppSessionId}
onChange={(e) => setWhatsAppSessionId(e.target.value.trim())}
className="w-full rounded-lg bg-slate-950/50 border border-slate-700/50 text-white text-sm px-3 py-2.5 focus:outline-none focus:ring-1 focus:ring-emerald-500/50 focus:border-emerald-500/50 transition-all placeholder-slate-600 font-mono"
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
autoFocus
/>
<p className="text-xs text-slate-500 mt-1.5">Paste the session UUID from your WhatsApp bot</p>
</div>

<div className="flex gap-3">
<button
type="button"
onClick={() => {
setShowWhatsAppModal(false);
setWhatsAppSessionId("");
setWhatsAppMessage(null);
}}
className="flex-1 px-4 py-2 rounded-lg text-sm font-medium text-slate-300 bg-slate-800 hover:bg-slate-700 transition-all cursor-pointer"
>
Cancel
</button>
<button
type="submit"
disabled={!whatsAppSessionId.trim() || whatsAppLinking}
className="flex-1 px-4 py-2 rounded-lg text-sm font-medium text-white bg-emerald-600 hover:bg-emerald-500 transition-all cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
{whatsAppLinking ? (
<span className="flex items-center justify-center gap-2">
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Linking...
</span>
) : (
"Link Session"
)}
</button>
</div>
</form>
</div>
</div>
)}

{/* Password Change Modal */}
{showPasswordModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
Expand Down
Loading