Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6d94fc7
[oko_pg_interface] add edit_customers for adding theme col
blacktoast Dec 12, 2025
d97712a
[backend] update customer's api of edit,create to store theme
blacktoast Dec 15, 2025
4e50a74
[customer_dashboard] add theme button and some guardrail for blocking…
blacktoast Dec 15, 2025
a63506b
[common] add CustomerTheme type
blacktoast Dec 15, 2025
4f417c6
[backed] add attached_api
blacktoast Dec 15, 2025
556146e
[backend] add attached_api to server routes
blacktoast Dec 15, 2025
6e5d421
[ui] add 'system' value in Theme type
blacktoast Dec 15, 2025
005fa53
[attached] get theme by using api
blacktoast Dec 15, 2025
541fd08
[embed] add useSetThemeInCallback and apply it
blacktoast Dec 15, 2025
8eea9c2
[backend] remove console
blacktoast Dec 15, 2025
c00f66d
update yarn lock
blacktoast Dec 15, 2025
6ff24b7
[ui] replace check icon
blacktoast Dec 22, 2025
7c5de12
[ct_dashboard] add ThemeButton and apply
blacktoast Dec 22, 2025
7b49af2
[ct_dashboard] change APIKeyList title text size
blacktoast Dec 22, 2025
5724cac
[user_dashboard] make ToastContainer on user_dashboard
blacktoast Dec 23, 2025
d99c6a0
[ct_dashboard] apply toast
blacktoast Dec 23, 2025
8ce3c7b
Merge branch 'main' into retto/Oko-469
blacktoast Dec 23, 2025
2dd0c35
[ct_dashboard] remove theme change in edit-form
blacktoast Dec 23, 2025
28b0bde
[ct_dashboard] update theme handling in DesignSection component
blacktoast Dec 23, 2025
88db38a
[ct_dashboard] remove duplicated logic
blacktoast Dec 23, 2025
464ed58
[ct_dashboard] edit text
blacktoast Dec 23, 2025
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
6 changes: 6 additions & 0 deletions apps/customer_dashboard/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { AccountInfo } from "@oko-wallet-ct-dashboard/components/account_info/ac
import { HomeBanner } from "@oko-wallet-ct-dashboard/components/home_banner/home_banner";
// import { SDKInstallationGuide } from "@oko-wallet-ct-dashboard/components/sdk_installation_guide/sdk_installation_guide";
import { APIKeyList } from "@oko-wallet-ct-dashboard/components/api_key_list/api_key_list";
import { DesignSection } from "@oko-wallet-ct-dashboard/components/design_section/design_section";
import { Spacing } from "@oko-wallet-common-ui/spacing/spacing";
import { ToastContainer } from "@oko-wallet-ct-dashboard/components/toast";

export default function Home() {
return (
Expand Down Expand Up @@ -38,6 +41,9 @@ export default function Home() {
{/* TODO: @Ryz0nd, @lidarbtc */}
{/* <SDKInstallationGuide /> */}
<APIKeyList />
<Spacing height={66} />
<DesignSection />
<ToastContainer />
</DashboardBody>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const APIKeyList = () => {
<div>
<Typography
tagType="h1"
size="display-sm"
size="display-xs"
weight="semibold"
color="primary"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.wrapper {
display: flex;
flex-direction: column;
gap: 32px;
}

.themeSection {
display: flex;
flex-direction: column;
width: 100%;

gap: 16px;
}

.themeOptions {
display: flex;
gap: 12px;

flex-wrap: wrap;
}

.themeActions {
display: flex;
gap: 12px;
}

.themeOptionButton {
padding: 8px 12px;
border-radius: 10px;
border: 1px solid var(--border-primary);
background: var(--bg-primary);
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;

&:hover {
border-color: #d0d5dd;
}

&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}

.themeOptionButtonActive {
background: var(--bg-brand-solid);
color: var(--text-primary-on-brand);
}

.error {
color: var(--text-error, #d92d20);
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"use client";

import { useLayoutEffect, useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { Button } from "@oko-wallet/oko-common-ui/button";
import { Typography } from "@oko-wallet/oko-common-ui/typography";
import type { CustomerTheme } from "@oko-wallet/oko-types/customers";
import { Spacing } from "@oko-wallet/oko-common-ui/spacing";

import { useCustomerInfo } from "@oko-wallet-ct-dashboard/hooks/use_customer_info";
import { useAppState } from "@oko-wallet-ct-dashboard/state";
import { requestUpdateCustomerInfo } from "@oko-wallet-ct-dashboard/fetch/customers";
import styles from "./design_section.module.scss";
import { ThemeButton } from "./theme_button";
import { displayToast } from "@oko-wallet-ct-dashboard/components/toast";

const THEME_OPTIONS: CustomerTheme[] = ["light", "dark", "system"];

export const DesignSection = () => {
const queryClient = useQueryClient();
const customer = useCustomerInfo();
const token = useAppState((state) => state.token);
const [savedTheme, setSavedTheme] = useState<CustomerTheme>(
customer.data?.theme ?? "system",
);
const [draftTheme, setDraftTheme] = useState<CustomerTheme>(
customer.data?.theme ?? "system",
);

useLayoutEffect(() => {
if (customer.data?.theme) {
setSavedTheme(customer.data.theme);
setDraftTheme(customer.data.theme);
}
}, [customer.data?.theme]);

const [isSavingTheme, setIsSavingTheme] = useState(false);
const [themeError, setThemeError] = useState<string | null>(null);

const hasThemeChange = draftTheme !== savedTheme;

const handleThemeSelect = (option: CustomerTheme) => {
if (isSavingTheme) {
return;
}

setThemeError(null);
setDraftTheme(option);
};

const handleThemeCancel = () => {
if (!hasThemeChange || isSavingTheme) {
return;
}

setThemeError(null);
setDraftTheme(savedTheme);
};

const handleThemeSave = async () => {
if (!hasThemeChange || isSavingTheme) {
return;
}

if (!token) {
setThemeError("Please log in to continue.");
return;
}

setIsSavingTheme(true);
setThemeError(null);

try {
const result = await requestUpdateCustomerInfo({
token,
theme: draftTheme,
});

if (result.success) {
setSavedTheme(draftTheme);
displayToast({
variant: "success",
title: "Saved",
});
await queryClient.invalidateQueries({ queryKey: ["customer"] });
} else {
setThemeError(result.msg ?? "Failed to update theme.");
}
} catch (err) {
setThemeError("An error occurred while updating the theme.");
} finally {
setIsSavingTheme(false);
}
};

return (
<div className={styles.wrapper}>
<div>
<Typography size="display-xs" weight="semibold" color="primary">
Design
</Typography>

<Spacing height={8} />

<Typography size="md" weight="medium" color="quaternary">
You can configure the Oko UI theme integrated into your app.
</Typography>
</div>
<div className={styles.themeSection}>
<div className={styles.themeOptions}>
{THEME_OPTIONS.map((option) => {
const label =
option === "system"
? "System"
: option === "light"
? "Light"
: "Dark";

return (
<ThemeButton
key={option}
theme={option}
onClick={() => handleThemeSelect(option)}
active={draftTheme === option}
label={label}
/>
);
})}
</div>

<div className={styles.themeActions}>
<Button
type="button"
variant="primary"
size="md"
onClick={handleThemeSave}
disabled={!hasThemeChange}
isLoading={isSavingTheme}
>
Save
</Button>
<Button
type="button"
variant="secondary"
size="md"
onClick={handleThemeCancel}
disabled={isSavingTheme}
>
Cancel
</Button>
</div>

{themeError && <div className={styles.error}>{themeError}</div>}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
.activeIndicator {
position: absolute;
bottom: 8;
right: 7;
width: 16px;
height: 16px;
background-color: var(--border-brand-alt);
border-radius: 50%;
}

.buttonContainer {
display: flex;
flex-direction: column;

gap: 8px;
}
.button {
margin: 0;
padding: 0;
border: none;
background: none;
cursor: pointer;
outline: none;
box-shadow: none;
border-radius: 0;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;

position: relative;

width: 100px;
height: 60px;
border-radius: 8px;
border: 1px solid var(--border-primary);
overflow: hidden;
transition: border 0.1s ease-in-out;

&:hover {
border: 2px solid var(--border-brand);
}

&.active {
border: 2px solid var(--border-brand-alt);
}
}

.lightThemeButton {
position: relative;
background-color: var(--gray-200);
overflow: hidden;

.lightThemeButtonInner {
margin-top: 14px;
margin-left: 24px;
background-color: var(--white);
border: 1px solid var(--border-primary);
width: 80px;
height: 50px;
border-top-left-radius: 8px;
}

.lightThemeButtonInnerText {
margin-top: 8px;
margin-left: 8px;
text-align: left;
}
}

.darkThemeButton {
position: relative;
background-color: var(--gray-500);
overflow: hidden;

.darkThemeButtonInner {
margin-top: 14px;
margin-left: 24px;
background-color: #22262f;
border: 1px solid #373a41;
width: 80px;
height: 50px;
border-top-left-radius: 8px;
}

.darkThemeButtonInnerText {
margin-top: 8px;
margin-left: 8px;
text-align: left;
color: #f7f7f7;
}
}

.systemThemeButtonContainer {
display: flex;

.lightThemeButton {
position: relative;
background-color: var(--gray-200);

.lightThemeButtonInner {
margin-top: 14px;
margin-left: 14px;
background-color: var(--white);
border: 1px solid var(--border-primary);
width: 80px;
height: 50px;
border-top-left-radius: 8px;
}

.lightThemeButtonInnerText {
margin-top: 8px;
margin-left: 8px;
text-align: left;
}
}

.darkThemeButton {
position: relative;
background-color: var(--gray-500);

.darkThemeButtonInner {
margin-top: 14px;
margin-left: 14px;
background-color: #22262f;
border: 1px solid #373a41;
width: 80px;
height: 50px;
border-top-left-radius: 8px;
}

.darkThemeButtonInnerText {
margin-top: 8px;
margin-left: 8px;
text-align: left;
color: #f7f7f7;
}
}
}
Loading