create-githat-app 1.8.13 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +222 -18
- package/package.json +2 -1
- package/templates/nextjs/app/(auth)/verify-email/page.tsx.hbs +35 -45
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
4
|
+
import { Command as Command8 } from "commander";
|
|
5
|
+
import * as p13 from "@clack/prompts";
|
|
6
|
+
import chalk11 from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/utils/ascii.ts
|
|
9
9
|
import figlet from "figlet";
|
|
@@ -11,7 +11,7 @@ import gradient from "gradient-string";
|
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
|
|
13
13
|
// src/constants.ts
|
|
14
|
-
var VERSION = "1.0
|
|
14
|
+
var VERSION = "1.9.0";
|
|
15
15
|
var DEFAULT_API_URL = "https://api.githat.io";
|
|
16
16
|
var DASHBOARD_URL = "https://githat.io/dashboard/apps";
|
|
17
17
|
var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
|
|
@@ -527,11 +527,12 @@ async function promptGitHat(existingKey) {
|
|
|
527
527
|
options: [
|
|
528
528
|
{ value: "forgot-password", label: "Forgot password", hint: "reset via email" },
|
|
529
529
|
{ value: "email-verification", label: "Email verification" },
|
|
530
|
+
{ value: "magic-link", label: "Magic link sign-in", hint: "passwordless email link" },
|
|
530
531
|
{ value: "org-management", label: "Organizations", hint: "teams & roles" },
|
|
531
532
|
{ value: "mcp-servers", label: "MCP servers", hint: "Model Context Protocol" },
|
|
532
533
|
{ value: "ai-agents", label: "AI agents", hint: "wallet-based identity" }
|
|
533
534
|
],
|
|
534
|
-
initialValues: ["forgot-password"],
|
|
535
|
+
initialValues: ["forgot-password", "email-verification", "magic-link"],
|
|
535
536
|
required: false
|
|
536
537
|
});
|
|
537
538
|
if (p5.isCancel(authFeatures)) {
|
|
@@ -616,7 +617,7 @@ function getDefaults(projectName, publishableKey, typescript, fullstack, backend
|
|
|
616
617
|
packageManager: detectPackageManager(),
|
|
617
618
|
publishableKey: publishableKey || "",
|
|
618
619
|
apiUrl: DEFAULT_API_URL,
|
|
619
|
-
authFeatures: template === "agent" ? ["forgot-password", "email-verification", "mcp-servers", "ai-agents"] : isMinimal ? [] : ["forgot-password"],
|
|
620
|
+
authFeatures: template === "agent" ? ["forgot-password", "email-verification", "magic-link", "mcp-servers", "ai-agents"] : isMinimal ? [] : ["forgot-password", "email-verification", "magic-link"],
|
|
620
621
|
databaseChoice: "none",
|
|
621
622
|
useTailwind: true,
|
|
622
623
|
includeDashboard: template === "agent" ? true : !isMinimal,
|
|
@@ -672,6 +673,7 @@ function answersToContext(answers) {
|
|
|
672
673
|
includeGithatFolder: answers.includeGithatFolder,
|
|
673
674
|
includeForgotPassword: answers.authFeatures.includes("forgot-password"),
|
|
674
675
|
includeEmailVerification: answers.authFeatures.includes("email-verification"),
|
|
676
|
+
includeMagicLink: answers.authFeatures.includes("magic-link"),
|
|
675
677
|
includeOrgManagement: answers.authFeatures.includes("org-management"),
|
|
676
678
|
includeMcpModule: answers.authFeatures.includes("mcp-servers"),
|
|
677
679
|
includeAgentModule: answers.authFeatures.includes("ai-agents"),
|
|
@@ -1648,35 +1650,236 @@ skillsCommand.action(() => {
|
|
|
1648
1650
|
console.log("");
|
|
1649
1651
|
});
|
|
1650
1652
|
|
|
1653
|
+
// src/commands/deploy/index.ts
|
|
1654
|
+
import { Command as Command7 } from "commander";
|
|
1655
|
+
import * as p12 from "@clack/prompts";
|
|
1656
|
+
import chalk10 from "chalk";
|
|
1657
|
+
import ora2 from "ora";
|
|
1658
|
+
import { execSync as execSync4, spawn } from "child_process";
|
|
1659
|
+
import { promises as fs8, existsSync as existsSync4 } from "fs";
|
|
1660
|
+
import path8 from "path";
|
|
1661
|
+
import * as tar from "tar";
|
|
1662
|
+
var DEFAULT_API_URL2 = "https://api.githat.io";
|
|
1663
|
+
async function readConfig(cwd) {
|
|
1664
|
+
const configPath = path8.join(cwd, ".githat", "config.json");
|
|
1665
|
+
let appId;
|
|
1666
|
+
let apiUrl = DEFAULT_API_URL2;
|
|
1667
|
+
if (existsSync4(configPath)) {
|
|
1668
|
+
try {
|
|
1669
|
+
const cfg = JSON.parse(await fs8.readFile(configPath, "utf-8"));
|
|
1670
|
+
appId = cfg.appId;
|
|
1671
|
+
if (cfg.apiUrl) apiUrl = cfg.apiUrl;
|
|
1672
|
+
} catch (err) {
|
|
1673
|
+
throw new Error(
|
|
1674
|
+
`Could not read .githat/config.json: ${err.message}`
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (!appId) appId = process.env.GITHAT_APP_ID;
|
|
1679
|
+
if (process.env.NEXT_PUBLIC_GITHAT_API_URL) apiUrl = process.env.NEXT_PUBLIC_GITHAT_API_URL;
|
|
1680
|
+
if (!appId) {
|
|
1681
|
+
throw new Error(
|
|
1682
|
+
[
|
|
1683
|
+
"No appId found. Either:",
|
|
1684
|
+
` - Run ${chalk10.cyan("githat init")} to bind this directory to a GitHat app, or`,
|
|
1685
|
+
` - Set ${chalk10.cyan("GITHAT_APP_ID")} in your environment, or`,
|
|
1686
|
+
` - Create ${chalk10.cyan(".githat/config.json")} with { "appId": "..." }`
|
|
1687
|
+
].join("\n")
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
let secretKey = process.env.GITHAT_SECRET_KEY || "";
|
|
1691
|
+
if (!secretKey) {
|
|
1692
|
+
const envPath = path8.join(cwd, ".env.local");
|
|
1693
|
+
if (existsSync4(envPath)) {
|
|
1694
|
+
const envText = await fs8.readFile(envPath, "utf-8");
|
|
1695
|
+
const m = envText.match(/^GITHAT_SECRET_KEY=(.*)$/m);
|
|
1696
|
+
if (m) secretKey = m[1].replace(/^["']|["']$/g, "").trim();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (!secretKey || !secretKey.startsWith("sk_live_")) {
|
|
1700
|
+
throw new Error(
|
|
1701
|
+
[
|
|
1702
|
+
"No GITHAT_SECRET_KEY found. Either:",
|
|
1703
|
+
` - Add ${chalk10.cyan("GITHAT_SECRET_KEY=sk_live_...")} to ${chalk10.cyan(".env.local")}, or`,
|
|
1704
|
+
` - Set the ${chalk10.cyan("GITHAT_SECRET_KEY")} env var (CI flow)`,
|
|
1705
|
+
"",
|
|
1706
|
+
chalk10.dim("Get yours at https://githat.io/dashboard/apps \u2192 your app \u2192 Keys \u2192 New secret key")
|
|
1707
|
+
].join("\n")
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
return { appId, apiUrl, secretKey };
|
|
1711
|
+
}
|
|
1712
|
+
async function packArtifact(cwd) {
|
|
1713
|
+
const outDir = path8.join(cwd, "out");
|
|
1714
|
+
if (!existsSync4(outDir)) {
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
`No \`out/\` directory at ${outDir}. Run \`npm run build\` first.`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
const entries = await fs8.readdir(outDir);
|
|
1720
|
+
if (entries.length === 0) {
|
|
1721
|
+
throw new Error("`out/` is empty. Build did not produce any files.");
|
|
1722
|
+
}
|
|
1723
|
+
const stream = tar.create(
|
|
1724
|
+
{
|
|
1725
|
+
gzip: true,
|
|
1726
|
+
cwd: outDir,
|
|
1727
|
+
// 'portable' strips uid/gid/atime/ctime so the bundle hashes
|
|
1728
|
+
// identically across machines (helpful if you later add deploy
|
|
1729
|
+
// dedup on content hash).
|
|
1730
|
+
portable: true
|
|
1731
|
+
},
|
|
1732
|
+
entries
|
|
1733
|
+
);
|
|
1734
|
+
const chunks = [];
|
|
1735
|
+
for await (const chunk of stream) chunks.push(chunk);
|
|
1736
|
+
return Buffer.concat(chunks);
|
|
1737
|
+
}
|
|
1738
|
+
async function uploadArtifact(cfg, artifact, commit, commitMessage) {
|
|
1739
|
+
const url = `${cfg.apiUrl.replace(/\/$/, "")}/apps/${cfg.appId}/deploy`;
|
|
1740
|
+
const headers = {
|
|
1741
|
+
"Content-Type": "application/gzip",
|
|
1742
|
+
"Authorization": `Bearer ${cfg.secretKey}`
|
|
1743
|
+
};
|
|
1744
|
+
if (commit) headers["X-GitHat-Commit"] = commit;
|
|
1745
|
+
if (commitMessage) {
|
|
1746
|
+
const subject = commitMessage.split(/\r?\n/, 1)[0] || "";
|
|
1747
|
+
const sanitized = subject.replace(/[\x00-\x1f\x7f]/g, " ").trim().slice(0, 200);
|
|
1748
|
+
if (sanitized) headers["X-GitHat-Commit-Message"] = sanitized;
|
|
1749
|
+
}
|
|
1750
|
+
const res = await fetch(url, { method: "POST", headers, body: artifact });
|
|
1751
|
+
const text4 = await res.text();
|
|
1752
|
+
let body;
|
|
1753
|
+
try {
|
|
1754
|
+
body = JSON.parse(text4);
|
|
1755
|
+
} catch {
|
|
1756
|
+
body = { error: text4 };
|
|
1757
|
+
}
|
|
1758
|
+
if (!res.ok) {
|
|
1759
|
+
throw new Error(
|
|
1760
|
+
`Deploy failed (${res.status}): ${body?.error || res.statusText}`
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
return body;
|
|
1764
|
+
}
|
|
1765
|
+
function maybeReadGitInfo(cwd) {
|
|
1766
|
+
try {
|
|
1767
|
+
const commit = execSync4("git rev-parse --short HEAD", {
|
|
1768
|
+
cwd,
|
|
1769
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1770
|
+
}).toString().trim();
|
|
1771
|
+
const message = execSync4("git log -1 --pretty=%B", {
|
|
1772
|
+
cwd,
|
|
1773
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1774
|
+
}).toString().trim();
|
|
1775
|
+
return { commit, message };
|
|
1776
|
+
} catch {
|
|
1777
|
+
return { commit: null, message: null };
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
async function ensureBuilt(cwd, force) {
|
|
1781
|
+
const outDir = path8.join(cwd, "out");
|
|
1782
|
+
if (existsSync4(outDir) && !force) {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
const spinner2 = ora2("Building static export (npm run build)...").start();
|
|
1786
|
+
await new Promise((resolve4, reject) => {
|
|
1787
|
+
const child = spawn("npm", ["run", "build"], {
|
|
1788
|
+
cwd,
|
|
1789
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1790
|
+
env: { ...process.env }
|
|
1791
|
+
});
|
|
1792
|
+
let stderr = "";
|
|
1793
|
+
child.stderr.on("data", (d) => {
|
|
1794
|
+
stderr += d.toString();
|
|
1795
|
+
});
|
|
1796
|
+
child.on("close", (code) => {
|
|
1797
|
+
if (code === 0) resolve4();
|
|
1798
|
+
else reject(new Error(`Build exited with code ${code}.
|
|
1799
|
+
${stderr.slice(-2e3)}`));
|
|
1800
|
+
});
|
|
1801
|
+
});
|
|
1802
|
+
spinner2.succeed("Build complete");
|
|
1803
|
+
}
|
|
1804
|
+
function formatBytes(n) {
|
|
1805
|
+
if (n < 1024) return `${n} B`;
|
|
1806
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
1807
|
+
return `${(n / 1024 / 1024).toFixed(2)} MB`;
|
|
1808
|
+
}
|
|
1809
|
+
var deployCommand = new Command7("deploy").description("Deploy this app to GitHat (no AWS credentials required)").option("--build", "Force a fresh build even if out/ exists", false).option("--cwd <dir>", "Run from this directory instead of the current one").action(async (opts) => {
|
|
1810
|
+
const cwd = path8.resolve(opts.cwd ?? process.cwd());
|
|
1811
|
+
try {
|
|
1812
|
+
const cfg = await readConfig(cwd);
|
|
1813
|
+
p12.note(
|
|
1814
|
+
[
|
|
1815
|
+
`${chalk10.dim("app: ")}${cfg.appId}`,
|
|
1816
|
+
`${chalk10.dim("api: ")}${cfg.apiUrl}`,
|
|
1817
|
+
`${chalk10.dim("cwd: ")}${cwd}`
|
|
1818
|
+
].join("\n"),
|
|
1819
|
+
"githat deploy"
|
|
1820
|
+
);
|
|
1821
|
+
await ensureBuilt(cwd, !!opts.build);
|
|
1822
|
+
const packSpin = ora2("Packing artifact...").start();
|
|
1823
|
+
const artifact = await packArtifact(cwd);
|
|
1824
|
+
packSpin.succeed(`Packed ${formatBytes(artifact.length)}`);
|
|
1825
|
+
if (artifact.length > 10 * 1024 * 1024) {
|
|
1826
|
+
throw new Error(
|
|
1827
|
+
`Artifact is ${formatBytes(artifact.length)} \u2014 over the 10 MB API limit. Reduce out/ size or wait for the chunked-upload flow.`
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
const upSpin = ora2("Uploading to GitHat...").start();
|
|
1831
|
+
const git = maybeReadGitInfo(cwd);
|
|
1832
|
+
const result = await uploadArtifact(cfg, artifact, git.commit, git.message);
|
|
1833
|
+
upSpin.succeed(
|
|
1834
|
+
`Uploaded ${result.filesUploaded} files (${formatBytes(result.bytesUploaded)})`
|
|
1835
|
+
);
|
|
1836
|
+
const lines = [
|
|
1837
|
+
chalk10.green.bold("\u{1F680} Deployment succeeded"),
|
|
1838
|
+
"",
|
|
1839
|
+
`${chalk10.dim("deployment id: ")}${result.deploymentId}`,
|
|
1840
|
+
`${chalk10.dim("cf invalidation:")} ${result.cloudfrontInvalidationId ?? chalk10.dim("(none)")}`,
|
|
1841
|
+
`${chalk10.dim("files: ")}${result.filesUploaded}`,
|
|
1842
|
+
`${chalk10.dim("size: ")}${formatBytes(result.bytesUploaded)}`
|
|
1843
|
+
];
|
|
1844
|
+
if (result.url) {
|
|
1845
|
+
lines.push("", `${chalk10.cyan("Live at:")} ${result.url}`);
|
|
1846
|
+
}
|
|
1847
|
+
p12.note(lines.join("\n"));
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
p12.cancel(chalk10.red(err.message));
|
|
1850
|
+
process.exit(1);
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1651
1854
|
// src/cli.ts
|
|
1652
|
-
var program = new
|
|
1855
|
+
var program = new Command8();
|
|
1653
1856
|
program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
|
|
1654
1857
|
program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "CI only: bake key into .env.local. Default flow is paste into .env.local after scaffold.").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("--plain", "Smallest scaffold: auth + a homepage. No dashboard.").option("--saas", "B2B starter: orgs, teams, RBAC, subscription billing.").option("--marketplace", "Multi-vendor commerce: anonymous-first browsing, Sebastn Connect.").option("--agent", "Web4 wallet-bound autonomous agent + MCP server registration.").option("--content", "Paywalled posts, newsletter, one-time purchases via Sebastn.").option("--dashboard", "Admin UI over your existing database, auth-gated.").option("--portfolio", "Personal portfolio: public projects, auth-gated editor.").option("--classroom", "Live student presentations with real-time audience feedback.").option("--fullstack", "Create fullstack project (Turborepo)").option("--backend <framework>", "Backend framework (hono, express, fastify)").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
|
|
1655
1858
|
try {
|
|
1656
1859
|
displayBanner();
|
|
1657
1860
|
if (!opts.yes && !opts.key) {
|
|
1658
|
-
|
|
1861
|
+
p13.note(
|
|
1659
1862
|
[
|
|
1660
|
-
|
|
1863
|
+
chalk11.bold("How the GitHat key flow works:"),
|
|
1661
1864
|
"",
|
|
1662
|
-
` ${
|
|
1663
|
-
` in ${
|
|
1865
|
+
` ${chalk11.cyan("1.")} We'll scaffold your project with a placeholder`,
|
|
1866
|
+
` in ${chalk11.bold(".env.local")} (gitignored \u2014 safe to keep secrets here)`,
|
|
1664
1867
|
"",
|
|
1665
|
-
` ${
|
|
1868
|
+
` ${chalk11.cyan("2.")} You open ${chalk11.cyan("https://githat.io/dashboard/apps")}`,
|
|
1666
1869
|
` and copy your publishable key`,
|
|
1667
1870
|
"",
|
|
1668
|
-
` ${
|
|
1669
|
-
` ${
|
|
1871
|
+
` ${chalk11.cyan("3.")} You paste it into ${chalk11.bold(".env.local")} and run`,
|
|
1872
|
+
` ${chalk11.dim("npm run dev")}`,
|
|
1670
1873
|
"",
|
|
1671
|
-
|
|
1672
|
-
|
|
1874
|
+
chalk11.dim("Why not pass the key on the command line? Shell"),
|
|
1875
|
+
chalk11.dim("history is forever. .env.local is more secure.")
|
|
1673
1876
|
].join("\n"),
|
|
1674
1877
|
"First time with GitHat?"
|
|
1675
1878
|
);
|
|
1676
1879
|
}
|
|
1677
1880
|
const typescript = opts.js ? false : opts.ts ? true : void 0;
|
|
1678
1881
|
if (opts.yes && !projectName) {
|
|
1679
|
-
|
|
1882
|
+
p13.cancel(chalk11.red("Project name is required when using --yes flag"));
|
|
1680
1883
|
process.exit(1);
|
|
1681
1884
|
}
|
|
1682
1885
|
const template = opts.marketplace ? "marketplace" : opts.classroom ? "classroom" : opts.portfolio ? "portfolio" : opts.agent ? "agent" : opts.saas ? "saas" : opts.content ? "content" : opts.dashboard ? "dashboard" : opts.plain ? "plain" : void 0;
|
|
@@ -1696,9 +1899,10 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
|
|
|
1696
1899
|
skipPrompts: opts.yes
|
|
1697
1900
|
});
|
|
1698
1901
|
} catch (err) {
|
|
1699
|
-
|
|
1902
|
+
p13.cancel(chalk11.red(err.message || "Something went wrong."));
|
|
1700
1903
|
process.exit(1);
|
|
1701
1904
|
}
|
|
1702
1905
|
});
|
|
1703
1906
|
program.addCommand(skillsCommand);
|
|
1907
|
+
program.addCommand(deployCommand);
|
|
1704
1908
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githat-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "GitHat CLI — scaffold apps and manage the skills marketplace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"gradient-string": "^3.0.0",
|
|
27
27
|
"handlebars": "^4.7.9",
|
|
28
28
|
"ora": "^9.3.0",
|
|
29
|
+
"tar": "^7.4.3",
|
|
29
30
|
"unzipper": "^0.12.3"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
@@ -1,57 +1,47 @@
|
|
|
1
1
|
{{#if includeEmailVerification}}
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
{
|
|
6
|
-
import
|
|
7
|
-
{
|
|
4
|
+
import { Suspense } from 'react';
|
|
5
|
+
import { useSearchParams } from 'next/navigation';
|
|
6
|
+
import Link from 'next/link';
|
|
7
|
+
import { VerifyEmailStatus } from '@githat/nextjs';
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
const [status, setStatus] = useState{{#if typescript}}<'loading' | 'success' | 'error'>{{/if}}('loading');
|
|
9
|
+
export const metadata = { title: 'Verify email — {{businessName}}' };
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Verify-email landing — handles the `/verify-email?token=…` link from
|
|
13
|
+
* the GitHat Auth verification email. Uses the SDK's VerifyEmailStatus
|
|
14
|
+
* component (consistent with how SignInForm / SignUpForm /
|
|
15
|
+
* ForgotPasswordForm / ResetPasswordForm / MagicLinkVerify wrap their
|
|
16
|
+
* respective flows in this scaffold). Every auth page is a thin
|
|
17
|
+
* wrapper around the SDK component.
|
|
18
|
+
*/
|
|
19
|
+
function VerifyEmailContent() {
|
|
20
|
+
const searchParams = useSearchParams();
|
|
21
|
+
const token = searchParams.get('token');
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{{#if
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} catch {
|
|
33
|
-
setStatus('error');
|
|
34
|
-
}
|
|
35
|
-
})();
|
|
36
|
-
}, []);
|
|
23
|
+
if (!token) {
|
|
24
|
+
return (
|
|
25
|
+
<div {{#if useTailwind}}className="text-center max-w-sm p-8"{{else}}style=\{{ textAlign: 'center', maxWidth: '24rem', padding: '2rem' }}{{/if}}>
|
|
26
|
+
<h1 {{#if useTailwind}}className="text-xl font-semibold text-white mb-2"{{else}}style=\{{ fontSize: '1.25rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}{{/if}}>
|
|
27
|
+
Verification link missing
|
|
28
|
+
</h1>
|
|
29
|
+
<p {{#if useTailwind}}className="text-zinc-400"{{else}}style=\{{ color: '#a1a1aa' }}{{/if}}>
|
|
30
|
+
<Link href="/sign-in" {{#if useTailwind}}className="text-violet-500 hover:underline"{{else}}style=\{{ color: '#7c3aed' }}{{/if}}>Back to sign in</Link>
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
37
35
|
|
|
36
|
+
return <VerifyEmailStatus token={token} signInUrl="/sign-in" />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function VerifyEmailPage() {
|
|
38
40
|
return (
|
|
39
41
|
<main {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b]"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b' }}{{/if}}>
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<>
|
|
44
|
-
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}>Email verified!</h1>
|
|
45
|
-
<p style=\{{ color: '#a1a1aa' }}>You can now <a href="/sign-in" style=\{{ color: '#7c3aed' }}>sign in</a>.</p>
|
|
46
|
-
</>
|
|
47
|
-
)}
|
|
48
|
-
{status === 'error' && (
|
|
49
|
-
<>
|
|
50
|
-
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}>Verification failed</h1>
|
|
51
|
-
<p style=\{{ color: '#a1a1aa' }}>The link may have expired. <a href="/sign-up" style=\{{ color: '#7c3aed' }}>Try again</a>.</p>
|
|
52
|
-
</>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
42
|
+
<Suspense fallback={null}>
|
|
43
|
+
<VerifyEmailContent />
|
|
44
|
+
</Suspense>
|
|
55
45
|
</main>
|
|
56
46
|
);
|
|
57
47
|
}
|