@firebase-oss/ui-shadcn 0.0.1-exp.dab7419

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.
Files changed (33) hide show
  1. package/README.md +53 -0
  2. package/dist/bin/cli.js +51 -0
  3. package/dist/bin/cli.js.map +1 -0
  4. package/dist/registry/apple-sign-in-button.json +39 -0
  5. package/dist/registry/country-selector.json +23 -0
  6. package/dist/registry/email-link-auth-form.json +27 -0
  7. package/dist/registry/email-link-auth-screen.json +26 -0
  8. package/dist/registry/facebook-sign-in-button.json +32 -0
  9. package/dist/registry/forgot-password-auth-form.json +26 -0
  10. package/dist/registry/forgot-password-auth-screen.json +24 -0
  11. package/dist/registry/github-sign-in-button.json +39 -0
  12. package/dist/registry/google-sign-in-button.json +44 -0
  13. package/dist/registry/microsoft-sign-in-button.json +39 -0
  14. package/dist/registry/multi-factor-auth-assertion-form.json +25 -0
  15. package/dist/registry/multi-factor-auth-assertion-screen.json +24 -0
  16. package/dist/registry/multi-factor-auth-enrollment-form.json +25 -0
  17. package/dist/registry/multi-factor-auth-enrollment-screen.json +24 -0
  18. package/dist/registry/oauth-button.json +23 -0
  19. package/dist/registry/oauth-screen.json +26 -0
  20. package/dist/registry/phone-auth-form.json +27 -0
  21. package/dist/registry/phone-auth-screen.json +27 -0
  22. package/dist/registry/policies.json +20 -0
  23. package/dist/registry/redirect-error.json +20 -0
  24. package/dist/registry/sign-in-auth-form.json +26 -0
  25. package/dist/registry/sign-in-auth-screen.json +26 -0
  26. package/dist/registry/sign-up-auth-form.json +26 -0
  27. package/dist/registry/sign-up-auth-screen.json +26 -0
  28. package/dist/registry/sms-multi-factor-assertion-form.json +26 -0
  29. package/dist/registry/sms-multi-factor-enrollment-form.json +27 -0
  30. package/dist/registry/totp-multi-factor-assertion-form.json +25 -0
  31. package/dist/registry/totp-multi-factor-enrollment-form.json +26 -0
  32. package/dist/registry/twitter-sign-in-button.json +32 -0
  33. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+
2
+ # Firebase UI for Web - Shadcn
3
+
4
+ The shadcn package exposes React components via the [Shadcn Registy](https://ui.shadcn.com/docs/registry), allowing users
5
+ to take advantage of Firebase UI for Web logic but bringing their own UI via Shadcn.
6
+
7
+ To get started, add the `@firebase` registry [namespace](https://ui.shadcn.com/docs/registry/namespace) to your `components.json`:
8
+
9
+ ```json
10
+ {
11
+ // ...
12
+ "registries": {
13
+ "@firebase": "https://firebaseopensource.com/r/{name}.json"
14
+ }
15
+ }
16
+ ```
17
+
18
+ Next install one of the registry components - this will automatically install the `@firebase-oss/ui-react` for you,
19
+ alongwith adding any additionally required components.
20
+
21
+ ```bash
22
+ npx shadcn@latest add @firebase/sign-up-auth-screen
23
+ ```
24
+
25
+ Before consuming a component, ensure you have initalized your Firebase UI application:
26
+
27
+ ```tsx
28
+ import { initalizeUI } from '@firebase-oss/ui-core';
29
+ import { FirebaseUIProvider } from '@firebase-oss/ui-react';
30
+ import { SignInAuthScreen } from '@/components/sign-in-auth-screen';
31
+
32
+ const ui = initializeUI(...);
33
+
34
+ function App() {
35
+ return (
36
+ <FirebaseUIProvider ui={ui}>
37
+ <SignInAuthScreen />
38
+ </FirebaseUIProvider>
39
+ );
40
+ }
41
+ ```
42
+
43
+ ## Building the registry
44
+
45
+ To build the registry, run the `build` script:
46
+
47
+ ```
48
+ pnpm build
49
+ ```
50
+
51
+ Note, the script run (`build.ts`) expects a domain, which replaces the `{{ DOMAIN }}` field within the
52
+ `registy-spec.json`. This enables building the registry for different domains without updating the domain
53
+ in the actual `registry.json` file Shadcn expects.
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'util';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ var __filename = fileURLToPath(import.meta.url);
8
+ var __dirname = path.dirname(__filename);
9
+ var { values, positionals } = parseArgs({
10
+ options: {
11
+ outDir: {
12
+ type: "string",
13
+ default: "./public-dev/r"
14
+ }
15
+ },
16
+ allowPositionals: true
17
+ });
18
+ var command = positionals[0];
19
+ var outputPath = positionals[1] || values.outDir;
20
+ var sourceDir = "../registry";
21
+ if (command === "copy") {
22
+ const sourcePath = path.resolve(__dirname, sourceDir);
23
+ const destPath = path.resolve(process.cwd(), outputPath);
24
+ if (!fs.existsSync(sourcePath)) {
25
+ console.error(`Error: Source directory "${sourcePath}" does not exist`);
26
+ process.exit(1);
27
+ }
28
+ if (!fs.existsSync(destPath)) {
29
+ fs.mkdirSync(destPath, { recursive: true });
30
+ }
31
+ const files = fs.readdirSync(sourcePath);
32
+ let copiedCount = 0;
33
+ for (const file of files) {
34
+ const sourceFile = path.join(sourcePath, file);
35
+ const destFile = path.join(destPath, file);
36
+ const stat = fs.statSync(sourceFile);
37
+ if (stat.isFile()) {
38
+ fs.copyFileSync(sourceFile, destFile);
39
+ copiedCount++;
40
+ console.log(`Copied: ${file}`);
41
+ }
42
+ }
43
+ console.log(`
44
+ Successfully copied ${copiedCount} item(s) from "${sourcePath}" to "${destPath}"`);
45
+ } else {
46
+ console.error(`Unknown command: ${command || "none"}`);
47
+ console.error("Usage: copy <output-path>");
48
+ process.exit(1);
49
+ }
50
+ //# sourceMappingURL=cli.js.map
51
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../cli.ts"],"names":[],"mappings":";;;;;;AAqBA,IAAM,UAAA,GAAa,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA;AAChD,IAAM,SAAA,GAAY,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA;AAEzC,IAAM,EAAE,MAAA,EAAQ,WAAA,EAAY,GAAI,SAAA,CAAU;AAAA,EACxC,OAAA,EAAS;AAAA,IACP,MAAA,EAAQ;AAAA,MACN,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS;AAAA;AACX,GACF;AAAA,EACA,gBAAA,EAAkB;AACpB,CAAC,CAAA;AAED,IAAM,OAAA,GAAU,YAAY,CAAC,CAAA;AAC7B,IAAM,UAAA,GAAa,WAAA,CAAY,CAAC,CAAA,IAAK,MAAA,CAAO,MAAA;AAG5C,IAAM,SAAA,GAAY,aAAA;AAElB,IAAI,YAAY,MAAA,EAAQ;AACtB,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,SAAS,CAAA;AAGpD,EAAA,MAAM,WAAW,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,UAAU,CAAA;AAGvD,EAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,UAAU,CAAA,EAAG;AAC9B,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,yBAAA,EAA4B,UAAU,CAAA,gBAAA,CAAkB,CAAA;AACtE,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAGA,EAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC5B,IAAA,EAAA,CAAG,SAAA,CAAU,QAAA,EAAU,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,EAC5C;AAGA,EAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,WAAA,CAAY,UAAU,CAAA;AACvC,EAAA,IAAI,WAAA,GAAc,CAAA;AAElB,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,IAAI,CAAA;AAC7C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,QAAA,EAAU,IAAI,CAAA;AAEzC,IAAA,MAAM,IAAA,GAAO,EAAA,CAAG,QAAA,CAAS,UAAU,CAAA;AACnC,IAAA,IAAI,IAAA,CAAK,QAAO,EAAG;AACjB,MAAA,EAAA,CAAG,YAAA,CAAa,YAAY,QAAQ,CAAA;AACpC,MAAA,WAAA,EAAA;AACA,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,QAAA,EAAW,IAAI,CAAA,CAAE,CAAA;AAAA,IAC/B;AAAA,EACF;AAEA,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,oBAAA,EAAyB,WAAW,CAAA,eAAA,EAAkB,UAAU,CAAA,MAAA,EAAS,QAAQ,CAAA,CAAA,CAAG,CAAA;AAClG,CAAA,MAAO;AACL,EAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,iBAAA,EAAoB,OAAA,IAAW,MAAM,CAAA,CAAE,CAAA;AACrD,EAAA,OAAA,CAAQ,MAAM,2BAA2B,CAAA;AACzC,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAChB","file":"cli.js","sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { parseArgs } from \"node:util\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst { values, positionals } = parseArgs({\n options: {\n outDir: {\n type: \"string\",\n default: \"./public-dev/r\",\n },\n },\n allowPositionals: true,\n});\n\nconst command = positionals[0];\nconst outputPath = positionals[1] || values.outDir;\n\n// Registry is at dist/registry, CLI is at dist/bin, so go up one level\nconst sourceDir = \"../registry\";\n\nif (command === \"copy\") {\n const sourcePath = path.resolve(__dirname, sourceDir);\n\n // Output path should be relative to where the user runs the command from\n const destPath = path.resolve(process.cwd(), outputPath);\n\n // Check if source directory exists\n if (!fs.existsSync(sourcePath)) {\n console.error(`Error: Source directory \"${sourcePath}\" does not exist`);\n process.exit(1);\n }\n\n // Create destination directory if it doesn't exist\n if (!fs.existsSync(destPath)) {\n fs.mkdirSync(destPath, { recursive: true });\n }\n\n // Copy files from source to destination\n const files = fs.readdirSync(sourcePath);\n let copiedCount = 0;\n\n for (const file of files) {\n const sourceFile = path.join(sourcePath, file);\n const destFile = path.join(destPath, file);\n\n const stat = fs.statSync(sourceFile);\n if (stat.isFile()) {\n fs.copyFileSync(sourceFile, destFile);\n copiedCount++;\n console.log(`Copied: ${file}`);\n }\n }\n\n console.log(`\\nSuccessfully copied ${copiedCount} item(s) from \"${sourcePath}\" to \"${destPath}\"`);\n} else {\n console.error(`Unknown command: ${command || \"none\"}`);\n console.error(\"Usage: copy <output-path>\");\n process.exit(1);\n}\n"]}
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "apple-sign-in-button",
4
+ "type": "registry:block",
5
+ "title": "Apple Sign In Button",
6
+ "description": "A button component for Apple OAuth authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://firebaseopensource.com/r/oauth-button.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/apple-sign-in-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { OAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type AppleSignInButtonProps, AppleLogo } from \"@firebase-oss/ui-react\";\n\nimport { OAuthButton } from \"@/components/oauth-button\";\n\nexport type { AppleSignInButtonProps };\n\nexport function AppleSignInButton({ provider, ...props }: AppleSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton {...props} provider={provider || new OAuthProvider(\"apple.com\")}>\n <AppleLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithApple\")}</span>\n </OAuthButton>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "css": {
21
+ "@layer components": {
22
+ "button[data-provider='apple.com'][data-themed='true']": {
23
+ "--apple-primary": "#000000",
24
+ "--primary": "var(--apple-primary)",
25
+ "--primary-foreground": "var(--color-white)"
26
+ }
27
+ },
28
+ "@variant dark": {
29
+ "button[data-provider='apple.com'][data-themed='true']": {
30
+ "--apple-primary": "var(--color-white)",
31
+ "--primary": "var(--apple-primary)",
32
+ "--primary-foreground": "var(--color-black)"
33
+ }
34
+ }
35
+ },
36
+ "meta": {
37
+ "version": "0.0.1-exp.dab7419"
38
+ }
39
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "country-selector",
4
+ "type": "registry:block",
5
+ "title": "Country Selector",
6
+ "description": "A country selector component for phone number input with country codes and flags.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "select"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/country-selector.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { forwardRef, useCallback, useImperativeHandle, useState } from \"react\";\nimport type { CountryCode, CountryData } from \"@firebase-oss/ui-core\";\nimport {\n type CountrySelectorRef,\n type CountrySelectorProps,\n useCountries,\n useDefaultCountry,\n} from \"@firebase-oss/ui-react\";\n\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\n\nexport type { CountrySelectorRef };\n\nexport const CountrySelector = forwardRef<CountrySelectorRef, CountrySelectorProps>((_props, ref) => {\n const countries = useCountries();\n const defaultCountry = useDefaultCountry();\n const [selected, setSelected] = useState<CountryData>(defaultCountry);\n\n const setCountry = useCallback(\n (code: CountryCode) => {\n const foundCountry = countries.find((country) => country.code === code);\n setSelected(foundCountry!);\n },\n [countries]\n );\n\n useImperativeHandle(\n ref,\n () => ({\n getCountry: () => selected,\n setCountry,\n }),\n [selected, setCountry]\n );\n\n return (\n <Select value={selected.code} onValueChange={setCountry}>\n <SelectTrigger className=\"w-[120px]\">\n <SelectValue>\n {selected.emoji} {selected.dialCode}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {countries.map((country) => (\n <SelectItem key={country.code} value={country.code}>\n {country.dialCode} ({country.name})\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n});\n\nCountrySelector.displayName = \"CountrySelector\";\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "meta": {
21
+ "version": "0.0.1-exp.dab7419"
22
+ }
23
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "email-link-auth-form",
4
+ "type": "registry:block",
5
+ "title": "Email Link Auth Form",
6
+ "description": "A form allowing users to sign in via email link.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "input",
12
+ "button",
13
+ "form",
14
+ "alert",
15
+ "https://firebaseopensource.com/r/policies.json"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "src/components/email-link-auth-form.tsx",
20
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport type { EmailLinkAuthFormSchema } from \"@firebase-oss/ui-core\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-oss/ui-core\";\nimport {\n useEmailLinkAuthFormAction,\n useEmailLinkAuthFormCompleteSignIn,\n useEmailLinkAuthFormSchema,\n useUI,\n type EmailLinkAuthFormProps,\n} from \"@firebase-oss/ui-react\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\n\nimport { Policies } from \"@/components/policies\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/button\";\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\n\nexport type { EmailLinkAuthFormProps };\n\nexport function EmailLinkAuthForm(props: EmailLinkAuthFormProps) {\n const { onEmailSent, onSignIn } = props;\n const ui = useUI();\n const schema = useEmailLinkAuthFormSchema();\n const action = useEmailLinkAuthFormAction();\n const [emailSent, setEmailSent] = useState(false);\n\n const form = useForm<EmailLinkAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n },\n });\n\n useEmailLinkAuthFormCompleteSignIn(onSignIn);\n\n async function onSubmit(values: EmailLinkAuthFormSchema) {\n try {\n await action(values);\n setEmailSent(true);\n onEmailSent?.();\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n if (emailSent) {\n return (\n <Alert>\n <AlertDescription>{getTranslation(ui, \"messages\", \"signInLinkSent\")}</AlertDescription>\n </Alert>\n );\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"emailAddress\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"email\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"sendSignInLink\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n",
21
+ "type": "registry:component"
22
+ }
23
+ ],
24
+ "meta": {
25
+ "version": "0.0.1-exp.dab7419"
26
+ }
27
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "email-link-auth-screen",
4
+ "type": "registry:block",
5
+ "title": "Email Link Auth Screen",
6
+ "description": "A screen allowing users to sign in via email link.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "separator",
12
+ "card",
13
+ "https://firebaseopensource.com/r/email-link-auth-form.json",
14
+ "https://firebaseopensource.com/r/redirect-error.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/email-link-auth-screen.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type EmailLinkAuthScreenProps, useOnUserAuthenticated } from \"@firebase-oss/ui-react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { EmailLinkAuthForm } from \"@/components/email-link-auth-form\";\nimport { MultiFactorAuthAssertionScreen } from \"@/components/multi-factor-auth-assertion-screen\";\nimport { RedirectError } from \"@/components/redirect-error\";\n\nexport type { EmailLinkAuthScreenProps };\n\nexport function EmailLinkAuthScreen({ children, onSignIn, ...props }: EmailLinkAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"signIn\");\n const subtitleText = getTranslation(ui, \"prompts\", \"signInToAccount\");\n\n useOnUserAuthenticated(onSignIn);\n\n if (ui.multiFactorResolver) {\n return <MultiFactorAuthAssertionScreen />;\n }\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <EmailLinkAuthForm {...props} />\n {children ? (\n <>\n <Separator className=\"my-4\" />\n <div className=\"space-y-2\">\n {children}\n <RedirectError />\n </div>\n </>\n ) : null}\n </CardContent>\n </Card>\n </div>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "facebook-sign-in-button",
4
+ "type": "registry:block",
5
+ "title": "Facebook Sign In Button",
6
+ "description": "A button component for Facebook OAuth authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://firebaseopensource.com/r/oauth-button.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/facebook-sign-in-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { FacebookAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type FacebookSignInButtonProps, FacebookLogo } from \"@firebase-oss/ui-react\";\n\nimport { OAuthButton } from \"@/components/oauth-button\";\n\nexport type { FacebookSignInButtonProps };\n\nexport function FacebookSignInButton({ provider, ...props }: FacebookSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton {...props} provider={provider || new FacebookAuthProvider()}>\n <FacebookLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithFacebook\")}</span>\n </OAuthButton>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "css": {
21
+ "@layer components": {
22
+ "button[data-provider='facebook.com'][data-themed='true']": {
23
+ "--facebook-primary": "#1877F2",
24
+ "--primary": "var(--facebook-primary)",
25
+ "--primary-foreground": "var(--color-white)"
26
+ }
27
+ }
28
+ },
29
+ "meta": {
30
+ "version": "0.0.1-exp.dab7419"
31
+ }
32
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "forgot-password-auth-form",
4
+ "type": "registry:block",
5
+ "title": "Forgot Password Auth Form",
6
+ "description": "A form allowing users to reset their password via email.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "input",
12
+ "button",
13
+ "form",
14
+ "https://firebaseopensource.com/r/policies.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/forgot-password-auth-form.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport type { ForgotPasswordAuthFormSchema } from \"@firebase-oss/ui-core\";\nimport {\n useForgotPasswordAuthFormAction,\n useForgotPasswordAuthFormSchema,\n useUI,\n type ForgotPasswordAuthFormProps,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-oss/ui-core\";\nimport { useState } from \"react\";\n\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Policies } from \"./policies\";\n\nexport type { ForgotPasswordAuthFormProps };\n\nexport function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) {\n const ui = useUI();\n const schema = useForgotPasswordAuthFormSchema();\n const action = useForgotPasswordAuthFormAction();\n const [emailSent, setEmailSent] = useState(false);\n\n const form = useForm<ForgotPasswordAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n },\n });\n\n async function onSubmit(values: ForgotPasswordAuthFormSchema) {\n try {\n await action(values);\n setEmailSent(true);\n props.onPasswordSent?.();\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n if (emailSent) {\n return (\n <div className=\"text-center space-y-4\">\n <div className=\"text-green-600 dark:text-green-400\">{getTranslation(ui, \"messages\", \"checkEmailForReset\")}</div>\n </div>\n );\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"emailAddress\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"email\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"resetPassword\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n {props.onBackToSignInClick ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={props.onBackToSignInClick}>\n <span className=\"text-xs\">&larr; {getTranslation(ui, \"labels\", \"backToSignIn\")}</span>\n </Button>\n ) : null}\n </form>\n </Form>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "forgot-password-auth-screen",
4
+ "type": "registry:block",
5
+ "title": "Forgot Password Auth Screen",
6
+ "description": "A screen allowing users to reset their password via email.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "card",
12
+ "https://firebaseopensource.com/r/forgot-password-auth-form.json"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "src/components/forgot-password-auth-screen.tsx",
17
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type ForgotPasswordAuthScreenProps } from \"@firebase-oss/ui-react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { ForgotPasswordAuthForm } from \"@/components/forgot-password-auth-form\";\n\nexport type { ForgotPasswordAuthScreenProps };\n\nexport function ForgotPasswordAuthScreen(props: ForgotPasswordAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"resetPassword\");\n const subtitleText = getTranslation(ui, \"prompts\", \"enterEmailToReset\");\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <ForgotPasswordAuthForm {...props} />\n </CardContent>\n </Card>\n </div>\n );\n}\n",
18
+ "type": "registry:component"
19
+ }
20
+ ],
21
+ "meta": {
22
+ "version": "0.0.1-exp.dab7419"
23
+ }
24
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "github-sign-in-button",
4
+ "type": "registry:block",
5
+ "title": "GitHub Sign In Button",
6
+ "description": "A button component for GitHub OAuth authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://firebaseopensource.com/r/oauth-button.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/github-sign-in-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { GithubAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type GitHubSignInButtonProps, GitHubLogo } from \"@firebase-oss/ui-react\";\n\nimport { OAuthButton } from \"@/components/oauth-button\";\n\nexport type { GitHubSignInButtonProps };\n\nexport function GitHubSignInButton({ provider, ...props }: GitHubSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton {...props} provider={provider || new GithubAuthProvider()}>\n <GitHubLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithGitHub\")}</span>\n </OAuthButton>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "css": {
21
+ "@layer components": {
22
+ "button[data-provider='github.com'][data-themed='true']": {
23
+ "--github-primary": "#000000",
24
+ "--primary": "var(--github-primary)",
25
+ "--primary-foreground": "var(--color-white)"
26
+ }
27
+ },
28
+ "@variant dark": {
29
+ "button[data-provider='github.com'][data-themed='true']": {
30
+ "--github-primary": "var(--color-white)",
31
+ "--primary": "var(--github-primary)",
32
+ "--primary-foreground": "var(--color-black)"
33
+ }
34
+ }
35
+ },
36
+ "meta": {
37
+ "version": "0.0.1-exp.dab7419"
38
+ }
39
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "google-sign-in-button",
4
+ "type": "registry:block",
5
+ "title": "Google Sign In Button",
6
+ "description": "A button component for Google OAuth authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://firebaseopensource.com/r/oauth-button.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/google-sign-in-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { GoogleAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type GoogleSignInButtonProps, GoogleLogo } from \"@firebase-oss/ui-react\";\n\nimport { OAuthButton } from \"@/components/oauth-button\";\n\nexport type { GoogleSignInButtonProps };\n\nexport function GoogleSignInButton({ provider, ...props }: GoogleSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton {...props} provider={provider || new GoogleAuthProvider()}>\n <GoogleLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithGoogle\")}</span>\n </OAuthButton>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "css": {
21
+ "@layer components": {
22
+ "button[data-provider='google.com'][data-themed='true']": {
23
+ "--google-primary": "#131314",
24
+ "--primary": "var(--google-primary)",
25
+ "--primary-foreground": "var(--color-white)"
26
+ },
27
+ "button[data-provider='google.com'][data-themed='neutral']": {
28
+ "--google-primary": "#F2F2F2",
29
+ "--primary": "var(--google-primary)",
30
+ "--primary-foreground": "var(--color-black)"
31
+ }
32
+ },
33
+ "@variant dark": {
34
+ "button[data-provider='google.com'][data-themed='true']": {
35
+ "--google-primary": "#FFFFFF",
36
+ "--primary": "var(--google-primary)",
37
+ "--primary-foreground": "var(--color-black)"
38
+ }
39
+ }
40
+ },
41
+ "meta": {
42
+ "version": "0.0.1-exp.dab7419"
43
+ }
44
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "microsoft-sign-in-button",
4
+ "type": "registry:block",
5
+ "title": "Microsoft Sign In Button",
6
+ "description": "A button component for Microsoft OAuth authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://firebaseopensource.com/r/oauth-button.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/microsoft-sign-in-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { OAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type MicrosoftSignInButtonProps, MicrosoftLogo } from \"@firebase-oss/ui-react\";\n\nimport { OAuthButton } from \"@/components/oauth-button\";\n\nexport type { MicrosoftSignInButtonProps };\n\nexport function MicrosoftSignInButton({ provider, ...props }: MicrosoftSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton {...props} provider={provider || new OAuthProvider(\"microsoft.com\")}>\n <MicrosoftLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithMicrosoft\")}</span>\n </OAuthButton>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "css": {
21
+ "@layer components": {
22
+ "button[data-provider='microsoft.com'][data-themed='true']": {
23
+ "--microsoft-primary": "#2F2F2F",
24
+ "--primary": "var(--microsoft-primary)",
25
+ "--primary-foreground": "var(--color-white)"
26
+ }
27
+ },
28
+ "@variant dark": {
29
+ "button[data-provider='microsoft.com'][data-themed='true']": {
30
+ "--microsoft-primary": "var(--color-white)",
31
+ "--primary": "var(--microsoft-primary)",
32
+ "--primary-foreground": "var(--color-black)"
33
+ }
34
+ }
35
+ },
36
+ "meta": {
37
+ "version": "0.0.1-exp.dab7419"
38
+ }
39
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "multi-factor-auth-assertion-form",
4
+ "type": "registry:block",
5
+ "title": "Multi-Factor Auth Assertion Form",
6
+ "description": "A form allowing users to complete multi-factor authentication during sign-in with TOTP or SMS options.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "button",
12
+ "https://firebaseopensource.com/r/sms-multi-factor-assertion-form.json",
13
+ "https://firebaseopensource.com/r/totp-multi-factor-assertion-form.json"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "src/components/multi-factor-auth-assertion-form.tsx",
18
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI } from \"@firebase-oss/ui-react\";\nimport {\n PhoneMultiFactorGenerator,\n TotpMultiFactorGenerator,\n type MultiFactorInfo,\n type UserCredential,\n} from \"firebase/auth\";\nimport { useState, type ComponentProps } from \"react\";\nimport { useMultiFactorAssertionCleanup } from \"@firebase-oss/ui-react\";\n\nimport { SmsMultiFactorAssertionForm } from \"@/components/sms-multi-factor-assertion-form\";\nimport { TotpMultiFactorAssertionForm } from \"@/components/totp-multi-factor-assertion-form\";\nimport { Button } from \"@/components/ui/button\";\n\nexport type MultiFactorAuthAssertionFormProps = {\n onSuccess?: (credential: UserCredential) => void;\n};\n\nexport function MultiFactorAuthAssertionForm({ onSuccess }: MultiFactorAuthAssertionFormProps) {\n const ui = useUI();\n const resolver = ui.multiFactorResolver;\n const mfaAssertionFactorPrompt = getTranslation(ui, \"prompts\", \"mfaAssertionFactorPrompt\");\n\n useMultiFactorAssertionCleanup();\n\n if (!resolver) {\n throw new Error(\"MultiFactorAuthAssertionForm requires a multi-factor resolver\");\n }\n\n // If only a single hint is provided, select it by default to improve UX.\n const [hint, setHint] = useState<MultiFactorInfo | undefined>(\n resolver.hints.length === 1 ? resolver.hints[0] : undefined\n );\n\n if (hint) {\n if (hint.factorId === PhoneMultiFactorGenerator.FACTOR_ID) {\n return <SmsMultiFactorAssertionForm hint={hint} onSuccess={onSuccess} />;\n }\n\n if (hint.factorId === TotpMultiFactorGenerator.FACTOR_ID) {\n return <TotpMultiFactorAssertionForm hint={hint} onSuccess={onSuccess} />;\n }\n }\n\n return (\n <div className=\"flex flex-col gap-2\">\n <p className=\"text-sm text-muted-foreground\">{mfaAssertionFactorPrompt}</p>\n {resolver.hints.map((hint) => {\n if (hint.factorId === TotpMultiFactorGenerator.FACTOR_ID) {\n return <TotpButton key={hint.factorId} onClick={() => setHint(hint)} />;\n }\n\n if (hint.factorId === PhoneMultiFactorGenerator.FACTOR_ID) {\n return <SmsButton key={hint.factorId} onClick={() => setHint(hint)} />;\n }\n\n return null;\n })}\n </div>\n );\n}\n\nfunction TotpButton(props: ComponentProps<typeof Button>) {\n const ui = useUI();\n const labelText = getTranslation(ui, \"labels\", \"mfaTotpVerification\");\n return <Button {...props}>{labelText}</Button>;\n}\n\nfunction SmsButton(props: ComponentProps<typeof Button>) {\n const ui = useUI();\n const labelText = getTranslation(ui, \"labels\", \"mfaSmsVerification\");\n return <Button {...props}>{labelText}</Button>;\n}\n",
19
+ "type": "registry:component"
20
+ }
21
+ ],
22
+ "meta": {
23
+ "version": "0.0.1-exp.dab7419"
24
+ }
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "multi-factor-auth-assertion-screen",
4
+ "type": "registry:block",
5
+ "title": "Multi-Factor Auth Assertion Screen",
6
+ "description": "A screen allowing users to complete multi-factor authentication during sign-in with TOTP or SMS options.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "card",
12
+ "https://firebaseopensource.com/r/multi-factor-auth-assertion-form.json"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "src/components/multi-factor-auth-assertion-screen.tsx",
17
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type MultiFactorAuthAssertionScreenProps } from \"@firebase-oss/ui-react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { MultiFactorAuthAssertionForm } from \"@/components/multi-factor-auth-assertion-form\";\n\nexport type MultiFactorAuthEnrollmentScreenProps = MultiFactorAuthAssertionScreenProps;\n\nexport function MultiFactorAuthAssertionScreen(props: MultiFactorAuthEnrollmentScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"multiFactorAssertion\");\n const subtitleText = getTranslation(ui, \"prompts\", \"mfaAssertionPrompt\");\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <MultiFactorAuthAssertionForm {...props} />\n </CardContent>\n </Card>\n </div>\n );\n}\n",
18
+ "type": "registry:component"
19
+ }
20
+ ],
21
+ "meta": {
22
+ "version": "0.0.1-exp.dab7419"
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "multi-factor-auth-enrollment-form",
4
+ "type": "registry:block",
5
+ "title": "Multi-Factor Auth Enrollment Form",
6
+ "description": "A form allowing users to select and configure multi-factor authentication methods.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "button",
12
+ "https://firebaseopensource.com/r/sms-multi-factor-enrollment-form.json",
13
+ "https://firebaseopensource.com/r/totp-multi-factor-enrollment-form.json"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "src/components/multi-factor-auth-enrollment-form.tsx",
18
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { type ComponentProps, useState } from \"react\";\nimport { FactorId } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI } from \"@firebase-oss/ui-react\";\n\nimport { SmsMultiFactorEnrollmentForm } from \"@/components/sms-multi-factor-enrollment-form\";\nimport { TotpMultiFactorEnrollmentForm } from \"@/components/totp-multi-factor-enrollment-form\";\nimport { Button } from \"@/components/ui/button\";\n\ntype Hint = (typeof FactorId)[keyof typeof FactorId];\n\nexport type MultiFactorAuthEnrollmentFormProps = {\n onEnrollment?: () => void;\n hints?: Hint[];\n};\n\nconst DEFAULT_HINTS = [FactorId.TOTP, FactorId.PHONE] as const;\n\nexport function MultiFactorAuthEnrollmentForm(props: MultiFactorAuthEnrollmentFormProps) {\n const hints = props.hints ?? DEFAULT_HINTS;\n\n if (hints.length === 0) {\n throw new Error(\"MultiFactorAuthEnrollmentForm must have at least one hint\");\n }\n\n // If only a single hint is provided, select it by default to improve UX.\n const [hint, setHint] = useState<Hint | undefined>(hints.length === 1 ? hints[0] : undefined);\n\n if (hint) {\n if (hint === FactorId.TOTP) {\n return <TotpMultiFactorEnrollmentForm onSuccess={props.onEnrollment} />;\n }\n\n if (hint === FactorId.PHONE) {\n return <SmsMultiFactorEnrollmentForm onSuccess={props.onEnrollment} />;\n }\n\n throw new Error(`Unknown multi-factor enrollment type: ${hint}`);\n }\n\n return (\n <div className=\"flex flex-col gap-2\">\n {hints.map((hint) => {\n if (hint === FactorId.TOTP) {\n return <TotpButton key={hint} onClick={() => setHint(hint)} />;\n }\n\n if (hint === FactorId.PHONE) {\n return <SmsButton key={hint} onClick={() => setHint(hint)} />;\n }\n\n return null;\n })}\n </div>\n );\n}\n\nfunction TotpButton(props: ComponentProps<typeof Button>) {\n const ui = useUI();\n const labelText = getTranslation(ui, \"labels\", \"mfaTotpVerification\");\n return (\n <Button {...props} variant=\"outline\">\n {labelText}\n </Button>\n );\n}\n\nfunction SmsButton(props: ComponentProps<typeof Button>) {\n const ui = useUI();\n const labelText = getTranslation(ui, \"labels\", \"mfaSmsVerification\");\n return (\n <Button {...props} variant=\"outline\">\n {labelText}\n </Button>\n );\n}\n",
19
+ "type": "registry:component"
20
+ }
21
+ ],
22
+ "meta": {
23
+ "version": "0.0.1-exp.dab7419"
24
+ }
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "multi-factor-auth-enrollment-screen",
4
+ "type": "registry:block",
5
+ "title": "Multi-Factor Auth Enrollment Screen",
6
+ "description": "A screen allowing users to set up multi-factor authentication with TOTP or SMS options.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "card",
12
+ "https://firebaseopensource.com/r/multi-factor-auth-enrollment-form.json"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "src/components/multi-factor-auth-enrollment-screen.tsx",
17
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type MultiFactorAuthEnrollmentFormProps } from \"@firebase-oss/ui-react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { MultiFactorAuthEnrollmentForm } from \"@/components/multi-factor-auth-enrollment-form\";\n\nexport type MultiFactorAuthEnrollmentScreenProps = MultiFactorAuthEnrollmentFormProps;\n\nexport function MultiFactorAuthEnrollmentScreen(props: MultiFactorAuthEnrollmentScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"multiFactorEnrollment\");\n const subtitleText = getTranslation(ui, \"prompts\", \"mfaEnrollmentPrompt\");\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <MultiFactorAuthEnrollmentForm {...props} />\n </CardContent>\n </Card>\n </div>\n );\n}\n",
18
+ "type": "registry:component"
19
+ }
20
+ ],
21
+ "meta": {
22
+ "version": "0.0.1-exp.dab7419"
23
+ }
24
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "oauth-button",
4
+ "type": "registry:block",
5
+ "title": "OAuth Button",
6
+ "description": "A button component for OAuth authentication providers.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "button"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/oauth-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { useUI, type OAuthButtonProps, useSignInWithProvider } from \"@firebase-oss/ui-react\";\nimport { Button } from \"@/components/ui/button\";\n\nexport type { OAuthButtonProps };\n\nexport function OAuthButton({ provider, children, themed, onSignIn }: OAuthButtonProps) {\n const ui = useUI();\n\n const { error, callback } = useSignInWithProvider(provider, onSignIn);\n\n return (\n <div>\n <Button\n type=\"button\"\n disabled={ui.state !== \"idle\"}\n onClick={callback}\n data-provider={provider.providerId}\n data-themed={themed}\n className=\"w-full\"\n variant={themed ? \"default\" : \"outline\"}\n >\n {children}\n </Button>\n {error && <div className=\"text-destructive text-left text-xs\">{error}</div>}\n </div>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "meta": {
21
+ "version": "0.0.1-exp.dab7419"
22
+ }
23
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "oauth-screen",
4
+ "type": "registry:block",
5
+ "title": "OAuth Screen",
6
+ "description": "A screen allowing users to sign in with OAuth providers.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "card",
12
+ "https://firebaseopensource.com/r/policies.json",
13
+ "https://firebaseopensource.com/r/multi-factor-auth-assertion-screen.json",
14
+ "https://firebaseopensource.com/r/redirect-error.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/oauth-screen.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { type User } from \"firebase/auth\";\nimport { type PropsWithChildren } from \"react\";\nimport { useUI, useOnUserAuthenticated } from \"@firebase-oss/ui-react\";\nimport { Card, CardContent, CardHeader, CardDescription, CardTitle } from \"@/components/ui/card\";\nimport { Policies } from \"@/components/policies\";\nimport { MultiFactorAuthAssertionScreen } from \"@/components/multi-factor-auth-assertion-screen\";\nimport { RedirectError } from \"@/components/redirect-error\";\n\nexport type OAuthScreenProps = PropsWithChildren<{\n onSignIn?: (user: User) => void;\n}>;\n\nexport function OAuthScreen({ children, onSignIn }: OAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"signIn\");\n const subtitleText = getTranslation(ui, \"prompts\", \"signInToAccount\");\n\n useOnUserAuthenticated(onSignIn);\n\n if (ui.multiFactorResolver) {\n return <MultiFactorAuthAssertionScreen />;\n }\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">{children}</div>\n <div className=\"mt-4 space-y-4\">\n <RedirectError />\n <Policies />\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "phone-auth-form",
4
+ "type": "registry:block",
5
+ "title": "Phone Auth Form",
6
+ "description": "A form allowing users to authenticate using their phone number with SMS verification.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "form",
12
+ "input",
13
+ "button",
14
+ "https://firebaseopensource.com/r/country-selector.json",
15
+ "https://firebaseopensource.com/r/policies.json"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "src/components/phone-auth-form.tsx",
20
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport {\n type PhoneAuthFormProps,\n usePhoneAuthNumberFormSchema,\n usePhoneAuthVerifyFormSchema,\n usePhoneNumberFormAction,\n useRecaptchaVerifier,\n useUI,\n useVerifyPhoneNumberFormAction,\n} from \"@firebase-oss/ui-react\";\nimport { useState } from \"react\";\nimport type { UserCredential } from \"firebase/auth\";\nimport { useRef } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport {\n FirebaseUIError,\n formatPhoneNumber,\n getTranslation,\n type PhoneAuthNumberFormSchema,\n type PhoneAuthVerifyFormSchema,\n} from \"@firebase-oss/ui-core\";\n\nimport { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Policies } from \"@/components/policies\";\nimport { CountrySelector, type CountrySelectorRef } from \"@/components/country-selector\";\nimport { InputOTP, InputOTPGroup, InputOTPSlot } from \"@/components/ui/input-otp\";\n\ntype VerifyPhoneNumberFormProps = {\n verificationId: string;\n onSuccess: (credential: UserCredential) => void;\n};\n\nfunction VerifyPhoneNumberForm(props: VerifyPhoneNumberFormProps) {\n const ui = useUI();\n const schema = usePhoneAuthVerifyFormSchema();\n const action = useVerifyPhoneNumberFormAction();\n\n const form = useForm<PhoneAuthVerifyFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n verificationId: props.verificationId,\n verificationCode: \"\",\n },\n });\n\n async function onSubmit(values: PhoneAuthVerifyFormSchema) {\n try {\n const credential = await action(values);\n props.onSuccess(credential);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-4\">\n <FormField\n control={form.control}\n name=\"verificationCode\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"verificationCode\")}</FormLabel>\n <FormDescription>{getTranslation(ui, \"prompts\", \"smsVerificationPrompt\")}</FormDescription>\n <FormControl>\n <InputOTP maxLength={6} {...field}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"verifyCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n\ntype PhoneNumberFormProps = {\n onSubmit: (verificationId: string) => void;\n};\n\nfunction PhoneNumberForm(props: PhoneNumberFormProps) {\n const ui = useUI();\n const recaptchaContainerRef = useRef<HTMLDivElement>(null);\n const recaptchaVerifier = useRecaptchaVerifier(recaptchaContainerRef);\n const countrySelector = useRef<CountrySelectorRef>(null);\n const action = usePhoneNumberFormAction();\n const schema = usePhoneAuthNumberFormSchema();\n\n const form = useForm<PhoneAuthNumberFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n phoneNumber: \"\",\n },\n });\n\n async function onSubmit(values: PhoneAuthNumberFormSchema) {\n try {\n const formatted = formatPhoneNumber(values.phoneNumber, countrySelector.current!.getCountry());\n const verificationId = await action({ phoneNumber: formatted, recaptchaVerifier: recaptchaVerifier! });\n props.onSubmit(verificationId);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-4\">\n <FormField\n control={form.control}\n name=\"phoneNumber\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"phoneNumber\")}</FormLabel>\n <FormControl>\n <div className=\"flex items-center gap-2\">\n <CountrySelector ref={countrySelector} />\n <Input {...field} type=\"tel\" />\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <div ref={recaptchaContainerRef} />\n <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"sendCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n\nexport type { PhoneAuthFormProps };\n\nexport function PhoneAuthForm(props: PhoneAuthFormProps) {\n const [verificationId, setVerificationId] = useState<string | null>(null);\n\n if (!verificationId) {\n return <PhoneNumberForm onSubmit={setVerificationId} />;\n }\n\n return (\n <VerifyPhoneNumberForm\n verificationId={verificationId}\n onSuccess={(credential) => {\n props.onSignIn?.(credential);\n }}\n />\n );\n}\n",
21
+ "type": "registry:component"
22
+ }
23
+ ],
24
+ "meta": {
25
+ "version": "0.0.1-exp.dab7419"
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "phone-auth-screen",
4
+ "type": "registry:block",
5
+ "title": "Phone Auth Screen",
6
+ "description": "A screen allowing users to authenticate using their phone number with SMS verification.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "card",
12
+ "separator",
13
+ "https://firebaseopensource.com/r/phone-auth-form.json",
14
+ "https://firebaseopensource.com/r/multi-factor-auth-assertion-form.json",
15
+ "https://firebaseopensource.com/r/redirect-error.json"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "src/components/phone-auth-screen.tsx",
20
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport type { PropsWithChildren } from \"react\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, useOnUserAuthenticated } from \"@firebase-oss/ui-react\";\nimport { Card, CardContent, CardHeader, CardDescription, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { PhoneAuthForm } from \"@/components/phone-auth-form\";\nimport { MultiFactorAuthAssertionScreen } from \"@/components/multi-factor-auth-assertion-screen\";\nimport { RedirectError } from \"@/components/redirect-error\";\nimport type { User } from \"firebase/auth\";\n\nexport type PhoneAuthScreenProps = PropsWithChildren<{\n onSignIn?: (user: User) => void;\n}>;\n\nexport function PhoneAuthScreen({ children, onSignIn }: PhoneAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"signIn\");\n const subtitleText = getTranslation(ui, \"prompts\", \"signInToAccount\");\n\n useOnUserAuthenticated(onSignIn);\n\n if (ui.multiFactorResolver) {\n return <MultiFactorAuthAssertionScreen />;\n }\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <PhoneAuthForm />\n {children ? (\n <>\n <Separator className=\"my-4\" />\n <div className=\"space-y-2\">\n {children}\n <RedirectError />\n </div>\n </>\n ) : null}\n </CardContent>\n </Card>\n </div>\n );\n}\n",
21
+ "type": "registry:component"
22
+ }
23
+ ],
24
+ "meta": {
25
+ "version": "0.0.1-exp.dab7419"
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "policies",
4
+ "type": "registry:block",
5
+ "title": "Policies",
6
+ "description": "A component allowing users to navigate to the terms of service and privacy policy.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "src/components/policies.tsx",
13
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { cn } from \"@/lib/utils\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, PolicyContext } from \"@firebase-oss/ui-react\";\nimport { cloneElement, useContext } from \"react\";\n\nexport function Policies() {\n const ui = useUI();\n const policies = useContext(PolicyContext);\n\n if (!policies) {\n return null;\n }\n\n const { termsOfServiceUrl, privacyPolicyUrl, onNavigate } = policies;\n const termsAndPrivacyText = getTranslation(ui, \"messages\", \"termsAndPrivacy\");\n const parts = termsAndPrivacyText.split(/(\\{tos\\}|\\{privacy\\})/);\n\n const className = cn(\"hover:underline font-semibold\");\n const Handler = onNavigate ? (\n <button className={className} />\n ) : (\n <a target=\"_blank\" rel=\"noopener noreferrer\" className={className} />\n );\n\n return (\n <div className=\"text-text-muted text-center text-xs\">\n {parts.map((part: string, index: number) => {\n if (part === \"{tos}\") {\n return cloneElement(Handler, {\n key: index,\n onClick: onNavigate ? () => onNavigate(termsOfServiceUrl) : undefined,\n href: onNavigate ? undefined : termsOfServiceUrl,\n children: getTranslation(ui, \"labels\", \"termsOfService\"),\n });\n }\n\n if (part === \"{privacy}\") {\n return cloneElement(Handler, {\n key: index,\n onClick: onNavigate ? () => onNavigate(privacyPolicyUrl) : undefined,\n href: onNavigate ? undefined : privacyPolicyUrl,\n children: getTranslation(ui, \"labels\", \"privacyPolicy\"),\n });\n }\n\n return <span key={index}>{part}</span>;\n })}\n </div>\n );\n}\n",
14
+ "type": "registry:component"
15
+ }
16
+ ],
17
+ "meta": {
18
+ "version": "0.0.1-exp.dab7419"
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "redirect-error",
4
+ "type": "registry:block",
5
+ "title": "Redirect Error",
6
+ "description": "A component that displays redirect errors from Firebase UI authentication flow.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "src/components/redirect-error.tsx",
13
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { useRedirectError } from \"@firebase-oss/ui-react\";\n\nexport function RedirectError() {\n const error = useRedirectError();\n\n if (!error) {\n return null;\n }\n\n return <div className=\"text-sm text-destructive\">{error}</div>;\n}\n",
14
+ "type": "registry:component"
15
+ }
16
+ ],
17
+ "meta": {
18
+ "version": "0.0.1-exp.dab7419"
19
+ }
20
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sign-in-auth-form",
4
+ "type": "registry:block",
5
+ "title": "Sign In Auth Form",
6
+ "description": "A form allowing users to sign in with email and password.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "input",
12
+ "button",
13
+ "form",
14
+ "https://firebaseopensource.com/r/policies.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/sign-in-auth-form.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport type { SignInAuthFormSchema } from \"@firebase-oss/ui-core\";\nimport {\n useSignInAuthFormAction,\n useSignInAuthFormSchema,\n useUI,\n type SignInAuthFormProps,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-oss/ui-core\";\n\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Policies } from \"./policies\";\n\nexport type { SignInAuthFormProps };\n\nexport function SignInAuthForm(props: SignInAuthFormProps) {\n const ui = useUI();\n const schema = useSignInAuthFormSchema();\n const action = useSignInAuthFormAction();\n\n const form = useForm<SignInAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n password: \"\",\n },\n });\n\n async function onSubmit(values: SignInAuthFormSchema) {\n try {\n const credential = await action(values);\n props.onSignIn?.(credential);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"emailAddress\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"email\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <FormLabel className=\"flex items-center gap-2\">\n <span className=\"grow\">{getTranslation(ui, \"labels\", \"password\")}</span>\n {props.onForgotPasswordClick ? (\n <Button type=\"button\" variant=\"link\" onClick={props.onForgotPasswordClick} size=\"sm\">\n <span className=\"text-xs\">{getTranslation(ui, \"labels\", \"forgotPassword\")}</span>\n </Button>\n ) : null}\n </FormLabel>\n <FormControl>\n <Input {...field} type=\"password\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"signIn\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n {props.onSignUpClick ? (\n <>\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={props.onSignUpClick}>\n <span className=\"text-xs\">\n {getTranslation(ui, \"prompts\", \"noAccount\")} {getTranslation(ui, \"labels\", \"signUp\")}\n </span>\n </Button>\n </>\n ) : null}\n </form>\n </Form>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sign-in-auth-screen",
4
+ "type": "registry:block",
5
+ "title": "Sign In Auth Screen",
6
+ "description": "A screen allowing users to sign in with email and password.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "separator",
12
+ "card",
13
+ "https://firebaseopensource.com/r/sign-in-auth-form.json",
14
+ "https://firebaseopensource.com/r/multi-factor-auth-assertion-screen.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/sign-in-auth-screen.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type SignInAuthScreenProps, useOnUserAuthenticated } from \"@firebase-oss/ui-react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { SignInAuthForm } from \"@/components/sign-in-auth-form\";\nimport { MultiFactorAuthAssertionScreen } from \"@/components/multi-factor-auth-assertion-screen\";\n\nexport type { SignInAuthScreenProps };\n\nexport function SignInAuthScreen({ children, onSignIn, ...props }: SignInAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"signIn\");\n const subtitleText = getTranslation(ui, \"prompts\", \"signInToAccount\");\n\n useOnUserAuthenticated(onSignIn);\n\n if (ui.multiFactorResolver) {\n return <MultiFactorAuthAssertionScreen />;\n }\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <SignInAuthForm {...props} />\n {children ? (\n <>\n <Separator className=\"my-4\" />\n <div className=\"space-y-2\">{children}</div>\n </>\n ) : null}\n </CardContent>\n </Card>\n </div>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sign-up-auth-form",
4
+ "type": "registry:block",
5
+ "title": "Sign Up Auth Form",
6
+ "description": "A form allowing users to sign up with email and password.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "input",
12
+ "button",
13
+ "form",
14
+ "https://firebaseopensource.com/r/policies.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/sign-up-auth-form.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport type { SignUpAuthFormSchema } from \"@firebase-oss/ui-core\";\nimport {\n useSignUpAuthFormAction,\n useSignUpAuthFormSchema,\n useUI,\n type SignUpAuthFormProps,\n useRequireDisplayName,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-oss/ui-core\";\n\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Policies } from \"./policies\";\n\nexport type { SignUpAuthFormProps };\n\nexport function SignUpAuthForm(props: SignUpAuthFormProps) {\n const ui = useUI();\n const schema = useSignUpAuthFormSchema();\n const action = useSignUpAuthFormAction();\n const requireDisplayName = useRequireDisplayName();\n\n const form = useForm<SignUpAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n password: \"\",\n displayName: requireDisplayName ? \"\" : undefined,\n },\n });\n\n async function onSubmit(values: SignUpAuthFormSchema) {\n try {\n const credential = await action(values);\n props.onSignUp?.(credential);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n {requireDisplayName ? (\n <FormField\n control={form.control}\n name=\"displayName\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"displayName\")}</FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n ) : null}\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"emailAddress\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"email\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"password\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"password\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"createAccount\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n {props.onSignInClick ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={props.onSignInClick}>\n <span className=\"text-xs\">\n {getTranslation(ui, \"prompts\", \"haveAccount\")} {getTranslation(ui, \"labels\", \"signIn\")}\n </span>\n </Button>\n ) : null}\n </form>\n </Form>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sign-up-auth-screen",
4
+ "type": "registry:block",
5
+ "title": "Sign Up Auth Screen",
6
+ "description": "A screen allowing users to sign up with email and password.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "separator",
12
+ "card",
13
+ "https://firebaseopensource.com/r/sign-up-auth-form.json",
14
+ "https://firebaseopensource.com/r/multi-factor-auth-assertion-screen.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/sign-up-auth-screen.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type SignUpAuthScreenProps, useOnUserAuthenticated } from \"@firebase-oss/ui-react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { SignUpAuthForm } from \"@/components/sign-up-auth-form\";\nimport { MultiFactorAuthAssertionScreen } from \"@/components/multi-factor-auth-assertion-screen\";\n\nexport type { SignUpAuthScreenProps };\n\nexport function SignUpAuthScreen({ children, onSignUp, ...props }: SignUpAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"signUp\");\n const subtitleText = getTranslation(ui, \"prompts\", \"enterDetailsToCreate\");\n\n useOnUserAuthenticated(onSignUp);\n\n if (ui.multiFactorResolver) {\n return <MultiFactorAuthAssertionScreen />;\n }\n\n return (\n <div className=\"max-w-sm mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <SignUpAuthForm {...props} />\n {children ? (\n <>\n <Separator className=\"my-4\" />\n <div className=\"space-y-2\">{children}</div>\n </>\n ) : null}\n </CardContent>\n </Card>\n </div>\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sms-multi-factor-assertion-form",
4
+ "type": "registry:block",
5
+ "title": "SMS Multi-Factor Assertion Form",
6
+ "description": "A form allowing users to complete SMS-based multi-factor authentication during sign-in.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "form",
12
+ "input",
13
+ "button",
14
+ "input-otp"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/sms-multi-factor-assertion-form.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { type UserCredential, type MultiFactorInfo } from \"firebase/auth\";\n\nimport { FirebaseUIError, getTranslation } from \"@firebase-oss/ui-core\";\nimport {\n useMultiFactorPhoneAuthVerifyFormSchema,\n useRecaptchaVerifier,\n useUI,\n useSmsMultiFactorAssertionPhoneFormAction,\n useSmsMultiFactorAssertionVerifyFormAction,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\n\nimport { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Button } from \"@/components/ui/button\";\nimport { InputOTP, InputOTPGroup, InputOTPSlot } from \"@/components/ui/input-otp\";\n\ntype PhoneMultiFactorInfo = MultiFactorInfo & {\n phoneNumber?: string;\n};\n\ntype SmsMultiFactorAssertionPhoneFormProps = {\n hint: MultiFactorInfo;\n onSubmit: (verificationId: string) => void;\n};\n\nfunction SmsMultiFactorAssertionPhoneForm(props: SmsMultiFactorAssertionPhoneFormProps) {\n const ui = useUI();\n const recaptchaContainerRef = useRef<HTMLDivElement>(null);\n const recaptchaVerifier = useRecaptchaVerifier(recaptchaContainerRef);\n const action = useSmsMultiFactorAssertionPhoneFormAction();\n const [error, setError] = useState<string | null>(null);\n\n const onSubmit = async () => {\n try {\n setError(null);\n const verificationId = await action({ hint: props.hint, recaptchaVerifier: recaptchaVerifier! });\n props.onSubmit(verificationId);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n setError(message);\n }\n };\n\n return (\n <div className=\"space-y-4\">\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"phoneNumber\")}</FormLabel>\n <FormDescription>\n {getTranslation(ui, \"messages\", \"mfaSmsAssertionPrompt\", {\n phoneNumber: (props.hint as PhoneMultiFactorInfo).phoneNumber || \"\",\n })}\n </FormDescription>\n </FormItem>\n <div className=\"fui-recaptcha-container\" ref={recaptchaContainerRef} />\n <Button onClick={onSubmit} disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"sendCode\")}\n </Button>\n {error && <div className=\"text-sm text-red-600\">{error}</div>}\n </div>\n );\n}\n\ntype SmsMultiFactorAssertionVerifyFormProps = {\n verificationId: string;\n onSuccess: (credential: UserCredential) => void;\n};\n\nfunction SmsMultiFactorAssertionVerifyForm(props: SmsMultiFactorAssertionVerifyFormProps) {\n const ui = useUI();\n const schema = useMultiFactorPhoneAuthVerifyFormSchema();\n const action = useSmsMultiFactorAssertionVerifyFormAction();\n\n const form = useForm<{ verificationId: string; verificationCode: string }>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n verificationId: props.verificationId,\n verificationCode: \"\",\n },\n });\n\n const onSubmit = async (values: { verificationId: string; verificationCode: string }) => {\n try {\n const credential = await action({\n verificationId: values.verificationId,\n verificationCode: values.verificationCode,\n });\n props.onSuccess(credential);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n };\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"verificationCode\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"verificationCode\")}</FormLabel>\n <FormDescription>{getTranslation(ui, \"prompts\", \"smsVerificationPrompt\")}</FormDescription>\n <FormControl>\n <InputOTP maxLength={6} {...field}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"verifyCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n\nexport type SmsMultiFactorAssertionFormProps = {\n hint: MultiFactorInfo;\n onSuccess?: (credential: UserCredential) => void;\n};\n\nexport function SmsMultiFactorAssertionForm(props: SmsMultiFactorAssertionFormProps) {\n const [verification, setVerification] = useState<{\n verificationId: string;\n } | null>(null);\n\n if (!verification) {\n return (\n <SmsMultiFactorAssertionPhoneForm\n hint={props.hint}\n onSubmit={(verificationId) => setVerification({ verificationId })}\n />\n );\n }\n\n return (\n <SmsMultiFactorAssertionVerifyForm\n verificationId={verification.verificationId}\n onSuccess={(credential) => {\n props.onSuccess?.(credential);\n }}\n />\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sms-multi-factor-enrollment-form",
4
+ "type": "registry:block",
5
+ "title": "SMS Multi-Factor Enrollment Form",
6
+ "description": "A form allowing users to enroll SMS-based multi-factor authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "form",
12
+ "input",
13
+ "button",
14
+ "input-otp",
15
+ "https://firebaseopensource.com/r/country-selector.json"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "src/components/sms-multi-factor-enrollment-form.tsx",
20
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator } from \"firebase/auth\";\nimport {\n enrollWithMultiFactorAssertion,\n FirebaseUIError,\n formatPhoneNumber,\n getTranslation,\n verifyPhoneNumber,\n} from \"@firebase-oss/ui-core\";\nimport { CountrySelector, type CountrySelectorRef } from \"@/components/country-selector\";\nimport {\n useMultiFactorPhoneAuthNumberFormSchema,\n useMultiFactorPhoneAuthVerifyFormSchema,\n useRecaptchaVerifier,\n useUI,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\n\nimport { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { InputOTP, InputOTPGroup, InputOTPSlot } from \"@/components/ui/input-otp\";\n\ntype MultiFactorEnrollmentPhoneNumberFormProps = {\n onSubmit: (verificationId: string, displayName?: string) => void;\n};\n\nfunction MultiFactorEnrollmentPhoneNumberForm(props: MultiFactorEnrollmentPhoneNumberFormProps) {\n const ui = useUI();\n const recaptchaContainerRef = useRef<HTMLDivElement>(null);\n const recaptchaVerifier = useRecaptchaVerifier(recaptchaContainerRef);\n const countrySelector = useRef<CountrySelectorRef>(null);\n const schema = useMultiFactorPhoneAuthNumberFormSchema();\n\n const form = useForm<{ displayName: string; phoneNumber: string }>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n displayName: \"\",\n phoneNumber: \"\",\n },\n });\n\n const onSubmit = async (values: { displayName: string; phoneNumber: string }) => {\n try {\n const formatted = formatPhoneNumber(values.phoneNumber, countrySelector.current!.getCountry());\n const mfaUser = multiFactor(ui.auth.currentUser!);\n const confirmationResult = await verifyPhoneNumber(ui, formatted, recaptchaVerifier!, mfaUser);\n props.onSubmit(confirmationResult, values.displayName);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n };\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"displayName\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"displayName\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"text\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"phoneNumber\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"phoneNumber\")}</FormLabel>\n <FormControl>\n <div className=\"flex items-center gap-2\">\n <CountrySelector ref={countrySelector} />\n <Input {...field} type=\"tel\" className=\"flex-grow\" />\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <div className=\"fui-recaptcha-container\" ref={recaptchaContainerRef} />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"sendCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n\ntype MultiFactorEnrollmentVerifyPhoneNumberFormProps = {\n verificationId: string;\n displayName?: string;\n onSuccess: () => void;\n};\n\nexport function MultiFactorEnrollmentVerifyPhoneNumberForm(props: MultiFactorEnrollmentVerifyPhoneNumberFormProps) {\n const ui = useUI();\n const schema = useMultiFactorPhoneAuthVerifyFormSchema();\n\n const form = useForm<{ verificationId: string; verificationCode: string }>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n verificationId: props.verificationId,\n verificationCode: \"\",\n },\n });\n\n const onSubmit = async (values: { verificationId: string; verificationCode: string }) => {\n try {\n const credential = PhoneAuthProvider.credential(values.verificationId, values.verificationCode);\n const assertion = PhoneMultiFactorGenerator.assertion(credential);\n await enrollWithMultiFactorAssertion(ui, assertion, props.displayName);\n props.onSuccess();\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n };\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"verificationCode\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"verificationCode\")}</FormLabel>\n <FormDescription>{getTranslation(ui, \"prompts\", \"smsVerificationPrompt\")}</FormDescription>\n <FormControl>\n <InputOTP maxLength={6} {...field}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"verifyCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n\nexport type SmsMultiFactorEnrollmentFormProps = {\n onSuccess?: () => void;\n};\n\nexport function SmsMultiFactorEnrollmentForm(props: SmsMultiFactorEnrollmentFormProps) {\n const ui = useUI();\n\n const [verification, setVerification] = useState<{\n verificationId: string;\n displayName?: string;\n } | null>(null);\n\n if (!ui.auth.currentUser) {\n throw new Error(\"User must be authenticated to enroll with multi-factor authentication\");\n }\n\n if (!verification) {\n return (\n <MultiFactorEnrollmentPhoneNumberForm\n onSubmit={(verificationId, displayName) => setVerification({ verificationId, displayName })}\n />\n );\n }\n\n return (\n <MultiFactorEnrollmentVerifyPhoneNumberForm\n {...verification}\n onSuccess={() => {\n props.onSuccess?.();\n }}\n />\n );\n}\n",
21
+ "type": "registry:component"
22
+ }
23
+ ],
24
+ "meta": {
25
+ "version": "0.0.1-exp.dab7419"
26
+ }
27
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "totp-multi-factor-assertion-form",
4
+ "type": "registry:block",
5
+ "title": "TOTP Multi-Factor Assertion Form",
6
+ "description": "A form allowing users to complete TOTP-based multi-factor authentication during sign-in.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "form",
12
+ "button",
13
+ "input-otp"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "src/components/totp-multi-factor-assertion-form.tsx",
18
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { type UserCredential, type MultiFactorInfo } from \"firebase/auth\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-oss/ui-core\";\nimport {\n useMultiFactorTotpAuthVerifyFormSchema,\n useUI,\n useTotpMultiFactorAssertionFormAction,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\n\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Button } from \"@/components/ui/button\";\nimport { InputOTP, InputOTPGroup, InputOTPSlot } from \"@/components/ui/input-otp\";\n\ntype TotpMultiFactorAssertionFormProps = {\n hint: MultiFactorInfo;\n onSuccess?: (credential: UserCredential) => void;\n};\n\nexport function TotpMultiFactorAssertionForm(props: TotpMultiFactorAssertionFormProps) {\n const ui = useUI();\n const schema = useMultiFactorTotpAuthVerifyFormSchema();\n const action = useTotpMultiFactorAssertionFormAction();\n\n const form = useForm<{ verificationCode: string }>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n verificationCode: \"\",\n },\n });\n\n const onSubmit = async (values: { verificationCode: string }) => {\n try {\n const credential = await action({ verificationCode: values.verificationCode, hint: props.hint });\n props.onSuccess?.(credential);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n };\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"verificationCode\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"verificationCode\")}</FormLabel>\n <FormControl>\n <InputOTP maxLength={6} {...field}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"verifyCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n",
19
+ "type": "registry:component"
20
+ }
21
+ ],
22
+ "meta": {
23
+ "version": "0.0.1-exp.dab7419"
24
+ }
25
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "totp-multi-factor-enrollment-form",
4
+ "type": "registry:block",
5
+ "title": "TOTP Multi-Factor Enrollment Form",
6
+ "description": "A form allowing users to enroll TOTP-based multi-factor authentication with QR code generation.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "form",
12
+ "input",
13
+ "button",
14
+ "input-otp"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "src/components/totp-multi-factor-enrollment-form.tsx",
19
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { useState } from \"react\";\nimport { TotpMultiFactorGenerator, type TotpSecret } from \"firebase/auth\";\nimport {\n enrollWithMultiFactorAssertion,\n FirebaseUIError,\n generateTotpQrCode,\n generateTotpSecret,\n getTranslation,\n} from \"@firebase-oss/ui-core\";\nimport {\n useMultiFactorTotpAuthNumberFormSchema,\n useMultiFactorTotpAuthVerifyFormSchema,\n useUI,\n} from \"@firebase-oss/ui-react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\n\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { InputOTP, InputOTPGroup, InputOTPSlot } from \"@/components/ui/input-otp\";\n\ntype TotpMultiFactorSecretGenerationFormProps = {\n onSubmit: (secret: TotpSecret, displayName: string) => void;\n};\n\nfunction TotpMultiFactorSecretGenerationForm(props: TotpMultiFactorSecretGenerationFormProps) {\n const ui = useUI();\n const schema = useMultiFactorTotpAuthNumberFormSchema();\n\n const form = useForm<{ displayName: string }>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n displayName: \"\",\n },\n });\n\n const onSubmit = async (values: { displayName: string }) => {\n try {\n const secret = await generateTotpSecret(ui);\n props.onSubmit(secret, values.displayName);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n };\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"displayName\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"displayName\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"text\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"generateQrCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n\ntype MultiFactorEnrollmentVerifyTotpFormProps = {\n secret: TotpSecret;\n displayName: string;\n onSuccess: () => void;\n};\n\nexport function MultiFactorEnrollmentVerifyTotpForm(props: MultiFactorEnrollmentVerifyTotpFormProps) {\n const ui = useUI();\n const schema = useMultiFactorTotpAuthVerifyFormSchema();\n\n const form = useForm<{ verificationCode: string }>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n verificationCode: \"\",\n },\n });\n\n const onSubmit = async (values: { verificationCode: string }) => {\n try {\n const assertion = TotpMultiFactorGenerator.assertionForEnrollment(props.secret, values.verificationCode);\n await enrollWithMultiFactorAssertion(ui, assertion, values.verificationCode);\n props.onSuccess();\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n };\n\n const qrCodeDataUrl = generateTotpQrCode(ui, props.secret, props.displayName);\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-y-4 items-center justify-center\">\n <img src={qrCodeDataUrl} alt=\"TOTP QR Code\" className=\"mx-auto\" />\n <code className=\"text-xs text-muted-foreground text-center\">{props.secret.secretKey.toString()}</code>\n <p className=\"text-xs text-muted-foreground text-center\">\n {getTranslation(ui, \"prompts\", \"mfaTotpQrCodePrompt\")}\n </p>\n </div>\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-y-4\">\n <FormField\n control={form.control}\n name=\"verificationCode\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"verificationCode\")}</FormLabel>\n <FormControl>\n <InputOTP maxLength={6} {...field}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"verifyCode\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n </div>\n );\n}\n\nexport type TotpMultiFactorEnrollmentFormProps = {\n onSuccess?: () => void;\n};\n\nexport function TotpMultiFactorEnrollmentForm(props: TotpMultiFactorEnrollmentFormProps) {\n const ui = useUI();\n\n const [enrollment, setEnrollment] = useState<{\n secret: TotpSecret;\n displayName: string;\n } | null>(null);\n\n if (!ui.auth.currentUser) {\n throw new Error(\"User must be authenticated to enroll with multi-factor authentication\");\n }\n\n if (!enrollment) {\n return (\n <TotpMultiFactorSecretGenerationForm onSubmit={(secret, displayName) => setEnrollment({ secret, displayName })} />\n );\n }\n\n return (\n <MultiFactorEnrollmentVerifyTotpForm\n {...enrollment}\n onSuccess={() => {\n props.onSuccess?.();\n }}\n />\n );\n}\n",
20
+ "type": "registry:component"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "version": "0.0.1-exp.dab7419"
25
+ }
26
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "twitter-sign-in-button",
4
+ "type": "registry:block",
5
+ "title": "Twitter Sign In Button",
6
+ "description": "A button component for Twitter OAuth authentication.",
7
+ "dependencies": [
8
+ "@firebase-oss/ui-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://firebaseopensource.com/r/oauth-button.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "src/components/twitter-sign-in-button.tsx",
16
+ "content": "/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\"use client\";\n\nimport { TwitterAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-oss/ui-core\";\nimport { useUI, type TwitterSignInButtonProps, TwitterLogo } from \"@firebase-oss/ui-react\";\n\nimport { OAuthButton } from \"@/components/oauth-button\";\n\nexport type { TwitterSignInButtonProps };\n\nexport function TwitterSignInButton({ provider, ...props }: TwitterSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton {...props} provider={provider || new TwitterAuthProvider()}>\n <TwitterLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithTwitter\")}</span>\n </OAuthButton>\n );\n}\n",
17
+ "type": "registry:component"
18
+ }
19
+ ],
20
+ "css": {
21
+ "@layer components": {
22
+ "button[data-provider='twitter.com'][data-themed='true']": {
23
+ "--twitter-primary": "#1DA1F2",
24
+ "--primary": "var(--twitter-primary)",
25
+ "--primary-foreground": "var(--color-white)"
26
+ }
27
+ }
28
+ },
29
+ "meta": {
30
+ "version": "0.0.1-exp.dab7419"
31
+ }
32
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@firebase-oss/ui-shadcn",
3
+ "version": "0.0.1-exp.dab7419",
4
+ "type": "module",
5
+ "bin": {
6
+ "ui-shadcn": "dist/bin/cli.js"
7
+ },
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "prepare": "pnpm run build",
13
+ "dev": "vite",
14
+ "build": "tsup && tsx build.ts --domain https://firebaseopensource.com",
15
+ "preview": "vite preview",
16
+ "test": "vitest run",
17
+ "publish:npm": "../../publish.sh"
18
+ },
19
+ "devDependencies": {
20
+ "@firebase-oss/ui-translations": "workspace:*",
21
+ "@tailwindcss/vite": "catalog:",
22
+ "@testing-library/jest-dom": "catalog:",
23
+ "@testing-library/react": "catalog:",
24
+ "@types/node": "catalog:",
25
+ "@types/react": "catalog:",
26
+ "@types/react-dom": "catalog:",
27
+ "@types/yargs-parser": "^21.0.3",
28
+ "@vitejs/plugin-react": "catalog:",
29
+ "firebase": "catalog:",
30
+ "react": "catalog:",
31
+ "react-dom": "catalog:",
32
+ "shadcn": "2.9.3-canary.0",
33
+ "tailwindcss": "catalog:",
34
+ "tsup": "catalog:",
35
+ "tsx": "^4.20.6",
36
+ "tw-animate-css": "^1.4.0",
37
+ "typescript": "catalog:",
38
+ "vite": "catalog:",
39
+ "vite-plugin-run": "^0.6.1",
40
+ "vitest": "catalog:",
41
+ "yargs-parser": "^22.0.0"
42
+ },
43
+ "dependencies": {
44
+ "@hookform/resolvers": "^5.2.2",
45
+ "@firebase-oss/ui-core": "workspace:*",
46
+ "@firebase-oss/ui-react": "workspace:*",
47
+ "@radix-ui/react-label": "^2.1.7",
48
+ "@radix-ui/react-select": "^2.2.6",
49
+ "@radix-ui/react-separator": "^1.1.7",
50
+ "@radix-ui/react-slot": "^1.2.3",
51
+ "class-variance-authority": "^0.7.1",
52
+ "clsx": "^2.1.1",
53
+ "input-otp": "^1.4.2",
54
+ "lucide-react": "^0.544.0",
55
+ "react-hook-form": "^7.64.0",
56
+ "tailwind-merge": "^3.3.1",
57
+ "zod": "catalog:"
58
+ }
59
+ }